summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml93
-rw-r--r--.rubocop.yml2
-rw-r--r--CHANGELOG.md215
-rw-r--r--CONTRIBUTING.md32
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile12
-rw-r--r--Gemfile.lock52
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/copy_as_gfm.js10
-rw-r--r--app/assets/javascripts/dispatcher.js7
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js5
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js26
-rw-r--r--app/assets/javascripts/how_to_merge.js12
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js13
-rw-r--r--app/assets/javascripts/layout_nav.js7
-rw-r--r--app/assets/javascripts/lazy_loader.js76
-rw-r--r--app/assets/javascripts/main.js14
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js2
-rw-r--r--app/assets/javascripts/new_sidebar.js23
-rw-r--r--app/assets/javascripts/protected_branches/index.js9
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js53
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js106
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js39
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js114
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit_list.js28
-rw-r--r--app/assets/javascripts/protected_branches/protected_branches_bundle.js5
-rw-r--r--app/assets/javascripts/protected_tags/index.js11
-rw-r--r--app/assets/javascripts/star.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js11
-rw-r--r--app/assets/stylesheets/framework/avatar.scss2
-rw-r--r--app/assets/stylesheets/framework/calendar.scss2
-rw-r--r--app/assets/stylesheets/framework/files.scss10
-rw-r--r--app/assets/stylesheets/framework/header.scss29
-rw-r--r--app/assets/stylesheets/framework/nav.scss14
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss10
-rw-r--r--app/assets/stylesheets/framework/typography.scss11
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/new_nav.scss26
-rw-r--r--app/assets/stylesheets/new_sidebar.scss123
-rw-r--r--app/assets/stylesheets/pages/boards.scss5
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss3
-rw-r--r--app/assets/stylesheets/pages/projects.scss3
-rw-r--r--app/controllers/admin/application_settings_controller.rb4
-rw-r--r--app/controllers/admin/dashboard_controller.rb2
-rw-r--r--app/controllers/admin/hook_logs_controller.rb4
-rw-r--r--app/controllers/admin/hooks_controller.rb15
-rw-r--r--app/controllers/admin/projects_controller.rb15
-rw-r--r--app/controllers/concerns/hooks_execution.rb13
-rw-r--r--app/controllers/projects/application_controller.rb1
-rw-r--r--app/controllers/projects/badges_controller.rb6
-rw-r--r--app/controllers/projects/hook_logs_controller.rb4
-rw-r--r--app/controllers/projects/hooks_controller.rb12
-rw-r--r--app/controllers/projects/issues_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/sessions_controller.rb8
-rw-r--r--app/finders/admin/projects_finder.rb33
-rw-r--r--app/helpers/avatars_helper.rb9
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/hooks_helper.rb17
-rw-r--r--app/helpers/issues_helper.rb26
-rw-r--r--app/helpers/lazy_image_tag_helper.rb24
-rw-r--r--app/helpers/system_note_helper.rb3
-rw-r--r--app/helpers/triggers_helper.rb2
-rw-r--r--app/helpers/version_check_helper.rb2
-rw-r--r--app/models/ci/build.rb16
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/pipeline_schedule.rb4
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/protected_ref.rb17
-rw-r--r--app/models/group.rb6
-rw-r--r--app/models/hooks/project_hook.rb25
-rw-r--r--app/models/hooks/service_hook.rb1
-rw-r--r--app/models/hooks/system_hook.rb10
-rw-r--r--app/models/hooks/web_hook.rb14
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/project_services/issue_tracker_service.rb8
-rw-r--r--app/models/project_services/jira_service.rb20
-rw-r--r--app/models/repository.rb13
-rw-r--r--app/models/system_note_metadata.rb3
-rw-r--r--app/policies/ci/build_policy.rb8
-rw-r--r--app/policies/ci/pipeline_policy.rb12
-rw-r--r--app/policies/global_policy.rb2
-rw-r--r--app/policies/project_policy.rb6
-rw-r--r--app/serializers/build_details_entity.rb3
-rw-r--r--app/serializers/deploy_key_entity.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb73
-rw-r--r--app/services/ci/create_trigger_request_service.rb8
-rw-r--r--app/services/issuable_base_service.rb1
-rw-r--r--app/services/issues/base_service.rb8
-rw-r--r--app/services/issues/close_service.rb4
-rw-r--r--app/services/issues/duplicate_service.rb24
-rw-r--r--app/services/issues/update_service.rb18
-rw-r--r--app/services/projects/update_service.rb9
-rw-r--r--app/services/quick_actions/interpret_service.rb18
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/system_note_service.rb38
-rw-r--r--app/services/test_hook_service.rb6
-rw-r--r--app/services/test_hooks/base_service.rb41
-rw-r--r--app/services/test_hooks/project_service.rb63
-rw-r--r--app/services/test_hooks/system_service.rb48
-rw-r--r--app/services/web_hook_service.rb11
-rw-r--r--app/services/wiki_pages/base_service.rb16
-rw-r--r--app/views/admin/application_settings/_form.html.haml15
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml8
-rw-r--r--app/views/admin/runners/index.html.haml33
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml16
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml4
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/header/_new.html.haml6
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml4
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml12
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml14
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml12
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml20
-rw-r--r--app/views/layouts/nav/_project.html.haml6
-rw-r--r--app/views/projects/blame/show.html.haml4
-rw-r--r--app/views/projects/blob/viewers/_image.html.haml2
-rw-r--r--app/views/projects/buttons/_star.html.haml2
-rw-r--r--app/views/projects/diffs/viewers/_image.html.haml14
-rw-r--r--app/views/projects/hooks/edit.html.haml3
-rw-r--r--app/views/projects/merge_requests/_how_to_merge.html.haml14
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml6
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml4
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml2
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml19
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml14
-rw-r--r--app/views/shared/_logo_type.svg1
-rw-r--r--app/views/shared/_mr_head.html.haml2
-rw-r--r--app/views/shared/_new_project_item_select.html.haml4
-rw-r--r--app/views/shared/icons/_icon_clone.svg3
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml4
-rw-r--r--app/views/shared/web_hooks/_test_button.html.haml12
-rw-r--r--app/workers/pipeline_schedule_worker.rb13
-rw-r--r--changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml4
-rw-r--r--changelogs/unreleased/12200-add-french-translation.yml4
-rw-r--r--changelogs/unreleased/13336-multiple-broadcast-messages.yml4
-rw-r--r--changelogs/unreleased/18000-remember-me-for-oauth-login.yml4
-rw-r--r--changelogs/unreleased/20628-add-oauth-implicit-grant.yml4
-rw-r--r--changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml4
-rw-r--r--changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-snippets-spinach.yml4
-rw-r--r--changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml4
-rw-r--r--changelogs/unreleased/23998-blame-age-map.yml4
-rw-r--r--changelogs/unreleased/2501-ce-port-update-welcome-page.yml4
-rw-r--r--changelogs/unreleased/25102-files-view-button.yml4
-rw-r--r--changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml4
-rw-r--r--changelogs/unreleased/25164-disable-fork-on-project-limit.yml4
-rw-r--r--changelogs/unreleased/26125-match-username-on-search.yml5
-rw-r--r--changelogs/unreleased/26212-upload-user-avatar-trough-api.yml4
-rw-r--r--changelogs/unreleased/26372-duplicate-issue-slash-command.yml4
-rw-r--r--changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml5
-rw-r--r--changelogs/unreleased/27586-center-dropdown.yml4
-rw-r--r--changelogs/unreleased/27645-html-email-brackets-bug.yml4
-rw-r--r--changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml4
-rw-r--r--changelogs/unreleased/28139-use-color-input-broadcast-messages.yml4
-rw-r--r--changelogs/unreleased/28202_decrease_abc_threshold_step2.yml4
-rw-r--r--changelogs/unreleased/28717-support-additional-prometheus-metrics.yml4
-rw-r--r--changelogs/unreleased/2971-multiproject-grah-ce-port.yml4
-rw-r--r--changelogs/unreleased/29893-change-menu-locations.yml3
-rw-r--r--changelogs/unreleased/30213-project-transfer-move-rollback.yml4
-rw-r--r--changelogs/unreleased/30634-protected-pipeline.yml5
-rw-r--r--changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml4
-rw-r--r--changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml4
-rw-r--r--changelogs/unreleased/31129-jira-project-key-elim.yml4
-rw-r--r--changelogs/unreleased/31397-job-detail-real-time.yml4
-rw-r--r--changelogs/unreleased/31415-responsive-pipelines-table-2.yml4
-rw-r--r--changelogs/unreleased/31533-usage-data-projects-stats.yml4
-rw-r--r--changelogs/unreleased/31982-liberation-mono-linux.yml4
-rw-r--r--changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml4
-rw-r--r--changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml4
-rw-r--r--changelogs/unreleased/32301-filter-archive-project-on-param-present.yml4
-rw-r--r--changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml4
-rw-r--r--changelogs/unreleased/32470-pag-links.yml4
-rw-r--r--changelogs/unreleased/32517-disable-hover-state.yml5
-rw-r--r--changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml4
-rw-r--r--changelogs/unreleased/32834-task-note-only.yml4
-rw-r--r--changelogs/unreleased/32838-admin-panel-spacing.yml4
-rw-r--r--changelogs/unreleased/33003-avatar-in-project-api.yml4
-rw-r--r--changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml4
-rw-r--r--changelogs/unreleased/33097-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/33130-remove-group-modal.yml4
-rw-r--r--changelogs/unreleased/33132-change-icon-color.yml4
-rw-r--r--changelogs/unreleased/33208-singup-active-state-underline.yml4
-rw-r--r--changelogs/unreleased/33360-generate-kubeconfig.yml4
-rw-r--r--changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml4
-rw-r--r--changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml4
-rw-r--r--changelogs/unreleased/33461-display-user-id.yml4
-rw-r--r--changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml4
-rw-r--r--changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33657-user-projects-api.yml4
-rw-r--r--changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml4
-rw-r--r--changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml4
-rw-r--r--changelogs/unreleased/33837-remove-trash-on-registry-image.yml4
-rw-r--r--changelogs/unreleased/33846-no-runner-for-admin.yml4
-rw-r--r--changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml4
-rw-r--r--changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml4
-rw-r--r--changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml4
-rw-r--r--changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml4
-rw-r--r--changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml4
-rw-r--r--changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml4
-rw-r--r--changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml4
-rw-r--r--changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml4
-rw-r--r--changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml4
-rw-r--r--changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml4
-rw-r--r--changelogs/unreleased/34309-drop-gfm-mr-ms.yml4
-rw-r--r--changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml4
-rw-r--r--changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml4
-rw-r--r--changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml4
-rw-r--r--changelogs/unreleased/34531-remove-scroll.yml4
-rw-r--r--changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml4
-rw-r--r--changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml4
-rw-r--r--changelogs/unreleased/34578-sidebar-padding.yml4
-rw-r--r--changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml4
-rw-r--r--changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml4
-rw-r--r--changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml4
-rw-r--r--changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34727-simplified-member-settings.yml4
-rw-r--r--changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml4
-rw-r--r--changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml4
-rw-r--r--changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml4
-rw-r--r--changelogs/unreleased/34881-add-russian-translations-to-i18n.yml4
-rw-r--r--changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml4
-rw-r--r--changelogs/unreleased/34930-fix-edited-by.yml4
-rw-r--r--changelogs/unreleased/35035-sidebar-job-spaces.yml4
-rw-r--r--changelogs/unreleased/35087-mr-status-misaligned.yml4
-rw-r--r--changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml4
-rw-r--r--changelogs/unreleased/35204-doc-api-ci-lint-typo.yml4
-rw-r--r--changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml4
-rw-r--r--changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml4
-rw-r--r--changelogs/unreleased/35391-fix-star-i18n-in-js.yml4
-rw-r--r--changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml4
-rw-r--r--changelogs/unreleased/35478-allow-admin-to-read-user-list.yml4
-rw-r--r--changelogs/unreleased/5971-webhook-testing.yml4
-rw-r--r--changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml6
-rw-r--r--changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml4
-rw-r--r--changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml4
-rw-r--r--changelogs/unreleased/bump-omniauth-ldap-gem-version.yml4
-rw-r--r--changelogs/unreleased/bvl-free-system-namespace.yml4
-rw-r--r--changelogs/unreleased/bvl-free-unused-names.yml5
-rw-r--r--changelogs/unreleased/bvl-rename-all-reserved-paths.yml4
-rw-r--r--changelogs/unreleased/commit-comments-limited-width.yml4
-rw-r--r--changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml4
-rw-r--r--changelogs/unreleased/dm-blob-binaryness-change.yml5
-rw-r--r--changelogs/unreleased/dm-commit-row-browse-button.yml4
-rw-r--r--changelogs/unreleased/dm-diff-viewers.yml4
-rw-r--r--changelogs/unreleased/dm-empty-state-new-merge-request.yml5
-rw-r--r--changelogs/unreleased/dm-group-page-name.yml4
-rw-r--r--changelogs/unreleased/dm-mail-room-check-without-omnibus.yml4
-rw-r--r--changelogs/unreleased/dm-page-image-size.yml4
-rw-r--r--changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml4
-rw-r--r--changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml4
-rw-r--r--changelogs/unreleased/dm-target-branch-slash-command-desc.yml4
-rw-r--r--changelogs/unreleased/dm-unnecessary-top-padding.yml4
-rw-r--r--changelogs/unreleased/doc-gitaly-network.yml4
-rw-r--r--changelogs/unreleased/dt-printing-to-api.yml4
-rw-r--r--changelogs/unreleased/dz-fix-calendar-today.yml4
-rw-r--r--changelogs/unreleased/enable-polling-env.yml4
-rw-r--r--changelogs/unreleased/enable-webpack-code-splitting.yml5
-rw-r--r--changelogs/unreleased/feature-add-support-for-services-configuration.yml4
-rw-r--r--changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml4
-rw-r--r--changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml4
-rw-r--r--changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml4
-rw-r--r--changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml4
-rw-r--r--changelogs/unreleased/feature-unify-email-layouts.yml4
-rw-r--r--changelogs/unreleased/feature-user-agent-details-api.yml4
-rw-r--r--changelogs/unreleased/feature-user-datetime-search-api-mysql.yml4
-rw-r--r--changelogs/unreleased/fix-33991.yml4
-rw-r--r--changelogs/unreleased/fix-assigned-issuable-lists.yml5
-rw-r--r--changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml4
-rw-r--r--changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml4
-rw-r--r--changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml4
-rw-r--r--changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml4
-rw-r--r--changelogs/unreleased/fix-mrs-merged-immediately.yml4
-rw-r--r--changelogs/unreleased/fix-n-plus-one-in-url-builder.yml4
-rw-r--r--changelogs/unreleased/fix-overflow-slash-commands.yml4
-rw-r--r--changelogs/unreleased/fix-runner_online_check.yml4
-rw-r--r--changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml4
-rw-r--r--changelogs/unreleased/fix-u2f-for-opera.yml4
-rw-r--r--changelogs/unreleased/fix_docs_commits_multiple_files.yml5
-rw-r--r--changelogs/unreleased/foreign-keys-for-project-model.yml4
-rw-r--r--changelogs/unreleased/gitaly-mandatory.yml4
-rw-r--r--changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml4
-rw-r--r--changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/help-landing-page-customizations.yml4
-rw-r--r--changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml4
-rw-r--r--changelogs/unreleased/issue_20900.yml4
-rw-r--r--changelogs/unreleased/issue_30126_be.yml4
-rw-r--r--changelogs/unreleased/issue_33205.yml4
-rw-r--r--changelogs/unreleased/issueable-list-cleanup.yml4
-rw-r--r--changelogs/unreleased/karma-headless-chrome.yml4
-rw-r--r--changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml4
-rw-r--r--changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml4
-rw-r--r--changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml4
-rw-r--r--changelogs/unreleased/monitoring-dashboard-fix-y-label.yml4
-rw-r--r--changelogs/unreleased/moved-submodules.yml4
-rw-r--r--changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml4
-rw-r--r--changelogs/unreleased/new-navigation-custom-logo.yml4
-rw-r--r--changelogs/unreleased/pat-alert-when-signin-disabled.yml4
-rw-r--r--changelogs/unreleased/pat-msg-on-auth-failure.yml4
-rw-r--r--changelogs/unreleased/polish-sidebar-toggle.yml4
-rw-r--r--changelogs/unreleased/post-upload-pack-opt-out.yml4
-rw-r--r--changelogs/unreleased/project-readme-limited-width.yml4
-rw-r--r--changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml4
-rw-r--r--changelogs/unreleased/replase_spinach_spec_create-feature.yml4
-rw-r--r--changelogs/unreleased/sh-add-mr-simple-mode.yml4
-rw-r--r--changelogs/unreleased/sh-allow-force-repo-create.yml4
-rw-r--r--changelogs/unreleased/sh-bump-oauth2-gem.yml4
-rw-r--r--changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml4
-rw-r--r--changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml4
-rw-r--r--changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml4
-rw-r--r--changelogs/unreleased/sh-optimize-project-commit-api.yml4
-rw-r--r--changelogs/unreleased/speed-up-graphs.yml4
-rw-r--r--changelogs/unreleased/speed-up-issue-counting-for-a-project.yml5
-rw-r--r--changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml4
-rw-r--r--changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml5
-rw-r--r--changelogs/unreleased/tc-follow-up-mia.yml4
-rw-r--r--changelogs/unreleased/tc-link-to-commit-on-help-page.yml4
-rw-r--r--changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml4
-rw-r--r--changelogs/unreleased/workhorse-2-3-0.yml4
-rw-r--r--changelogs/unreleased/zj-commit-status-sortable-name.yml4
-rw-r--r--changelogs/unreleased/zj-faster-charts-page.yml4
-rw-r--r--changelogs/unreleased/zj-pipeline-badge-improvements.yml4
-rw-r--r--changelogs/unreleased/zj-review-apps-usage-data.yml4
-rw-r--r--changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml4
-rw-r--r--config/environments/test.rb2
-rw-r--r--config/gitlab.yml.example50
-rw-r--r--config/initializers/1_settings.rb18
-rw-r--r--config/initializers/8_metrics.rb3
-rw-r--r--config/initializers/grape_route_helpers_fix.rb35
-rw-r--r--config/initializers/lograge.rb2
-rw-r--r--config/prometheus/additional_metrics.yml23
-rw-r--r--config/routes/api.rb2
-rw-r--r--config/routes/project.rb6
-rw-r--r--config/webpack.config.js3
-rw-r--r--db/migrate/20170710083355_clean_stage_id_reference_migration.rb18
-rw-r--r--db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb34
-rw-r--r--db/schema.rb2
-rw-r--r--doc/README.md8
-rw-r--r--doc/administration/auth/ldap.md65
-rw-r--r--doc/administration/container_registry.md29
-rw-r--r--doc/administration/high_availability/database.md7
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md16
-rw-r--r--doc/api/README.md3
-rw-r--r--doc/api/ci/lint.md2
-rw-r--r--doc/api/group_milestones.md120
-rw-r--r--doc/api/issues.md40
-rw-r--r--doc/api/projects.md91
-rw-r--r--doc/api/v3_to_v4.md8
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md5
-rw-r--r--doc/articles/index.md21
-rw-r--r--doc/ci/yaml/README.md26
-rw-r--r--doc/development/background_migrations.md36
-rw-r--r--doc/development/code_review.md49
-rw-r--r--doc/development/fe_guide/performance.md12
-rw-r--r--doc/development/fe_guide/style_guide_js.md1
-rw-r--r--doc/development/sidekiq_style_guide.md7
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/README.md20
-rw-r--r--doc/integration/external-issue-tracker.md6
-rw-r--r--doc/integration/img/enable_trello_powerup.pngbin0 -> 17905 bytes
-rw-r--r--doc/integration/img/trello_card_with_gitlab_powerup.pngbin0 -> 18667 bytes
-rw-r--r--doc/integration/trello_power_up.md42
-rw-r--r--doc/user/index.md175
-rw-r--r--doc/user/project/integrations/bugzilla.md4
-rw-r--r--doc/user/project/integrations/img/webhook_testing.pngbin0 -> 191267 bytes
-rw-r--r--doc/user/project/integrations/jira.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md20
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md1
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md2
-rw-r--r--doc/user/project/integrations/redmine.md2
-rw-r--r--doc/user/project/integrations/webhooks.md9
-rw-r--r--doc/user/project/pipelines/schedules.md7
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--features/project/badges/build.feature27
-rw-r--r--features/steps/project/badges/build.rb32
-rw-r--r--features/steps/project/services.rb1
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--lib/api/access_requests.rb1
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/award_emoji.rb1
-rw-r--r--lib/api/broadcast_messages.rb1
-rw-r--r--lib/api/deploy_keys.rb1
-rw-r--r--lib/api/entities.rb58
-rw-r--r--lib/api/environments.rb1
-rw-r--r--lib/api/group_milestones.rb85
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/related_resources_helpers.rb28
-rw-r--r--lib/api/issues.rb3
-rw-r--r--lib/api/labels.rb1
-rw-r--r--lib/api/members.rb1
-rw-r--r--lib/api/merge_requests.rb18
-rw-r--r--lib/api/milestone_responses.rb98
-rw-r--r--lib/api/milestones.rb154
-rw-r--r--lib/api/notes.rb1
-rw-r--r--lib/api/project_hooks.rb1
-rw-r--r--lib/api/project_milestones.rb91
-rw-r--r--lib/api/project_snippets.rb1
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/runner.rb1
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/services.rb6
-rw-r--r--lib/api/snippets.rb1
-rw-r--r--lib/api/system_hooks.rb1
-rw-r--r--lib/api/triggers.rb12
-rw-r--r--lib/api/users.rb5
-rw-r--r--lib/api/v3/entities.rb31
-rw-r--r--lib/api/v3/triggers.rb11
-rw-r--r--lib/api/variables.rb1
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb16
-rw-r--r--lib/banzai/filter/image_link_filter.rb2
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb1
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/external_issue_parser.rb10
-rw-r--r--lib/ci/api/entities.rb2
-rw-r--r--lib/ci/api/triggers.rb11
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb3
-rw-r--r--lib/gitlab/background_migration.rb2
-rw-r--r--lib/gitlab/badge/pipeline/metadata.rb (renamed from lib/gitlab/badge/build/metadata.rb)8
-rw-r--r--lib/gitlab/badge/pipeline/status.rb (renamed from lib/gitlab/badge/build/status.rb)10
-rw-r--r--lib/gitlab/badge/pipeline/template.rb (renamed from lib/gitlab/badge/build/template.rb)9
-rw-r--r--lib/gitlab/ci/config/entry/job.rb10
-rw-r--r--lib/gitlab/ci/trace/stream.rb3
-rw-r--r--lib/gitlab/data_builder/push.rb2
-rw-r--r--lib/gitlab/data_builder/wiki_page.rb22
-rw-r--r--lib/gitlab/ee_compat_check.rb13
-rw-r--r--lib/gitlab/git/commit.rb38
-rw-r--r--lib/gitlab/git/diff.rb2
-rw-r--r--lib/gitlab/git/diff_collection.rb74
-rw-r--r--lib/gitlab/git/repository.rb10
-rw-r--r--lib/gitlab/git/tree.rb59
-rw-r--r--lib/gitlab/gitaly_client.rb6
-rw-r--r--lib/gitlab/gitaly_client/commit.rb14
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb51
-rw-r--r--lib/gitlab/gitaly_client/diff.rb2
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb16
-rw-r--r--lib/gitlab/i18n.rb6
-rw-r--r--lib/gitlab/ldap/config.rb56
-rw-r--r--lib/gitlab/path_regex.rb20
-rw-r--r--lib/gitlab/reference_extractor.rb7
-rw-r--r--lib/gitlab/regex.rb16
-rw-r--r--lib/gitlab/slash_commands/issue_command.rb2
-rw-r--r--lib/gitlab/untrusted_regexp.rb12
-rw-r--r--lib/gitlab/usage_data.rb15
-rw-r--r--lib/gitlab/user_access.rb36
-rw-r--r--lib/gitlab/workhorse.rb7
-rw-r--r--lib/tasks/gitlab/task_helpers.rb9
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
-rw-r--r--locale/fr/gitlab.po1123
-rw-r--r--locale/ja/gitlab.po33
-rw-r--r--locale/pt_BR/gitlab.po27
-rw-r--r--locale/uk/gitlab.po108
-rw-r--r--locale/zh_CN/gitlab.po10
-rw-r--r--locale/zh_HK/gitlab.po8
-rw-r--r--locale/zh_TW/gitlab.po37
-rw-r--r--qa/qa.rb9
-rw-r--r--qa/qa/page/dashboard/groups.rb (renamed from qa/qa/page/main/groups.rb)6
-rw-r--r--qa/qa/page/group/show.rb11
-rw-r--r--qa/qa/page/main/menu.rb7
-rw-r--r--qa/qa/scenario/gitlab/project/create.rb4
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb21
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb28
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb21
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb61
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb7
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb48
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb6
-rw-r--r--spec/controllers/sessions_controller_spec.rb8
-rw-r--r--spec/factories/ci/builds.rb6
-rw-r--r--spec/factories/project_hooks.rb1
-rw-r--r--spec/features/admin/admin_appearance_spec.rb4
-rw-r--r--spec/features/admin/admin_hooks_spec.rb6
-rw-r--r--spec/features/admin/admin_runners_spec.rb9
-rw-r--r--spec/features/dashboard/issues_spec.rb18
-rw-r--r--spec/features/explore/new_menu_spec.rb17
-rw-r--r--spec/features/issuables/markdown_references_spec.rb193
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb63
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb8
-rw-r--r--spec/features/oauth_login_spec.rb4
-rw-r--r--spec/features/projects/badges/list_spec.rb12
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb70
-rw-r--r--spec/features/projects/features_visibility_spec.rb19
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb24
-rw-r--r--spec/features/projects/services/jira_service_spec.rb21
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb2
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb25
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/finders/admin/projects_finder_spec.rb136
-rw-r--r--spec/helpers/application_helper_spec.rb7
-rw-r--r--spec/helpers/avatars_helper_spec.rb48
-rw-r--r--spec/helpers/hooks_helper_spec.rb20
-rw-r--r--spec/helpers/issues_helper_spec.rb8
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js9
-rw-r--r--spec/javascripts/fixtures/u2f.rb4
-rw-r--r--spec/javascripts/lazy_loader_spec.js57
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js1
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb19
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb89
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb22
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb6
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb (renamed from spec/lib/gitlab/badge/build/metadata_spec.rb)6
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb (renamed from spec/lib/gitlab/badge/build/status_spec.rb)37
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb (renamed from spec/lib/gitlab/badge/build/template_spec.rb)18
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb43
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb21
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb116
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb23
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb44
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb19
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb248
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb61
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb31
-rw-r--r--spec/lib/gitlab/regex_spec.rb11
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb20
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb29
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb7
-rw-r--r--spec/migrations/clean_stage_id_reference_migration_spec.rb34
-rw-r--r--spec/models/ability_spec.rb8
-rw-r--r--spec/models/ci/build_spec.rb74
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/concerns/mentionable_spec.rb18
-rw-r--r--spec/models/group_spec.rb4
-rw-r--r--spec/models/hooks/project_hook_spec.rb6
-rw-r--r--spec/models/hooks/service_hook_spec.rb4
-rw-r--r--spec/models/hooks/system_hook_spec.rb3
-rw-r--r--spec/models/list_spec.rb6
-rw-r--r--spec/models/merge_request_spec.rb52
-rw-r--r--spec/models/notification_setting_spec.rb9
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb10
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb2
-rw-r--r--spec/models/project_spec.rb57
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/repository_spec.rb16
-rw-r--r--spec/models/route_spec.rb2
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/policies/ci/build_policy_spec.rb76
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb66
-rw-r--r--spec/policies/global_policy_spec.rb20
-rw-r--r--spec/policies/project_policy_spec.rb42
-rw-r--r--spec/requests/api/group_milestones_spec.rb21
-rw-r--r--spec/requests/api/issues_spec.rb13
-rw-r--r--spec/requests/api/merge_requests_spec.rb14
-rw-r--r--spec/requests/api/project_milestones_spec.rb25
-rw-r--r--spec/requests/api/projects_spec.rb57
-rw-r--r--spec/requests/api/triggers_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb19
-rw-r--r--spec/requests/api/v3/triggers_spec.rb3
-rw-r--r--spec/requests/ci/api/builds_spec.rb66
-rw-r--r--spec/requests/ci/api/triggers_spec.rb14
-rw-r--r--spec/routing/project_routing_spec.rb22
-rw-r--r--spec/serializers/build_details_entity_spec.rb89
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb4
-rw-r--r--spec/serializers/job_entity_spec.rb11
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb6
-rw-r--r--spec/serializers/pipeline_entity_spec.rb4
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb33
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb229
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb24
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb31
-rw-r--r--spec/services/ci/retry_build_service_spec.rb4
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb20
-rw-r--r--spec/services/create_deployment_service_spec.rb2
-rw-r--r--spec/services/git_push_service_spec.rb58
-rw-r--r--spec/services/issues/close_service_spec.rb8
-rw-r--r--spec/services/issues/duplicate_service_spec.rb80
-rw-r--r--spec/services/issues/update_service_spec.rb21
-rw-r--r--spec/services/merge_requests/build_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb9
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb49
-rw-r--r--spec/services/system_note_service_spec.rb50
-rw-r--r--spec/services/test_hook_service_spec.rb14
-rw-r--r--spec/services/test_hooks/project_service_spec.rb188
-rw-r--r--spec/services/test_hooks/system_service_spec.rb82
-rw-r--r--spec/services/web_hook_service_spec.rb6
-rw-r--r--spec/spec_helper.rb7
-rw-r--r--spec/support/api/milestones_shared_examples.rb (renamed from spec/requests/api/milestones_spec.rb)190
-rw-r--r--spec/support/devise_helpers.rb14
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb19
-rw-r--r--spec/support/jira_service_helper.rb2
-rw-r--r--spec/support/login_helpers.rb6
-rw-r--r--spec/support/matchers/markdown_matchers.rb4
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb4
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb2
-rw-r--r--spec/support/stub_configuration.rb5
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb15
-rw-r--r--spec/workers/post_receive_spec.rb1
631 files changed, 8284 insertions, 2620 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1a65e0473c4..adde3400107 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,20 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6"
-cache:
+.default-cache: &default-cache
key: "ruby-233-with-yarn"
paths:
- - vendor/ruby
- - .yarn-cache/
+ - vendor/ruby
+ - .yarn-cache/
+
+.push-cache: &push-cache
+ cache:
+ <<: *default-cache
+ policy: push
+
+.pull-cache: &pull-cache
+ cache:
+ <<: *default-cache
+ policy: pull
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
@@ -24,11 +34,11 @@ before_script:
- source scripts/prepare_build.sh
stages:
-- build
-- prepare
-- test
-- post-test
-- pages
+ - build
+ - prepare
+ - test
+ - post-test
+ - pages
# Predefined scopes
.dedicated-runner: &dedicated-runner
@@ -41,10 +51,6 @@ stages:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
- cache:
- key: "knapsack"
- paths:
- - knapsack/
artifacts:
expire_in: 31d
paths:
@@ -79,8 +85,9 @@ stages:
- /(^docs[\/-].*|.*-docs$)/
.rspec-knapsack: &rspec-knapsack
- stage: test
<<: *dedicated-runner
+ <<: *pull-cache
+ stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
@@ -110,8 +117,9 @@ stages:
<<: *except-docs
.spinach-knapsack: &spinach-knapsack
- stage: test
<<: *dedicated-runner
+ <<: *pull-cache
+ stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
@@ -157,6 +165,7 @@ build-package:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
stage: build
+ cache: {}
when: manual
script:
- scripts/trigger-build
@@ -170,6 +179,11 @@ knapsack:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
+ cache:
+ key: knapsack
+ paths:
+ - knapsack/
+ policy: pull
script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
@@ -182,6 +196,11 @@ update-knapsack:
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
+ cache:
+ key: knapsack
+ paths:
+ - knapsack/
+ policy: push
script:
- retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
@@ -194,6 +213,8 @@ setup-test-env:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
+ cache:
+ <<: *default-cache
script:
- node --version
- yarn install --pure-lockfile --cache-folder .yarn-cache
@@ -273,6 +294,7 @@ spinach-mysql 4 5: *spinach-knapsack-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
+ <<: *pull-cache
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
@@ -281,6 +303,7 @@ spinach-mysql 4 5: *spinach-knapsack-mysql
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
stage: test
script:
- bundle exec rake $CI_JOB_NAME
@@ -297,9 +320,9 @@ static-analysis:
# - Check validity of relative links
# - Make sure cURL examples in API docs use the full switches
docs lint:
+ <<: *dedicated-runner
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
- <<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
@@ -342,9 +365,10 @@ ee_compat_check:
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
script:
- bundle exec rake db:migrate:reset
@@ -357,11 +381,12 @@ db:migrate:reset-mysql:
<<: *use-mysql
.migration-paths: &migration-paths
- stage: test
<<: *dedicated-runner
+ <<: *only-canonical-masters
+ <<: *pull-cache
+ stage: test
variables:
SETUP_DB: "false"
- <<: *only-canonical-masters
script:
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
@@ -382,9 +407,10 @@ migration:path-mysql:
<<: *use-mysql
.db-rollback: &db-rollback
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
@@ -398,9 +424,10 @@ db:rollback-mysql:
<<: *use-mysql
.db-seed_fu: &db-seed_fu
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
variables:
SIZE: "1"
SETUP_DB: "false"
@@ -425,9 +452,10 @@ db:seed_fu-mysql:
# Frontend-related jobs
gitlab:assets:compile:
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
dependencies: []
variables:
NODE_ENV: "production"
@@ -445,14 +473,15 @@ gitlab:assets:compile:
name: webpack-report
expire_in: 31d
paths:
- - webpack-report/
+ - webpack-report/
karma:
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
- stage: test
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
+ stage: test
variables:
BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
@@ -470,6 +499,7 @@ karma:
codeclimate:
<<: *except-docs
+ <<: *pull-cache
before_script: []
image: docker:latest
stage: test
@@ -485,10 +515,11 @@ codeclimate:
paths: [codeclimate.json]
coverage:
- stage: post-test
- services: []
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: post-test
+ services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
@@ -505,7 +536,10 @@ coverage:
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
stage: post-test
+ dependencies:
+ - setup-test-env
before_script: []
script:
- find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
@@ -517,9 +551,10 @@ lint:javascript:report:
- eslint-report.html
pages:
+ <<: *dedicated-runner
+ <<: *pull-cache
before_script: []
stage: pages
- <<: *dedicated-runner
dependencies:
- coverage
- karma
@@ -543,6 +578,7 @@ pages:
# rubygems.org in the future.
cache gems:
<<: *dedicated-runner
+ <<: *pull-cache
only:
- tags
variables:
@@ -557,8 +593,9 @@ cache gems:
- master@gitlab-org/gitlab-ee
gitlab_git_test:
+ <<: *pull-cache
+ <<: *except-docs
variables:
SETUP_DB: "false"
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
- <<: *except-docs
diff --git a/.rubocop.yml b/.rubocop.yml
index 9785e7626f9..f661a29d9d1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -563,7 +563,7 @@ Style/Proc:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
- Max: 57.08
+ Max: 56.96
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de3b4b0d3e7..580d2357512 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,209 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.4.1 (2017-07-25)
+
+- Fix pipeline_schedules pages throwing error 500 (when ref is empty). !12983
+- Fix editing project with container images present. !13028
+- Fix some invalid entries in PO files. !13032
+- Fix cross site request protection when logging in as a regular user when LDAP is enabled. !13049
+- Fix bug causing metrics files to be truncated. !35420
+- Fix anonymous access to public projects in groups with pending invites.
+- Fixed issue boards sidebar close icon size.
+- Fixed duplicate new milestone buttons when new navigation is turned on.
+- Fix margins in the mini graph for pipeline in commits box.
+
+## 9.4.0 (2017-07-22)
+
+- Add blame view age mapping. !7198 (Jeff Stubler)
+- Add support for image and services configuration in .gitlab-ci.yml. !8578
+- Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients. !9045 (jneen)
+- Use fa-chevron-down on dropdown arrows for consistency. !9659 (TM Lee)
+- Update the devise mail templates to match the design of the pipeline emails. !10483 (Alexis Reigel)
+- Handle renamed submodules in repository browser. !10798 (David Turner)
+- Display all current broadcast messages, not just the last one. !11113 (rickettm)
+- Fix CI/CD status in case there are only allowed to failed jobs in the pipeline. !11166
+- Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname. !11218 (Stefan Hanreich)
+- Moved "Members in a project" menu entry and path locations. !11560
+- Additional Prometheus metrics support. !11712
+- Rename all reserved paths that could have been created. !11713
+- Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name. !11713
+- Fix offline runner detection. !11751 (Alessio Caiazza)
+- Use authorize_update_pipeline_schedule in PipelineSchedulesController. !11846
+- Rollback project repo move if there is an error in Projects::TransferService. !11877
+- Help landing page customizations. !11878 (Robin Bobbitt)
+- Fixes "sign in / Register" active state underline misalignment. !11890 (Frank Sierra)
+- Honor the "Remember me" parameter for OAuth-based login. !11963
+- Instruct user to use personal access token for Git over HTTP. !11986 (Robin Bobbitt)
+- Accept image for avatar in project API. !11988 (Ivan Chernov)
+- Supplement Simplified Chinese translation of Project Page & Repository Page. !11994 (Huang Tao)
+- Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page. !11995 (Huang Tao)
+- Make the revision on the `/help` page clickable. !12016
+- Display issue state in issue links section of merge request widget. !12021
+- Enable support for webpack code-splitting by dynamically setting publicPath at runtime. !12032
+- Replace PhantomJS with headless Chrome for karma test suite. !12036
+- Prevent description change notes when toggling tasks. !12057 (Jared Deckard <jared.deckard@gmail.com>)
+- Update QA Dockerfile to lock Chrome browser version. !12071
+- Fix FIDO U2F for Opera browser. !12082 (Jakub Kramarz and Jonas Kalderstam)
+- Supplement Bulgarian translation of Project Page & Repository Page. !12083 (Lyubomir Vasilev)
+- Removes deleted_at and pending_delete occurrences in Project related queries. !12091
+- Provide hint to create a personal access token for Git over HTTP. !12105 (Robin Bobbitt)
+- Display own user id in account settings page. !12141 (Riccardo Padovani)
+- Accept image for avatar in user API. !12143 (Ivan Chernov)
+- Disable fork button on project limit. !12145 (Ivan Chernov)
+- Added "created_after" and "created_before" params to issuables. !12151 (Kyle Bishop @kybishop)
+- Supplement Portuguese Brazil translation of Project Page & Repository Page. !12156 (Huang Tao)
+- Add review apps to usage metrics. !12185
+- Adding French translations. !12200 (Erwan "Dremor" Georget)
+- Ensures default user limits when external user is unchecked. !12218
+- Provide KUBECONFIG from KubernetesService for runners. !12223
+- Filter archived project in API v3 only if param present. !12245 (Ivan Chernov)
+- Add explicit message when no runners on admin. !12266 (Takuya Noguchi)
+- Split pipelines as internal and external in the usage data. !12277
+- Fix API Scoping. !12300
+- Remove registry image delete button if user cant delete it. !12317 (Ivan Chernov)
+- Allow the feature flags to be enabled/disabled with more granularity. !12357
+- Allow to enable the performance bar per user or Feature group. !12362
+- Rename duplicated variables with the same key for projects. Add environment_scope column to variables and add unique constraint to make sure that no variables could be created with the same key within a project. !12363
+- Add variables to pipelines schedules. !12372
+- Add User#full_private_access? to check if user has access to all private groups & projects. !12373
+- Change milestone endpoint for groups. !12374 (Takuya Noguchi)
+- Improve performance of the pipeline charts page. !12378
+- Add option to run Gitaly on a remote server. !12381
+- #20628 Enable implicit grant in GitLab as OAuth Provider. !12384 (Mateusz Pytel)
+- Replace 'snippets/snippets.feature' spinach with rspec. !12385 (Alexander Randa @randaalex)
+- Add Simplified Chinese translations of Commits Page. !12405 (Huang Tao)
+- Add Traditional Chinese in HongKong translations of Commits Page. !12406 (Huang Tao)
+- Add Traditional Chinese in Taiwan translations of Commits Page. !12407 (Huang Tao)
+- Add Portuguese Brazil translations of Commits Page. !12408 (Huang Tao)
+- Add French translations of Commits Page. !12409 (Huang Tao)
+- Add Esperanto translations of Commits Page. !12410 (Huang Tao)
+- Add Bulgarian translations of Commits Page. !12411 (Huang Tao)
+- Remove bin/ci/upgrade.rb as not working all. !12414 (Takuya Noguchi)
+- Store merge request ref_fetched status in the database. !12424
+- Replace 'dashboard/merge_requests' spinach with rspec. !12440 (Alexander Randa (@randaalex))
+- Add Esperanto translations for Cycle Analytics, Project, and Repository pages. !12442 (Huang Tao)
+- Allow unauthenticated access to the /api/v4/users API. !12445
+- Drop GFM support for the title of Milestone/MergeRequest in template. !12451 (Takuya Noguchi)
+- Replace 'dashboard/todos' spinach with rspec. !12453 (Alexander Randa (@randaalex))
+- Cache open issue and merge request counts for project tabs to speed up project pages. !12457
+- Introduce cache policies for CI jobs. !12483
+- Improve support for external issue references. !12485
+- Fix errors caused by attempts to report already blocked or deleted users. !12502 (Horacio Bertorello)
+- Allow customize CI config path. !12509 (Keith Pope)
+- Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page. !12514 (Huang Tao)
+- Closes any open Autocomplete of the markdown editor when the form is closed. !12521
+- Inserts exact matches of name, username and email to the top of the search list. !12525
+- Use smaller min-width for dropdown-menu-nav only on mobile. !12528 (Takuya Noguchi)
+- Hide archived project labels from group issue tracker. !12547 (Horacio Bertorello)
+- Replace 'dashboard/new-project.feature' spinach with rspec. !12550 (Alexander Randa (@randaalex))
+- Remove group modal like remove project modal (requires typing + confirmation). !12569 (Diego Souza)
+- Add Italian translation of Cycle Analytics Page & Project Page & Repository Page. !12578 (Huang Tao)
+- Add Group secret variables. !12582
+- Update jobs page output to have a scrollable page. !12587
+- Add user projects API. !12596 (Ivan Chernov)
+- Allow creation of files and directories with spaces through Web UI. !12608
+- Improve members view on mobile. !12619
+- Fixed the chart legend not being set correctly. !12628
+- Add Italian translations of Commits Page. !12645 (Huang Tao)
+- Allow admins to disable all restricted visibility levels. !12649
+- Allow admins to retrieve user agent details for an issue or snippet. !12655
+- Update welcome page UX for new users. !12662
+- N+1 problems on milestone page. !12670 (Takuya Noguchi)
+- Upgrade GitLab Workhorse to v2.3.0. !12676
+- Remove option to disable Gitaly. !12677
+- Improve the performance of the project list API. !12679
+- Add creation time filters to user search API for admins. !12682
+- Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12693 (Huang Tao)
+- Undo adding the /reassign quick action. !12701
+- Fix dashboard labels dropdown. !12708
+- Username and password are no longer stripped from import url on mirror update. !12725
+- Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12743 (Huang Tao)
+- Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12744 (Huang Tao)
+- Prevent bad data being added to application settings when Redis is unavailable. !12750
+- Do not show pipeline schedule button for non-member. !12757 (Takuya Noguchi)
+- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
+- Recover from renaming project that has container images. !12840
+- Exact matches of username and email are now on top of the user search. !12868
+- Use Ghost user for last_edited_by and merge_user when original user is deleted. !12933
+- Fix docker tag reference routing constraints. !12961
+- Optimize creation of commit API by using Repository#commit instead of Repository#commits.
+- Speed up used languages calculation on charts page.
+- Make loading new merge requests (those created after the 9.4 upgrade) faster.
+- Ensure participants for issues, merge requests, etc. are calculated correctly when sending notifications.
+- Handle nameless legacy jobs.
+- Bump Faraday and dependent OAuth2 gem version to support no_proxy variable.
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Render CI statuses with warnings in orange.
+- Document the Delete Merged Branches functionality.
+- Add wells to admin dashboard overview to fix spacing problems.
+- Removes hover style for nodes that are either links or buttons in the pipeline graph.
+- more visual contrast in pagination widget.
+- Deprecate Healthcheck Access Token in favor of IP whitelist.
+- Drop GFM support for issuable title on milestone for consistency and performance. (Takuya Noguchi)
+- fix left & right padding on sidebar.
+- Cleanup minor UX issues in the performance dashboard.
+- Remove two columned layout from project member settings.
+- Make font size of contextual sub menu items 14px.
+- Fix vertical space in job details sidebar.
+- Fix alignment of controls in mr issuable list.
+- Add wip message to new navigation preference section.
+- Add group members counting and plan related data on namespaces API.
+- Fix spacing on runner buttons.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+- Change order of monospace fonts to fix bug on some linux distros.
+- Limit commit & snippets comments width.
+- Fixed dashboard milestone tabs not loading.
+- Detect if file that appears to be text in the first 1024 bytes is actually binary afer loading all data.
+- Fix inconsistent display of the "Browse files" button in the commit list.
+- Implement diff viewers.
+- Fix 'New merge request' button for users who don't have push access to canonical project.
+- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
+- Show group name instead of path on group page.
+- Don't check if MailRoom is running on Omnibus.
+- Limit OpenGraph image size to 64x64.
+- Don't show auxiliary blob viewer for README when there is no wiki.
+- Strip trailing whitespace in relative submodule URL.
+- Update /target_branch slash command description to be more consistent.
+- Remove unnecessary top padding on group MR index.
+- Added printing_merge_requst_link_enabled to the API. (David Turner <dturner@twosigma.com>)
+- Re-enable realtime for environments table.
+- Create responsive mobile view for pipelines table.
+- Adds realtime feature to job show view header and sidebar info. Updates UX.
+- Use color inputs for broadcast messages.
+- Center dropdown for mini graph.
+- Users can subscribe to group labels on the group labels page.
+- Add issuable-list class to shared mr/issue lists to fix new responsive layout design.
+- Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor of "slash commands".
+- Don't mark empty MRs as merged on push to the target branch.
+- Improve issue rendering performance with lots of notes from other users.
+- Fixed overflow on mobile screens for the slash commands.
+- Fix an infinite loop when handling user-supplied regular expressions.
+- Fixed sidebar not collapsing on merge requests in mobile screens.
+- Speed up project removals by adding foreign keys with cascading deletes to various tables.
+- Fix mobile view of files view buttons.
+- Fixed dropdown filter input not focusing after transition.
+- Fixed GFM references not being included when updating issues inline.
+- Remove issues/merge requests drag n drop and sorting from milestone view.
+- Add native group milestones.
+- Fix API bug accepting wrong parameter to create merge request.
+- Clean up UI of issuable lists and make more responsive.
+- Improve the overall UX for the new monitoring dashboard.
+- Fixed the y_label not setting correctly for each graph on the monitoring dashboard.
+- Changed utilities imports from ~ to relative paths.
+- Remove unused space in sidebar todo toggle when not signed in.
+- Limit the width of the projects README text.
+- Add a simple mode to merge request API.
+- Make Project#ensure_repository force create a repo.
+- Use uploads/system directory for personal snippets.
+- Defer project destroys within a namespace in Groups::DestroyService#async_execute.
+- Log rescued exceptions to Sentry.
+- Remove remaining N+1 queries in merge requests API with emojis and labels.
+
+## 9.3.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
## 9.3.8 (2017-07-19)
- Improve support for external issue references. !12485
@@ -270,6 +473,10 @@ entry.
- Remove foreigh key on ci_trigger_schedules only if it exists.
- Allow translation of Pipeline Schedules.
+## 9.2.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
## 9.2.8 (2017-07-19)
- Improve support for external issue references. !12485
@@ -521,6 +728,10 @@ entry.
- Fix preemptive scroll bar on user activity calendar.
- Pipeline chat notifications convert seconds to minutes and hours.
+## 9.1.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
## 9.1.8 (2017-07-19)
- Improve support for external issue references. !12485
@@ -840,6 +1051,10 @@ entry.
- Only send chat notifications for the default branch.
- Don't fill in the default kubernetes namespace.
+## 9.0.12 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
## 9.0.11 (2017-07-19)
- Renders 404 if given project is not readable by the user on Todos dashboard.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 89e505709a3..12fb34b24be 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -31,7 +31,7 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- - [Technical debt](#technical-debt)
+ - [Technical and UX debt](#technical-and-ux-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
@@ -114,8 +114,8 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, etc.
-- Team: ~CI, ~Discussion, ~Edge, ~Frontend, ~Platform, etc.
+- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Team: ~CI, ~Discussion, ~Edge, ~Platform, etc.
- Priority: ~Deliverable, ~Stretch
All labels, their meaning and priority are defined on the
@@ -278,7 +278,7 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label `feature proposal` to the issue or add the following
+members to add the label ~"feature proposal" to the issue or add the following
code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
@@ -344,27 +344,29 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
-### Technical debt
+### Technical and UX debt
-In order to track things that can be improved in GitLab's codebase, we created
-the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+In order to track things that can be improved in GitLab's codebase,
+we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+For user experience improvements, we use the ~"UX debt" label.
-This label should be added to issues that describe things that can be improved,
-shortcuts that have been taken, code that needs refactoring, features that need
-additional attention, and all other things that have been left behind due to
-high velocity of development.
+These labels should be added to issues that describe things that can be improved,
+shortcuts that have been taken, features that need additional attention, and all
+other things that have been left behind due to high velocity of development.
+For example, code that needs refactoring should use the ~"technical debt" label,
+user experience refinements should use the ~"UX debt" label.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
-can be combined with the `technical debt` label, to make it easier to schedule
+can be combined with these labels, to make it easier to schedule
the improvements for a release.
-Issues tagged with the `technical debt` label have the same priority like issues
+Issues tagged with these labels have the same priority like issues
that describe a new feature to be introduced in GitLab, and should be scheduled
for a release by the appropriate person.
-Make sure to mention the merge request that the `technical debt` issue is
-associated with in the description of the issue.
+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.
### Stewardship
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 59dad104b0b..ca222b7cf39 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.21.2
+0.23.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index c7cb1311a64..8a30e8f94a3 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.3.1
+5.4.0
diff --git a/Gemfile b/Gemfile
index da66651b894..43109de1b45 100644
--- a/Gemfile
+++ b/Gemfile
@@ -16,6 +16,7 @@ gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
gem 'rugged', '~> 0.25.1.1'
+gem 'grape-route-helpers', '~> 2.0.0'
gem 'faraday', '~> 0.12'
@@ -60,7 +61,8 @@ gem 'browser', '~> 2.2'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
+gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
+gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
@@ -71,7 +73,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
-gem 'grape', '~> 0.19.0'
+gem 'grape', '~> 0.19.2'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
@@ -164,7 +166,7 @@ gem 'rainbow', '~> 2.2'
gem 'settingslogic', '~> 2.0.9'
# Linear-time regex library for untrusted regular expressions
-gem 're2', '~> 1.0.0'
+gem 're2', '~> 1.1.1'
# Misc
@@ -353,7 +355,7 @@ group :development, :test do
end
group :test do
- gem 'shoulda-matchers', '~> 2.8.0', require: false
+ gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.6.2'
gem 'webmock', '~> 2.3.2'
@@ -386,7 +388,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
-gem 'gitaly', '~> 0.14.0'
+gem 'gitaly', '~> 0.18.0'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index dfa7acc8917..6c2ac9368f2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -269,7 +269,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly (0.14.0)
+ gitaly (0.18.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -288,11 +288,11 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.1)
- gitlab_omniauth-ldap (1.2.1)
- net-ldap (~> 0.9)
- omniauth (~> 1.0)
- pyu-ruby-sasl (~> 0.0.3.1)
- rubyntlm (~> 0.3)
+ gitlab_omniauth-ldap (2.0.3)
+ net-ldap (~> 0.16)
+ omniauth (~> 1.3)
+ pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
+ rubyntlm (~> 0.5)
globalid (0.3.7)
activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.1)
@@ -332,19 +332,23 @@ GEM
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
- grape (0.19.1)
+ grape (0.19.2)
activesupport
builder
hashie (>= 2.1.0)
multi_json (>= 1.3.2)
multi_xml (>= 0.5.2)
- mustermann-grape (~> 0.4.0)
+ mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
+ grape-route-helpers (2.0.0)
+ activesupport
+ grape (~> 0.16, >= 0.16.0)
+ rake
grpc (1.4.0)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
@@ -463,12 +467,11 @@ GEM
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
- mustermann (0.4.0)
- tool (~> 0.2)
- mustermann-grape (0.4.0)
- mustermann (= 0.4.0)
+ mustermann (1.0.0)
+ mustermann-grape (1.0.0)
+ mustermann (~> 1.0.0)
mysql2 (0.4.5)
- net-ldap (0.12.1)
+ net-ldap (0.16.0)
netrc (0.11.0)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
@@ -592,7 +595,7 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
- prometheus-client-mmap (0.7.0.beta9)
+ prometheus-client-mmap (0.7.0.beta10)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
@@ -657,7 +660,7 @@ GEM
debugger-ruby_core_source (~> 1.3)
rdoc (4.2.2)
json (~> 1.4)
- re2 (1.0.0)
+ re2 (1.1.1)
recaptcha (3.0.0)
json
recursive-open-struct (1.0.0)
@@ -741,7 +744,7 @@ GEM
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
- rubyntlm (0.5.2)
+ rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.4.0)
@@ -775,8 +778,8 @@ GEM
sexp_processor (4.9.0)
sham_rack (1.3.6)
rack
- shoulda-matchers (2.8.0)
- activesupport (>= 3.0.0)
+ shoulda-matchers (3.1.2)
+ activesupport (>= 4.0.0)
sidekiq (5.0.4)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
@@ -850,7 +853,6 @@ GEM
timfel-krb5-auth (0.8.3)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
- tool (0.2.3)
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
@@ -972,17 +974,18 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly (~> 0.14.0)
+ gitaly (~> 0.18.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
- gitlab_omniauth-ldap (~> 1.2.1)
+ gitlab_omniauth-ldap (~> 2.0.3)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
- grape (~> 0.19.0)
+ grape (~> 0.19.2)
grape-entity (~> 0.6.0)
+ grape-route-helpers (~> 2.0.0)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
@@ -1010,6 +1013,7 @@ DEPENDENCIES
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.5)
+ net-ldap
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
@@ -1057,7 +1061,7 @@ DEPENDENCIES
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
- re2 (~> 1.0.0)
+ re2 (~> 1.1.1)
recaptcha (~> 3.0)
redcarpet (~> 3.4)
redis (~> 3.2)
@@ -1086,7 +1090,7 @@ DEPENDENCIES
sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
- shoulda-matchers (~> 2.8.0)
+ shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.0)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
diff --git a/VERSION b/VERSION
index be3d36737cc..027fe8dd2cf 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.4.0-pre
+9.5.0-pre
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index ba9d9a3e1f7..54257531284 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import './lib/utils/common_utils';
+import { placeholderImage } from './lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
@@ -56,6 +57,11 @@ const gfmRules = {
return text;
},
},
+ ImageLazyLoadFilter: {
+ 'img'(el, text) {
+ return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ },
+ },
VideoLinkFilter: {
'.video-container'(el) {
const videoEl = el.querySelector('video');
@@ -163,7 +169,9 @@ const gfmRules = {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
'img'(el) {
- return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ const imageSrc = el.src;
+ const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
+ return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 9e90a36a364..ffe97c071ba 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -41,7 +41,6 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
-import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
import Pipelines from './pipelines';
import BlobViewer from './blob/viewer/index';
@@ -396,12 +395,6 @@ import PerformanceBar from './performance_bar';
new Search();
break;
case 'projects:settings:repository:show':
- // Initialize Protected Branch Settings
- new gl.ProtectedBranchCreate();
- new gl.ProtectedBranchEditList();
- // Initialize Protected Tag Settings
- new ProtectedTagCreate();
- new ProtectedTagEditList();
// Initialize expandable settings panels
initSettingsPanels();
break;
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 5838b1bdbb7..a81389ab088 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -2,8 +2,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { input, tokenKeys } = options;
+ super(options);
this.config = {
Filter: {
template: 'hint',
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 34a9e34070c..2615d626c4c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -5,8 +5,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { input, endpoint, symbol } = options;
+ super(options);
this.symbol = symbol;
this.config = {
Ajax: {
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 19fed771197..7246ccbb281 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -5,8 +5,9 @@ import './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
class DropdownUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { tokenKeys } = options;
+ super(options);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index 4209ca0d6e2..9e9a9ef74be 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -1,7 +1,7 @@
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
+ constructor({ droplab, dropdown, input, filter }) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 6bc6bc43f51..61cef435209 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -42,13 +42,19 @@ class FilteredSearchDropdownManager {
milestone: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
+ extraArguments: {
+ endpoint: `${this.baseEndpoint}/milestones.json`,
+ symbol: '%',
+ },
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
+ extraArguments: {
+ endpoint: `${this.baseEndpoint}/labels.json`,
+ symbol: '~',
+ },
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
@@ -97,13 +103,19 @@ class FilteredSearchDropdownManager {
let forceShowList = false;
if (!mappingKey.reference) {
- const dl = this.droplab;
- const defaultArguments =
- [null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
- const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
+ const defaultArguments = {
+ droplab: this.droplab,
+ dropdown: element,
+ input: this.filteredSearchInput,
+ tokenKeys: this.filteredSearchTokenKeys,
+ filter: key,
+ };
+ const extraArguments = mappingKey.extraArguments || {};
+ const glArguments = Object.assign({}, defaultArguments, extraArguments);
// Passing glArguments to `new gl[glClass](<arguments>)`
- mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
+ mappingKey.reference =
+ new (Function.prototype.bind.apply(gl[glClass], [null, glArguments]))();
}
if (firstLoad) {
diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js
new file mode 100644
index 00000000000..f739db751a6
--- /dev/null
+++ b/app/assets/javascripts/how_to_merge.js
@@ -0,0 +1,12 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const modal = $('#modal_merge_info').modal({
+ modal: true,
+ show: false,
+ });
+ $('.how_to_merge_link').bind('click', () => {
+ modal.show();
+ });
+ $('.modal-header .close').bind('click', () => {
+ modal.hide();
+ });
+});
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 4f376599ba9..d314f3c4d43 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -86,10 +86,23 @@ export default class IssuableBulkUpdateSidebar {
this.toggleCheckboxDisplay(enable);
if (enable) {
+ this.initAffix();
SidebarHeightManager.init();
}
}
+ initAffix() {
+ if (!this.$sidebar.hasClass('affix-top')) {
+ const offsetTop = $('.scrolling-tabs-container').outerHeight() + $('.sub-nav-scroll').outerHeight();
+
+ this.$sidebar.affix({
+ offset: {
+ top: offsetTop,
+ },
+ });
+ }
+ }
+
updateSelectedIssuableIds() {
this.$issuableIdsInput.val(IssuableBulkUpdateSidebar.getCheckedIssueIds());
}
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 71064ccc539..6186ffe20b3 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
import _ from 'underscore';
+import Cookies from 'js-cookie';
+import NewNavSidebar from './new_sidebar';
(function() {
var hideEndFade;
@@ -53,6 +55,11 @@ import _ from 'underscore';
}
$(() => {
+ if (Cookies.get('new_nav') === 'true') {
+ const newNavSidebar = new NewNavSidebar();
+ newNavSidebar.bindEvents();
+ }
+
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
});
}).call(window);
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
new file mode 100644
index 00000000000..3d64b121fa7
--- /dev/null
+++ b/app/assets/javascripts/lazy_loader.js
@@ -0,0 +1,76 @@
+/* eslint-disable one-export, one-var, one-var-declaration-per-line */
+
+import _ from 'underscore';
+
+export const placeholderImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+const SCROLL_THRESHOLD = 300;
+
+export default class LazyLoader {
+ constructor(options = {}) {
+ this.lazyImages = [];
+ this.observerNode = options.observerNode || '#content-body';
+
+ const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
+ const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
+
+ window.addEventListener('scroll', throttledScrollCheck);
+ window.addEventListener('resize', debouncedElementsInView);
+
+ const scrollContainer = options.scrollContainer || window;
+ scrollContainer.addEventListener('load', () => this.loadCheck());
+ }
+ searchLazyImages() {
+ this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
+ this.checkElementsInView();
+ }
+ startContentObserver() {
+ const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
+
+ if (contentNode) {
+ const observer = new MutationObserver(() => this.searchLazyImages());
+
+ observer.observe(contentNode, {
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+ loadCheck() {
+ this.searchLazyImages();
+ this.startContentObserver();
+ }
+ scrollCheck() {
+ requestAnimationFrame(() => this.checkElementsInView());
+ }
+ checkElementsInView() {
+ const scrollTop = pageYOffset;
+ const visHeight = scrollTop + innerHeight + SCROLL_THRESHOLD;
+ let imgBoundRect, imgTop, imgBound;
+
+ // Loading Images which are in the current viewport or close to them
+ this.lazyImages = this.lazyImages.filter((selectedImage) => {
+ if (selectedImage.getAttribute('data-src')) {
+ imgBoundRect = selectedImage.getBoundingClientRect();
+
+ imgTop = scrollTop + imgBoundRect.top;
+ imgBound = imgTop + imgBoundRect.height;
+
+ if (scrollTop < imgBound && visHeight > imgTop) {
+ LazyLoader.loadImage(selectedImage);
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ });
+ }
+ static loadImage(img) {
+ if (img.getAttribute('data-src')) {
+ img.setAttribute('src', img.getAttribute('data-src'));
+ img.removeAttribute('data-src');
+ img.classList.remove('lazy');
+ img.classList.add('js-lazy-loaded');
+ }
+ }
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 26c67fb721c..e96d51de838 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -109,6 +109,7 @@ import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
+import LazyLoader from './lazy_loader';
import './line_highlighter';
import './logo';
import './member_expiration_date';
@@ -166,6 +167,11 @@ window.addEventListener('load', function onLoad() {
gl.utils.handleLocationHash();
}, false);
+gl.lazyLoader = new LazyLoader({
+ scrollContainer: window,
+ observerNode: '#content-body'
+});
+
$(function () {
var $body = $('body');
var $document = $(document);
@@ -284,13 +290,7 @@ $(function () {
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', function () {
- $('.header-content .title, .header-content .navbar-sub-nav').toggle();
- $('.header-content .header-logo').toggle();
- $('.header-content .navbar-collapse').toggle();
- $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
- return $('.navbar-toggle').toggleClass('active');
- });
+ $('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded'));
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index c4e379a4a0b..8be7314ded8 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -175,7 +175,7 @@ import Cookies from 'js-cookie';
getConflictsCountText() {
const count = this.getConflictsCount();
- const text = count ? 'conflicts' : 'conflict';
+ const text = count > 1 ? 'conflicts' : 'conflict';
return `${count} ${text}`;
},
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
new file mode 100644
index 00000000000..5f98aff8ced
--- /dev/null
+++ b/app/assets/javascripts/new_sidebar.js
@@ -0,0 +1,23 @@
+export default class NewNavSidebar {
+ constructor() {
+ this.initDomElements();
+ }
+
+ initDomElements() {
+ this.$sidebar = $('.nav-sidebar');
+ this.$overlay = $('.mobile-overlay');
+ this.$openSidebar = $('.toggle-mobile-nav');
+ this.$closeSidebar = $('.close-nav-button');
+ }
+
+ bindEvents() {
+ this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
+ this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
+ this.$overlay.on('click', () => this.toggleSidebarNav(false));
+ }
+
+ toggleSidebarNav(show) {
+ this.$sidebar.toggleClass('nav-sidebar-expanded', show);
+ this.$overlay.toggleClass('mobile-nav-open', show);
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/index.js b/app/assets/javascripts/protected_branches/index.js
new file mode 100644
index 00000000000..c9e7af127d2
--- /dev/null
+++ b/app/assets/javascripts/protected_branches/index.js
@@ -0,0 +1,9 @@
+/* eslint-disable no-unused-vars */
+
+import ProtectedBranchCreate from './protected_branch_create';
+import ProtectedBranchEditList from './protected_branch_edit_list';
+
+$(() => {
+ const protectedBranchCreate = new ProtectedBranchCreate();
+ const protectedBranchEditList = new ProtectedBranchEditList();
+});
diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
index 42993a252c3..38b1406a99f 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
@@ -1,31 +1,26 @@
-/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */
+export default class ProtectedBranchAccessDropdown {
+ constructor(options) {
+ this.options = options;
+ this.initDropdown();
+ }
-(global => {
- global.gl = global.gl || {};
-
- gl.ProtectedBranchAccessDropdown = class {
- constructor(options) {
- const { $dropdown, data, onSelect } = options;
-
- $dropdown.glDropdown({
- data: data,
- selectable: true,
- inputId: $dropdown.data('input-id'),
- fieldName: $dropdown.data('field-name'),
- toggleLabel(item, el) {
- if (el.is('.is-active')) {
- return item.text;
- } else {
- return 'Select';
- }
- },
- clicked(opts) {
- const { e } = opts;
-
- e.preventDefault();
- onSelect();
+ initDropdown() {
+ const { $dropdown, data, onSelect } = this.options;
+ $dropdown.glDropdown({
+ data,
+ selectable: true,
+ inputId: $dropdown.data('input-id'),
+ fieldName: $dropdown.data('field-name'),
+ toggleLabel(item, $el) {
+ if ($el.is('.is-active')) {
+ return item.text;
}
- });
- }
- };
-})(window);
+ return 'Select';
+ },
+ clicked(options) {
+ options.e.preventDefault();
+ onSelect();
+ },
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 57ea2f52814..10da3783123 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,55 +1,51 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
-/* global ProtectedBranchDropdown */
-
-(global => {
- global.gl = global.gl || {};
-
- gl.ProtectedBranchCreate = class {
- constructor() {
- this.$wrap = this.$form = $('#new_protected_branch');
- this.buildDropdowns();
- }
-
- buildDropdowns() {
- const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
- const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
-
- // Cache callback
- this.onSelectCallback = this.onSelect.bind(this);
-
- // Allowed to Merge dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: $allowedToMergeDropdown,
- data: gon.merge_access_levels,
- onSelect: this.onSelectCallback
- });
-
- // Allowed to Push dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: $allowedToPushDropdown,
- data: gon.push_access_levels,
- onSelect: this.onSelectCallback
- });
-
- // Select default
- $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
- $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
-
- // Protected branch dropdown
- new ProtectedBranchDropdown({
- $dropdown: this.$wrap.find('.js-protected-branch-select'),
- onSelect: this.onSelectCallback
- });
- }
-
- // This will run after clicked callback
- onSelect() {
- // Enable submit button
- const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
- const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
- const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
-
- this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
- }
- };
-})(window);
+import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
+import ProtectedBranchDropdown from './protected_branch_dropdown';
+
+export default class ProtectedBranchCreate {
+ constructor() {
+ this.$form = $('.js-new-protected-branch');
+ this.buildDropdowns();
+ }
+
+ buildDropdowns() {
+ const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
+ const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
+
+ // Cache callback
+ this.onSelectCallback = this.onSelect.bind(this);
+
+ // Allowed to Merge dropdown
+ this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+
+ // Allowed to Push dropdown
+ this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+
+ // Select default
+ $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
+ $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
+
+ // Protected branch dropdown
+ this.protectedBranchDropdown = new ProtectedBranchDropdown({
+ $dropdown: this.$form.find('.js-protected-branch-select'),
+ onSelect: this.onSelectCallback,
+ });
+ }
+
+ // This will run after clicked callback
+ onSelect() {
+ // Enable submit button
+ const $branchInput = this.$form.find('input[name="protected_branch[name]"]');
+ const $allowedToMergeInput = this.$form.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
+ const $allowedToPushInput = this.$form.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
+
+ this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
index bc6110fcd4e..cc0b2ebe071 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
@@ -1,6 +1,10 @@
-/* eslint-disable comma-dangle, no-unused-vars */
-
-class ProtectedBranchDropdown {
+export default class ProtectedBranchDropdown {
+ /**
+ * @param {Object} options containing
+ * `$dropdown` target element
+ * `onSelect` event callback
+ * $dropdown must be an element created using `dropdown_branch()` rails helper
+ */
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
@@ -12,7 +16,7 @@ class ProtectedBranchDropdown {
this.bindEvents();
// Hide footer
- this.$dropdownFooter.addClass('hidden');
+ this.toggleFooter(true);
}
buildDropdown() {
@@ -21,7 +25,7 @@ class ProtectedBranchDropdown {
filterable: true,
remote: false,
search: {
- fields: ['title']
+ fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
@@ -36,10 +40,9 @@ class ProtectedBranchDropdown {
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
- const { $el, e } = options;
- e.preventDefault();
+ options.e.preventDefault();
this.onSelect();
- }
+ },
});
}
@@ -64,20 +67,22 @@ class ProtectedBranchDropdown {
}
toggleCreateNewButton(branchName) {
- this.selectedBranch = {
- title: branchName,
- id: branchName,
- text: branchName
- };
-
if (branchName) {
+ this.selectedBranch = {
+ title: branchName,
+ id: branchName,
+ text: branchName,
+ };
+
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.text(branchName);
}
- this.$dropdownFooter.toggleClass('hidden', !branchName);
+ this.toggleFooter(!branchName);
}
-}
-window.ProtectedBranchDropdown = ProtectedBranchDropdown;
+ toggleFooter(toggleState) {
+ this.$dropdownFooter.toggleClass('hidden', toggleState);
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js
index 6ef59e94384..3b920942a3f 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js
@@ -1,69 +1,67 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
+/* eslint-disable no-new */
/* global Flash */
-(global => {
- global.gl = global.gl || {};
+import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
- gl.ProtectedBranchEdit = class {
- constructor(options) {
- this.$wrap = options.$wrap;
- this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
- this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+export default class ProtectedBranchEdit {
+ constructor(options) {
+ this.$wrap = options.$wrap;
+ this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
+ this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+ this.onSelectCallback = this.onSelect.bind(this);
- this.buildDropdowns();
- }
+ this.buildDropdowns();
+ }
- buildDropdowns() {
- // Allowed to merge dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: this.$allowedToMergeDropdown,
- data: gon.merge_access_levels,
- onSelect: this.onSelect.bind(this)
- });
+ buildDropdowns() {
+ // Allowed to merge dropdown
+ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelectCallback,
+ });
- // Allowed to push dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: this.$allowedToPushDropdown,
- data: gon.push_access_levels,
- onSelect: this.onSelect.bind(this)
- });
- }
+ // Allowed to push dropdown
+ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+ }
- onSelect() {
- const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
- const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
+ onSelect() {
+ const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
+ const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
- // Do not update if one dropdown has not selected any option
- if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
+ // Do not update if one dropdown has not selected any option
+ if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
- this.$allowedToMergeDropdown.disable();
- this.$allowedToPushDropdown.disable();
+ this.$allowedToMergeDropdown.disable();
+ this.$allowedToPushDropdown.disable();
- $.ajax({
- type: 'POST',
- url: this.$wrap.data('url'),
- dataType: 'json',
- data: {
- _method: 'PATCH',
- protected_branch: {
- merge_access_levels_attributes: [{
- id: this.$allowedToMergeDropdown.data('access-level-id'),
- access_level: $allowedToMergeInput.val()
- }],
- push_access_levels_attributes: [{
- id: this.$allowedToPushDropdown.data('access-level-id'),
- access_level: $allowedToPushInput.val()
- }]
- }
+ $.ajax({
+ type: 'POST',
+ url: this.$wrap.data('url'),
+ dataType: 'json',
+ data: {
+ _method: 'PATCH',
+ protected_branch: {
+ merge_access_levels_attributes: [{
+ id: this.$allowedToMergeDropdown.data('access-level-id'),
+ access_level: $allowedToMergeInput.val(),
+ }],
+ push_access_levels_attributes: [{
+ id: this.$allowedToPushDropdown.data('access-level-id'),
+ access_level: $allowedToPushInput.val(),
+ }],
},
- error() {
- $.scrollTo(0);
- new Flash('Failed to update branch!');
- }
- }).always(() => {
- this.$allowedToMergeDropdown.enable();
- this.$allowedToPushDropdown.enable();
- });
- }
- };
-})(window);
+ },
+ error() {
+ new Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
+ },
+ }).always(() => {
+ this.$allowedToMergeDropdown.enable();
+ this.$allowedToPushDropdown.enable();
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
index 336fa6c57a7..b40d3827c30 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
@@ -1,18 +1,18 @@
-/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
+/* eslint-disable no-new */
-(global => {
- global.gl = global.gl || {};
+import ProtectedBranchEdit from './protected_branch_edit';
- gl.ProtectedBranchEditList = class {
- constructor() {
- this.$wrap = $('.protected-branches-list');
+export default class ProtectedBranchEditList {
+ constructor() {
+ this.$wrap = $('.protected-branches-list');
+ this.initEditForm();
+ }
- // Build edit forms
- this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
- new gl.ProtectedBranchEdit({
- $wrap: $(el)
- });
+ initEditForm() {
+ this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
+ new ProtectedBranchEdit({
+ $wrap: $(el),
});
- }
- };
-})(window);
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
deleted file mode 100644
index 874d70a1431..00000000000
--- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import './protected_branch_access_dropdown';
-import './protected_branch_create';
-import './protected_branch_dropdown';
-import './protected_branch_edit';
-import './protected_branch_edit_list';
diff --git a/app/assets/javascripts/protected_tags/index.js b/app/assets/javascripts/protected_tags/index.js
index 61e7ba53862..b1618e24e49 100644
--- a/app/assets/javascripts/protected_tags/index.js
+++ b/app/assets/javascripts/protected_tags/index.js
@@ -1,2 +1,9 @@
-export { default as ProtectedTagCreate } from './protected_tag_create';
-export { default as ProtectedTagEditList } from './protected_tag_edit_list';
+/* eslint-disable no-unused-vars */
+
+import ProtectedTagCreate from './protected_tag_create';
+import ProtectedTagEditList from './protected_tag_edit_list';
+
+$(() => {
+ const protectedtTagCreate = new ProtectedTagCreate();
+ const protectedtTagEditList = new ProtectedTagEditList();
+});
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 6d38124f1c1..3a06b477d7c 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,6 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
+import { __, s__ } from './locale';
+
export default class Star {
constructor() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
@@ -11,10 +13,10 @@ export default class Star {
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
- $starSpan.removeClass('starred').text('Star');
+ $starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
- $starSpan.addClass('starred').text('Unstar');
+ $starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index e8e22ad93a5..744a1cd24fa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -108,7 +108,8 @@ export default {
</div>
<mr-widget-memory-usage
v-if="deployment.metrics_url"
- :metricsUrl="deployment.metrics_url"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 76cb71b6c12..534e2a88eff 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -7,7 +7,14 @@ import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MemoryUsage',
props: {
- metricsUrl: { type: String, required: true },
+ metricsUrl: {
+ type: String,
+ required: true,
+ },
+ metricsMonitoringUrl: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -124,7 +131,7 @@ export default {
<p
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
- Memory usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
+ <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
</p>
<p
v-if="shouldShowLoadFailure"
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 06f7af33f94..0dfa7a31d31 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -35,6 +35,8 @@
width: 40px;
height: 40px;
padding: 0;
+ background: $avatar-background;
+ overflow: hidden;
&.avatar-inline {
float: none;
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 759401a7806..0ac095f7d8f 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -93,7 +93,7 @@
.is-selected .pika-day,
.pika-day:hover,
- .is-today .pika-day:hover {
+ .is-today .pika-day {
background: $gl-primary;
color: $white-light;
box-shadow: none;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index c7c2684d548..8ad082f7a65 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -163,8 +163,18 @@
td.blame-commit {
padding: 5px 10px;
min-width: 400px;
+ max-width: 400px;
background: $gray-light;
border-left: 3px solid;
+
+ .commit-row-title {
+ display: flex;
+ }
+
+ .item-title {
+ flex: 1;
+ margin-right: 0.5em;
+ }
}
@for $i from 0 through 5 {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 20fb10c28d4..605f4284bb5 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -132,6 +132,22 @@ header {
}
}
+ &.navbar-gitlab-new {
+ .fa-times {
+ display: none;
+ }
+
+ .menu-expanded {
+ .fa-ellipsis-v {
+ display: none;
+ }
+
+ .fa-times {
+ display: block;
+ }
+ }
+ }
+
.global-dropdown {
position: absolute;
left: -10px;
@@ -171,6 +187,19 @@ header {
min-height: $header-height;
padding-left: 30px;
+ &.menu-expanded {
+ @media (max-width: $screen-xs-max) {
+ .header-logo,
+ .title-container {
+ display: none;
+ }
+
+ .navbar-collapse {
+ display: block;
+ }
+ }
+ }
+
.dropdown-menu {
margin-top: -5px;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index e71bf04aec7..35b4d77a5ab 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -182,6 +182,12 @@
}
}
+ &.nav-controls-new-nav {
+ > .dropdown {
+ margin-right: 0;
+ }
+ }
+
> .btn-grouped {
float: none;
}
@@ -190,14 +196,6 @@
display: none;
}
- .btn,
- .dropdown,
- .dropdown-toggle,
- input,
- form {
- height: 35px;
- }
-
input {
display: inline-block;
position: relative;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 542b641e3dd..49b2f0e43a4 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -92,7 +92,6 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
- padding: 10px 0;
}
.issues-bulk-update.right-sidebar {
@@ -104,6 +103,15 @@
&.right-sidebar-expanded {
@include maintain-sidebar-dimensions;
width: $gutter-width;
+
+ .issuable-sidebar-header {
+ // matches `.top-area .nav-controls` for issuable index pages
+ padding: 11px 0;
+ }
+
+ .block:last-of-type {
+ border: none;
+ }
}
&.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 8a58c1ed567..befd8133be0 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -11,8 +11,17 @@
}
img {
- max-width: 100%;
+ /*max-width: 100%;*/
margin: 0 0 8px;
+ min-width: 200px;
+ min-height: 100px;
+ background-color: $gray-lightest;
+ }
+
+ img.js-lazy-loaded {
+ min-width: none;
+ min-height: none;
+ background-color: none;
}
p a:not(.no-attachment-icon) img {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7016208f624..cf0a1ad57d0 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -379,7 +379,9 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
* Avatar
*/
$avatar_radius: 50%;
-$avatar-border: $border-color;
+$avatar-border: $gray-normal;
+$avatar-border-hover: $gray-darker;
+$avatar-background: $gray-lightest;
$gl-avatar-size: 40px;
/*
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 2ee831e5e32..360ffda8d71 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -21,6 +21,11 @@ header.navbar-gitlab-new {
padding-right: 0;
color: currentColor;
+ img {
+ height: 28px;
+ margin-right: 10px;
+ }
+
> a {
display: flex;
align-items: center;
@@ -41,10 +46,22 @@ header.navbar-gitlab-new {
}
}
+ .logo-text {
+ line-height: initial;
+
+ svg {
+ width: 55px;
+ height: 15px;
+ margin: 0;
+ fill: $white-light;
+ }
+ }
+
&:hover,
&:focus {
- color: $tanuki-yellow;
- text-decoration: none;
+ .logo-text svg {
+ fill: $tanuki-yellow;
+ }
}
}
}
@@ -274,9 +291,7 @@ header.navbar-gitlab-new {
.breadcrumbs {
display: flex;
- min-height: 60px;
- padding-top: $gl-padding-top;
- padding-bottom: $gl-padding-top;
+ min-height: 61px;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
@@ -300,6 +315,7 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
+ align-items: center;
.dropdown-menu-projects {
margin-top: -$gl-padding;
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index bd9a5d7392d..ae43197a1a6 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -23,44 +23,82 @@ $new-sidebar-width: 220px;
position: fixed;
height: 100%;
}
+
+ .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
+ padding: 10px 0 15px;
+ }
}
.context-header {
- border-bottom: 1px solid $border-color;
- font-weight: 600;
- display: flex;
- align-items: center;
- padding: 10px 16px 10px 10px;
- color: $gl-text-color;
-
- .avatar-container {
- flex: 0 0 40px;
- background-color: $white-light;
- }
+ position: relative;
- &:hover {
- background-color: $hover-background;
- color: $hover-color;
- border-color: $hover-background;
+ a {
+ border-bottom: 1px solid $border-color;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ padding: 10px 16px 10px 10px;
+ color: $gl-text-color;
- .avatar-container {
- border-color: transparent;
+ @media (max-width: $screen-xs-max) {
+ padding-right: 30px;
}
- .settings-avatar {
- background-color: $indigo-500;
+ &:hover {
+ background-color: $hover-background;
+ color: $hover-color;
+ border-color: $hover-background;
- i {
- color: $hover-color;
+ .avatar-container {
+ border-color: transparent;
+ }
+
+ .settings-avatar {
+ background-color: $indigo-500;
+
+ i {
+ color: $hover-color;
+ }
}
}
}
+ .avatar-container {
+ flex: 0 0 40px;
+ background-color: $white-light;
+ }
+
.project-title,
.group-title {
overflow: hidden;
text-overflow: ellipsis;
}
+
+
+ &:hover {
+ .close-nav-button {
+ color: $white-light;
+ }
+ }
+
+ .close-nav-button {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ background-color: transparent;
+ border: 0;
+ padding: 0 10px;
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+
+ &:hover {
+ color: $gl-text-color;
+ }
+ }
}
.settings-avatar {
@@ -79,7 +117,7 @@ $new-sidebar-width: 220px;
position: fixed;
z-index: 400;
width: $new-sidebar-width;
- transition: width $sidebar-transition-duration;
+ transition: left $sidebar-transition-duration;
top: 50px;
bottom: 0;
left: 0;
@@ -87,6 +125,10 @@ $new-sidebar-width: 220px;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
+ &.nav-sidebar-expanded {
+ left: 0;
+ }
+
a {
transition: none;
text-decoration: none;
@@ -117,7 +159,7 @@ $new-sidebar-width: 220px;
}
@media (max-width: $screen-xs-max) {
- width: 0;
+ left: (-$new-sidebar-width);
}
}
@@ -127,7 +169,6 @@ $new-sidebar-width: 220px;
> li {
a {
- font-size: 12px;
padding: 8px 16px 8px 24px;
&:hover,
@@ -183,6 +224,38 @@ $new-sidebar-width: 220px;
}
}
+.toggle-mobile-nav {
+ display: none;
+ background-color: transparent;
+ border: 0;
+ padding: 6px 16px;
+ margin: 0 16px 0 -15px;
+ height: 46px;
+ border-right: 1px solid $gl-text-color-quaternary;
+
+ i {
+ font-size: 20px;
+ color: $gl-text-color-secondary;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: inline-block;
+ }
+}
+
+.mobile-overlay {
+ display: none;
+
+ &.mobile-nav-open {
+ display: block;
+ position: fixed;
+ background-color: $black-transparent;
+ height: 100%;
+ width: 100%;
+ z-index: 300;
+ }
+}
+
// Make issue boards full-height now that sub-nav is gone
@@ -192,7 +265,7 @@ $new-sidebar-width: 220px;
@media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
// scss-lint:disable DuplicateProperty
- height: calc(100vh - 120px);
+ height: calc(100vh - 180px);
// scss-lint:enable DuplicateProperty
}
}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index df858cffe09..6039cda96d8 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -431,7 +431,10 @@
margin: 5px;
}
-.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar {
+.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar,
+.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
+ position: absolute;
+
&.right-sidebar {
top: 0;
bottom: 0;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index a5e4c3311f8..fd0871ec0b8 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -54,7 +54,11 @@
.mr-widget-pipeline-graph {
display: inline-block;
vertical-align: middle;
- margin: 0 -6px 0 0;
+ margin-right: 4px;
+
+ .stage-cell .stage-container {
+ margin: 3px 3px 3px 0;
+ }
.dropdown-menu {
margin-top: 11px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 9637d26e56d..d3862df20d3 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -597,7 +597,7 @@
}
// Dropdown button in mini pipeline graph
-.mini-pipeline-graph-dropdown-toggle {
+button.mini-pipeline-graph-dropdown-toggle {
border-radius: 100px;
background-color: $white-light;
border-width: 1px;
@@ -608,6 +608,7 @@
padding: 0;
transition: all 0.2s linear;
position: relative;
+ vertical-align: middle;
> .fa.fa-caret-down {
position: absolute;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index c1423965d0a..a3e07a36c33 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -742,7 +742,8 @@ pre.light-well {
}
}
-.protected-tags-list {
+.protected-tags-list,
+.protected-branches-list {
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index c1bc4c0d675..4c0f7556894 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -76,11 +76,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(
- application_setting_params_ce
+ application_setting_params_attributes
)
end
- def application_setting_params_ce
+ def application_setting_params_attributes
[
:admin_notification_email,
:after_sign_out_path,
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 8360ce08bdc..05e749c00c0 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,6 +1,6 @@
class Admin::DashboardController < Admin::ApplicationController
def index
- @projects = Project.with_route.limit(10)
+ @projects = Project.without_deleted.with_route.limit(10)
@users = User.limit(10)
@groups = Group.with_route.limit(10)
end
diff --git a/app/controllers/admin/hook_logs_controller.rb b/app/controllers/admin/hook_logs_controller.rb
index aa069b89563..3017f96c26f 100644
--- a/app/controllers/admin/hook_logs_controller.rb
+++ b/app/controllers/admin/hook_logs_controller.rb
@@ -10,9 +10,9 @@ class Admin::HookLogsController < Admin::ApplicationController
end
def retry
- status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+ result = hook.execute(hook_log.request_data, hook_log.trigger)
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
redirect_to edit_admin_hook_path(@hook)
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 054c3500b35..77e3c95d197 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -38,9 +38,9 @@ class Admin::HooksController < Admin::ApplicationController
end
def test
- status, message = hook.execute(sample_hook_data, 'system_hooks')
+ result = TestHooks::SystemService.new(hook, current_user, params[:trigger]).execute
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
redirect_back_or_default
end
@@ -66,15 +66,4 @@ class Admin::HooksController < Admin::ApplicationController
:url
)
end
-
- def sample_hook_data
- {
- event_name: "project_create",
- name: "Ruby",
- path: "ruby",
- project_id: 1,
- owner_name: "Someone",
- owner_email: "example@gitlabhq.com"
- }
- end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 984d5398708..0b6cd71e651 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -3,18 +3,9 @@ class Admin::ProjectsController < Admin::ApplicationController
before_action :group, only: [:show, :transfer]
def index
- params[:sort] ||= 'latest_activity_desc'
- @projects = Project.with_statistics
- @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
- @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
- @projects = @projects.with_push if params[:with_push].present?
- @projects = @projects.abandoned if params[:abandoned].present?
- @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
- @projects = @projects.non_archived unless params[:archived].present?
- @projects = @projects.personal(current_user) if params[:personal].present?
- @projects = @projects.search(params[:name]) if params[:name].present?
- @projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
+ finder = Admin::ProjectsFinder.new(params: params, current_user: current_user)
+ @projects = finder.execute
+ @sort = finder.sort
respond_to do |format|
format.html
diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb
index 846cd60518f..a22e46b4860 100644
--- a/app/controllers/concerns/hooks_execution.rb
+++ b/app/controllers/concerns/hooks_execution.rb
@@ -3,11 +3,14 @@ module HooksExecution
private
- def set_hook_execution_notice(status, message)
- if status && status >= 200 && status < 400
- flash[:notice] = "Hook executed successfully: HTTP #{status}"
- elsif status
- flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
+ def set_hook_execution_notice(result)
+ http_status = result[:http_status]
+ message = result[:message]
+
+ if http_status && http_status >= 200 && http_status < 400
+ flash[:notice] = "Hook executed successfully: HTTP #{http_status}"
+ elsif http_status
+ flash[:alert] = "Hook executed successfully but returned HTTP #{http_status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 95de3a44641..221e01b415a 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -22,6 +22,7 @@ class Projects::ApplicationController < ApplicationController
def project
return @project if @project
+ return nil unless params[:project_id] || params[:id]
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 6c25cd83a24..06ba73d8e8d 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -3,11 +3,11 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:index]
before_action :no_cache_headers, except: [:index]
- def build
- build_status = Gitlab::Badge::Build::Status
+ def pipeline
+ pipeline_status = Gitlab::Badge::Pipeline::Status
.new(project, params[:ref])
- render_badge build_status
+ render_badge pipeline_status
end
def coverage
diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb
index b9c4b29580a..745e89fc843 100644
--- a/app/controllers/projects/hook_logs_controller.rb
+++ b/app/controllers/projects/hook_logs_controller.rb
@@ -14,9 +14,9 @@ class Projects::HookLogsController < Projects::ApplicationController
end
def retry
- status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+ result = hook.execute(hook_log.request_data, hook_log.trigger)
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
redirect_to edit_project_hook_path(@project, @hook)
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 18895c3f0f3..85d35900c71 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -9,6 +9,10 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings"
+ def index
+ redirect_to project_settings_integrations_path(@project)
+ end
+
def create
@hook = @project.hooks.new(hook_params)
@hook.save
@@ -33,13 +37,9 @@ class Projects::HooksController < Projects::ApplicationController
end
def test
- if !@project.empty_repo?
- status, message = TestHookService.new.execute(hook, current_user)
+ result = TestHooks::ProjectService.new(hook, current_user, params[:trigger]).execute
- set_hook_execution_notice(status, message)
- else
- flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
- end
+ set_hook_execution_notice(result)
redirect_back_or_default(default: { action: 'index' })
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 0ac9da2ff0f..e2ccabb22db 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -8,7 +8,6 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new]
- before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
@@ -243,19 +242,19 @@ class Projects::IssuesController < Projects::ApplicationController
end
def authorize_update_issue!
- return render_404 unless can?(current_user, :update_issue, @issue)
+ render_404 unless can?(current_user, :update_issue, @issue)
end
def authorize_admin_issues!
- return render_404 unless can?(current_user, :admin_issue, @project)
+ render_404 unless can?(current_user, :admin_issue, @project)
end
def authorize_create_merge_request!
- return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
+ render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
def check_issues_available!
- return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
+ return render_404 unless @project.feature_available?(:issues, current_user)
end
def redirect_to_external_issue_tracker
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 70c41da4de5..d361e661d0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -223,12 +223,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_project_environment_deployment_path(environment.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,
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index ea7ceb3eaa5..15a2ff56b92 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -35,7 +35,7 @@ module Projects
def define_badges_variables
@ref = params[:ref] || @project.default_branch || 'master'
- @badges = [Gitlab::Badge::Build::Status,
+ @badges = [Gitlab::Badge::Pipeline::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index c769693255c..2d7cbd4614e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -296,10 +296,10 @@ class ProjectsController < Projects::ApplicationController
def project_params
params.require(:project)
- .permit(project_params_ce)
+ .permit(project_params_attributes)
end
- def project_params_ce
+ def project_params_attributes
[
:avatar,
:build_allow_git_fetch,
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 0e8a57f8e03..69513f4dadc 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -5,6 +5,14 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_two_factor_requirement, only: [:destroy]
+ # Explicitly call protect from forgery before anything else. Otherwise the
+ # CSFR-token might be cleared before authentication is done. This was the case
+ # when LDAP was enabled and the `OmniauthCallbacksController` is loaded
+ #
+ # *Note:* `prepend: true` is the default for rails4, but this will be changed
+ # to `prepend: false` in rails5.
+ protect_from_forgery prepend: true, with: :exception
+
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create]
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
new file mode 100644
index 00000000000..a5ba791a513
--- /dev/null
+++ b/app/finders/admin/projects_finder.rb
@@ -0,0 +1,33 @@
+class Admin::ProjectsFinder
+ attr_reader :sort, :namespace_id, :visibility_level, :with_push,
+ :abandoned, :last_repository_check_failed, :archived,
+ :personal, :name, :page, :current_user
+
+ def initialize(params:, current_user:)
+ @current_user = current_user
+ @sort = params.fetch(:sort) { 'latest_activity_desc' }
+ @namespace_id = params[:namespace_id]
+ @visibility_level = params[:visibility_level]
+ @with_push = params[:with_push]
+ @abandoned = params[:abandoned]
+ @last_repository_check_failed = params[:last_repository_check_failed]
+ @archived = params[:archived]
+ @personal = params[:personal]
+ @name = params[:name]
+ @page = params[:page]
+ end
+
+ def execute
+ items = Project.with_statistics
+ items = items.in_namespace(namespace_id) if namespace_id.present?
+ items = items.where(visibility_level: visibility_level) if visibility_level.present?
+ items = items.with_push if with_push.present?
+ items = items.abandoned if abandoned.present?
+ items = items.where(last_repository_check_failed: true) if last_repository_check_failed.present?
+ items = items.non_archived unless archived.present?
+ items = items.personal(current_user) if personal.present?
+ items = items.search(name) if name.present?
+ items = items.sort(sort)
+ items.includes(:namespace).order("namespaces.path, projects.name ASC").page(page)
+ end
+end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index bbe7f3c8fb4..0e068d4b51c 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -11,17 +11,12 @@ module AvatarsHelper
def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
- css_class = options[:css_class] || ''
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
data_attributes = { container: 'body' }
- if options[:lazy]
- data_attributes[:src] = avatar_url
- end
-
image_tag(
- options[:lazy] ? '' : avatar_url,
- class: "avatar has-tooltip s#{avatar_size} #{css_class}",
+ avatar_url,
+ class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]),
alt: "#{user_name}'s avatar",
title: user_name,
data: data_attributes
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index fdbca789d21..5f11fe62030 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -61,8 +61,8 @@ module EmailsHelper
else
image_tag(
image_url('mailers/gitlab_header_logo.gif'),
- size: "55x50",
- alt: "GitLab"
+ size: '55x50',
+ alt: 'GitLab'
)
end
end
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
new file mode 100644
index 00000000000..551b9cca6b1
--- /dev/null
+++ b/app/helpers/hooks_helper.rb
@@ -0,0 +1,17 @@
+module HooksHelper
+ def link_to_test_hook(hook, trigger)
+ path = case hook
+ when ProjectHook
+ project = hook.project
+ test_project_hook_path(project, hook, trigger: trigger)
+ when SystemHook
+ test_admin_hook_path(hook, trigger: trigger)
+ end
+
+ trigger_human_name = trigger.to_s.tr('_', ' ').camelize
+
+ link_to path, rel: 'nofollow' do
+ content_tag(:span, trigger_human_name)
+ end
+ end
+end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 42b6cfdf02f..7e1ccb23e9e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -17,10 +17,10 @@ module IssuesHelper
return '' if project.nil?
url =
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
+ if options[:internal]
+ url_for_internal_issue(issue_iid, project, options)
else
- project.issues_tracker.issue_url(issue_iid)
+ url_for_tracker_issue(issue_iid, project, options)
end
# Ensure we return a valid URL to prevent possible XSS.
@@ -29,6 +29,24 @@ module IssuesHelper
''
end
+ def url_for_tracker_issue(issue_iid, project, options)
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+ end
+
+ def url_for_internal_issue(issue_iid, project = @project, options = {})
+ helpers = Gitlab::Routing.url_helpers
+
+ if options[:only_path]
+ helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ else
+ helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ end
+ end
+
def bulk_update_milestone_options
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
@@ -158,4 +176,6 @@ module IssuesHelper
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
+ module_function :url_for_internal_issue
+ module_function :url_for_tracker_issue
end
diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb
new file mode 100644
index 00000000000..2c5619ac41b
--- /dev/null
+++ b/app/helpers/lazy_image_tag_helper.rb
@@ -0,0 +1,24 @@
+module LazyImageTagHelper
+ def placeholder_image
+ "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+ end
+
+ # Override the default ActionView `image_tag` helper to support lazy-loading
+ def image_tag(source, options = {})
+ options = options.symbolize_keys
+
+ unless options.delete(:lazy) == false
+ options[:data] ||= {}
+ options[:data][:src] = path_to_image(source)
+ options[:class] ||= ""
+ options[:class] << " lazy"
+
+ source = placeholder_image
+ end
+
+ super(source, options)
+ end
+
+ # Required for Banzai::Filter::ImageLazyLoadFilter
+ module_function :placeholder_image
+end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index 209bd56b78a..08fd97cd048 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -18,7 +18,8 @@ module SystemNoteHelper
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
'moved' => 'icon_arrow_circle_o_right',
- 'outdated' => 'icon_edit'
+ 'outdated' => 'icon_edit',
+ 'duplicate' => 'icon_clone'
}.freeze
def icon_for_system_note(note)
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index a48d4475e97..ce435ca2241 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -8,6 +8,6 @@ module TriggersHelper
end
def service_trigger_url(service)
- "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger"
+ "#{Settings.gitlab.url}/api/v4/projects/#{service.project_id}/services/#{service.to_param}/trigger"
end
end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 456598b4c28..3b175251446 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -2,7 +2,7 @@ module VersionCheckHelper
def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled
image_url = VersionCheck.new.url
- image_tag image_url, class: 'js-version-status-badge'
+ image_tag image_url, class: 'js-version-status-badge', lazy: false
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 432f3f242eb..416a2a33378 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -96,6 +96,14 @@ module Ci
BuildSuccessWorker.perform_async(id)
end
end
+
+ before_transition any => [:failed] do |build|
+ next if build.retries_max.zero?
+
+ if build.retries_count < build.retries_max
+ Ci::Build.retry(build, build.user)
+ end
+ end
end
def detailed_status(current_user)
@@ -130,6 +138,14 @@ module Ci
success? || failed? || canceled?
end
+ def retries_count
+ pipeline.builds.retried.where(name: self.name).count
+ end
+
+ def retries_max
+ self.options.fetch(:retry, 0).to_i
+ end
+
def latest?
!retried?
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index b646b32fc64..e5b615a7cc0 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -21,7 +21,7 @@ module Ci
has_many :merge_requests, foreign_key: "head_pipeline_id"
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
- has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
+ has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
has_many :manual_actions, -> { latest.manual_actions.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :artifacts, -> { latest.with_artifacts_not_expired.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index e4ae1b35f66..085eeeae157 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -40,10 +40,6 @@ module Ci
update_attribute(:active, false)
end
- def runnable_by_owner?
- Ability.allowed?(owner, :create_pipeline, project)
- end
-
def set_next_run_at
self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 95152dcd68c..48547a938fc 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,7 +11,7 @@ module CacheMarkdownField
extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output
- CACHE_VERSION = 1
+ CACHE_VERSION = 2
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index fc6b840f7a8..ef95d6b0f98 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -17,7 +17,13 @@ module ProtectedRef
class_methods do
def protected_ref_access_levels(*types)
types.each do |type|
- has_many :"#{type}_access_levels", dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ # We need to set `inverse_of` to make sure the `belongs_to`-object is set
+ # when creating children using `accepts_nested_attributes_for`.
+ #
+ # If we don't `protected_branch` or `protected_tag` would be empty and
+ # `project` cannot be delegated to it, which in turn would cause validations
+ # to fail.
+ has_many :"#{type}_access_levels", dependent: :destroy, inverse_of: self.model_name.singular # rubocop:disable Cop/ActiveRecordDependent
validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." }
@@ -25,8 +31,8 @@ module ProtectedRef
end
end
- def protected_ref_accessible_to?(ref, user, action:)
- access_levels_for_ref(ref, action: action).any? do |access_level|
+ def protected_ref_accessible_to?(ref, user, action:, protected_refs: nil)
+ access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level|
access_level.check_access(user)
end
end
@@ -37,8 +43,9 @@ module ProtectedRef
end
end
- def access_levels_for_ref(ref, action:)
- self.matching(ref).map(&:"#{action}_access_levels").flatten
+ def access_levels_for_ref(ref, action:, protected_refs: nil)
+ self.matching(ref, protected_refs: protected_refs)
+ .map(&:"#{action}_access_levels").flatten
end
def matching(ref_name, protected_refs: nil)
diff --git a/app/models/group.rb b/app/models/group.rb
index dfa4e8adedd..bd5735ed82e 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -167,10 +167,14 @@ class Group < Namespace
end
def has_owner?(user)
+ return false unless user
+
members_with_parents.owners.where(user_id: user).any?
end
def has_master?(user)
+ return false unless user
+
members_with_parents.masters.where(user_id: user).any?
end
@@ -212,7 +216,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.non_request.where(source_id: ancestors.pluck(:id).push(id))
+ GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil)
end
def users_with_parents
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index ee6165fd32d..a8c424a6614 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,11 +1,20 @@
class ProjectHook < WebHook
- belongs_to :project
+ TRIGGERS = {
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events,
+ issue_hooks: :issues_events,
+ confidential_issue_hooks: :confidential_issues_events,
+ note_hooks: :note_events,
+ merge_request_hooks: :merge_requests_events,
+ job_hooks: :job_events,
+ pipeline_hooks: :pipeline_events,
+ wiki_page_hooks: :wiki_page_events
+ }.freeze
+
+ TRIGGERS.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
- scope :issue_hooks, -> { where(issues_events: true) }
- scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) }
- scope :note_hooks, -> { where(note_events: true) }
- scope :merge_request_hooks, -> { where(merge_requests_events: true) }
- scope :job_hooks, -> { where(job_events: true) }
- scope :pipeline_hooks, -> { where(pipeline_events: true) }
- scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
+ belongs_to :project
+ validates :project, presence: true
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 40e43c27f91..aef11514945 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -1,5 +1,6 @@
class ServiceHook < WebHook
belongs_to :service
+ validates :service, presence: true
def execute(data)
WebHookService.new(self, data, 'service_hook').execute
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 1584235ab00..180c479c41b 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,5 +1,13 @@
class SystemHook < WebHook
- scope :repository_update_hooks, -> { where(repository_update_events: true) }
+ TRIGGERS = {
+ repository_update_hooks: :repository_update_events,
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events
+ }.freeze
+
+ TRIGGERS.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
default_value_for :push_events, false
default_value_for :repository_update_events, true
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7a9f8997959..5a70e114f56 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,22 +1,8 @@
class WebHook < ActiveRecord::Base
include Sortable
- default_value_for :push_events, true
- default_value_for :issues_events, false
- default_value_for :confidential_issues_events, false
- default_value_for :note_events, false
- default_value_for :merge_requests_events, false
- default_value_for :tag_push_events, false
- default_value_for :job_events, false
- default_value_for :pipeline_events, false
- default_value_for :repository_update_events, false
- default_value_for :enable_ssl_verification, true
-
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- scope :push_hooks, -> { where(push_events: true) }
- scope :tag_push_hooks, -> { where(tag_push_events: true) }
-
validates :url, presence: true, url: true
def execute(data, hook_name)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index e4e7999d0f2..a910099b4c1 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -596,7 +596,7 @@ class MergeRequest < ActiveRecord::Base
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
def cache_merge_request_closes_issues!(current_user)
- return if project.has_external_issue_tracker?
+ return unless project.issues_enabled?
transaction do
self.merge_requests_closing_issues.delete_all
diff --git a/app/models/project.rb b/app/models/project.rb
index 0b357d5d003..d827bfaa806 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -734,9 +734,11 @@ class Project < ActiveRecord::Base
end
def get_issue(issue_id, current_user)
- if default_issues_tracker?
- IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
- else
+ issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled?
+
+ if issue
+ issue
+ elsif external_issue_tracker
ExternalIssue.new(issue_id, self)
end
end
@@ -758,7 +760,7 @@ class Project < ActiveRecord::Base
end
def external_issue_reference_pattern
- external_issue_tracker.class.reference_pattern
+ external_issue_tracker.class.reference_pattern(only_long: issues_enabled?)
end
def default_issues_tracker?
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 6d6a3ae3647..31984c5d7ed 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -8,8 +8,12 @@ class IssueTrackerService < Service
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
- def self.reference_pattern
- @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
+ def self.reference_pattern(only_long: false)
+ if only_long
+ %r{(\b[A-Z][A-Z0-9_]+-)(?<issue>\d+)}
+ else
+ %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
+ end
end
def default?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 5498a2e17b2..37f2c96a22f 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -3,10 +3,8 @@ class JiraService < IssueTrackerService
validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
- validates :project_key, presence: true, if: :activated?
- prop_accessor :username, :password, :url, :api_url, :project_key,
- :jira_issue_transition_id, :title, :description
+ prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
before_update :reset_password
@@ -18,7 +16,7 @@ class JiraService < IssueTrackerService
end
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
- def self.reference_pattern
+ def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
@@ -54,10 +52,6 @@ class JiraService < IssueTrackerService
@client ||= JIRA::Client.new(options)
end
- def jira_project
- @jira_project ||= jira_request { client.Project.find(project_key) }
- end
-
def help
"You need to configure JIRA before enabling this service. For more details
read the
@@ -88,18 +82,12 @@ class JiraService < IssueTrackerService
[
{ type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com', required: true },
{ type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
- { type: 'text', name: 'project_key', placeholder: 'Project Key', required: true },
{ type: 'text', name: 'username', placeholder: '', required: true },
{ type: 'password', name: 'password', placeholder: '', required: true },
- { type: 'text', name: 'jira_issue_transition_id', placeholder: '' }
+ { type: 'text', name: 'jira_issue_transition_id', title: 'Transition ID', placeholder: '' }
]
end
- # URLs to redirect from Gitlab issues pages to jira issue tracker
- def project_url
- "#{url}/issues/?jql=project=#{project_key}"
- end
-
def issues_url
"#{url}/browse/:id"
end
@@ -184,7 +172,7 @@ class JiraService < IssueTrackerService
def test_settings
return unless client_url.present?
# Test settings by getting the project
- jira_request { jira_project.present? }
+ jira_request { client.ServerInfo.all.attrs }
end
private
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8663cf5e602..d27eeff9fb4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -471,8 +471,17 @@ class Repository
end
cache_method :root_ref
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
def exists?
- refs_directory_exists?
+ return false unless path_with_namespace
+
+ Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
+ if enabled
+ raw_repository.exists?
+ else
+ refs_directory_exists?
+ end
+ end
end
cache_method :exists?
@@ -1095,8 +1104,6 @@ class Repository
end
def refs_directory_exists?
- return false unless path_with_namespace
-
File.exist?(File.join(path_to_repo, 'refs'))
end
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 414c95f7705..0b33e45473b 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,7 +1,8 @@
class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
commit description merge confidential visible label assignee cross_reference
- title time_tracking branch milestone discussion task moved opened closed merged
+ title time_tracking branch milestone discussion task moved
+ opened closed merged duplicate
outdated
].freeze
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 386822d3ff6..984e5482288 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,17 +1,15 @@
module Ci
class BuildPolicy < CommitStatusPolicy
- condition(:protected_action) do
- next false unless @subject.action?
-
+ condition(:protected_ref) do
access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
if @subject.tag?
!access.can_create_tag?(@subject.ref)
else
- !access.can_merge_to_branch?(@subject.ref)
+ !access.can_update_branch?(@subject.ref)
end
end
- rule { protected_action }.prevent :update_build
+ rule { protected_ref }.prevent :update_build
end
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index a2dde95dbc8..4e689a9efd5 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -1,5 +1,17 @@
module Ci
class PipelinePolicy < BasePolicy
delegate { @subject.project }
+
+ condition(:protected_ref) do
+ access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
+
+ if @subject.tag?
+ !access.can_create_tag?(@subject.ref)
+ else
+ !access.can_update_branch?(@subject.ref)
+ end
+ end
+
+ rule { protected_ref }.prevent :update_pipeline
end
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 55eefa76d3f..1c91425f589 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy
prevent :log_in
end
- rule { ~restricted_public_level }.policy do
+ rule { admin | ~restricted_public_level }.policy do
enable :read_users_list
end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 323131c0f7e..0133091db57 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -10,7 +10,8 @@ class ProjectPolicy < BasePolicy
desc "User is a project owner"
condition :owner do
- @user && project.owner == @user || (project.group && project.group.has_owner?(@user))
+ (project.owner.present? && project.owner == @user) ||
+ project.group&.has_owner?(@user)
end
desc "Project has public builds enabled"
@@ -287,9 +288,6 @@ class ProjectPolicy < BasePolicy
prevent :create_issue
prevent :update_issue
prevent :admin_issue
- end
-
- rule { issues_disabled & default_issues_tracker }.policy do
prevent :read_issue
end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 20f9938f038..743a08acefe 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -16,7 +16,8 @@ class BuildDetailsEntity < JobEntity
end
expose :path do |build|
- project_merge_request_path(project, build.merge_request)
+ project_merge_request_path(build.merge_request.project,
+ build.merge_request)
end
end
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index 068013c8829..c75431a79ae 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -9,7 +9,7 @@ class DeployKeyEntity < Grape::Entity
expose :created_at
expose :updated_at
expose :projects, using: ProjectEntity do |deploy_key|
- deploy_key.projects.select { |project| options[:user].can?(:read_project, project) }
+ deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) }
end
expose :can_edit
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 273386776fa..21e2ef153de 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -15,12 +15,40 @@ module Ci
pipeline_schedule: schedule
)
+ result = validate(current_user || trigger_request.trigger.owner,
+ ignore_skip_ci: ignore_skip_ci,
+ save_on_errors: save_on_errors)
+
+ return result if result
+
+ Ci::Pipeline.transaction do
+ update_merge_requests_head_pipeline if pipeline.save
+
+ Ci::CreatePipelineStagesService
+ .new(project, current_user)
+ .execute(pipeline)
+ end
+
+ cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
+
+ pipeline_created_counter.increment(source: source)
+
+ pipeline.tap(&:process!)
+ end
+
+ private
+
+ def validate(triggering_user, ignore_skip_ci:, save_on_errors:)
unless project.builds_enabled?
return error('Pipeline is disabled')
end
- unless trigger_request || can?(current_user, :create_pipeline, project)
- return error('Insufficient permissions to create a new pipeline')
+ unless allowed_to_trigger_pipeline?(triggering_user)
+ if can?(triggering_user, :create_pipeline, project)
+ return error("Insufficient permissions for protected ref '#{ref}'")
+ else
+ return error('Insufficient permissions to create a new pipeline')
+ end
end
unless branch? || tag?
@@ -46,24 +74,29 @@ module Ci
unless pipeline.has_stage_seeds?
return error('No stages / jobs for this pipeline.')
end
+ end
- Ci::Pipeline.transaction do
- update_merge_requests_head_pipeline if pipeline.save
-
- Ci::CreatePipelineStagesService
- .new(project, current_user)
- .execute(pipeline)
+ def allowed_to_trigger_pipeline?(triggering_user)
+ if triggering_user
+ allowed_to_create?(triggering_user)
+ else # legacy triggers don't have a corresponding user
+ !project.protected_for?(ref)
end
+ end
- cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
-
- pipeline_created_counter.increment(source: source)
+ def allowed_to_create?(triggering_user)
+ access = Gitlab::UserAccess.new(triggering_user, project: project)
- pipeline.tap(&:process!)
+ can?(triggering_user, :create_pipeline, project) &&
+ if branch?
+ access.can_update_branch?(ref)
+ elsif tag?
+ access.can_create_tag?(ref)
+ else
+ true # Allow it for now and we'll reject when we check ref existence
+ end
end
- private
-
def update_merge_requests_head_pipeline
return unless pipeline.latest?
@@ -113,15 +146,21 @@ module Ci
end
def branch?
- project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
+ return @is_branch if defined?(@is_branch)
+
+ @is_branch =
+ project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
end
def tag?
- project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
+ return @is_tag if defined?(@is_tag)
+
+ @is_tag =
+ project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
end
def ref
- Gitlab::Git.ref_name(origin_ref)
+ @ref ||= Gitlab::Git.ref_name(origin_ref)
end
def valid_sha?
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index cf3d4aee2bc..a43d0e4593c 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,12 +1,14 @@
module Ci
- class CreateTriggerRequestService
- def execute(project, trigger, ref, variables = nil)
+ module CreateTriggerRequestService
+ Result = Struct.new(:trigger_request, :pipeline)
+
+ def self.execute(project, trigger, ref, variables = nil)
trigger_request = trigger.trigger_requests.create(variables: variables)
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref)
.execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
- trigger_request if pipeline.persisted?
+ Result.new(trigger_request, pipeline)
end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 9078b1f0983..ea497729115 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -58,6 +58,7 @@ class IssuableBaseService < BaseService
params.delete(:assignee_ids)
params.delete(:assignee_id)
params.delete(:due_date)
+ params.delete(:canonical_issue_id)
end
filter_assignee(issuable)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 34199eb5d13..4c198fc96ea 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -7,6 +7,14 @@ module Issues
issue_data
end
+ def reopen_service
+ Issues::ReopenService
+ end
+
+ def close_service
+ Issues::CloseService
+ end
+
private
def create_assignee_note(issue, old_assignees)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index ddef5281498..74459c3342c 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -16,13 +16,13 @@ module Issues
# The code calling this method is responsible for ensuring that a user is
# allowed to close the given issue.
def close_issue(issue, commit: nil, notifications: true, system_note: true)
- if project.jira_tracker? && project.jira_service.active
+ if project.jira_tracker? && project.jira_service.active && issue.is_a?(ExternalIssue)
project.jira_service.close_issue(commit, issue)
todo_service.close_issue(issue, current_user)
return issue
end
- if project.default_issues_tracker? && issue.close
+ if project.issues_enabled? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications
diff --git a/app/services/issues/duplicate_service.rb b/app/services/issues/duplicate_service.rb
new file mode 100644
index 00000000000..5c0854e664d
--- /dev/null
+++ b/app/services/issues/duplicate_service.rb
@@ -0,0 +1,24 @@
+module Issues
+ class DuplicateService < Issues::BaseService
+ def execute(duplicate_issue, canonical_issue)
+ return if canonical_issue == duplicate_issue
+ return unless can?(current_user, :update_issue, duplicate_issue)
+ return unless can?(current_user, :create_note, canonical_issue)
+
+ create_issue_duplicate_note(duplicate_issue, canonical_issue)
+ create_issue_canonical_note(canonical_issue, duplicate_issue)
+
+ close_service.new(project, current_user, {}).execute(duplicate_issue)
+ end
+
+ private
+
+ def create_issue_duplicate_note(duplicate_issue, canonical_issue)
+ SystemNoteService.mark_duplicate_issue(duplicate_issue, duplicate_issue.project, current_user, canonical_issue)
+ end
+
+ def create_issue_canonical_note(canonical_issue, duplicate_issue)
+ SystemNoteService.mark_canonical_issue_of_duplicate(canonical_issue, canonical_issue.project, current_user, duplicate_issue)
+ end
+ end
+end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index cd9f9a4a16e..8d918ccc635 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -5,6 +5,7 @@ module Issues
def execute(issue)
handle_move_between_iids(issue)
filter_spam_check_params
+ change_issue_duplicate(issue)
update(issue)
end
@@ -53,14 +54,6 @@ module Issues
end
end
- def reopen_service
- Issues::ReopenService
- end
-
- def close_service
- Issues::CloseService
- end
-
def handle_move_between_iids(issue)
return unless params[:move_between_iids]
@@ -72,6 +65,15 @@ module Issues
issue.move_between(issue_before, issue_after)
end
+ def change_issue_duplicate(issue)
+ canonical_issue_id = params.delete(:canonical_issue_id)
+ canonical_issue = IssuesFinder.new(current_user).find_by(id: canonical_issue_id)
+
+ if canonical_issue
+ Issues::DuplicateService.new(project, current_user).execute(issue, canonical_issue)
+ end
+ end
+
private
def get_issue_if_allowed(project, iid)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 30ca95eef7a..d81035e4eba 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -5,7 +5,7 @@ module Projects
return error('New visibility level not allowed!')
end
- if project.has_container_registry_tags?
+ if renaming_project_with_container_registry_tags?
return error('Cannot rename project because it contains container registry tags!')
end
@@ -44,6 +44,13 @@ module Projects
true
end
+ def renaming_project_with_container_registry_tags?
+ new_path = params[:path]
+
+ new_path && new_path != project.path &&
+ project.has_container_registry_tags?
+ end
+
def changing_default_branch?
new_branch = params[:default_branch]
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 6f82159e6c7..5dc1b91d2c0 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -471,6 +471,24 @@ module QuickActions
end
end
+ desc 'Mark this issue as a duplicate of another issue'
+ explanation do |duplicate_reference|
+ "Marks this issue as a duplicate of #{duplicate_reference}."
+ end
+ params '#issue'
+ condition do
+ issuable.is_a?(Issue) &&
+ issuable.persisted? &&
+ current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ end
+ command :duplicate do |duplicate_param|
+ canonical_issue = extract_references(duplicate_param, :issue).first
+
+ if canonical_issue.present?
+ @updates[:canonical_issue_id] = canonical_issue.id
+ end
+ end
+
def extract_users(params)
return [] if params.nil?
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index ed476fc9d0c..bd58a54592f 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -4,7 +4,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
- SystemHook.send(hooks_scope).each do |hook|
+ SystemHook.public_send(hooks_scope).find_each do |hook|
hook.async_execute(data, 'system_hooks')
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index da0f21d449a..2dbee9c246e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -552,6 +552,44 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
+ # Called when a Noteable has been marked as a duplicate of another Issue
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # canonical_issue - Issue that this is a duplicate of
+ #
+ # Example Note text:
+ #
+ # "marked this issue as a duplicate of #1234"
+ #
+ # "marked this issue as a duplicate of other_project#5678"
+ #
+ # Returns the created Note object
+ def mark_duplicate_issue(noteable, project, author, canonical_issue)
+ body = "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}"
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
+ end
+
+ # Called when a Noteable has been marked as the canonical Issue of a duplicate
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # duplicate_issue - Issue that was a duplicate of this
+ #
+ # Example Note text:
+ #
+ # "marked #1234 as a duplicate of this issue"
+ #
+ # "marked other_project#5678 as a duplicate of this issue"
+ #
+ # Returns the created Note object
+ def mark_canonical_issue_of_duplicate(noteable, project, author, duplicate_issue)
+ body = "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue"
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
+ end
+
private
def notes_for_mentioner(mentioner, noteable, notes)
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
deleted file mode 100644
index 280c81f7d2d..00000000000
--- a/app/services/test_hook_service.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class TestHookService
- def execute(hook, current_user)
- data = Gitlab::DataBuilder::Push.build_sample(hook.project, current_user)
- hook.execute(data, 'push_hooks')
- end
-end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
new file mode 100644
index 00000000000..74ba814afff
--- /dev/null
+++ b/app/services/test_hooks/base_service.rb
@@ -0,0 +1,41 @@
+module TestHooks
+ class BaseService
+ attr_accessor :hook, :current_user, :trigger
+
+ def initialize(hook, current_user, trigger)
+ @hook = hook
+ @current_user = current_user
+ @trigger = trigger
+ end
+
+ def execute
+ trigger_data_method = "#{trigger}_data"
+
+ if !self.respond_to?(trigger_data_method, true) ||
+ !hook.class::TRIGGERS.value?(trigger.to_sym)
+
+ return error('Testing not available for this hook')
+ end
+
+ error_message = catch(:validation_error) do
+ sample_data = self.__send__(trigger_data_method)
+
+ return hook.execute(sample_data, trigger)
+ end
+
+ error(error_message)
+ end
+
+ private
+
+ def error(message, http_status = nil)
+ result = {
+ message: message,
+ status: :error
+ }
+
+ result[:http_status] = http_status if http_status
+ result
+ end
+ end
+end
diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb
new file mode 100644
index 00000000000..01d5d774cd5
--- /dev/null
+++ b/app/services/test_hooks/project_service.rb
@@ -0,0 +1,63 @@
+module TestHooks
+ class ProjectService < TestHooks::BaseService
+ private
+
+ def project
+ @project ||= hook.project
+ end
+
+ def push_events_data
+ throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo?
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ alias_method :tag_push_events_data, :push_events_data
+
+ def note_events_data
+ note = project.notes.first
+ throw(:validation_error, 'Ensure the project has notes.') unless note.present?
+
+ Gitlab::DataBuilder::Note.build(note, current_user)
+ end
+
+ def issues_events_data
+ issue = project.issues.first
+ throw(:validation_error, 'Ensure the project has issues.') unless issue.present?
+
+ issue.to_hook_data(current_user)
+ end
+
+ alias_method :confidential_issues_events_data, :issues_events_data
+
+ def merge_requests_events_data
+ merge_request = project.merge_requests.first
+ throw(:validation_error, 'Ensure the project has merge requests.') unless merge_request.present?
+
+ merge_request.to_hook_data(current_user)
+ end
+
+ def job_events_data
+ build = project.builds.first
+ throw(:validation_error, 'Ensure the project has CI jobs.') unless build.present?
+
+ Gitlab::DataBuilder::Build.build(build)
+ end
+
+ def pipeline_events_data
+ pipeline = project.pipelines.first
+ throw(:validation_error, 'Ensure the project has CI pipelines.') unless pipeline.present?
+
+ Gitlab::DataBuilder::Pipeline.build(pipeline)
+ end
+
+ def wiki_page_events_data
+ page = project.wiki.pages.first
+ if !project.wiki_enabled? || page.blank?
+ throw(:validation_error, 'Ensure the wiki is enabled and has pages.')
+ end
+
+ Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
+ end
+ end
+end
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
new file mode 100644
index 00000000000..76c3c19bd74
--- /dev/null
+++ b/app/services/test_hooks/system_service.rb
@@ -0,0 +1,48 @@
+module TestHooks
+ class SystemService < TestHooks::BaseService
+ private
+
+ def project
+ @project ||= begin
+ project = Project.first
+
+ throw(:validation_error, 'Ensure that at least one project exists.') unless project
+
+ project
+ end
+ end
+
+ def push_events_data
+ if project.empty_repo?
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
+ end
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ def tag_push_events_data
+ if project.repository.tags.empty?
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has tags.")
+ end
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ def repository_update_events_data
+ commit = project.commit
+ ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
+
+ unless commit
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
+ end
+
+ change = Gitlab::DataBuilder::Repository.single_change(
+ commit.parent_id || Gitlab::Git::BLANK_SHA,
+ commit.id,
+ ref
+ )
+
+ Gitlab::DataBuilder::Repository.update(project, current_user, [change], [ref])
+ end
+ end
+end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 4241b912d5b..a5110a23cad 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -39,7 +39,11 @@ class WebHookService
execution_duration: Time.now - start_time
)
- [response.code, response.to_s]
+ {
+ status: :success,
+ http_status: response.code,
+ message: response.to_s
+ }
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
log_execution(
trigger: hook_name,
@@ -52,7 +56,10 @@ class WebHookService
Rails.logger.error("WebHook Error => #{e}")
- [nil, e.to_s]
+ {
+ status: :error,
+ message: e.to_s
+ }
end
def async_execute
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
index 14317ea65c8..260c04a8b94 100644
--- a/app/services/wiki_pages/base_service.rb
+++ b/app/services/wiki_pages/base_service.rb
@@ -1,23 +1,9 @@
module WikiPages
class BaseService < ::BaseService
- def hook_data(page, action)
- hook_data = {
- object_kind: page.class.name.underscore,
- user: current_user.hook_attrs,
- project: @project.hook_attrs,
- wiki: @project.wiki.hook_attrs,
- object_attributes: page.hook_attrs
- }
-
- page_url = Gitlab::UrlBuilder.build(page)
- hook_data[:object_attributes].merge!(url: page_url, action: action)
- hook_data
- end
-
private
def execute_hooks(page, action = 'create')
- page_data = hook_data(page, action)
+ page_data = Gitlab::DataBuilder::WikiPage.build(page, current_user, action)
@project.execute_hooks(page_data, :wiki_page_hooks)
@project.execute_services(page_data, :wiki_page_hooks)
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 26f7c1a473a..8bb2a563990 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -315,7 +315,9 @@
%fieldset
%legend Metrics - Prometheus
%p
- Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
+ Enable a Prometheus metrics endpoint at
+ %code= metrics_path
+ to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
= link_to 'here', admin_health_check_path
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
@@ -327,10 +329,13 @@
= f.label :prometheus_metrics_enabled do
= f.check_box :prometheus_metrics_enabled
Enable Prometheus Metrics
- - unless Gitlab::Metrics.metrics_folder_present?
- .help-block
- %strong.cred WARNING:
- Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
+ - unless Gitlab::Metrics.metrics_folder_present?
+ .help-block
+ %strong.cred WARNING:
+ Environment variable
+ %code prometheus_multiproc_dir
+ does not exist or is not pointing to a valid directory.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/gitlab_metrics', anchor: 'metrics-shared-directory')
%fieldset
%legend Profiling - Performance Bar
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 128b5dc01ab..8e94e68bc11 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -150,7 +150,7 @@
.well-segment.well-centered
= link_to admin_groups_path do
%h3.text-center
- Groups
+ Groups:
= number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 0e35a1905bf..665e8c7e74f 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -12,7 +12,7 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
- = link_to 'Test hook', test_admin_hook_path(@hook), class: 'btn btn-default'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index e92b8bc39f4..fed6002528d 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -22,12 +22,12 @@
- @hooks.each do |hook|
%li
.controls
- = link_to 'Test hook', test_admin_hook_path(hook), class: 'btn btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-small'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url
%div
- - %w(repository_update_events push_events tag_push_events issues_events note_events merge_requests_events job_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray= trigger.titleize
+ - SystemHook::TRIGGERS.each_value do |event|
+ - if hook.public_send(event)
+ %span.label.label-gray= event.to_s.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 2da8f615470..126550ee10e 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -2,26 +2,6 @@
= render "admin/dashboard/head"
%div{ class: container_class }
-
- %p.prepend-top-default
- %span
- To register a new Runner you should enter the following registration
- token.
- With this token the Runner will request a unique Runner token and use
- that for future communication.
- %br
- Registration token is
- %code#runners-token= current_application_settings.runners_registration_token
-
- .bs-callout.clearfix
- .pull-left
- %p
- You can reset runners registration token by pressing a button below.
- .prepend-top-10
- = button_to "Reset runners registration token", reset_runners_token_admin_application_settings_path,
- method: :put, class: 'btn btn-default',
- data: { confirm: 'Are you sure you want to reset registration token?' }
-
.bs-callout
%p
A 'Runner' is a process which runs a job.
@@ -46,6 +26,19 @@
%span.label.label-danger paused
\- Runner will not receive any new jobs
+ .bs-callout.clearfix
+ .pull-left
+ %p
+ You can reset runners registration token by pressing a button below.
+ .prepend-top-10
+ = button_to _("Reset runners registration token"), reset_runners_token_admin_application_settings_path,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: _("Are you sure you want to reset registration token?") }
+
+ = render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: current_application_settings.runners_registration_token,
+ type: 'shared' }
+
.append-bottom-20.clearfix
.pull-left
= form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
new file mode 100644
index 00000000000..b75dab0acc5
--- /dev/null
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -0,0 +1,16 @@
+- link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'
+.bs-callout.help-callout
+ %h4= _("How to setup a #{type} Runner for a new project")
+
+ %ol
+ %li
+ = _("Install a Runner compatible with GitLab CI")
+ = (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe
+ %li
+ = _("Specify the following URL during the Runner setup:")
+ %code= root_url(only_path: false)
+ %li
+ = _("Use the following registration token during setup:")
+ %code#registration_token= registration_token
+ %li
+ = _("Start the Runner!")
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index e80d10dc8f1..bfd7dd25a7d 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -7,6 +7,6 @@
%span.light
- has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
- %fieldset
+ %fieldset.prepend-top-10
= check_box_tag :remember_me
- = label_tag :remember_me, 'Remember Me'
+ = label_tag :remember_me, 'Remember me'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index e90197320f2..873220cc73d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -10,6 +10,8 @@
- if content_for?(:sub_nav)
= yield :sub_nav
.content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" }
+ - if show_new_nav?
+ .mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- if show_new_nav?
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
index 4697d91724b..60940dba475 100644
--- a/app/views/layouts/header/_new.html.haml
+++ b/app/views/layouts/header/_new.html.haml
@@ -6,8 +6,8 @@
%h1.title
= link_to root_path, title: 'Dashboard' do
= brand_header_logo
- %span.hidden-xs
- GitLab
+ %span.logo-text.hidden-xs
+ = render 'shared/logo_type.svg'
- if current_user
= render "layouts/nav/new_dashboard"
@@ -81,6 +81,6 @@
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
- = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
+ = icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index 9aed0efae1c..4db84771f4e 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -3,6 +3,10 @@
%nav.breadcrumbs{ role: "navigation" }
.breadcrumbs-container{ class: [container_class, @content_class] }
+ - if defined?(@new_sidebar)
+ = button_tag class: 'toggle-mobile-nav', type: 'button' do
+ %span.sr-only Open sidebar
+ = icon ('bars')
.breadcrumbs-links.js-title-container
- unless hide_top_links
.title
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index 5cc636e89ef..95443de40c2 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -1,8 +1,12 @@
.nav-sidebar
- = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
- .avatar-container.s40.settings-avatar
- = icon('wrench')
- .project-title Admin Area
+ .context-header
+ = link_to admin_root_path, title: 'Admin Overview' do
+ .avatar-container.s40.settings-avatar
+ = icon('wrench')
+ .project-title Admin Area
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index 6e0c45739f1..a7897c09e79 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,9 +1,13 @@
.nav-sidebar
- = link_to group_path(@group), title: @group.name, class: 'context-header' do
- .avatar-container.s40.group-avatar
- = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
- .group-title
- = @group.name
+ .context-header
+ = link_to group_path(@group), title: @group.name do
+ .avatar-container.s40.group-avatar
+ = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
+ .group-title
+ = @group.name
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
= link_to group_path(@group), title: 'About group' do
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
index 033ea149cfb..239e6b949e2 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -1,8 +1,12 @@
.nav-sidebar
- = link_to profile_path, title: 'Profile Settings', class: 'context-header' do
- .avatar-container.s40.settings-avatar
- = icon('user')
- .project-title User Settings
+ .context-header
+ = link_to profile_path, title: 'Profile Settings' do
+ .avatar-container.s40.settings-avatar
+ = icon('user')
+ .project-title User Settings
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index 882123c0b0a..00395b222e4 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,10 +1,14 @@
.nav-sidebar
- can_edit = can?(current_user, :admin_project, @project)
- = link_to project_path(@project), title: @project.name, class: 'context-header' do
- .avatar-container.s40.project-avatar
- = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
- .project-title
- = @project.name
+ .context-header
+ = link_to project_path(@project), title: @project.name do
+ .avatar-container.s40.project-avatar
+ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
+ .project-title
+ = @project.name
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
= link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do
@@ -71,10 +75,10 @@
Registry
- if project_nav_tab? :issues
- = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
- - if @project.default_issues_tracker?
+ - if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
Issues
@@ -109,7 +113,7 @@
Milestones
- if project_nav_tab? :merge_requests
- = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
+ = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
%span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index fb90bb4b472..924cd2e9681 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -23,16 +23,16 @@
Registry
- if project_nav_tab? :issues
- = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
- - if @project.default_issues_tracker?
+ - if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
- if project_nav_tab? :merge_requests
- controllers = [:merge_requests, 'projects/merge_requests/conflicts']
- - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker?
+ - controllers.push(:merge_requests, :labels, :milestones) unless @project.issues_enabled?
= nav_link(controller: controllers) do
= link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index f11afe8fc22..c7359d873d9 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -21,8 +21,8 @@
.commit
= author_avatar(commit, size: 36)
.commit-row-title
- %strong
- = link_to_gfm truncate(commit.title, length: 35), project_commit_path(@project, commit.id), class: "cdark"
+ %span.item-title.str-truncated-100
+ = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
.pull-right
= link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
&nbsp;
diff --git a/app/views/projects/blob/viewers/_image.html.haml b/app/views/projects/blob/viewers/_image.html.haml
index 640d59b3174..1650aa8197f 100644
--- a/app/views/projects/blob/viewers/_image.html.haml
+++ b/app/views/projects/blob/viewers/_image.html.haml
@@ -1,2 +1,2 @@
.file-content.image_file
- %img{ src: blob_raw_url, alt: viewer.blob.name }
+ %img{ 'data-src': blob_raw_url, alt: viewer.blob.name }
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index e248676be0d..c82ae35a685 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -2,7 +2,7 @@
= link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project)
= icon('star')
- %span.starred= _('Unstar')
+ %span.starred= _('Unstar')
- else
= icon('star-o')
%span= s_('StarProject|Star')
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
index 33d3dcbeafa..05877ceed3d 100644
--- a/app/views/projects/diffs/viewers/_image.html.haml
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -8,7 +8,7 @@
.image
%span.wrap
.frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
- %img{ src: blob_raw_path, alt: diff_file.file_path }
+ %img{ 'data-src': blob_raw_path, alt: diff_file.file_path }
%p.image-info= number_to_human_size(blob.size)
- else
.image
@@ -16,7 +16,7 @@
%span.wrap
.frame.deleted
%a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ %img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path }
%p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size)
|
@@ -28,7 +28,7 @@
%span.wrap
.frame.added
%a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %img{ 'data-src': blob_raw_path, alt: diff_file.new_path }
%p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size)
|
@@ -41,10 +41,10 @@
.swipe.view.hide
.swipe-frame
.frame.deleted
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ %img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path }
.swipe-wrap
.frame.added
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %img{ 'data-src': blob_raw_path, alt: diff_file.new_path }
%span.swipe-bar
%span.top-handle
%span.bottom-handle
@@ -52,9 +52,9 @@
.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ %img{ 'data-src': old_blob_raw_path, alt: diff_file.old_path }
.frame.added
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %img{ 'data-src': blob_raw_path, alt: diff_file.new_path }
.controls
.transparent
.drag-track
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index 4944e0c8041..c8c17d2d828 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -13,9 +13,10 @@
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
- = link_to 'Test hook', test_project_hook_path(@project, @hook), class: 'btn btn-default'
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook
= link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
= render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
+
diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
index 766cb272bec..917ec7fdbda 100644
--- a/app/views/projects/merge_requests/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
@@ -1,3 +1,6 @@
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag('how_to_merge')
+
#modal_merge_info.modal
.modal-dialog
.modal-content
@@ -50,14 +53,3 @@
= succeed '.' do
You can also checkout merge requests locally by
= link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
-
-:javascript
- $(function(){
- var modal = $('#modal_merge_info').modal({modal: true, show:false});
- $('.how_to_merge_link').bind("click", function(){
- modal.show();
- });
- $('.modal-header .close').bind("click", function(){
- modal.hide();
- })
- })
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index bfeb746ee83..c020e7db380 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -4,7 +4,7 @@
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests"
-- unless @project.default_issues_tracker?
+- unless @project.issues_enabled?
= content_for :sub_nav do
= render "projects/merge_requests/head"
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index a89387bc8f1..e0b29b0c2e1 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title 'Milestones'
-- if show_new_nav?
+- if show_new_nav? && can?(current_user, :admin_milestone, @project)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone'
@@ -11,10 +11,10 @@
.top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
- .nav-controls
+ .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'btn btn-new', title: 'New milestone' do
+ = link_to new_project_milestone_path(@project), class: "btn btn-new #{("visible-xs" if show_new_nav?)}", title: 'New milestone' do
New milestone
.milestones
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 97c0407a01d..7343d6e039c 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -4,7 +4,7 @@
= pipeline_schedule.description
%td.branch-name-cell
= icon('code-fork')
- - if pipeline_schedule.ref
+ - if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td
- if pipeline_schedule.last_pipeline
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 5c00bb6883c..2a0704bc7af 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,4 +1,4 @@
-.panel.panel-default.protected-branches-list
+.panel.panel-default.protected-branches-list.js-protected-branches-list
- if @protected_branches.empty?
.panel-heading
%h3.panel-title
@@ -23,6 +23,8 @@
- if can_admin_project
%th
%tbody
+ %tr
+ %td.flash-container{ colspan: 5 }
= yield
= paginate @protected_branches, theme: 'gitlab'
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 b619fa57e05..9f0c4f3b3a8 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
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f|
.panel.panel-default
.panel-heading
%h3.panel-title
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 6e3cd4ada71..3f42ae58438 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,4 +1,4 @@
-.panel.panel-default.protected-tags-list
+.panel.panel-default.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.panel-heading
%h3.panel-title
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index f8835454140..28ccbf7eb15 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -1,21 +1,8 @@
%h3 Specific Runners
-.bs-callout.help-callout
- %h4 How to setup a specific Runner for a new project
-
- %ol
- %li
- Install a Runner compatible with GitLab CI
- (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
- %li
- Specify the following URL during the Runner setup:
- %code= root_url(only_path: false)
- %li
- Use the following registration token during setup:
- %code= @project.runners_token
- %li
- Start the Runner!
-
+= render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: @project.runners_token,
+ type: 'specific' }
- if @project_runners.any?
%h4.underlined-title Runners activated for this project
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index 00700e286c3..d5792e95f5a 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -3,14 +3,14 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- - %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events job_events pipeline_events wiki_page_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray.deploy-project-label= trigger.titleize
+ - ProjectHook::TRIGGERS.each_value do |event|
+ - if hook.public_send(event)
+ %span.label.label-gray.deploy-project-label= event.to_s.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
- SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
- = link_to "Edit", edit_project_hook_path(@project, hook), class: "btn btn-sm"
- = link_to "Test", test_project_hook_path(@project, hook), class: "btn btn-sm"
- = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+ SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
+ = link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-small'
+ = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/shared/_logo_type.svg b/app/views/shared/_logo_type.svg
new file mode 100644
index 00000000000..cb07e2634a9
--- /dev/null
+++ b/app/views/shared/_logo_type.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 617 169"><path d="M315.26 2.97h-21.8l.1 162.5h88.3v-20.1h-66.5l-.1-142.4M465.89 136.95c-5.5 5.7-14.6 11.4-27 11.4-16.6 0-23.3-8.2-23.3-18.9 0-16.1 11.2-23.8 35-23.8 4.5 0 11.7.5 15.4 1.2v30.1h-.1m-22.6-98.5c-17.6 0-33.8 6.2-46.4 16.7l7.7 13.4c8.9-5.2 19.8-10.4 35.5-10.4 17.9 0 25.8 9.2 25.8 24.6v7.9c-3.5-.7-10.7-1.2-15.1-1.2-38.2 0-57.6 13.4-57.6 41.4 0 25.1 15.4 37.7 38.7 37.7 15.7 0 30.8-7.2 36-18.9l4 15.9h15.4v-83.2c-.1-26.3-11.5-43.9-44-43.9M557.63 149.1c-8.2 0-15.4-1-20.8-3.5V70.5c7.4-6.2 16.6-10.7 28.3-10.7 21.1 0 29.2 14.9 29.2 39 0 34.2-13.1 50.3-36.7 50.3m9.2-110.6c-19.5 0-30 13.3-30 13.3v-21l-.1-27.8h-21.3l.1 158.5c10.7 4.5 25.3 6.9 41.2 6.9 40.7 0 60.3-26 60.3-70.9-.1-35.5-18.2-59-50.2-59M77.9 20.6c19.3 0 31.8 6.4 39.9 12.9l9.4-16.3C114.5 6 97.3 0 78.9 0 32.5 0 0 28.3 0 85.4c0 59.8 35.1 83.1 75.2 83.1 20.1 0 37.2-4.7 48.4-9.4l-.5-63.9V75.1H63.6v20.1h38l.5 48.5c-5 2.5-13.6 4.5-25.3 4.5-32.2 0-53.8-20.3-53.8-63-.1-43.5 22.2-64.6 54.9-64.6M231.43 2.95h-21.3l.1 27.3v94.3c0 26.3 11.4 43.9 43.9 43.9 4.5 0 8.9-.4 13.1-1.2v-19.1c-3.1.5-6.4.7-9.9.7-17.9 0-25.8-9.2-25.8-24.6v-65h35.7v-17.8h-35.7l-.1-38.5M155.96 165.47h21.3v-124h-21.3v124M155.96 24.37h21.3V3.07h-21.3v21.3"/></svg>
diff --git a/app/views/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
index 4211ec6351d..e7355ae2eea 100644
--- a/app/views/shared/_mr_head.html.haml
+++ b/app/views/shared/_mr_head.html.haml
@@ -1,4 +1,4 @@
-- if @project.default_issues_tracker?
+- if @project.issues_enabled?
= render "projects/issues/head"
- else
= render "projects/merge_requests/head"
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index c1acee1a211..5f3cdaefd54 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,6 +1,6 @@
- if @projects.any?
.project-item-select-holder
- = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }, with_feature_enabled: local_assigns[:with_feature_enabled]
- %a.btn.btn-new.new-project-item-select-button{ data: { relative_path: local_assigns[:path] } }
+ = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
+ %a.btn.btn-new.new-project-item-select-button
= local_assigns[:label]
= icon('caret-down')
diff --git a/app/views/shared/icons/_icon_clone.svg b/app/views/shared/icons/_icon_clone.svg
new file mode 100644
index 00000000000..ccc897aa98f
--- /dev/null
+++ b/app/views/shared/icons/_icon_clone.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14">
+<path d="M13 12.75v-8.5q0-0.102-0.074-0.176t-0.176-0.074h-8.5q-0.102 0-0.176 0.074t-0.074 0.176v8.5q0 0.102 0.074 0.176t0.176 0.074h8.5q0.102 0 0.176-0.074t0.074-0.176zM14 4.25v8.5q0 0.516-0.367 0.883t-0.883 0.367h-8.5q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h8.5q0.516 0 0.883 0.367t0.367 0.883zM11 1.25v1.25h-1v-1.25q0-0.102-0.074-0.176t-0.176-0.074h-8.5q-0.102 0-0.176 0.074t-0.074 0.176v8.5q0 0.102 0.074 0.176t0.176 0.074h1.25v1h-1.25q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h8.5q0.516 0 0.883 0.367t0.367 0.883z"></path>
+</svg>
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 964fe5220f7..0d507cc7a6e 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -1,9 +1,9 @@
- type = local_assigns.fetch(:type)
-%aside.issues-bulk-update.js-right-sidebar.right-sidebar.affix-top{ data: { "offset-top" => "50", "spy" => "affix" }, "aria-live" => "polite" }
+%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? } }
.issuable-sidebar.hidden
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
- .block
+ .block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.pull-left
= button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true
= button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide pull-right"
diff --git a/app/views/shared/web_hooks/_test_button.html.haml b/app/views/shared/web_hooks/_test_button.html.haml
new file mode 100644
index 00000000000..cf1d5e061c6
--- /dev/null
+++ b/app/views/shared/web_hooks/_test_button.html.haml
@@ -0,0 +1,12 @@
+- triggers = local_assigns.fetch(:triggers)
+- button_class = local_assigns.fetch(:button_class, '')
+- hook = local_assigns.fetch(:hook)
+
+.hook-test-button.dropdown.inline
+ %button.btn{ 'data-toggle' => 'dropdown', class: button_class }
+ Test
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
+ - triggers.each_value do |event|
+ %li
+ = link_to_test_hook(hook, event)
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 7b485b3363c..d7087f20dfc 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -6,15 +6,12 @@ class PipelineScheduleWorker
Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
.preload(:owner, :project).find_each do |schedule|
begin
- unless schedule.runnable_by_owner?
- schedule.deactivate!
- next
- end
-
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
+ pipeline = Ci::CreatePipelineService.new(schedule.project,
+ schedule.owner,
+ ref: schedule.ref)
.execute(:schedule, save_on_errors: false, schedule: schedule)
+
+ schedule.deactivate! unless pipeline.persisted?
rescue => e
Rails.logger.error "#{schedule.id}: Failed to create a scheduled pipeline: #{e.message}"
ensure
diff --git a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
deleted file mode 100644
index 2c915e62357..00000000000
--- a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added "created_after" and "created_before" params to issuables
-merge_request: 12151
-author: Kyle Bishop @kybishop
diff --git a/changelogs/unreleased/12200-add-french-translation.yml b/changelogs/unreleased/12200-add-french-translation.yml
deleted file mode 100644
index f31d982e0b9..00000000000
--- a/changelogs/unreleased/12200-add-french-translation.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Adding French translations"
-merge_request: 12200
-author : Erwan "Dremor" Georget
diff --git a/changelogs/unreleased/13336-multiple-broadcast-messages.yml b/changelogs/unreleased/13336-multiple-broadcast-messages.yml
deleted file mode 100644
index 7dc73e1c6ea..00000000000
--- a/changelogs/unreleased/13336-multiple-broadcast-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display all current broadcast messages, not just the last one
-merge_request: 11113
-author: rickettm
diff --git a/changelogs/unreleased/18000-remember-me-for-oauth-login.yml b/changelogs/unreleased/18000-remember-me-for-oauth-login.yml
deleted file mode 100644
index 1ef92756a76..00000000000
--- a/changelogs/unreleased/18000-remember-me-for-oauth-login.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Honor the "Remember me" parameter for OAuth-based login
-merge_request: 11963
-author:
diff --git a/changelogs/unreleased/20628-add-oauth-implicit-grant.yml b/changelogs/unreleased/20628-add-oauth-implicit-grant.yml
deleted file mode 100644
index 58a28142feb..00000000000
--- a/changelogs/unreleased/20628-add-oauth-implicit-grant.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "#20628 Enable implicit grant in GitLab as OAuth Provider"
-merge_request: 12384
-author: Mateusz Pytel
diff --git a/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml b/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml
new file mode 100644
index 00000000000..c4c3fc7ceb2
--- /dev/null
+++ b/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add coordinator url to admin area runner page
+merge_request: 11603
+author:
diff --git a/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml b/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml
new file mode 100644
index 00000000000..837a34bd067
--- /dev/null
+++ b/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml
@@ -0,0 +1,4 @@
+---
+title: Declare related resources into V4 API entities
+merge_request:
+author:
diff --git a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
deleted file mode 100644
index 07c201de96e..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/merge_requests' spinach with rspec
-merge_request: 12440
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
deleted file mode 100644
index a5f78202c93..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/new-project.feature' spinach with rspec
-merge_request: 12550
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
deleted file mode 100644
index 65df9a836a5..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/todos' spinach with rspec
-merge_request: 12453
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-snippets-spinach.yml b/changelogs/unreleased/23036-replace-snippets-spinach.yml
deleted file mode 100644
index 545805b1302..00000000000
--- a/changelogs/unreleased/23036-replace-snippets-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'snippets/snippets.feature' spinach with rspec
-merge_request: 12385
-author: Alexander Randa @randaalex
diff --git a/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml b/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
deleted file mode 100644
index 442406c3c04..00000000000
--- a/changelogs/unreleased/23162-allow-creation-of-files-and-dirs-with-spaces-in-web-ui.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow creation of files and directories with spaces through Web UI
-merge_request: 12608
-author:
diff --git a/changelogs/unreleased/23998-blame-age-map.yml b/changelogs/unreleased/23998-blame-age-map.yml
deleted file mode 100644
index 26a38f0939c..00000000000
--- a/changelogs/unreleased/23998-blame-age-map.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add blame view age mapping
-merge_request: 7198
-author: Jeff Stubler
diff --git a/changelogs/unreleased/2501-ce-port-update-welcome-page.yml b/changelogs/unreleased/2501-ce-port-update-welcome-page.yml
deleted file mode 100644
index cac8a522308..00000000000
--- a/changelogs/unreleased/2501-ce-port-update-welcome-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update welcome page UX for new users
-merge_request: 12662
-author:
diff --git a/changelogs/unreleased/25102-files-view-button.yml b/changelogs/unreleased/25102-files-view-button.yml
deleted file mode 100644
index 4ba815d9464..00000000000
--- a/changelogs/unreleased/25102-files-view-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix mobile view of files view buttons
-merge_request:
-author:
diff --git a/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml b/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml
deleted file mode 100644
index 6688e79588f..00000000000
--- a/changelogs/unreleased/25103-mobile-members-page-user-avatar-is-misaligned.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve members view on mobile
-merge_request: 12619
-author:
diff --git a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
deleted file mode 100644
index 9fa824b161d..00000000000
--- a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disable fork button on project limit
-merge_request: 12145
-author: Ivan Chernov
diff --git a/changelogs/unreleased/26125-match-username-on-search.yml b/changelogs/unreleased/26125-match-username-on-search.yml
deleted file mode 100644
index 74e918bec16..00000000000
--- a/changelogs/unreleased/26125-match-username-on-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Inserts exact matches of name, username and email to the top of the search
- list
-merge_request: 12525
-author:
diff --git a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
deleted file mode 100644
index 667454ae95d..00000000000
--- a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Accept image for avatar in user API
-merge_request: 12143
-author: Ivan Chernov
diff --git a/changelogs/unreleased/26372-duplicate-issue-slash-command.yml b/changelogs/unreleased/26372-duplicate-issue-slash-command.yml
new file mode 100644
index 00000000000..3108344e0bf
--- /dev/null
+++ b/changelogs/unreleased/26372-duplicate-issue-slash-command.yml
@@ -0,0 +1,4 @@
+---
+title: Added /duplicate quick action to close a duplicate issue
+merge_request: 12845
+author: Ryan Scott
diff --git a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
deleted file mode 100644
index 497239db808..00000000000
--- a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor
- of "slash commands"
-merge_request:
-author:
diff --git a/changelogs/unreleased/27586-center-dropdown.yml b/changelogs/unreleased/27586-center-dropdown.yml
deleted file mode 100644
index 4935f7504f7..00000000000
--- a/changelogs/unreleased/27586-center-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Center dropdown for mini graph
-merge_request:
-author:
diff --git a/changelogs/unreleased/27645-html-email-brackets-bug.yml b/changelogs/unreleased/27645-html-email-brackets-bug.yml
deleted file mode 100644
index e8004d03884..00000000000
--- a/changelogs/unreleased/27645-html-email-brackets-bug.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients
-merge_request: 9045
-author: jneen
diff --git a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
deleted file mode 100644
index 92b5b59f46f..00000000000
--- a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use fa-chevron-down on dropdown arrows for consistency
-merge_request: 9659
-author: TM Lee
diff --git a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
deleted file mode 100644
index 97ebabaff1c..00000000000
--- a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use color inputs for broadcast messages
-merge_request:
-author:
diff --git a/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml b/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml
new file mode 100644
index 00000000000..b8f30b52b18
--- /dev/null
+++ b/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml
@@ -0,0 +1,4 @@
+---
+title: Decrease ABC threshold to 56.96
+merge_request: 11227
+author: Maxim Rydkin
diff --git a/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml b/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
deleted file mode 100644
index 720a79b8e1c..00000000000
--- a/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Additional Prometheus metrics support
-merge_request: 11712
-author:
diff --git a/changelogs/unreleased/2971-multiproject-grah-ce-port.yml b/changelogs/unreleased/2971-multiproject-grah-ce-port.yml
new file mode 100644
index 00000000000..37584cac6ab
--- /dev/null
+++ b/changelogs/unreleased/2971-multiproject-grah-ce-port.yml
@@ -0,0 +1,4 @@
+---
+title: Fix vertical alignment in firefox and safari for pipeline mini graph
+merge_request:
+author:
diff --git a/changelogs/unreleased/29893-change-menu-locations.yml b/changelogs/unreleased/29893-change-menu-locations.yml
deleted file mode 100644
index d348adc2d74..00000000000
--- a/changelogs/unreleased/29893-change-menu-locations.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: Moved "Members in a project" menu entry and path locations
-merge_request: 11560
diff --git a/changelogs/unreleased/30213-project-transfer-move-rollback.yml b/changelogs/unreleased/30213-project-transfer-move-rollback.yml
deleted file mode 100644
index 3eb1e399c54..00000000000
--- a/changelogs/unreleased/30213-project-transfer-move-rollback.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rollback project repo move if there is an error in Projects::TransferService
-merge_request: 11877
-author:
diff --git a/changelogs/unreleased/30634-protected-pipeline.yml b/changelogs/unreleased/30634-protected-pipeline.yml
new file mode 100644
index 00000000000..e46538e5b46
--- /dev/null
+++ b/changelogs/unreleased/30634-protected-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow running the pipeline if ref is protected and user cannot merge the
+ branch or create the tag
+merge_request: 11910
+author:
diff --git a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
deleted file mode 100644
index 83ce3fb4d0a..00000000000
--- a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Removes deleted_at and pending_delete occurrences in Project related queries
-merge_request: 12091
-author:
diff --git a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
deleted file mode 100644
index 3058404b3f8..00000000000
--- a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ensures default user limits when external user is unchecked
-merge_request: 12218
-author:
diff --git a/changelogs/unreleased/31129-jira-project-key-elim.yml b/changelogs/unreleased/31129-jira-project-key-elim.yml
new file mode 100644
index 00000000000..bfa0e99f250
--- /dev/null
+++ b/changelogs/unreleased/31129-jira-project-key-elim.yml
@@ -0,0 +1,4 @@
+---
+title: Remove project_key from the Jira configuration
+merge_request: 12050
+author:
diff --git a/changelogs/unreleased/31397-job-detail-real-time.yml b/changelogs/unreleased/31397-job-detail-real-time.yml
deleted file mode 100644
index 90487a1e75a..00000000000
--- a/changelogs/unreleased/31397-job-detail-real-time.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds realtime feature to job show view header and sidebar info. Updates UX.
-merge_request:
-author:
diff --git a/changelogs/unreleased/31415-responsive-pipelines-table-2.yml b/changelogs/unreleased/31415-responsive-pipelines-table-2.yml
deleted file mode 100644
index 59402b85871..00000000000
--- a/changelogs/unreleased/31415-responsive-pipelines-table-2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Create responsive mobile view for pipelines table
-merge_request:
-author:
diff --git a/changelogs/unreleased/31533-usage-data-projects-stats.yml b/changelogs/unreleased/31533-usage-data-projects-stats.yml
new file mode 100644
index 00000000000..11bb6118337
--- /dev/null
+++ b/changelogs/unreleased/31533-usage-data-projects-stats.yml
@@ -0,0 +1,4 @@
+---
+title: Add Slack and JIRA services counts to Usage Data
+merge_request:
+author:
diff --git a/changelogs/unreleased/31982-liberation-mono-linux.yml b/changelogs/unreleased/31982-liberation-mono-linux.yml
deleted file mode 100644
index c0f29cf4c47..00000000000
--- a/changelogs/unreleased/31982-liberation-mono-linux.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change order of monospace fonts to fix bug on some linux distros
-merge_request:
-author:
diff --git a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
deleted file mode 100644
index 99e64b9b467..00000000000
--- a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix spacing on runner buttons.
-merge_request: !12535
-author:
diff --git a/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml b/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml
deleted file mode 100644
index 7fc9e0a4f0e..00000000000
--- a/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
-merge_request: 11229
-author: @blackst0ne
diff --git a/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml b/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
deleted file mode 100644
index d6534ed4e1a..00000000000
--- a/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Filter archived project in API v3 only if param present
-merge_request: 12245
-author: Ivan Chernov
diff --git a/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml b/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
deleted file mode 100644
index ebb27d118d7..00000000000
--- a/changelogs/unreleased/32408-enable-disable-all-restricted-visibility-levels.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow admins to disable all restricted visibility levels
-merge_request: 12649
-author:
diff --git a/changelogs/unreleased/32470-pag-links.yml b/changelogs/unreleased/32470-pag-links.yml
deleted file mode 100644
index d0fd284f3ee..00000000000
--- a/changelogs/unreleased/32470-pag-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: more visual contrast in pagination widget
-merge_request:
-author:
diff --git a/changelogs/unreleased/32517-disable-hover-state.yml b/changelogs/unreleased/32517-disable-hover-state.yml
deleted file mode 100644
index 31b02778963..00000000000
--- a/changelogs/unreleased/32517-disable-hover-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes hover style for nodes that are either links or buttons in the pipeline
- graph
-merge_request:
-author:
diff --git a/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml b/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
deleted file mode 100644
index 7784d7d0ce0..00000000000
--- a/changelogs/unreleased/32815--Add-Custom-CI-Config-Path.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow customize CI config path
-merge_request: 12509
-author: Keith Pope
diff --git a/changelogs/unreleased/32834-task-note-only.yml b/changelogs/unreleased/32834-task-note-only.yml
deleted file mode 100644
index c9ea61ec4ec..00000000000
--- a/changelogs/unreleased/32834-task-note-only.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent description change notes when toggling tasks
-merge_request: 12057
-author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/32838-admin-panel-spacing.yml b/changelogs/unreleased/32838-admin-panel-spacing.yml
deleted file mode 100644
index ccd703fa43f..00000000000
--- a/changelogs/unreleased/32838-admin-panel-spacing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add wells to admin dashboard overview to fix spacing problems
-merge_request:
-author:
diff --git a/changelogs/unreleased/33003-avatar-in-project-api.yml b/changelogs/unreleased/33003-avatar-in-project-api.yml
deleted file mode 100644
index 41d796ebb32..00000000000
--- a/changelogs/unreleased/33003-avatar-in-project-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Accept image for avatar in project API
-merge_request: 11988
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
deleted file mode 100644
index d3172c405c3..00000000000
--- a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use authorize_update_pipeline_schedule in PipelineSchedulesController
-merge_request: 11846
-author:
diff --git a/changelogs/unreleased/33097-issue-tracker.yml b/changelogs/unreleased/33097-issue-tracker.yml
new file mode 100644
index 00000000000..0b13f7165db
--- /dev/null
+++ b/changelogs/unreleased/33097-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Associate Issues tab only with internal issues tracker
+merge_request:
+author:
diff --git a/changelogs/unreleased/33130-remove-group-modal.yml b/changelogs/unreleased/33130-remove-group-modal.yml
deleted file mode 100644
index 4672d41ded5..00000000000
--- a/changelogs/unreleased/33130-remove-group-modal.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Remove group modal like remove project modal (requires typing + confirmation)"
-merge_request: 12569
-author: Diego Souza
diff --git a/changelogs/unreleased/33132-change-icon-color.yml b/changelogs/unreleased/33132-change-icon-color.yml
deleted file mode 100644
index c0e148f985b..00000000000
--- a/changelogs/unreleased/33132-change-icon-color.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Render CI statuses with warnings in orange
-merge_request:
-author:
diff --git a/changelogs/unreleased/33208-singup-active-state-underline.yml b/changelogs/unreleased/33208-singup-active-state-underline.yml
deleted file mode 100644
index cddb43214ea..00000000000
--- a/changelogs/unreleased/33208-singup-active-state-underline.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes "sign in / Register" active state underline misalignment
-merge_request: 11890
-author: Frank Sierra
diff --git a/changelogs/unreleased/33360-generate-kubeconfig.yml b/changelogs/unreleased/33360-generate-kubeconfig.yml
deleted file mode 100644
index 96f0b1bc93f..00000000000
--- a/changelogs/unreleased/33360-generate-kubeconfig.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Provide KUBECONFIG from KubernetesService for runners
-merge_request: 12223
-author:
diff --git a/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml b/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml
deleted file mode 100644
index 4a7b02fec94..00000000000
--- a/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display issue state in issue links section of merge request widget
-merge_request: 12021
-author:
diff --git a/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml b/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
deleted file mode 100644
index a7d8ac9054b..00000000000
--- a/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Simplified Chinese translation of Project Page & Repository Page
-merge_request: 11994
-author: Huang Tao
diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
deleted file mode 100644
index e383bab23d6..00000000000
--- a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page
-merge_request: 11995
-author: Huang Tao
diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
deleted file mode 100644
index d6b1b2524c6..00000000000
--- a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
-merge_request: 12514
-author: Huang Tao
diff --git a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
deleted file mode 100644
index 385f18e2560..00000000000
--- a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Document the Delete Merged Branches functionality
-merge_request:
-author:
diff --git a/changelogs/unreleased/33461-display-user-id.yml b/changelogs/unreleased/33461-display-user-id.yml
deleted file mode 100644
index cba94625b07..00000000000
--- a/changelogs/unreleased/33461-display-user-id.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display own user id in account settings page
-merge_request: 12141
-author: Riccardo Padovani
diff --git a/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml b/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
deleted file mode 100644
index 590472c0990..00000000000
--- a/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update QA Dockerfile to lock Chrome browser version
-merge_request: 12071
-author:
diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
deleted file mode 100644
index 4f2ba2e1de3..00000000000
--- a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Bulgarian translation of Project Page & Repository Page
-merge_request: 12083
-author: Lyubomir Vasilev
diff --git a/changelogs/unreleased/33657-user-projects-api.yml b/changelogs/unreleased/33657-user-projects-api.yml
deleted file mode 100644
index a8d485865e9..00000000000
--- a/changelogs/unreleased/33657-user-projects-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add user projects API
-merge_request: 12596
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml b/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml
deleted file mode 100644
index d2bdc631d2a..00000000000
--- a/changelogs/unreleased/33672-supplement_portuguese_brazil_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Portuguese Brazil translation of Project Page & Repository Page
-merge_request: 12156
-author: Huang Tao
diff --git a/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml b/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml
deleted file mode 100644
index 7402c33c5c6..00000000000
--- a/changelogs/unreleased/33748-fix-n-plus-1-query-in-the-projects-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve the performance of the project list API
-merge_request: 12679
-author:
diff --git a/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml b/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
deleted file mode 100644
index c2bce368a58..00000000000
--- a/changelogs/unreleased/33772-readonly-gitlab-ci-cache.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Introduce cache policies for CI jobs
-merge_request: 12483
-author:
diff --git a/changelogs/unreleased/33837-remove-trash-on-registry-image.yml b/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
deleted file mode 100644
index 2d337f5e6e4..00000000000
--- a/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove registry image delete button if user cant delete it
-merge_request: 12317
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33846-no-runner-for-admin.yml b/changelogs/unreleased/33846-no-runner-for-admin.yml
deleted file mode 100644
index a2d46802c61..00000000000
--- a/changelogs/unreleased/33846-no-runner-for-admin.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add explicit message when no runners on admin
-merge_request: 12266
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml b/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml
deleted file mode 100644
index 810cc8489b5..00000000000
--- a/changelogs/unreleased/33929-allow-to-enable-perf-bar-for-a-group.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow to enable the performance bar per user or Feature group
-merge_request: 12362
-author:
diff --git a/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml b/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml
deleted file mode 100644
index a08795e1a26..00000000000
--- a/changelogs/unreleased/33949-deprecate-healthcheck-access-token.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Deprecate Healthcheck Access Token in favor of IP whitelist
-merge_request:
-author:
diff --git a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
deleted file mode 100644
index 4bacfca7551..00000000000
--- a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Store merge request ref_fetched status in the database
-merge_request: 12424
-author:
diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
deleted file mode 100644
index 69d5d34b072..00000000000
--- a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow the feature flags to be enabled/disabled with more granularity
-merge_request: 12357
-author:
diff --git a/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml b/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml
new file mode 100644
index 00000000000..1911705dd2b
--- /dev/null
+++ b/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml
@@ -0,0 +1,4 @@
+---
+title: Added link to the MR widget that directs to the monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
deleted file mode 100644
index 8f8b5a96c2b..00000000000
--- a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change milestone endpoint for groups
-merge_request: 12374
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
deleted file mode 100644
index a3ade8db214..00000000000
--- a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow unauthenticated access to the /api/v4/users API
-merge_request: 12445
-author:
diff --git a/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml b/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
deleted file mode 100644
index 1a631c3f0a4..00000000000
--- a/changelogs/unreleased/34169-add-simplified-chinese-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Simplified Chinese translations of Commits Page
-merge_request: 12405
-author: Huang Tao
diff --git a/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml b/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
deleted file mode 100644
index 3cf7c0b547f..00000000000
--- a/changelogs/unreleased/34171-add-traditional-chinese-in-hongkong-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Traditional Chinese in HongKong translations of Commits Page
-merge_request: 12406
-author: Huang Tao
diff --git a/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml b/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml
deleted file mode 100644
index 224b9e1852f..00000000000
--- a/changelogs/unreleased/34172-add-traditional-chinese-in-taiwan-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Traditional Chinese in Taiwan translations of Commits Page
-merge_request: 12407
-author: Huang Tao
diff --git a/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml b/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml
deleted file mode 100644
index 16a9216852d..00000000000
--- a/changelogs/unreleased/34173-add-portuguese-brazil-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Portuguese Brazil translations of Commits Page
-merge_request: 12408
-author: Huang Tao
diff --git a/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml b/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
deleted file mode 100644
index b43a38f3794..00000000000
--- a/changelogs/unreleased/34175-add-esperanto-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Esperanto translations of Commits Page
-merge_request: 12410
-author: Huang Tao
diff --git a/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml b/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
deleted file mode 100644
index 9177ae3acd1..00000000000
--- a/changelogs/unreleased/34176-add-bulgarian-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Bulgarian translations of Commits Page
-merge_request: 12411
-author: Huang Tao
diff --git a/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml b/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
deleted file mode 100644
index 4fa385c3c27..00000000000
--- a/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove bin/ci/upgrade.rb as not working all
-merge_request: 12414
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
deleted file mode 100644
index af743f3e506..00000000000
--- a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Esperanto translations for Cycle Analytics, Project, and Repository pages
-merge_request: 12442
-author: Huang Tao
diff --git a/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml b/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
deleted file mode 100644
index 42e906d24c6..00000000000
--- a/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Drop GFM support for issuable title on milestone for consistency and performance
-merge_request:
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
deleted file mode 100644
index 07fe79e90ee..00000000000
--- a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Drop GFM support for the title of Milestone/MergeRequest in template
-merge_request: 12451
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml b/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
new file mode 100644
index 00000000000..d188a558d38
--- /dev/null
+++ b/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
@@ -0,0 +1,4 @@
+---
+title: Lazy load images for better Frontend performance
+merge_request: 12503
+author:
diff --git a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
deleted file mode 100644
index 4911315d018..00000000000
--- a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Closes any open Autocomplete of the markdown editor when the form is closed
-merge_request: 12521
-author:
diff --git a/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml b/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml
deleted file mode 100644
index 99291b4c75a..00000000000
--- a/changelogs/unreleased/34468-remove-extra-blank-on-admin-on-mobile.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use smaller min-width for dropdown-menu-nav only on mobile
-merge_request: 12528
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34531-remove-scroll.yml b/changelogs/unreleased/34531-remove-scroll.yml
deleted file mode 100644
index c3c5289f66f..00000000000
--- a/changelogs/unreleased/34531-remove-scroll.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update jobs page output to have a scrollable page
-merge_request: 12587
-author:
diff --git a/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml b/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
deleted file mode 100644
index 31f4262c9f9..00000000000
--- a/changelogs/unreleased/34544-add-italian-translation-of-cycle-analytics-page-&-project-page-&-repository-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
-merge_request: 12578
-author: Huang Tao
diff --git a/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml b/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml
new file mode 100644
index 00000000000..e843bbac239
--- /dev/null
+++ b/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml
@@ -0,0 +1,4 @@
+---
+title: Extract "@request.env[devise.mapping] = Devise.mappings[:user]" to a test helper
+merge_request: 12742
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/34578-sidebar-padding.yml b/changelogs/unreleased/34578-sidebar-padding.yml
deleted file mode 100644
index dc4647298e6..00000000000
--- a/changelogs/unreleased/34578-sidebar-padding.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: fix left & right padding on sidebar
-merge_request:
-author:
diff --git a/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml b/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml
deleted file mode 100644
index 11c01d28dc2..00000000000
--- a/changelogs/unreleased/34590-fix-dashboard-labels-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix dashboard labels dropdown
-merge_request: 12708
-author:
diff --git a/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml b/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml
deleted file mode 100644
index 736991318d7..00000000000
--- a/changelogs/unreleased/34653-minor-ux-cleanups-for-performance-dashboard.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Cleanup minor UX issues in the performance dashboard
-merge_request:
-author:
diff --git a/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml b/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml
deleted file mode 100644
index c7a68935e8c..00000000000
--- a/changelogs/unreleased/34655-label-field-for-setting-a-chart-s-legend-text-is-not-working.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed the chart legend not being set correctly
-merge_request: 12628
-author:
diff --git a/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml b/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
deleted file mode 100644
index 90a1f8c98fe..00000000000
--- a/changelogs/unreleased/34688-add-italian-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Italian translations of Commits Page
-merge_request: 12645
-author: Huang Tao
diff --git a/changelogs/unreleased/34727-simplified-member-settings.yml b/changelogs/unreleased/34727-simplified-member-settings.yml
deleted file mode 100644
index 8c4844c001b..00000000000
--- a/changelogs/unreleased/34727-simplified-member-settings.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove two columned layout from project member settings
-merge_request:
-author:
diff --git a/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml b/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml
deleted file mode 100644
index 8df3a1a6940..00000000000
--- a/changelogs/unreleased/34736-n-1-problem-on-milestone-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: N+1 problems on milestone page
-merge_request: 12670
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml b/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml
deleted file mode 100644
index 40a24847580..00000000000
--- a/changelogs/unreleased/34789-add-japanese-translations-of-commits-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts.
-merge_request: 12693
-author: Huang Tao
diff --git a/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml b/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml
deleted file mode 100644
index 4e8a042fdb5..00000000000
--- a/changelogs/unreleased/34880-add-ukrainian-translations-to-i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts.
-merge_request: 12744
-author: Huang Tao
diff --git a/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml b/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml
deleted file mode 100644
index aed05dd1031..00000000000
--- a/changelogs/unreleased/34881-add-russian-translations-to-i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts.
-merge_request: 12743
-author: Huang Tao
diff --git a/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml b/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml
deleted file mode 100644
index 22c9c45bc75..00000000000
--- a/changelogs/unreleased/34907-dont-show-pipeline-schedule-button-for-non-member.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Do not show pipeline schedule button for non-member
-merge_request: 12757
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34930-fix-edited-by.yml b/changelogs/unreleased/34930-fix-edited-by.yml
deleted file mode 100644
index f133dfab0c2..00000000000
--- a/changelogs/unreleased/34930-fix-edited-by.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use Ghost user for last_edited_by and merge_user when original user is deleted
-merge_request: 12933
-author:
diff --git a/changelogs/unreleased/35035-sidebar-job-spaces.yml b/changelogs/unreleased/35035-sidebar-job-spaces.yml
deleted file mode 100644
index a9a0211efd9..00000000000
--- a/changelogs/unreleased/35035-sidebar-job-spaces.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix vertical space in job details sidebar
-merge_request:
-author:
diff --git a/changelogs/unreleased/35087-mr-status-misaligned.yml b/changelogs/unreleased/35087-mr-status-misaligned.yml
deleted file mode 100644
index 3be43125a61..00000000000
--- a/changelogs/unreleased/35087-mr-status-misaligned.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix alignment of controls in mr issuable list
-merge_request:
-author:
diff --git a/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml b/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml
new file mode 100644
index 00000000000..4fd60a79782
--- /dev/null
+++ b/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml
@@ -0,0 +1,4 @@
+---
+title: Use only CSS to truncate commit message in blame
+merge_request: 12900
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml b/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml
new file mode 100644
index 00000000000..45b6c57579b
--- /dev/null
+++ b/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml
@@ -0,0 +1,4 @@
+---
+title: Add link to doc/api/ci/lint.md
+merge_request: 12914
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml b/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml
deleted file mode 100644
index 680e1cd8222..00000000000
--- a/changelogs/unreleased/35209-add-wip-info-new-nav-pref.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add wip message to new navigation preference section
-merge_request:
-author:
diff --git a/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml b/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml
new file mode 100644
index 00000000000..73808030f4c
--- /dev/null
+++ b/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml
@@ -0,0 +1,4 @@
+---
+title: Pending delete projects should not show in deploy keys.
+merge_request: 13088
+author:
diff --git a/changelogs/unreleased/35391-fix-star-i18n-in-js.yml b/changelogs/unreleased/35391-fix-star-i18n-in-js.yml
new file mode 100644
index 00000000000..a6fd4dc89fd
--- /dev/null
+++ b/changelogs/unreleased/35391-fix-star-i18n-in-js.yml
@@ -0,0 +1,4 @@
+---
+title: Fix translations for Star/Unstar in JS file
+merge_request:
+author:
diff --git a/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml b/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml
new file mode 100644
index 00000000000..fa906accbb8
--- /dev/null
+++ b/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes 500 error caused by pending delete projects in admin dashboard
+merge_request: 13067
+author:
diff --git a/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml b/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml
new file mode 100644
index 00000000000..da4b730f0ca
--- /dev/null
+++ b/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admin to read_users_list even if it's restricted
+merge_request: 13066
+author:
diff --git a/changelogs/unreleased/5971-webhook-testing.yml b/changelogs/unreleased/5971-webhook-testing.yml
new file mode 100644
index 00000000000..58233091977
--- /dev/null
+++ b/changelogs/unreleased/5971-webhook-testing.yml
@@ -0,0 +1,4 @@
+---
+title: Allow testing any events for project hooks and system hooks
+merge_request: 11728
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
deleted file mode 100644
index 4948d415bed..00000000000
--- a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Rename duplicated variables with the same key for projects. Add environment_scope
- column to variables and add unique constraint to make sure that no variables could
- be created with the same key within a project
-merge_request: 12363
-author:
diff --git a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
deleted file mode 100644
index f2591042e98..00000000000
--- a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add group members counting and plan related data on namespaces API
-merge_request:
-author:
diff --git a/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml b/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml
new file mode 100644
index 00000000000..b5cf521561a
--- /dev/null
+++ b/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml
@@ -0,0 +1,4 @@
+---
+title: Add instrumentation to MarkupHelper#link_to_gfm
+merge_request: 13069
+author:
diff --git a/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml b/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml
new file mode 100644
index 00000000000..42e1c9e8f83
--- /dev/null
+++ b/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent LDAP login callback from being called with a GET request
+merge_request: 13059
+author:
diff --git a/changelogs/unreleased/bvl-free-system-namespace.yml b/changelogs/unreleased/bvl-free-system-namespace.yml
deleted file mode 100644
index 6c2d1e0e61f..00000000000
--- a/changelogs/unreleased/bvl-free-system-namespace.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name"
-merge_request: 11713
-author:
diff --git a/changelogs/unreleased/bvl-free-unused-names.yml b/changelogs/unreleased/bvl-free-unused-names.yml
new file mode 100644
index 00000000000..53acb95e5bb
--- /dev/null
+++ b/changelogs/unreleased/bvl-free-unused-names.yml
@@ -0,0 +1,5 @@
+---
+title: Free up some top level words, reject top level groups named like files in the
+ public folder
+merge_request: 12932
+author:
diff --git a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
deleted file mode 100644
index f37f2fa94ae..00000000000
--- a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename all reserved paths that could have been created
-merge_request: 11713
-author:
diff --git a/changelogs/unreleased/commit-comments-limited-width.yml b/changelogs/unreleased/commit-comments-limited-width.yml
deleted file mode 100644
index 97f50105495..00000000000
--- a/changelogs/unreleased/commit-comments-limited-width.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit commit & snippets comments width
-merge_request:
-author:
diff --git a/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml b/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml
deleted file mode 100644
index 357a623e0e8..00000000000
--- a/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed dashboard milestone tabs not loading
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-blob-binaryness-change.yml b/changelogs/unreleased/dm-blob-binaryness-change.yml
deleted file mode 100644
index f3e3af26f12..00000000000
--- a/changelogs/unreleased/dm-blob-binaryness-change.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Detect if file that appears to be text in the first 1024 bytes is actually
- binary afer loading all data
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-commit-row-browse-button.yml b/changelogs/unreleased/dm-commit-row-browse-button.yml
deleted file mode 100644
index 4240a7de5de..00000000000
--- a/changelogs/unreleased/dm-commit-row-browse-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix inconsistent display of the "Browse files" button in the commit list
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-diff-viewers.yml b/changelogs/unreleased/dm-diff-viewers.yml
deleted file mode 100644
index e5b1352c8f1..00000000000
--- a/changelogs/unreleased/dm-diff-viewers.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Implement diff viewers
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
deleted file mode 100644
index 5fad7a0f883..00000000000
--- a/changelogs/unreleased/dm-empty-state-new-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 'New merge request' button for users who don't have push access to canonical
- project
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-group-page-name.yml b/changelogs/unreleased/dm-group-page-name.yml
deleted file mode 100644
index 233879364e3..00000000000
--- a/changelogs/unreleased/dm-group-page-name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show group name instead of path on group page
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml b/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml
deleted file mode 100644
index 7fd252e9b8b..00000000000
--- a/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't check if MailRoom is running on Omnibus
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-page-image-size.yml b/changelogs/unreleased/dm-page-image-size.yml
deleted file mode 100644
index b18c00470fc..00000000000
--- a/changelogs/unreleased/dm-page-image-size.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit OpenGraph image size to 64x64
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml b/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml
deleted file mode 100644
index 8b026a4c289..00000000000
--- a/changelogs/unreleased/dm-readme-auxiliary-blob-viewer-without-wiki.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't show auxiliary blob viewer for README when there is no wiki
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml b/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
deleted file mode 100644
index 616241dd941..00000000000
--- a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Strip trailing whitespace in relative submodule URL
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-target-branch-slash-command-desc.yml b/changelogs/unreleased/dm-target-branch-slash-command-desc.yml
deleted file mode 100644
index 768ddf0416e..00000000000
--- a/changelogs/unreleased/dm-target-branch-slash-command-desc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update /target_branch slash command description to be more consistent
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-unnecessary-top-padding.yml b/changelogs/unreleased/dm-unnecessary-top-padding.yml
deleted file mode 100644
index 4557c06f8e7..00000000000
--- a/changelogs/unreleased/dm-unnecessary-top-padding.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unnecessary top padding on group MR index
-merge_request:
-author:
diff --git a/changelogs/unreleased/doc-gitaly-network.yml b/changelogs/unreleased/doc-gitaly-network.yml
deleted file mode 100644
index 5376d8d5096..00000000000
--- a/changelogs/unreleased/doc-gitaly-network.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add option to run Gitaly on a remote server
-merge_request: 12381
-author:
diff --git a/changelogs/unreleased/dt-printing-to-api.yml b/changelogs/unreleased/dt-printing-to-api.yml
deleted file mode 100644
index 5253b57f21a..00000000000
--- a/changelogs/unreleased/dt-printing-to-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added printing_merge_requst_link_enabled to the API
-merge_request:
-author: David Turner <dturner@twosigma.com>
diff --git a/changelogs/unreleased/dz-fix-calendar-today.yml b/changelogs/unreleased/dz-fix-calendar-today.yml
new file mode 100644
index 00000000000..5320d8b26b5
--- /dev/null
+++ b/changelogs/unreleased/dz-fix-calendar-today.yml
@@ -0,0 +1,4 @@
+---
+title: Fix today day highlight in calendar
+merge_request: 13048
+author:
diff --git a/changelogs/unreleased/enable-polling-env.yml b/changelogs/unreleased/enable-polling-env.yml
deleted file mode 100644
index b3f65f02574..00000000000
--- a/changelogs/unreleased/enable-polling-env.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Re-enable realtime for environments table
-merge_request:
-author:
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
deleted file mode 100644
index d61c3b97d11..00000000000
--- a/changelogs/unreleased/enable-webpack-code-splitting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable support for webpack code-splitting by dynamically setting publicPath
- at runtime
-merge_request: 12032
-author:
diff --git a/changelogs/unreleased/feature-add-support-for-services-configuration.yml b/changelogs/unreleased/feature-add-support-for-services-configuration.yml
deleted file mode 100644
index 88a3eacd774..00000000000
--- a/changelogs/unreleased/feature-add-support-for-services-configuration.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add support for image and services configuration in .gitlab-ci.yml
-merge_request: 8578
-author:
diff --git a/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml
new file mode 100644
index 00000000000..bdafc5929c0
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to configure automatic retry of a failed CI/CD job
+merge_request: 12909
+author:
diff --git a/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml b/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml
deleted file mode 100644
index 333895ffba9..00000000000
--- a/changelogs/unreleased/feature-intermediate-12729-group-secret-variables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Group secret variables
-merge_request: 12582
-author:
diff --git a/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml b/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml
deleted file mode 100644
index d497575b7f3..00000000000
--- a/changelogs/unreleased/feature-intermediate-32568-adding-variables-to-pipelines-schedules.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add variables to pipelines schedules
-merge_request: 12372
-author:
diff --git a/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml b/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
deleted file mode 100644
index bbcf2946ea7..00000000000
--- a/changelogs/unreleased/feature-no-hypen-at-end-of-commit-ref-slug.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
-merge_request: 11218
-author: Stefan Hanreich
diff --git a/changelogs/unreleased/feature-unify-email-layouts.yml b/changelogs/unreleased/feature-unify-email-layouts.yml
deleted file mode 100644
index 7a2e3f20b6b..00000000000
--- a/changelogs/unreleased/feature-unify-email-layouts.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update the devise mail templates to match the design of the pipeline emails
-merge_request: 10483
-author: Alexis Reigel
diff --git a/changelogs/unreleased/feature-user-agent-details-api.yml b/changelogs/unreleased/feature-user-agent-details-api.yml
deleted file mode 100644
index 839ec7d21cd..00000000000
--- a/changelogs/unreleased/feature-user-agent-details-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow admins to retrieve user agent details for an issue or snippet
-merge_request: 12655
-author:
diff --git a/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml b/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml
deleted file mode 100644
index 27ac50c6cc2..00000000000
--- a/changelogs/unreleased/feature-user-datetime-search-api-mysql.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add creation time filters to user search API for admins
-merge_request: 12682
-author:
diff --git a/changelogs/unreleased/fix-33991.yml b/changelogs/unreleased/fix-33991.yml
deleted file mode 100644
index 39732611b6e..00000000000
--- a/changelogs/unreleased/fix-33991.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Users can subscribe to group labels on the group labels page
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml
deleted file mode 100644
index fc2cd18ddb6..00000000000
--- a/changelogs/unreleased/fix-assigned-issuable-lists.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add issuable-list class to shared mr/issue lists to fix new responsive layout
- design
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml b/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml
deleted file mode 100644
index 2e0573beab6..00000000000
--- a/changelogs/unreleased/fix-exact-matches-of-username-and-email-on-top-of-the-user-search.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Exact matches of username and email are now on top of the user search
-merge_request: 12868
-author:
diff --git a/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml b/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml
new file mode 100644
index 00000000000..7a68e91c6d3
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml
@@ -0,0 +1,4 @@
+---
+title: Fix job merge request link to a forked source project
+merge_request: 12965
+author:
diff --git a/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml b/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
deleted file mode 100644
index f59c6ecd90c..00000000000
--- a/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix CI/CD status in case there are only allowed to failed jobs in the pipeline
-merge_request: 11166
-author:
diff --git a/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml b/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml
deleted file mode 100644
index 7adc53eb8fa..00000000000
--- a/changelogs/unreleased/fix-gb-recover-from-renaming-project-with-container-images.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Recover from renaming project that has container images
-merge_request: 12840
-author:
diff --git a/changelogs/unreleased/fix-mrs-merged-immediately.yml b/changelogs/unreleased/fix-mrs-merged-immediately.yml
deleted file mode 100644
index 41c06614e6d..00000000000
--- a/changelogs/unreleased/fix-mrs-merged-immediately.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't mark empty MRs as merged on push to the target branch
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml b/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml
deleted file mode 100644
index 5781316cfd9..00000000000
--- a/changelogs/unreleased/fix-n-plus-one-in-url-builder.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve issue rendering performance with lots of notes from other users
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-overflow-slash-commands.yml b/changelogs/unreleased/fix-overflow-slash-commands.yml
deleted file mode 100644
index 98ec399e8cb..00000000000
--- a/changelogs/unreleased/fix-overflow-slash-commands.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed overflow on mobile screens for the slash commands
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-runner_online_check.yml b/changelogs/unreleased/fix-runner_online_check.yml
deleted file mode 100644
index bc0de979b4c..00000000000
--- a/changelogs/unreleased/fix-runner_online_check.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix offline runner detection
-merge_request: 11751
-author: Alessio Caiazza
diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
deleted file mode 100644
index 856990a6126..00000000000
--- a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed sidebar not collapsing on merge requests in mobile screens
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-u2f-for-opera.yml b/changelogs/unreleased/fix-u2f-for-opera.yml
deleted file mode 100644
index 0eafb8eff9a..00000000000
--- a/changelogs/unreleased/fix-u2f-for-opera.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix FIDO U2F for Opera browser
-merge_request: 12082
-author: Jakub Kramarz and Jonas Kalderstam
diff --git a/changelogs/unreleased/fix_docs_commits_multiple_files.yml b/changelogs/unreleased/fix_docs_commits_multiple_files.yml
deleted file mode 100644
index 36567354b28..00000000000
--- a/changelogs/unreleased/fix_docs_commits_multiple_files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Documentation bugfix of invalid JSON payload example of Create a commit with
- multiple files and actions
-merge_request: 12117
-author: @blackst0ne
diff --git a/changelogs/unreleased/foreign-keys-for-project-model.yml b/changelogs/unreleased/foreign-keys-for-project-model.yml
deleted file mode 100644
index 3648b1c3735..00000000000
--- a/changelogs/unreleased/foreign-keys-for-project-model.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Speed up project removals by adding foreign keys with cascading deletes to various tables
-merge_request:
-author:
diff --git a/changelogs/unreleased/gitaly-mandatory.yml b/changelogs/unreleased/gitaly-mandatory.yml
deleted file mode 100644
index c060e0add29..00000000000
--- a/changelogs/unreleased/gitaly-mandatory.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove option to disable Gitaly
-merge_request: 12677
-author:
diff --git a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
deleted file mode 100644
index ec2f4f9c3d8..00000000000
--- a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix errors caused by attempts to report already blocked or deleted users
-merge_request: 12502
-author: Horacio Bertorello
diff --git a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
deleted file mode 100644
index 3b465d84126..00000000000
--- a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Hide archived project labels from group issue tracker
-merge_request: 12547
-author: Horacio Bertorello
diff --git a/changelogs/unreleased/help-landing-page-customizations.yml b/changelogs/unreleased/help-landing-page-customizations.yml
deleted file mode 100644
index 58cab751ded..00000000000
--- a/changelogs/unreleased/help-landing-page-customizations.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Help landing page customizations
-merge_request: 11878
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml b/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml
deleted file mode 100644
index 05d52fcad0f..00000000000
--- a/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed dropdown filter input not focusing after transition
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_20900.yml b/changelogs/unreleased/issue_20900.yml
deleted file mode 100644
index e8cef6d2bce..00000000000
--- a/changelogs/unreleased/issue_20900.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove issues/merge requests drag n drop and sorting from milestone view
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_30126_be.yml b/changelogs/unreleased/issue_30126_be.yml
deleted file mode 100644
index 96bb8d9574b..00000000000
--- a/changelogs/unreleased/issue_30126_be.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add native group milestones
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_33205.yml b/changelogs/unreleased/issue_33205.yml
deleted file mode 100644
index 54b442048d8..00000000000
--- a/changelogs/unreleased/issue_33205.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix API bug accepting wrong parameter to create merge request
-merge_request:
-author:
diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml
deleted file mode 100644
index d3d67d04574..00000000000
--- a/changelogs/unreleased/issueable-list-cleanup.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Clean up UI of issuable lists and make more responsive
-merge_request:
-author:
diff --git a/changelogs/unreleased/karma-headless-chrome.yml b/changelogs/unreleased/karma-headless-chrome.yml
deleted file mode 100644
index af3e9b3b0f9..00000000000
--- a/changelogs/unreleased/karma-headless-chrome.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace PhantomJS with headless Chrome for karma test suite
-merge_request: 12036
-author:
diff --git a/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml b/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml
new file mode 100644
index 00000000000..80e6c50d5b3
--- /dev/null
+++ b/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml
@@ -0,0 +1,4 @@
+---
+title: Add LDAP SSL certificate verification option
+merge_request:
+author:
diff --git a/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml b/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml
new file mode 100644
index 00000000000..37a5fa66d13
--- /dev/null
+++ b/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml
@@ -0,0 +1,4 @@
+---
+title: Improve redirect route query performance
+merge_request: 13062
+author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml b/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
deleted file mode 100644
index f84d41b7929..00000000000
--- a/changelogs/unreleased/monitoring-dashboard-fine-tuning-ux.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve the overall UX for the new monitoring dashboard
-merge_request:
-author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
deleted file mode 100644
index 8a0e9ca855c..00000000000
--- a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
-merge_request:
-author:
diff --git a/changelogs/unreleased/moved-submodules.yml b/changelogs/unreleased/moved-submodules.yml
deleted file mode 100644
index eee858717ed..00000000000
--- a/changelogs/unreleased/moved-submodules.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Handle renamed submodules in repository browser'
-merge_request: 10798
-author: David Turner
diff --git a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
deleted file mode 100644
index 14b5493a246..00000000000
--- a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed utilities imports from ~ to relative paths
-merge_request:
-author:
diff --git a/changelogs/unreleased/new-navigation-custom-logo.yml b/changelogs/unreleased/new-navigation-custom-logo.yml
new file mode 100644
index 00000000000..22e6c5dc7e5
--- /dev/null
+++ b/changelogs/unreleased/new-navigation-custom-logo.yml
@@ -0,0 +1,4 @@
+---
+title: Fix sizing of custom header logo in new navigation
+merge_request:
+author:
diff --git a/changelogs/unreleased/pat-alert-when-signin-disabled.yml b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
deleted file mode 100644
index dca3670aeb7..00000000000
--- a/changelogs/unreleased/pat-alert-when-signin-disabled.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Provide hint to create a personal access token for Git over HTTP
-merge_request: 12105
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/pat-msg-on-auth-failure.yml b/changelogs/unreleased/pat-msg-on-auth-failure.yml
deleted file mode 100644
index c1b1528bb7a..00000000000
--- a/changelogs/unreleased/pat-msg-on-auth-failure.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Instruct user to use personal access token for Git over HTTP
-merge_request: 11986
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/polish-sidebar-toggle.yml b/changelogs/unreleased/polish-sidebar-toggle.yml
deleted file mode 100644
index 41ec567fc52..00000000000
--- a/changelogs/unreleased/polish-sidebar-toggle.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unused space in sidebar todo toggle when not signed in
-merge_request:
-author:
diff --git a/changelogs/unreleased/post-upload-pack-opt-out.yml b/changelogs/unreleased/post-upload-pack-opt-out.yml
new file mode 100644
index 00000000000..302a99795a0
--- /dev/null
+++ b/changelogs/unreleased/post-upload-pack-opt-out.yml
@@ -0,0 +1,4 @@
+---
+title: Enable gitaly_post_upload_pack by default
+merge_request: 13078
+author:
diff --git a/changelogs/unreleased/project-readme-limited-width.yml b/changelogs/unreleased/project-readme-limited-width.yml
deleted file mode 100644
index 17d87a5691e..00000000000
--- a/changelogs/unreleased/project-readme-limited-width.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit the width of the projects README text
-merge_request:
-author:
diff --git a/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml b/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
deleted file mode 100644
index 38227ebfa7a..00000000000
--- a/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'profile/notifications.feature' spinach test with an rspec analog
-merge_request: 12345
-author: @blackst0ne
diff --git a/changelogs/unreleased/replase_spinach_spec_create-feature.yml b/changelogs/unreleased/replase_spinach_spec_create-feature.yml
deleted file mode 100644
index 0613d195d56..00000000000
--- a/changelogs/unreleased/replase_spinach_spec_create-feature.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'create.feature' spinach test with an rspec analog
-merge_request: 12343
-author: @blackst0ne
diff --git a/changelogs/unreleased/sh-add-mr-simple-mode.yml b/changelogs/unreleased/sh-add-mr-simple-mode.yml
deleted file mode 100644
index 0033ca28444..00000000000
--- a/changelogs/unreleased/sh-add-mr-simple-mode.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add a simple mode to merge request API
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-allow-force-repo-create.yml b/changelogs/unreleased/sh-allow-force-repo-create.yml
deleted file mode 100644
index 2a65ba807bb..00000000000
--- a/changelogs/unreleased/sh-allow-force-repo-create.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make Project#ensure_repository force create a repo
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-bump-oauth2-gem.yml b/changelogs/unreleased/sh-bump-oauth2-gem.yml
deleted file mode 100644
index b894a64968b..00000000000
--- a/changelogs/unreleased/sh-bump-oauth2-gem.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Bump Faraday and dependent OAuth2 gem version to support no_proxy variable
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
deleted file mode 100644
index 9309f961345..00000000000
--- a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml b/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
deleted file mode 100644
index ec9ceab3d81..00000000000
--- a/changelogs/unreleased/sh-log-application-controller-exceptions-sentry.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Log rescued exceptions to Sentry
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml b/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml
deleted file mode 100644
index 9589659cdc2..00000000000
--- a/changelogs/unreleased/sh-optimize-mr-api-emojis-and-labels.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove remaining N+1 queries in merge requests API with emojis and labels
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-optimize-project-commit-api.yml b/changelogs/unreleased/sh-optimize-project-commit-api.yml
deleted file mode 100644
index e6a8a80593c..00000000000
--- a/changelogs/unreleased/sh-optimize-project-commit-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
-merge_request:
-author:
diff --git a/changelogs/unreleased/speed-up-graphs.yml b/changelogs/unreleased/speed-up-graphs.yml
deleted file mode 100644
index 7cb155af6fd..00000000000
--- a/changelogs/unreleased/speed-up-graphs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Speed up used languages calculation on charts page
-merge_request:
-author:
diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
deleted file mode 100644
index 6bf03d9a382..00000000000
--- a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache open issue and merge request counts for project tabs to speed up project
- pages
-merge_request: 12457
-author:
diff --git a/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml b/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml
deleted file mode 100644
index 00f55edc2b7..00000000000
--- a/changelogs/unreleased/speed-up-merge-request-all-commits-shas.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make loading new merge requests (those created after the 9.4 upgrade) faster
-merge_request:
-author:
diff --git a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
deleted file mode 100644
index 7e66ea4ca8b..00000000000
--- a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure participants for issues, merge requests, etc. are calculated correctly
- when sending notifications
-merge_request:
-author:
diff --git a/changelogs/unreleased/tc-follow-up-mia.yml b/changelogs/unreleased/tc-follow-up-mia.yml
deleted file mode 100644
index 6327f02032e..00000000000
--- a/changelogs/unreleased/tc-follow-up-mia.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Undo adding the /reassign quick action
-merge_request: 12701
-author:
diff --git a/changelogs/unreleased/tc-link-to-commit-on-help-page.yml b/changelogs/unreleased/tc-link-to-commit-on-help-page.yml
deleted file mode 100644
index 3d11ba43d1f..00000000000
--- a/changelogs/unreleased/tc-link-to-commit-on-help-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make the revision on the `/help` page clickable
-merge_request: 12016
-author:
diff --git a/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml b/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
deleted file mode 100644
index 7bcbd6468c7..00000000000
--- a/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add User#full_private_access? to check if user has access to all private groups & projects
-merge_request: 12373
-author:
diff --git a/changelogs/unreleased/workhorse-2-3-0.yml b/changelogs/unreleased/workhorse-2-3-0.yml
deleted file mode 100644
index 17992c8b0ff..00000000000
--- a/changelogs/unreleased/workhorse-2-3-0.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Upgrade GitLab Workhorse to v2.3.0
-merge_request: 12676
-author:
diff --git a/changelogs/unreleased/zj-commit-status-sortable-name.yml b/changelogs/unreleased/zj-commit-status-sortable-name.yml
deleted file mode 100644
index 1be9ac6380f..00000000000
--- a/changelogs/unreleased/zj-commit-status-sortable-name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Handle nameless legacy jobs
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-faster-charts-page.yml b/changelogs/unreleased/zj-faster-charts-page.yml
deleted file mode 100644
index 9afcf111328..00000000000
--- a/changelogs/unreleased/zj-faster-charts-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve performance of the pipeline charts page
-merge_request: 12378
-author:
diff --git a/changelogs/unreleased/zj-pipeline-badge-improvements.yml b/changelogs/unreleased/zj-pipeline-badge-improvements.yml
new file mode 100644
index 00000000000..735192ede2d
--- /dev/null
+++ b/changelogs/unreleased/zj-pipeline-badge-improvements.yml
@@ -0,0 +1,4 @@
+---
+title: Update build badges to be pipeline badges and display passing instead of success
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-review-apps-usage-data.yml b/changelogs/unreleased/zj-review-apps-usage-data.yml
deleted file mode 100644
index 7d224d0fc32..00000000000
--- a/changelogs/unreleased/zj-review-apps-usage-data.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add review apps to usage metrics
-merge_request: 12185
-author:
diff --git a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
deleted file mode 100644
index 0ace7b99657..00000000000
--- a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Split pipelines as internal and external in the usage data
-merge_request: 12277
-author:
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 986107150cf..278144b8943 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -45,7 +45,7 @@ Rails.application.configure do
config.active_job.queue_adapter = :test
if ENV['CI'] && !ENV['RAILS_ENABLE_TEST_LOG']
- config.logger = Logger.new(nil)
+ config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
config.log_level = :fatal
end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index cb007813b65..e9bf2df490f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -228,7 +228,8 @@ production: &base
# ==========================
## LDAP settings
- # You can inspect a sample of the LDAP users with login access by running:
+ # You can test connections and inspect a sample of the LDAP users with login
+ # access by running:
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
@@ -251,13 +252,45 @@ production: &base
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
+ # Example: 'ldap.mydomain.com'
host: '_your_ldap_server'
- port: 389
- uid: 'sAMAccountName'
- method: 'plain' # "tls" or "ssl" or "plain"
+ # This port is an example, it is sometimes different but it is always an integer and not a string
+ port: 389 # usually 636 for SSL
+ uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
+
+ # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ # Encryption method. The "method" key is deprecated in favor of
+ # "encryption".
+ #
+ # Examples: "start_tls" or "simple_tls" or "plain"
+ #
+ # Deprecated values: "tls" was replaced with "start_tls" and "ssl" was
+ # replaced with "simple_tls".
+ #
+ encryption: 'plain'
+
+ # Enables SSL certificate verification if encryption method is
+ # "start_tls" or "simple_tls". (Defaults to false for backward-
+ # compatibility)
+ verify_certificates: false
+
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_cert: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
# A value of 0 means there is no timeout.
@@ -286,17 +319,20 @@ production: &base
# Base where we can search for users
#
- # Ex. ou=People,dc=gitlab,dc=example
+ # Ex. 'ou=People,dc=gitlab,dc=example' or 'DC=mydomain,DC=com'
#
base: ''
# Filter LDAP users
#
- # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
+ # Example for getting only specific users:
+ # '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+ #
user_filter: ''
# LDAP attributes that GitLab will use to create an account for the LDAP user.
@@ -674,7 +710,7 @@ test:
host: 127.0.0.1
port: 3890
uid: 'uid'
- method: 'plain' # "tls" or "ssl" or "plain"
+ encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
base: 'dc=example,dc=com'
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index ec7ce51b542..201a1d062b9 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -145,6 +145,24 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['attributes'] = {} if server['attributes'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+
+ # For backwards compatibility
+ server['encryption'] ||= server['method']
+ server['encryption'] = 'simple_tls' if server['encryption'] == 'ssl'
+ server['encryption'] = 'start_tls' if server['encryption'] == 'tls'
+
+ # Certificates are not verified for backwards compatibility.
+ # This default should be flipped to true in 9.5.
+ if server['verify_certificates'].nil?
+ server['verify_certificates'] = false
+
+ message = <<-MSG.strip_heredoc
+ LDAP SSL certificate verification is disabled for backwards-compatibility.
+ Please add the "verify_certificates" option to gitlab.yml for each LDAP
+ server. Certificate verification will be enabled by default in GitLab 9.5.
+ MSG
+ Rails.logger.warn(message)
+ end
end
end
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index 25630b298ce..2aeb94d47cd 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -114,6 +114,9 @@ def instrument_classes(instrumentation)
# This is a Rails scope so we have to instrument it manually.
instrumentation.instrument_method(Project, :visible_to_user)
+ # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/34509
+ instrumentation.instrument_method(MarkupHelper, :link_to_gfm)
+
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
end
diff --git a/config/initializers/grape_route_helpers_fix.rb b/config/initializers/grape_route_helpers_fix.rb
new file mode 100644
index 00000000000..d3cf9e453d0
--- /dev/null
+++ b/config/initializers/grape_route_helpers_fix.rb
@@ -0,0 +1,35 @@
+if defined?(GrapeRouteHelpers)
+ module GrapeRouteHelpers
+ class DecoratedRoute
+ # GrapeRouteHelpers gem tries to parse the versions
+ # from a string, not supporting Grape `version` array definition.
+ #
+ # Without the following fix, we get this on route helpers generation:
+ #
+ # => undefined method `scan' for ["v3", "v4"]
+ #
+ # 2.0.0 implementation of this method:
+ #
+ # ```
+ # def route_versions
+ # version_pattern = /[^\[",\]\s]+/
+ # if route_version
+ # route_version.scan(version_pattern)
+ # else
+ # [nil]
+ # end
+ # end
+ # ```
+ def route_versions
+ return [nil] if route_version.nil? || route_version.empty?
+
+ if route_version.is_a?(String)
+ version_pattern = /[^\[",\]\s]+/
+ route_version.scan(version_pattern)
+ else
+ route_version
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 14902316240..c9a515dfcd5 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -13,7 +13,7 @@ unless Sidekiq.server?
# Add request parameters to log output
config.lograge.custom_options = lambda do |event|
{
- time: event.time,
+ time: event.time.utc.iso8601(3),
params: event.payload[:params].except(%w(controller action format))
}
end
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index 60355e9140c..61d39e7bfcf 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -1,3 +1,24 @@
+- group: HA Proxy
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - haproxy_frontend_http_requests_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m]))'
+ label: Total
+ unit: req / sec
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
+ required_metrics:
+ - haproxy_frontend_http_responses_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
+ label: HTTP Errors
+ unit: "%"
- group: AWS Elastic Load Balancer
priority: 10
metrics:
@@ -56,7 +77,7 @@
- nginx_responses_total
weight: 1
queries:
- - query_range: 'sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}})'
+ - query_range: 'sum(rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) / sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
label: HTTP Errors
unit: "%"
- group: Kubernetes
diff --git a/config/routes/api.rb b/config/routes/api.rb
index 69c8efc151c..ce7a7c88900 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,2 +1,2 @@
API::API.logger Rails.logger
-mount API::API => '/api'
+mount API::API => '/'
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 62cab25c763..06928c7b9ce 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -272,7 +272,7 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do
resources :repository, only: [] do
resources :tags, only: [:destroy],
- constraints: { id: Gitlab::Regex.container_registry_reference_regex }
+ constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end
end
@@ -379,7 +379,9 @@ constraints(ProjectUrlConstrainer.new) do
collection do
scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
constraints format: /svg/ do
- get :build
+ # Keep around until 10.0, see gitlab-org/gitlab-ce#35307
+ get :build, to: "badges#pipeline"
+ get :pipeline
get :coverage
end
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 1113241e402..f08daa2fddb 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -42,6 +42,7 @@ var config = {
group: './group.js',
groups: './groups/index.js',
groups_list: './groups_list.js',
+ how_to_merge: './how_to_merge.js',
issue_show: './issue_show/index.js',
integrations: './integrations',
job_details: './jobs/job_details_bundle.js',
@@ -56,7 +57,7 @@ var config = {
pipelines_details: './pipelines/pipeline_details_bundle.js',
profile: './profile/profile_bundle.js',
prometheus_metrics: './prometheus_metrics',
- protected_branches: './protected_branches/protected_branches_bundle.js',
+ protected_branches: './protected_branches',
protected_tags: './protected_tags',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
diff --git a/db/migrate/20170710083355_clean_stage_id_reference_migration.rb b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb
new file mode 100644
index 00000000000..681203eaf40
--- /dev/null
+++ b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb
@@ -0,0 +1,18 @@
+class CleanStageIdReferenceMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ ##
+ # `MigrateStageIdReferenceInBackground` background migration cleanup.
+ #
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateBuildStageIdReference')
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
new file mode 100644
index 00000000000..db60c2087b9
--- /dev/null
+++ b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
@@ -0,0 +1,34 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLowerPathIndexToRedirectRoutes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_on_redirect_routes_lower_path'
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON redirect_routes (LOWER(path));"
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ # Why not use remove_concurrent_index_by_name?
+ #
+ # `index_exists?` doesn't work on this index. Perhaps this is related to the
+ # fact that the index doesn't show up in the schema. And apparently it isn't
+ # trivial to write a query that checks for an index. BUT there is a
+ # convenient `IF EXISTS` parameter for `DROP INDEX`.
+ if supports_drop_index_concurrently?
+ disable_statement_timeout
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ else
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0ba2bd31517..61bcd8c7e95 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170717150329) do
+ActiveRecord::Schema.define(version: 20170724214302) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/README.md b/doc/README.md
index 1a7638b3d7e..ac7311a8c13 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -11,15 +11,11 @@ self-hosted, free to use. Every feature available in GitLab CE is also available
self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**.
- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
-**GitLab EE** contains all features available in **GitLab CE**,
+> **GitLab EE** contains all features available in **GitLab CE**,
plus premium features available in each version: **Enterprise Edition Starter**
(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in
**EES** is also available in **EEP**.
-**Note:** _We are unifying the documentation for CE and EE. To check if certain feature is
-available in CE or EE, look for a note right below the page title containing the GitLab
-version which introduced that feature._
-
----
Shortcuts to GitLab's most visited docs:
@@ -40,6 +36,7 @@ Shortcuts to GitLab's most visited docs:
### User account
+- [User documentation](user/index.md)
- [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
- [Profile settings](profile/README.md): Manage your profile settings, two factor authentication and more.
- [User permissions](user/permissions.md): Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
@@ -115,6 +112,7 @@ Manage files and branches from the UI (user interface):
- [Project Services](user/project/integrations/project_services.md): Integrate a project with external services, such as CI and chat.
- [GitLab Integration](integration/README.md): Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication.
+- [Trello Power-Up](integration/trello_power_up.md): Integrate with GitLab's Trello Power-Up
----
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index c8987dea5e2..a7395e03d1c 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -69,14 +69,42 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
# Example: 'ldap.mydomain.com'
host: '_your_ldap_server'
# This port is an example, it is sometimes different but it is always an integer and not a string
- port: 389
+ port: 389 # usually 636 for SSL
uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
- method: 'plain' # "tls" or "ssl" or "plain"
# Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ # Encryption method. The "method" key is deprecated in favor of
+ # "encryption".
+ #
+ # Examples: "start_tls" or "simple_tls" or "plain"
+ #
+ # Deprecated values: "tls" was replaced with "start_tls" and "ssl" was
+ # replaced with "simple_tls".
+ #
+ encryption: 'plain'
+
+ # Enables SSL certificate verification if encryption method is
+ # "start_tls" or "simple_tls". (Defaults to false for backward-
+ # compatibility)
+ verify_certificates: false
+
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_cert: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
# A value of 0 means there is no timeout.
@@ -116,8 +144,8 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
- # Below an example for get only specific users
- # Example: '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+ # Example for getting only specific users:
+ # '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
#
user_filter: ''
@@ -228,7 +256,7 @@ Tip: If you want to limit access to the nested members of an Active Directory
group you can use the following syntax:
```
-(memberOf:1.2.840.113556.1.4.1941=CN=My Group,DC=Example,DC=com)
+(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
```
Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
@@ -250,6 +278,19 @@ In other words, if an existing GitLab user wants to enable LDAP sign-in for
themselves, they should check that their GitLab email address matches their
LDAP email address, and then sign into GitLab via their LDAP credentials.
+## Encryption
+
+### TLS Server Authentication
+
+There are two encryption methods, `simple_tls` and `start_tls`.
+
+For either encryption method, if setting `validate_certificates: false`, TLS
+encryption is established with the LDAP server before any LDAP-protocol data is
+exchanged but no validation of the LDAP server's SSL certificate is performed.
+
+>**Note**: Before GitLab 9.5, `validate_certificates: false` is the default if
+unspecified.
+
## Limitations
### TLS Client Authentication
@@ -259,14 +300,6 @@ You should disable anonymous LDAP authentication and enable simple or SASL
authentication. The TLS client authentication setting in your LDAP server cannot
be mandatory and clients cannot be authenticated with the TLS protocol.
-### TLS Server Authentication
-
-Not supported by GitLab's configuration options.
-When setting `method: ssl`, the underlying authentication method used by
-`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
-the LDAP server before any LDAP-protocol data is exchanged but no validation of
-the LDAP server's SSL certificate is performed.
-
## Troubleshooting
### Debug LDAP user filter with ldapsearch
@@ -306,9 +339,9 @@ tree and traverse it.
### Connection Refused
If you are getting 'Connection Refused' errors when trying to connect to the
-LDAP server please double-check the LDAP `port` and `method` settings used by
-GitLab. Common combinations are `method: 'plain'` and `port: 389`, OR
-`method: 'ssl'` and `port: 636`.
+LDAP server please double-check the LDAP `port` and `encryption` settings used by
+GitLab. Common combinations are `encryption: 'plain'` and `port: 389`, OR
+`encryption: 'simple_tls'` and `port: 636`.
### Troubleshooting
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index afafb6bf1f5..8cb0e5b1562 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -465,23 +465,42 @@ on how to achieve that.
## Disable Container Registry but use GitLab as an auth endpoint
-You can disable the embedded Container Registry to use an external one, but
-still use GitLab as an auth endpoint.
-
**Omnibus GitLab**
+
+You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
+
1. Open `/etc/gitlab/gitlab.rb` and set necessary configurations:
```ruby
- registry['enable'] = false
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "registry.gitlab.example.com"
gitlab_rails['registry_port'] = "5005"
gitlab_rails['registry_api_url'] = "http://localhost:5000"
- gitlab_rails['registry_key_path'] = "/var/opt/gitlab/gitlab-rails/certificate.key"
gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
gitlab_rails['registry_issuer'] = "omnibus-gitlab-issuer"
```
+1. A certificate keypair is required for GitLab and the Container Registry to
+ communicate securely. By default omnibus-gitlab will generate one keypair,
+ which is saved to `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key`.
+ When using an non-bundled Container Registry, you will need to supply a
+ custom certificate key. To do that, add the following to
+ `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ gitlab_rails['registry_key_path'] = "/custom/path/to/registry-key.key"
+ # registry['internal_key'] should contain the contents of the custom key
+ # file. Line breaks in the key file should be marked using `\n` character
+ # Example:
+ registry['internal_key'] = "---BEGIN RSA PRIVATE KEY---\nMIIEpQIBAA\n"
+ ```
+
+ **Note:** The file specified at `registry_key_path` gets populated with the
+ content specified by `internal_key`, each time reconfigure is executed. If
+ no file is specified, omnibus-gitlab will default it to
+ `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` and will populate
+ it.
+
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index da9687aa849..ca6d8d2de67 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -97,9 +97,12 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
Enter new password:
Enter it again:
```
-
-1. Enable the `pg_trgm` extension:
+1. Exit from editing `template1` prompt by typing `\q` and Enter.
+1. Enable the `pg_trgm` extension within the `gitlabhq_production` database:
+
```
+ gitlab-psql -d gitlabhq_production
+
CREATE EXTENSION pg_trgm;
# Output:
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 7072ab5d02a..6baae20d16a 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -20,7 +20,7 @@ it, the client IP needs to be [included in a whitelist][whitelist].
Currently the embedded Prometheus server is not automatically configured to
collect metrics from this endpoint. We recommend setting up another Prometheus
server, because the embedded server configuration is overwritten once every
-[reconfigure of GitLab][reconfigure]. In the future this will not be required.
+[reconfigure of GitLab][reconfigure]. In the future this will not be required.
## Metrics available
@@ -46,6 +46,20 @@ In this experimental phase, only a few metrics are available:
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
+## Metrics shared directory
+
+GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
+Those files are shared among all instances running under Unicorn server.
+The directory needs to be accessible to all running Unicorn's processes otherwise
+metrics will not function correctly.
+
+For best performance its advisable that this directory will be located in `tmpfs`.
+
+Its location is configured using environment variable `prometheus_multiproc_dir`.
+
+If GitLab is installed using Omnibus and `tmpfs` is available then metrics
+directory will be automatically configured.
+
[↠Back to the main Prometheus page](index.md)
[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
diff --git a/doc/api/README.md b/doc/api/README.md
index 95e7a457848..a888c0ebb4e 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,7 +29,8 @@ following locations:
- [Keys](keys.md)
- [Labels](labels.md)
- [Merge Requests](merge_requests.md)
-- [Milestones](milestones.md)
+- [Project milestones](milestones.md)
+- [Group milestones](group_milestones.md)
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md)
diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md
index 6a4dca92cfe..e4a6dc809b1 100644
--- a/doc/api/ci/lint.md
+++ b/doc/api/ci/lint.md
@@ -47,3 +47,5 @@ Example responses:
"error": "content is missing"
}
```
+
+[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
new file mode 100644
index 00000000000..086fba7e91d
--- /dev/null
+++ b/doc/api/group_milestones.md
@@ -0,0 +1,120 @@
+# Group milestones API
+
+## List group milestones
+
+Returns a list of group milestones.
+
+```
+GET /groups/:id/milestones
+GET /groups/:id/milestones?iids=42
+GET /groups/:id/milestones?iids[]=42&iids[]=43
+GET /groups/:id/milestones?state=active
+GET /groups/:id/milestones?state=closed
+GET /groups/:id/milestones?search=version
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
+| `state` | string | optional | Return only `active` or `closed` milestones` |
+| `search` | string | optional | Return only milestones with a title or description matching the provided string |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/milestones
+```
+
+Example Response:
+
+```json
+[
+ {
+ "id": 12,
+ "iid": 3,
+ "group_id": 16,
+ "title": "10.0",
+ "description": "Version",
+ "due_date": "2013-11-29",
+ "start_date": "2013-11-10",
+ "state": "active",
+ "updated_at": "2013-10-02T09:24:18Z",
+ "created_at": "2013-10-02T09:24:18Z"
+ }
+]
+```
+
+
+## Get single milestone
+
+Gets a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of the group milestone
+
+## Create new milestone
+
+Creates a new group milestone.
+
+```
+POST /groups/:id/milestones
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `title` (required) - The title of an milestone
+- `description` (optional) - The description of the milestone
+- `due_date` (optional) - The due date of the milestone
+- `start_date` (optional) - The start date of the milestone
+
+## Edit milestone
+
+Updates an existing group milestone.
+
+```
+PUT /groups/:id/milestones/:milestone_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
+- `title` (optional) - The title of a milestone
+- `description` (optional) - The description of a milestone
+- `due_date` (optional) - The due date of the milestone
+- `start_date` (optional) - The start date of the milestone
+- `state_event` (optional) - The state event of the milestone (close|activate)
+
+## Get all issues assigned to a single milestone
+
+Gets all issues assigned to a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id/issues
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
+
+## Get all merge requests assigned to a single milestone
+
+Gets all merge requests assigned to a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id/merge_requests
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
diff --git a/doc/api/issues.md b/doc/api/issues.md
index a00a63bad4b..0e391c75cd3 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -356,7 +356,13 @@ Example response:
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/example/example/issues/1",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -418,7 +424,13 @@ Example response:
"user_notes_count": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/14",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -481,7 +493,13 @@ Example response:
"user_notes_count": 0,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/15",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -567,7 +585,13 @@ Example response:
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -632,7 +656,13 @@ Example response:
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 61ae89a64c0..d3f8e509612 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -99,7 +99,16 @@ Parameters:
"repository_size": 1038090,
"lfs_objects_size": 0,
"job_artifacts_size": 0
- }
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ },
},
{
"id": 6,
@@ -168,6 +177,15 @@ Parameters:
"repository_size": 2066080,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
}
]
@@ -257,6 +275,15 @@ Parameters:
"repository_size": 1038090,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
},
{
@@ -326,6 +353,15 @@ Parameters:
"repository_size": 2066080,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
}
]
@@ -427,6 +463,15 @@ Parameters:
"repository_size": 1038090,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
}
```
@@ -659,7 +704,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -725,7 +779,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -809,7 +872,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -893,7 +965,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 9db8e0351cf..9835fab7c98 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -2,9 +2,11 @@
Since GitLab 9.0, API V4 is the preferred version to be used.
-API V3 will be removed in GitLab 9.5, to be released on August 22, 2017. In the
-meantime, we advise you to make any necessary changes to applications that use
-V3. The V3 API documentation is still [available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
+API V3 will be unsupported from GitLab 9.5, to be released on August
+22, 2017. It will be removed in GitLab 9.5 or later. In the meantime, we advise
+you to make any necessary changes to applications that use V3. The V3 API
+documentation is still
+[available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
Below are the changes made between V3 and V4.
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index 6892905dd94..130e8f542b4 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -120,7 +120,8 @@ gitlab_rails['ldap_servers'] = {
'host' => 'ad.example.org',
'port' => 636,
'uid' => 'sAMAccountName',
- 'method' => 'ssl',
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=org',
'password' => 'Password1',
'active_directory' => true,
@@ -255,7 +256,7 @@ If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab
## LDAP extended features on GitLab EE
-With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/giltab-ee/), besides everything we just described, you'll
+With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/), besides everything we just described, you'll
have extended functionalities with LDAP, such as:
- Group sync
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 342fa88e80f..a4e41517d83 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -11,6 +11,7 @@ They are written by members of the GitLab Team and by
- **LDAP**
- [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)
+ - [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/)
## Git
@@ -23,3 +24,23 @@ They are written by members of the GitLab Team and by
- [Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)
- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)
- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)
+- [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+- [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+
+## Sofware development
+
+- [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/)
+- [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
+- [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/)
+- [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/)
+- [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+
+## Build, test, and deploy with GitLab CI/CD
+
+**Build, test, and deploy** the software you develop with **[GitLab CI/CD](../ci/README.md)**
+
+- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
+- [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
+- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
+- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
+- [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 724843a4d56..e12ef6e2685 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -395,6 +395,7 @@ job_name:
| after_script | no | Override a set of commands that are executed after job |
| environment | no | Defines a name of environment to which deployment is done by this job |
| coverage | no | Define code coverage settings for a given job |
+| retry | no | Define how many times a job can be auto-retried in case of a failure |
### script
@@ -1129,9 +1130,33 @@ A simple example:
```yaml
job1:
+ script: rspec
coverage: '/Code coverage: \d+\.\d+/'
```
+### retry
+
+**Notes:**
+- [Introduced][ce-3442] in GitLab 9.5.
+
+`retry` allows you to configure how many times a job is going to be retried in
+case of a failure.
+
+When a job fails, and has `retry` configured it is going to be processed again
+up to the amount of times specified by the `retry` keyword.
+
+If `retry` is set to 2, and a job succeeds in a second run (first retry), it won't be retried
+again. `retry` value has to be a positive integer, equal or larger than 0, but
+lower or equal to 2 (two retries maximum, three runs in total).
+
+A simple example:
+
+```yaml
+test:
+ script: rspec
+ retry: 2
+```
+
## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
@@ -1506,3 +1531,4 @@ CI with various languages.
[variables]: ../variables/README.md
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
+[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index 72a34aa7de9..e67db9ff142 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -31,6 +31,18 @@ It's also possible for different migrations to be executed at the same time.
This means that different background migrations should not migrate data in a
way that would cause conflicts.
+## Idempotence
+
+Background migrations are executed in a context of a Sidekiq process.
+Usual Sidekiq rules apply, especially the rule that jobs should be small
+and idempotent.
+
+See [Sidekiq best practices guidelines](https://github.com/mperham/sidekiq/wiki/Best-Practices)
+for more details.
+
+Make sure that in case that your migration job is going to be retried data
+integrity is guarateed.
+
## How It Works
Background migrations are simple classes that define a `perform` method. A
@@ -212,3 +224,27 @@ end
This migration will then process any jobs for the ExtractServicesUrl migration
and continue once all jobs have been processed. Once done you can safely remove
the `services.properties` column.
+
+## Testing
+
+It is required to write tests for background migrations' scheduling migration
+(either a regular migration or a post deployment migration), background
+migration itself and a cleanup migration. You can use the `:migration` RSpec
+tag when testing a regular / post deployment migration.
+See [README][migrations-readme].
+
+When you do that, keep in mind that `before` and `after` RSpec hooks are going
+to migrate you database down and up, which can result in other background
+migrations being called. That means that using `spy` test doubles with
+`have_received` is encouraged, instead of using regular test doubles, because
+your expectations defined in a `it` block can conflict with what is being
+called in RSpec hooks. See [gitlab-org/gitlab-ce#35351][issue-rspec-hooks]
+for more details.
+
+## Best practices
+
+1. Make sure that background migration jobs are idempotent.
+1. Make sure that tests you write are not false positives.
+
+[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
+[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4ed89146072..e3f37616757 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -133,6 +133,55 @@ reviewee.
tomorrow. When you are not able to find the right balance, ask other people
about their opinion.
+### GitLab-specific concerns
+
+GitLab is used in a lot of places. Many users use
+our [Omnibus packages](https://about.gitlab.com/installation/), but some use
+the [Docker images](https://docs.gitlab.com/omnibus/docker/), some are
+[installed from source](https://docs.gitlab.com/ce/install/installation.html),
+and there are other installation methods available. GitLab.com itself is a large
+Enterprise Edition instance. This has some implications:
+
+1. **Query changes** should be tested to ensure that they don't result in worse
+ performance at the scale of GitLab.com:
+ 1. Generating large quantities of data locally can help.
+ 2. Asking for query plans from GitLab.com is the most reliable way to validate
+ these.
+2. **Database migrations** must be:
+ 1. Reversible.
+ 2. Performant at the scale of GitLab.com - ask a maintainer to test the
+ migration on the staging environment if you aren't sure.
+ 3. Categorised correctly:
+ - Regular migrations run before the new code is running on the instance.
+ - [Post-deployment migrations](post_deployment_migrations.md) run _after_
+ the new code is deployed, when the instance is configured to do that.
+ - [Background migrations](background_migrations.md) run in Sidekiq, and
+ should only be done for migrations that would take an extreme amount of
+ time at GitLab.com scale.
+3. **Sidekiq workers**
+ [cannot change in a backwards-incompatible way](sidekiq_style_guide.md#removing-or-renaming-queues):
+ 1. Sidekiq queues are not drained before a deploy happens, so there will be
+ workers in the queue from the previous version of GitLab.
+ 2. If you need to change a method signature, try to do so across two releases,
+ and accept both the old and new arguments in the first of those.
+ 3. Similarly, if you need to remove a worker, stop it from being scheduled in
+ one release, then remove it in the next. This will allow existing jobs to
+ execute.
+ 4. Don't forget, not every instance will upgrade to every intermediate version
+ (some people may go from X.1.0 to X.10.0, or even try bigger upgrades!), so
+ try to be liberal in accepting the old format if it is cheap to do so.
+4. **Cached values** may persist across releases. If you are changing the type a
+ cached value returns (say, from a string or nil to an array), change the
+ cache key at the same time.
+5. **Settings** should be added as a
+ [last resort](https://about.gitlab.com/handbook/product/#convention-over-configuration).
+ If you're adding a new setting in `gitlab.yml`:
+ 1. Try to avoid that, and add to `ApplicationSetting` instead.
+ 2. Ensure that it is also
+ [added to Omnibus](https://docs.gitlab.com/omnibus/settings/gitlab.yml.html#adding-a-new-setting-to-gitlab-yml).
+6. **Filesystem access** can be slow, so try to avoid
+ [shared files](shared_files.md) when an alternative solution is available.
+
### Credits
Largely based on the [thoughtbot code review guide].
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 2ddcbe13afa..f25313d6cff 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -23,6 +23,18 @@ controlled by the server.
1. The backend code will most likely be using etags. You do not and should not check for status
`304 Not Modified`. The browser will transform it for you.
+### Lazy Loading
+
+To improve the time to first render we are using lazy loading for images. This works by setting
+the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
+the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
+
+* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src`
+* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+
+If you are asynchronously adding content which contains lazy images then you need to call the function
+`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
+
## Reducing Asset Footprint
### Page-specific JavaScript
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index ae844fa1051..149a0159680 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -447,6 +447,7 @@ A forEach will cause side effects, it will be mutating the array being iterated.
1. `name`
1. `props`
1. `mixins`
+ 1. `directives`
1. `data`
1. `components`
1. `computedProps`
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index e3a20f29a09..1e9fdbc65e2 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -36,3 +36,10 @@ slow jobs blocking work (even for different jobs) on the shared queue.
Each Sidekiq worker must be tested using RSpec, just like any other class. These
tests should be placed in `spec/workers`.
+
+## Removing or renaming queues
+
+Try to avoid renaming or removing queues in minor and patch releases.
+During online update instance can have pending jobs and removing the queue can
+lead to those jobs being stuck forever. If you can't write migration for those
+Sidekiq jobs, please consider doing rename or remove queue in major release only. \ No newline at end of file
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5e981b0b3e7..8ded607bcab 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
If you want to use Kerberos for user authentication, then install libkrb5-dev:
diff --git a/doc/integration/README.md b/doc/integration/README.md
index e56e58498a6..d70b9a7f54b 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,19 +5,23 @@ trackers and external authentication.
See the documentation below for details on how to configure these services.
-- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Akismet](akismet.md) Configure Akismet to stop spam
+- [Auth0 OmniAuth](auth0.md) Enable the Auth0 OmniAuth provider
+- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your
+Bitbucket.org account
+- [CAS](cas.md) Configure GitLab to sign in using CAS
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
+- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
+- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
-- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
-- [CAS](cas.md) Configure GitLab to sign in using CAS
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
- [OpenID Connect](openid_connect_provider.md) Use GitLab as an identity provider
-- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
-- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
-- [Akismet](akismet.md) Configure Akismet to stop spam
-- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
- [PlantUML](../administration/integration/plantuml.md) Configure PlantUML to use diagrams in AsciiDoc documents.
+- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
+- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
+- [Trello](trello_power_up.md) Integrate Trello with GitLab
> GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 2dd9b33273c..372e1909330 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -4,14 +4,12 @@ GitLab has a great issue tracker but you can also use an external one such as
Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow
you to do the following:
-- the **Issues** link on the GitLab project pages takes you to the appropriate
- issue index of the external tracker
-- clicking **New issue** on the project dashboard creates a new issue on the
- external tracker
- you can reference these external issues inside GitLab interface
(merge requests, commits, comments) and they will be automatically converted
into links
+You can have enabled both external and internal GitLab issue trackers in parallel. The **Issues** link always opens the internal issue tracker and in case the internal issue tracker is disabled the link is not visible in the menu.
+
## Configuration
The configuration is done via a project's **Services**.
diff --git a/doc/integration/img/enable_trello_powerup.png b/doc/integration/img/enable_trello_powerup.png
new file mode 100644
index 00000000000..65d01f1c38c
--- /dev/null
+++ b/doc/integration/img/enable_trello_powerup.png
Binary files differ
diff --git a/doc/integration/img/trello_card_with_gitlab_powerup.png b/doc/integration/img/trello_card_with_gitlab_powerup.png
new file mode 100644
index 00000000000..2965dc35855
--- /dev/null
+++ b/doc/integration/img/trello_card_with_gitlab_powerup.png
Binary files differ
diff --git a/doc/integration/trello_power_up.md b/doc/integration/trello_power_up.md
new file mode 100644
index 00000000000..d264486a872
--- /dev/null
+++ b/doc/integration/trello_power_up.md
@@ -0,0 +1,42 @@
+# Trello Power-Up
+
+GitLab's Trello Power-Up enables you to seamlessly attach
+GitLab **merge requests** to Trello cards.
+
+![GitLab Trello PowerUp - Trello card](img/trello_card_with_gitlab_powerup.png)
+
+## Configuring the Power-Up
+
+In order to get started, you will need to configure your Power-Up.
+
+In Trello:
+
+1. Go to your Trello board
+1. Select `Power-Ups` to see a listing of all the available Power-Ups
+1. Look for a row that says `GitLab` and select the `Enable` button
+1. Select the `Settings` (gear) icon
+1. In the popup menu, select `Authorize Account`
+
+In this popup, fill in your `API URL` and `Personal Access Token`. After that, you will be able to attach any merge request to any Trello card on your selected Trello board.
+
+## What is my API URL?
+
+Your API URL should be your GitLab instance URL with `/api/v4` appended in the end of the URL.
+For example, if your GitLab instance URL is `https://gitlab.com`, your API URL would be `https://gitlab.com/api/v4`.
+If your instance's URL is `https://example.com`, your API URL will be `https://example.com/api/v4`.
+
+![configure GitLab Trello PowerUp in Trello](img/enable_trello_powerup.png)
+
+## What is my Personal Access Token?
+
+Your GitLab's personal access token will enable your GitLab account to be accessed
+from Trello.
+
+> Find it in GitLab by clicking on your avatar (upright corner), from which you access
+your user **Settings** > **Access Tokens**.
+
+Learn more about generating a personal access token in the
+[Personal Access Token Documentation][personal-access-token-documentation].
+Don't forget to check the API scope checkbox!
+
+[personal-access-token-documentation]: ../user/profile/personal_access_tokens.html
diff --git a/doc/user/index.md b/doc/user/index.md
new file mode 100644
index 00000000000..f545dbffde3
--- /dev/null
+++ b/doc/user/index.md
@@ -0,0 +1,175 @@
+# User documentation
+
+Welcome to GitLab! We're glad to have you here!
+
+As a GitLab user you'll have access to all the features
+your [subscription](https://about.gitlab.com/products/)
+includes, except [GitLab administrator](../README.md#administrator-documentation)
+settings, unless you have admin privileges to install, configure,
+and upgrade your GitLab instance.
+
+For GitLab.com, admin privileges are restricted to the GitLab team.
+
+If you run your own GitLab instance and are looking for the administration settings,
+please refer to the [administration](../README.md#administrator-documentation)
+documentation.
+
+## Overview
+
+GitLab is a fully integrated software development platform that enables you
+and your team to work cohesively, faster, transparently, and effectively,
+since the discussion of a new idea until taking that idea to production all
+all the way through, from within the same platform.
+
+Please check this page for an overview on [GitLab's features](https://about.gitlab.com/features/).
+
+## Use cases
+
+GitLab is a git-based platforms that integrates a great number of essential tools for software development and deployment, and project management:
+
+- Code hosting in repositories with version control
+- Track proposals for new implementations, bug reports, and feedback with a
+fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
+- Organize and prioritize with [Issue Boards](project/issues/index.md#issue-boards)
+- Code review in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
+branch with [Review Apps](../ci/review_apps/index.md)
+- Build, test and deploy with built-in [Continuous Integration](../ci/README.md)
+- Deploy your personal and professional static websites with [GitLab Pages](project/pages/index.md)
+- Integrate with Docker with [GitLab Container Registry](project/container_registry.md)
+- Track the development lifecycle with [GitLab Cycle Analytics](project/cycle_analytics.md)
+
+With GitLab Enterprise Edition, you can also:
+
+- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html)
+- Improve collaboration with
+[Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
+[Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
+- Create formal relashionships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html)
+- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
+- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance
+- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html)
+- [Mirror a repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html) from elsewhere on your local server.
+- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html)
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
+- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
+- View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+- Leverage your continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
+
+### Articles
+
+For a complete workflow use case please check [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+
+For more use cases please check our [Technical Articles](../articles/index.md).
+
+## Projects
+
+In GitLab, you can create projects for numerous reasons, such as, host
+your code, use it as an issue tracker, collaborate on code, and continuously
+build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
+it all at once, from one single project.
+
+### Issues
+
+Explore the best of GitLab [Issues](project/issues/index.md).
+
+### Merge Requests
+
+Collanorate on code, gather reviews, live preview changes per branch, and
+request approvals with [Merge Requests](project/merge_requests/index.md).
+
+### Milestones
+
+Work on multiple issues and merge requests towards the same target date
+with [Milestones](project/milestones/index.md).
+
+### GitLab Pages
+
+Publish your static site directly from GitLab with [GitLab Pages](project/pages/index.md). You
+can [build, test, and deploy any Static Site Generator](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) with Pages.
+
+### Container Registry
+
+Build and deploy Docker images with [GitLab Container Registry](project/container_registry.md).
+
+## GitLab CI/CD
+
+Use built-in [GitLab CI/CD](../ci/README.md) to test, build, and deploy your applications
+directly from GitLab. No third-party integrations needed.
+
+### Auto Deploy
+
+Deploy your application out-of-the-box with [GitLab Auto Deploy](../ci/autodeploy/index.md).
+
+### Review Apps
+
+Live-preview the changes introduced by a merge request with [Review Apps](../ci/review_apps/index.md).
+
+## Groups
+
+With GitLab [Groups](group/index.md) you can assemble related projects together
+and grant members access to several projects at once.
+
+### Subgroups
+
+Groups can also be nested in [subgroups](group/subgroups/index.md).
+
+## Account
+
+There is a lot you can customize and configure
+to enjoy the best of GitLab.
+
+Manage your user settings to change your personal info,
+personal access tokens, authorized applications, integrations, etc.
+
+### Authentication
+
+Read through the [authentication](../topics/authentication/index.md) methods available in GitLab.
+
+### Permissions
+
+Learn the different set of [permissions](permissions.md) for user type (guest, reporter, developer, master, owner).
+
+## Integrations
+
+[Integrate GitLab](../integration/README.md) with your preferred tool,
+such as Trello, JIRA, etc.
+
+## Git and GitLab
+
+Learn what is [Git](../topics/git/index.md) and its best practices.
+
+## Discussions
+
+In GitLab, you can comment and mention collaborators in issues,
+merge requests, code snippets, and commits.
+
+When performing inline reviews to implementations
+to your codebase through merge requests you can
+gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
+
+## Todos
+
+Never forget to reply to your collaborators. [GitLab Todos](../workflow/todos.md)
+are a tool for working faster and more effectively with your team,
+by listing all user or group mentions, as well as issues and merge
+requests you're assigned to.
+
+## Snippets
+
+[Snippets](snippets.md) are code blocks that you want to store in GitLab, from which
+you have quick access to. You can also gather feedback on them through
+[discussions](#discussions).
+
+## Webhooks
+
+Configure [webhooks](project/integrations/webhooks.html) to listen for
+specific events like pushes, issues or merge requests. GitLab will send a
+POST request with data to the webhook URL.
+
+## API
+
+Automate GitLab via [API](../api/README.html).
+
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 6a040516231..ba2adc1afda 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -20,10 +20,12 @@ Once you have configured and enabled Bugzilla:
## Referencing issues in Bugzilla
Issues in Bugzilla can be referenced in two alternative ways:
-1. `#<ID>` where `<ID>` is a number (example `#143`)
+1. `#<ID>` where `<ID>` is a number (example `#143`).
2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
+We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
+
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
diff --git a/doc/user/project/integrations/img/webhook_testing.png b/doc/user/project/integrations/img/webhook_testing.png
new file mode 100644
index 00000000000..176dcec9d8a
--- /dev/null
+++ b/doc/user/project/integrations/img/webhook_testing.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index cf03f2a9033..cfa4c8a93f8 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -98,11 +98,11 @@ in the table below.
| Field | Description |
| ----- | ----------- |
| `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
-| `JIRA API URL` | The base URL to the JIRA instance API. E.g., `https://jira-api.example.com`. This is optional. If not entered, the Web URL value be used. |
+| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
| `Project key` | Put a JIRA project key (in uppercase), e.g. `MARS` in this field. This is only for testing the configuration settings. JIRA integration in GitLab works with _all_ JIRA projects in your JIRA instance. This field will be removed in a future release. |
| `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+| `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
After saving the configuration, your GitLab project will be able to interact
with all JIRA projects in your JIRA instance.
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
new file mode 100644
index 00000000000..309da610cc0
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -0,0 +1,20 @@
+# Monitoring HA Proxy
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
+
+GitLab has support for automatically detecting and monitoring HA Proxy. This is provided by leveraging the [HA Proxy Exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates HA Proxy statistics into a Prometheus readable form.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
+| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
+
+## Configuring Prometheus to monitor for HA Proxy metrics
+
+To get started with NGINX monitoring, you should install and configure the [HA Proxy exporter](https://github.com/prometheus/haproxy_exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint.
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index 55146e57370..546e1f51df5 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -4,6 +4,7 @@
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
* [Kubernetes](kubernetes.md)
* [NGINX](nginx.md)
+* [HA Proxy](haproxy.md)
* [Amazon Cloud Watch](cloudwatch.md)
We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome.
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index fe238e74e36..b3470773996 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -9,7 +9,7 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
| ---- | ----- |
| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) |
| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000 |
-| HTTP Error Rate (%) | sum(nginx_responses_total{status_code="5xx", %{environment_filter}}) / sum(nginx_responses_total{server_zone!="*", server_zone!="_", %{environment_filter}}) |
+| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m])) |
## Configuring Prometheus to monitor for NGINX metrics
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 8026f1f57bc..cf92465da53 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -30,5 +30,7 @@ Issues in Redmine can be referenced in two alternative ways:
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
+We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
+
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 023c6932e41..c03a2df9a72 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1014,6 +1014,13 @@ X-Gitlab-Event: Build Hook
}
```
+## Testing webhooks
+
+You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project.
+> For example: for triggering `Push Events` your project should have at least one commit.
+
+![Webhook testing](img/webhook_testing.png)
+
## Troubleshoot webhooks
Gitlab stores each perform of the webhook.
@@ -1056,7 +1063,7 @@ Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
8000`. Then add your server as a webhook receiver in GitLab as
`http://my.host:8000/`.
-When you press 'Test Hook' in GitLab, you should see something like this in the
+When you press 'Test' in GitLab, you should see something like this in the
console:
```
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 258b3a2f955..9ad15a12c3c 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -71,9 +71,10 @@ The next time a pipeline is scheduled, your credentials will be used.
>**Note:**
When the owner of the schedule doesn't have the ability to create pipelines
-anymore, due to e.g., being blocked or removed from the project, the schedule
-is deactivated. Another user can take ownership and activate it, so the
-schedule can be run again.
+anymore, due to e.g., being blocked or removed from the project, or lacking
+the permission to run on protected branches or tags. When this happened, the
+schedule is deactivated. Another user can take ownership and activate it, so
+the schedule can be run again.
## Advanced admin configuration
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 19b51c83222..ce4dd4e99d5 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -37,3 +37,4 @@ do.
| `/target_branch <Branch Name>` | Set target branch for current merge request |
| `/award :emoji:` | Toggle award for :emoji: |
| `/board_move ~column` | Move issue to column on the board |
+| `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue |
diff --git a/features/project/badges/build.feature b/features/project/badges/build.feature
deleted file mode 100644
index bcf80ed620e..00000000000
--- a/features/project/badges/build.feature
+++ /dev/null
@@ -1,27 +0,0 @@
-Feature: Project Badges Build
- Background:
- Given I sign in as a user
- And I own a project
- And project has CI enabled
- And project has a recent build
-
- Scenario: I want to see a badge for successfully built project
- Given recent build is successful
- When I display builds badge for a master branch
- Then I should see a build success badge
-
- Scenario: I want to see a badge for project with failed builds
- Given recent build failed
- When I display builds badge for a master branch
- Then I should see a build failed badge
-
- Scenario: I want to see a badge for project with running builds
- Given recent build is successful
- And project has another build that is running
- When I display builds badge for a master branch
- Then I should see a build running badge
-
- Scenario: I want to see a fresh badge on each request
- Given recent build is successful
- When I display builds badge for a master branch
- Then I should see a badge that has not been cached
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
deleted file mode 100644
index 5a9094ee9d3..00000000000
--- a/features/steps/project/badges/build.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedBuilds
- include RepoHelpers
-
- step 'I display builds badge for a master branch' do
- visit build_project_badges_path(@project, ref: :master, format: :svg)
- end
-
- step 'I should see a build success badge' do
- expect_badge('success')
- end
-
- step 'I should see a build failed badge' do
- expect_badge('failed')
- end
-
- step 'I should see a build running badge' do
- expect_badge('running')
- end
-
- step 'I should see a badge that has not been cached' do
- expect(page.response_headers['Cache-Control']).to include 'no-cache'
- end
-
- def expect_badge(status)
- svg = Nokogiri::XML.parse(page.body)
- expect(page.response_headers['Content-Type']).to include('image/svg+xml')
- expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
- end
-end
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 906a81b29b3..7e2a357f6b2 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -175,7 +175,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
fill_in 'JIRA API URL', with: 'http://jira.example/api'
fill_in 'Username', with: 'gitlab'
fill_in 'Password', with: 'gitlab'
- fill_in 'Project Key', with: 'GITLAB'
click_button 'Save'
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index a2f5d2e1515..9d38939378d 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'Image should be shown on the page' do
- expect(page).to have_xpath("//img[@src=\"image.jpg\"]")
+ expect(page).to have_xpath("//img[@data-src=\"image.jpg\"]")
end
step 'I click on image link' do
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index c9b5f58c557..cdacf9839e5 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -68,6 +68,7 @@ module API
delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id])
+ status 204
::Members::DestroyService.new(source, current_user, params)
.execute(:requesters)
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index efcf0976a81..3bdafa3edc1 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -3,6 +3,7 @@ module API
include APIGuard
allow_access_with_scope :api
+ prefix :api
version %w(v3 v4), using: :path
@@ -109,7 +110,8 @@ module API
mount ::API::Members
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
- mount ::API::Milestones
+ mount ::API::ProjectMilestones
+ mount ::API::GroupMilestones
mount ::API::Namespaces
mount ::API::Notes
mount ::API::NotificationSettings
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 56f19f89642..5a028fc9d0b 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -88,6 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
+ status 204
award.destroy
end
end
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 395c401203c..9980aec4752 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -91,6 +91,7 @@ module API
delete ':id' do
message = find_message
+ status 204
message.destroy
end
end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index d5c2f3d5094..42e7c1486b0 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -125,6 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key
+ status 204
key.destroy
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 09a88869063..5cdc441e8cb 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -82,6 +82,38 @@ module API
end
class Project < Grape::Entity
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ expose :_links do
+ expose :self do |project|
+ expose_url(api_v4_projects_path(id: project.id))
+ end
+
+ expose :issues, if: -> (*args) { issues_available?(*args) } do |project|
+ expose_url(api_v4_projects_issues_path(id: project.id))
+ end
+
+ expose :merge_requests, if: -> (*args) { mrs_available?(*args) } do |project|
+ expose_url(api_v4_projects_merge_requests_path(id: project.id))
+ end
+
+ expose :repo_branches do |project|
+ expose_url(api_v4_projects_repository_branches_path(id: project.id))
+ end
+
+ expose :labels do |project|
+ expose_url(api_v4_projects_labels_path(id: project.id))
+ end
+
+ expose :events do |project|
+ expose_url(api_v4_projects_events_path(id: project.id))
+ end
+
+ expose :members do |project|
+ expose_url(api_v4_projects_members_path(id: project.id))
+ end
+ end
+
expose :id, :description, :default_branch, :tag_list
expose :archived?, as: :archived
expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url
@@ -109,7 +141,7 @@ module API
user.avatar_url(only_path: false)
end
expose :star_count, :forks_count
- expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
+ expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
expose :ci_config_path
@@ -269,8 +301,8 @@ module API
class Milestone < Grape::Entity
expose :id, :iid
- expose(:project_id) { |entity| entity&.project_id }
- expose(:group_id) { |entity| entity&.group_id }
+ expose :project_id, if: -> (entity, options) { entity&.project_id }
+ expose :group_id, if: -> (entity, options) { entity&.group_id }
expose :title, :description
expose :state, :created_at, :updated_at
expose :due_date
@@ -297,6 +329,26 @@ module API
end
class Issue < IssueBasic
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ expose :_links do
+ expose :self do |issue|
+ expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid))
+ end
+
+ expose :notes do |issue|
+ expose_url(api_v4_projects_issues_notes_path(id: issue.project_id, noteable_id: issue.iid))
+ end
+
+ expose :award_emoji do |issue|
+ expose_url(api_v4_projects_issues_award_emoji_path(id: issue.project_id, issue_iid: issue.iid))
+ end
+
+ expose :project do |issue|
+ expose_url(api_v4_projects_path(id: issue.project_id))
+ end
+ end
+
expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 945771d46f3..c774a5c6685 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -79,6 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
+ status 204
environment.destroy
end
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
new file mode 100644
index 00000000000..b85eb59dc0a
--- /dev/null
+++ b/lib/api/group_milestones.rb
@@ -0,0 +1,85 @@
+module API
+ class GroupMilestones < Grape::API
+ include MilestoneResponses
+ include PaginationParams
+
+ before do
+ authenticate!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: { id: %r{[^/]+} } do
+ desc 'Get a list of group milestones' do
+ success Entities::Milestone
+ end
+ params do
+ use :list_params
+ end
+ get ":id/milestones" do
+ list_milestones_for(user_group)
+ end
+
+ desc 'Get a single group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ end
+ get ":id/milestones/:milestone_id" do
+ authorize! :read_group, user_group
+
+ get_milestone_for(user_group)
+ end
+
+ desc 'Create a new group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :title, type: String, desc: 'The title of the milestone'
+ use :optional_params
+ end
+ post ":id/milestones" do
+ authorize! :admin_milestones, user_group
+
+ create_milestone_for(user_group)
+ end
+
+ desc 'Update an existing group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ use :update_params
+ end
+ put ":id/milestones/:milestone_id" do
+ authorize! :admin_milestones, user_group
+
+ update_milestone_for(user_group)
+ end
+
+ desc 'Get all issues for a single group milestone' do
+ success Entities::IssueBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ use :pagination
+ end
+ get ":id/milestones/:milestone_id/issues" do
+ milestone_issuables_for(user_group, :issue)
+ end
+
+ desc 'Get all merge requests for a single group milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequestBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ milestone_issuables_for(user_group, :merge_request)
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ebbaed0cbb7..49c3b2278c7 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -125,6 +125,8 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
+
+ status 204
::Groups::DestroyService.new(group, current_user).execute
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 0f4791841d2..57e3e93500f 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -25,6 +25,10 @@ module API
initial_current_user != current_user
end
+ def user_group
+ @group ||= find_group!(params[:id])
+ end
+
def user_project
@project ||= find_project!(params[:id])
end
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
new file mode 100644
index 00000000000..769cc1457fc
--- /dev/null
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -0,0 +1,28 @@
+module API
+ module Helpers
+ module RelatedResourcesHelpers
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ def issues_available?(project, options)
+ available?(:issues, project, options[:current_user])
+ end
+
+ def mrs_available?(project, options)
+ available?(:merge_requests, project, options[:current_user])
+ end
+
+ def expose_url(path)
+ url_options = Rails.application.routes.default_url_options
+ protocol, host, port = url_options.slice(:protocol, :host, :port).values
+
+ URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
+ end
+
+ private
+
+ def available?(feature, project, current_user)
+ project.feature_available?(feature, current_user)
+ end
+ end
+ end
+end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 64be08094ed..93ebe18508d 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -112,7 +112,7 @@ module API
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- get ":id/issues/:issue_iid" do
+ get ":id/issues/:issue_iid", as: :api_v4_project_issue do
issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
@@ -224,6 +224,7 @@ module API
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
+ status 204
issue.destroy
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 20b25529d0c..4520c98d951 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -56,6 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
+ status 204
label.destroy
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index c200e46a328..bb970b7cd54 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -96,6 +96,7 @@ module API
# Ensure that memeber exists
source.members.find_by!(user_id: params[:user_id])
+ status 204
::Members::DestroyService.new(source, current_user, declared_params).execute
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index ac33b2b801c..f64ac659413 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -29,14 +29,6 @@ module API
render_api_error!(errors, 400)
end
- def issue_entity(project)
- if project.has_external_issue_tracker?
- Entities::ExternalIssue
- else
- Entities::IssueBasic
- end
- end
-
def find_merge_requests(args = {})
args = params.merge(args)
@@ -137,6 +129,7 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
+ status 204
merge_request.destroy
end
@@ -277,7 +270,14 @@ module API
get ':id/merge_requests/:merge_request_iid/closes_issues' do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
- present paginate(issues), with: issue_entity(user_project), current_user: current_user
+ issues = paginate(issues)
+
+ external_issues, internal_issues = issues.partition { |issue| issue.is_a?(ExternalIssue) }
+
+ data = Entities::IssueBasic.represent(internal_issues, current_user: current_user)
+ data += Entities::ExternalIssue.represent(external_issues, current_user: current_user)
+
+ data.as_json
end
end
end
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
new file mode 100644
index 00000000000..ef09d9505d2
--- /dev/null
+++ b/lib/api/milestone_responses.rb
@@ -0,0 +1,98 @@
+module API
+ module MilestoneResponses
+ extend ActiveSupport::Concern
+
+ included do
+ helpers do
+ params :optional_params do
+ optional :description, type: String, desc: 'The description of the milestone'
+ optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
+ optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
+ end
+
+ params :list_params do
+ optional :state, type: String, values: %w[active closed all], default: 'all',
+ desc: 'Return "active", "closed", or "all" milestones'
+ optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
+ optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
+ use :pagination
+ end
+
+ params :update_params do
+ requires :milestone_id, type: Integer, desc: 'The milestone ID number'
+ optional :title, type: String, desc: 'The title of the milestone'
+ optional :state_event, type: String, values: %w[close activate],
+ desc: 'The state event of the milestone '
+ use :optional_params
+ at_least_one_of :title, :description, :due_date, :state_event
+ end
+
+ def list_milestones_for(parent)
+ milestones = parent.milestones
+ milestones = Milestone.filter_by_state(milestones, params[:state])
+ milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
+ milestones = filter_by_search(milestones, params[:search]) if params[:search]
+
+ present paginate(milestones), with: Entities::Milestone
+ end
+
+ def get_milestone_for(parent)
+ milestone = parent.milestones.find(params[:milestone_id])
+ present milestone, with: Entities::Milestone
+ end
+
+ def create_milestone_for(parent)
+ milestone = ::Milestones::CreateService.new(parent, current_user, declared_params).execute
+
+ if milestone.valid?
+ present milestone, with: Entities::Milestone
+ else
+ render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
+ end
+ end
+
+ def update_milestone_for(parent)
+ milestone = parent.milestones.find(params.delete(:milestone_id))
+
+ milestone_params = declared_params(include_missing: false)
+ milestone = ::Milestones::UpdateService.new(parent, current_user, milestone_params).execute(milestone)
+
+ if milestone.valid?
+ present milestone, with: Entities::Milestone
+ else
+ render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
+ end
+ end
+
+ def milestone_issuables_for(parent, type)
+ milestone = parent.milestones.find(params[:milestone_id])
+
+ finder_klass, entity = get_finder_and_entity(type)
+
+ params = build_finder_params(milestone, parent)
+
+ issuables = finder_klass.new(current_user, params).execute
+ present paginate(issuables), with: entity, current_user: current_user
+ end
+
+ def build_finder_params(milestone, parent)
+ finder_params = { milestone_title: milestone.title, sort: 'label_priority' }
+
+ if parent.is_a?(Group)
+ finder_params.merge(group_id: parent.id)
+ else
+ finder_params.merge(project_id: parent.id)
+ end
+ end
+
+ def get_finder_and_entity(type)
+ if type == :issue
+ [IssuesFinder, Entities::IssueBasic]
+ else
+ [MergeRequestsFinder, Entities::MergeRequestBasic]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
deleted file mode 100644
index 3541d3c95fb..00000000000
--- a/lib/api/milestones.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-module API
- class Milestones < Grape::API
- include PaginationParams
-
- before { authenticate! }
-
- helpers do
- def filter_milestones_state(milestones, state)
- case state
- when 'active' then milestones.active
- when 'closed' then milestones.closed
- else milestones
- end
- end
-
- params :optional_params do
- optional :description, type: String, desc: 'The description of the milestone'
- optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
- optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
- end
- end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: { id: %r{[^/]+} } do
- desc 'Get a list of project milestones' do
- success Entities::Milestone
- end
- params do
- optional :state, type: String, values: %w[active closed all], default: 'all',
- desc: 'Return "active", "closed", or "all" milestones'
- optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
- optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
- use :pagination
- end
- get ":id/milestones" do
- authorize! :read_milestone, user_project
-
- milestones = user_project.milestones
- milestones = filter_milestones_state(milestones, params[:state])
- milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
- milestones = filter_by_search(milestones, params[:search]) if params[:search]
-
- present paginate(milestones), with: Entities::Milestone
- end
-
- desc 'Get a single project milestone' do
- success Entities::Milestone
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- end
- get ":id/milestones/:milestone_id" do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
- present milestone, with: Entities::Milestone
- end
-
- desc 'Create a new project milestone' do
- success Entities::Milestone
- end
- params do
- requires :title, type: String, desc: 'The title of the milestone'
- use :optional_params
- end
- post ":id/milestones" do
- authorize! :admin_milestone, user_project
-
- milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute
-
- if milestone.valid?
- present milestone, with: Entities::Milestone
- else
- render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
- end
- end
-
- desc 'Update an existing project milestone' do
- success Entities::Milestone
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- optional :title, type: String, desc: 'The title of the milestone'
- optional :state_event, type: String, values: %w[close activate],
- desc: 'The state event of the milestone '
- use :optional_params
- at_least_one_of :title, :description, :due_date, :state_event
- end
- put ":id/milestones/:milestone_id" do
- authorize! :admin_milestone, user_project
- milestone = user_project.milestones.find(params.delete(:milestone_id))
-
- milestone_params = declared_params(include_missing: false)
- milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone)
-
- if milestone.valid?
- present milestone, with: Entities::Milestone
- else
- render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
- end
- end
-
- desc 'Get all issues for a single project milestone' do
- success Entities::IssueBasic
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- use :pagination
- end
- get ":id/milestones/:milestone_id/issues" do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
-
- finder_params = {
- project_id: user_project.id,
- milestone_title: milestone.title,
- sort: 'label_priority'
- }
-
- issues = IssuesFinder.new(current_user, finder_params).execute
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
- end
-
- desc 'Get all merge requests for a single project milestone' do
- detail 'This feature was introduced in GitLab 9.'
- success Entities::MergeRequestBasic
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- use :pagination
- end
- get ':id/milestones/:milestone_id/merge_requests' do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
-
- finder_params = {
- project_id: user_project.id,
- milestone_title: milestone.title,
- sort: 'label_priority'
- }
-
- merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
- present paginate(merge_requests),
- with: Entities::MergeRequestBasic,
- current_user: current_user,
- project: user_project
- end
- end
- end
-end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 01ca62b593f..65ff89edf65 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -131,6 +131,7 @@ module API
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
+ status 204
::Notes::DestroyService.new(user_project, current_user).execute(note)
end
end
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 7a345289617..649dd891f56 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -96,6 +96,7 @@ module API
delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
+ status 204
hook.destroy
end
end
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
new file mode 100644
index 00000000000..451998c726a
--- /dev/null
+++ b/lib/api/project_milestones.rb
@@ -0,0 +1,91 @@
+module API
+ class ProjectMilestones < Grape::API
+ include PaginationParams
+ include MilestoneResponses
+
+ before do
+ authenticate!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
+ desc 'Get a list of project milestones' do
+ success Entities::Milestone
+ end
+ params do
+ use :list_params
+ end
+ get ":id/milestones" do
+ authorize! :read_milestone, user_project
+
+ list_milestones_for(user_project)
+ end
+
+ desc 'Get a single project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ end
+ get ":id/milestones/:milestone_id" do
+ authorize! :read_milestone, user_project
+
+ get_milestone_for(user_project)
+ end
+
+ desc 'Create a new project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :title, type: String, desc: 'The title of the milestone'
+ use :optional_params
+ end
+ post ":id/milestones" do
+ authorize! :admin_milestone, user_project
+
+ create_milestone_for(user_project)
+ end
+
+ desc 'Update an existing project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ use :update_params
+ end
+ put ":id/milestones/:milestone_id" do
+ authorize! :admin_milestone, user_project
+
+ update_milestone_for(user_project)
+ end
+
+ desc 'Get all issues for a single project milestone' do
+ success Entities::IssueBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ":id/milestones/:milestone_id/issues" do
+ authorize! :read_milestone, user_project
+
+ milestone_issuables_for(user_project, :issue)
+ end
+
+ desc 'Get all merge requests for a single project milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequestBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ authorize! :read_milestone, user_project
+
+ milestone_issuables_for(user_project, :merge_request)
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 3320eadff0d..f3d905b0068 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -116,6 +116,7 @@ module API
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
+ status 204
snippet.destroy
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index c459257158d..89dda88d3f5 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -361,6 +361,7 @@ module API
authorize! :remove_fork_project, user_project
if user_project.forked?
+ status 204
user_project.forked_project_link.destroy
else
not_modified!
@@ -405,6 +406,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
+ status 204
link.destroy
end
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 4552115b3e2..405d25ca3c1 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -45,6 +45,7 @@ module API
end
delete '/' do
authenticate_runner!
+ status 204
Ci::Runner.find_by_token(params[:token]).destroy
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index db6c7c59092..5bf5a18e42f 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -79,6 +79,7 @@ module API
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
+ status 204
runner.destroy!
end
end
@@ -134,6 +135,7 @@ module API
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+ status 204
runner_project.destroy
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 7488f95a9b7..843c05ae32e 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -313,12 +313,6 @@ module API
desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
},
{
- required: true,
- name: :project_key,
- type: String,
- desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
- },
- {
required: false,
name: :username,
type: String,
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index fd634037a77..35ece56c65c 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -123,6 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet
+ status 204
snippet.destroy
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index ed7b23b474a..c0179037440 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -66,6 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
+ status 204
hook.destroy
end
end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index a9f2ca2608e..9375e7eb768 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -27,12 +27,13 @@ module API
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
- if trigger_request
- present trigger_request.pipeline, with: Entities::Pipeline
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present pipeline, with: Entities::Pipeline
else
- errors = 'No pipeline created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
@@ -142,6 +143,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
+ status 204
trigger.destroy
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 81c68ea2658..a590f2692a2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -235,6 +235,7 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ status 204
key.destroy
end
@@ -306,6 +307,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
+ status 204
user.delete_async(deleted_by: current_user, params: params)
end
@@ -406,6 +408,7 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
delete ':impersonation_token_id' do
+ status 204
find_impersonation_token.revoke!
end
end
@@ -483,6 +486,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ status 204
key.destroy
end
@@ -534,6 +538,7 @@ module API
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
+ status 204
Emails::DestroyService.new(current_user, email: email.email).execute
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 3759250f7f6..773f667abe0 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -259,11 +259,40 @@ module API
expose :job_events, as: :build_events
end
- class Issue < ::API::Entities::Issue
+ class ProjectEntity < Grape::Entity
+ expose :id, :iid
+ expose(:project_id) { |entity| entity&.project.try(:id) }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
+ end
+
+ class IssueBasic < ProjectEntity
+ expose :label_names, as: :labels
+ expose :milestone, using: ::API::Entities::Milestone
+ expose :assignees, :author, using: ::API::Entities::UserBasic
+
+ expose :assignee, using: ::API::Entities::UserBasic do |issue, options|
+ issue.assignees.first
+ end
+
+ expose :user_notes_count
+ expose :upvotes, :downvotes
+ expose :due_date
+ expose :confidential
+
+ expose :web_url do |issue, options|
+ Gitlab::UrlBuilder.build(issue)
+ end
+ end
+
+ class Issue < IssueBasic
unexpose :assignees
expose :assignee do |issue, options|
::API::Entities::UserBasic.represent(issue.assignees.first, options)
end
+ expose :subscribed do |issue, options|
+ issue.subscribed?(options[:current_user], options[:project] || issue.project)
+ end
end
end
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index a23d6b6b48c..e9d4c35307b 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -28,12 +28,13 @@ module API
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
- if trigger_request
- present trigger_request, with: ::API::V3::Entities::TriggerRequest
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present result.trigger_request, with: ::API::V3::Entities::TriggerRequest
else
- errors = 'No builds created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 7fa528fb2d3..7c0fdd3d1be 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -88,6 +88,7 @@ module API
variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable
+ status 204
variable.destroy
end
end
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 0ea4eeaed5b..2e259904673 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -118,7 +118,7 @@ module Banzai
end
if path
- content_tag(:img, nil, src: path, class: 'gfm')
+ content_tag(:img, nil, data: { src: path }, class: 'gfm')
end
end
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
new file mode 100644
index 00000000000..7a81d583b82
--- /dev/null
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -0,0 +1,16 @@
+module Banzai
+ module Filter
+ # HTML filter that moves the value of the src attribute to the data-src attribute so it can be lazy loaded
+ class ImageLazyLoadFilter < HTML::Pipeline::Filter
+ def call
+ doc.xpath('descendant-or-self::img').each do |img|
+ img['class'] ||= '' << 'lazy'
+ img['data-src'] = img['src']
+ img['src'] = LazyImageTagHelper.placeholder_image
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index 123c92fd250..f318c425962 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -10,7 +10,7 @@ module Banzai
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
- href: img['src'],
+ href: img['data-src'] || img['src'],
target: '_blank',
rel: 'noopener noreferrer'
)
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index ba1a5ac84b3..ce1ab977d3b 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -20,7 +20,7 @@ module Banzai
end
def url_for_object(issue, project)
- IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path])
+ IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
end
def project_from_ref(ref)
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 9e23c8f8c55..c2fed57a0d8 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -22,6 +22,7 @@ module Banzai
doc.css('img, video').each do |el|
process_link_attr el.attribute('src')
+ process_link_attr el.attribute('data-src')
end
doc
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index bd4d1aa9ff8..3208abfc538 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -16,6 +16,7 @@ module Banzai
Filter::MathFilter,
Filter::UploadLinkFilter,
Filter::VideoLinkFilter,
+ Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
Filter::EmojiFilter,
Filter::TableOfContentsFilter,
diff --git a/lib/banzai/reference_parser/external_issue_parser.rb b/lib/banzai/reference_parser/external_issue_parser.rb
index 6307c1b571a..1802cd04854 100644
--- a/lib/banzai/reference_parser/external_issue_parser.rb
+++ b/lib/banzai/reference_parser/external_issue_parser.rb
@@ -21,10 +21,14 @@ module Banzai
gather_attributes_per_project(nodes, self.class.data_attribute)
end
- private
-
+ # we extract only external issue trackers references here, we don't extract cross-project references,
+ # so we don't need to do anything here.
def can_read_reference?(user, ref_project, node)
- can?(user, :read_issue, ref_project)
+ true
+ end
+
+ def nodes_visible_to_user(user, nodes)
+ nodes
end
end
end
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index 6b82b2b4f13..31f66dd5a58 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -52,7 +52,7 @@ module Ci
# when old API will be removed (planned for August 2017).
model.options.dup.tap do |options|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
- options[:services].map! do |service|
+ options[:services]&.map! do |service|
if service.is_a?(Hash)
service[:name]
else
diff --git a/lib/ci/api/triggers.rb b/lib/ci/api/triggers.rb
index 6e622601680..6225203f223 100644
--- a/lib/ci/api/triggers.rb
+++ b/lib/ci/api/triggers.rb
@@ -24,12 +24,13 @@ module Ci
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref], variables)
- if trigger_request
- present trigger_request, with: Entities::TriggerRequest
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref], variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present result.trigger_request, with: Entities::TriggerRequest
else
- errors = 'No builds created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index cf3a0336792..3a4911b23b0 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -83,7 +83,8 @@ module Ci
before_script: job[:before_script],
script: job[:script],
after_script: job[:after_script],
- environment: job[:environment]
+ environment: job[:environment],
+ retry: job[:retry]
}.compact }
end
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index b0741b1fba7..d3f66877672 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -26,7 +26,7 @@ module Gitlab
next unless migration_class == steal_class
begin
- perform(migration_class, migration_args, retries: 3) if job.delete
+ perform(migration_class, migration_args) if job.delete
rescue Exception # rubocop:disable Lint/RescueException
BackgroundMigrationWorker # enqueue this migration again
.perform_async(migration_class, migration_args)
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/pipeline/metadata.rb
index 2ee35a0d4c1..db1e9f8cfb8 100644
--- a/lib/gitlab/badge/build/metadata.rb
+++ b/lib/gitlab/badge/pipeline/metadata.rb
@@ -1,8 +1,8 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Class that describes build badge metadata
+ # Class that describes pipeline badge metadata
#
class Metadata < Badge::Metadata
def initialize(badge)
@@ -11,11 +11,11 @@ module Gitlab
end
def title
- 'build status'
+ 'pipeline status'
end
def image_url
- build_project_badges_url(@project, @ref, format: :svg)
+ pipeline_project_badges_url(@project, @ref, format: :svg)
end
def link_url
diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/pipeline/status.rb
index b762d85b6e5..5fee7a93475 100644
--- a/lib/gitlab/badge/build/status.rb
+++ b/lib/gitlab/badge/pipeline/status.rb
@@ -1,8 +1,8 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Build status badge
+ # Pipeline status badge
#
class Status < Badge::Base
attr_reader :project, :ref
@@ -15,7 +15,7 @@ module Gitlab
end
def entity
- 'build'
+ 'pipeline'
end
def status
@@ -25,11 +25,11 @@ module Gitlab
end
def metadata
- @metadata ||= Build::Metadata.new(self)
+ @metadata ||= Pipeline::Metadata.new(self)
end
def template
- @template ||= Build::Template.new(self)
+ @template ||= Pipeline::Template.new(self)
end
end
end
diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/pipeline/template.rb
index bc0e0cd441d..e09db32262d 100644
--- a/lib/gitlab/badge/build/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -1,12 +1,13 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Class that represents a build badge template.
+ # Class that represents a pipeline badge template.
#
# Template object will be passed to badge.svg.erb template.
#
class Template < Badge::Template
+ STATUS_RENAME = { 'success' => 'passed' }.freeze
STATUS_COLOR = {
success: '#4c1',
failed: '#e05d44',
@@ -27,11 +28,11 @@ module Gitlab
end
def value_text
- @status.to_s
+ STATUS_RENAME[@status.to_s] || @status.to_s
end
def key_width
- 38
+ 62
end
def value_width
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 176301bcca1..32f5c6ab142 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
- after_script variables environment coverage].freeze
+ after_script variables environment coverage retry].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -23,6 +23,9 @@ module Gitlab
with_options allow_nil: true do
validates :tags, array_of_strings: true
validates :allow_failure, boolean: true
+ validates :retry, numericality: { only_integer: true,
+ greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 2 }
validates :when,
inclusion: { in: %w[on_success on_failure always manual],
message: 'should be on_success, on_failure, ' \
@@ -76,9 +79,9 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
- :artifacts, :commands, :environment, :coverage
+ :artifacts, :commands, :environment, :coverage, :retry
- attributes :script, :tags, :allow_failure, :when, :dependencies
+ attributes :script, :tags, :allow_failure, :when, :dependencies, :retry
def compose!(deps = nil)
super do
@@ -142,6 +145,7 @@ module Gitlab
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
+ retry: retry_defined? ? retry_value.to_i : nil,
artifacts: artifacts_value,
after_script: after_script_value,
ignore: ignored? }
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index 5d6977106d6..8503ecf8700 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -67,13 +67,14 @@ module Gitlab
def extract_coverage(regex)
return unless valid?
- return unless regex
+ return unless regex.present?
regex = Gitlab::UntrustedRegexp.new(regex)
match = ""
reverse_line do |line|
+ line.chomp!
matches = regex.scan(line)
next unless matches.is_a?(Array)
next if matches.empty?
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index e81d19a7a2e..8c8729b6557 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -74,6 +74,8 @@ module Gitlab
build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end
+ private
+
def checkout_sha(repository, newrev, ref)
# Checkout sha is nil when we remove branch or tag
return if Gitlab::Git.blank_ref?(newrev)
diff --git a/lib/gitlab/data_builder/wiki_page.rb b/lib/gitlab/data_builder/wiki_page.rb
new file mode 100644
index 00000000000..226974b698c
--- /dev/null
+++ b/lib/gitlab/data_builder/wiki_page.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module DataBuilder
+ module WikiPage
+ extend self
+
+ def build(wiki_page, user, action)
+ wiki = wiki_page.wiki
+
+ {
+ object_kind: wiki_page.class.name.underscore,
+ user: user.hook_attrs,
+ project: wiki.project.hook_attrs,
+ wiki: wiki.hook_attrs,
+ object_attributes: wiki_page.hook_attrs.merge(
+ url: Gitlab::UrlBuilder.build(wiki_page),
+ action: action
+ )
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index edd7795eef0..85e6db0a689 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -237,6 +237,10 @@ module Gitlab
branch_name.parameterize << '.patch'
end
+ def patch_url
+ "https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/#{ENV['CI_JOB_ID']}/artifacts/raw/ee_compat_check/patches/#{ce_patch_name}"
+ end
+
def step(desc, cmd = nil)
puts "\n=> #{desc}\n"
@@ -303,14 +307,11 @@ module Gitlab
2. Apply your branch's patch to EE
- # In the CE repo
- $ git fetch origin master
- $ git diff --binary origin/master...HEAD -- > #{ce_branch}.patch
-
# In the EE repo
$ git fetch origin master
$ git checkout -b #{ee_branch_prefix} origin/master
- $ git apply --3way path/to/#{ce_branch}.patch
+ $ wget #{patch_url}
+ $ git apply --3way #{ce_patch_name}
At this point you might have conflicts such as:
@@ -324,7 +325,7 @@ module Gitlab
If the patch couldn't be applied cleanly, use the following command:
# In the EE repo
- $ git apply --reject path/to/#{ce_branch}.patch
+ $ git apply --reject #{ce_patch_name}
This option makes git apply the parts of the patch that are applicable,
and leave the rejected hunks in corresponding `.rej` files.
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 76a562f356e..09511cc6504 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -98,17 +98,13 @@ module Gitlab
# Commit.between(repo, '29eda46b', 'master')
#
def between(repo, base, head)
- commits = Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled|
+ Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled|
if is_enabled
repo.gitaly_commit_client.between(base, head)
else
- repo.commits_between(base, head)
+ repo.commits_between(base, head).map { |c| decorate(c) }
end
end
-
- commits.map do |commit|
- decorate(commit)
- end
rescue Rugged::ReferenceError
[]
end
@@ -135,6 +131,16 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326
def find_all(repo, options = {})
+ Gitlab::GitalyClient.migrate(:find_all_commits) do |is_enabled|
+ if is_enabled
+ find_all_by_gitaly(repo, options)
+ else
+ find_all_by_rugged(repo, options)
+ end
+ end
+ end
+
+ def find_all_by_rugged(repo, options = {})
actual_options = options.dup
allowed_options = [:ref, :max_count, :skip, :order]
@@ -173,6 +179,10 @@ module Gitlab
[]
end
+ def find_all_by_gitaly(repo, options = {})
+ Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
+ end
+
def decorate(commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref)
end
@@ -214,11 +224,12 @@ module Gitlab
def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
- if raw_commit.is_a?(Hash)
+ case raw_commit
+ when Hash
init_from_hash(raw_commit)
- elsif raw_commit.is_a?(Rugged::Commit)
+ when Rugged::Commit
init_from_rugged(raw_commit)
- elsif raw_commit.is_a?(Gitaly::GitCommit)
+ when Gitlab::GitalyClient::Commit
init_from_gitaly(raw_commit)
else
raise "Invalid raw commit type: #{raw_commit.class}"
@@ -298,7 +309,14 @@ module Gitlab
end
def parents
- raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
+ case raw_commit
+ when Rugged::Commit
+ raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
+ when Gitlab::GitalyClient::Commit
+ parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact
+ else
+ raise NotImplementedError, "commit source doesn't support #parents"
+ end
end
def stats
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index cf95f673667..9e00abefd02 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -234,6 +234,8 @@ module Gitlab
@new_file = diff.from_id == BLANK_SHA
@renamed_file = diff.from_path != diff.to_path
@deleted_file = diff.to_id == BLANK_SHA
+
+ collapse! if diff.respond_to?(:collapsed) && diff.collapsed
end
def prune_diff_if_eligible
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 0d8fe185ac5..87ed9c3ea26 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -7,16 +7,28 @@ module Gitlab
DEFAULT_LIMITS = { max_files: 100, max_lines: 5000 }.freeze
+ attr_reader :limits
+
+ delegate :max_files, :max_lines, :max_bytes, :safe_max_files, :safe_max_lines, :safe_max_bytes, to: :limits
+
+ def self.collection_limits(options = {})
+ limits = {}
+ limits[:max_files] = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
+ limits[:max_lines] = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
+ limits[:max_bytes] = limits[:max_files] * 5.kilobytes # Average 5 KB per file
+ limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
+ limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
+ limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
+
+ OpenStruct.new(limits)
+ end
+
def initialize(iterator, options = {})
@iterator = iterator
- @max_files = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
- @max_lines = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
- @max_bytes = @max_files * 5.kilobytes # Average 5 KB per file
- @safe_max_files = [@max_files, DEFAULT_LIMITS[:max_files]].min
- @safe_max_lines = [@max_lines, DEFAULT_LIMITS[:max_lines]].min
- @safe_max_bytes = @safe_max_files * 5.kilobytes # Average 5 KB per file
+ @limits = self.class.collection_limits(options)
@enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true)
+ @from_gitaly = options.fetch(:from_gitaly, false)
@line_count = 0
@byte_count = 0
@@ -26,9 +38,23 @@ module Gitlab
end
def each(&block)
- Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
- each_patch(&block)
+ @array.each(&block)
+
+ return if @overflow
+ return if @iterator.nil?
+
+ Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
+ if is_enabled && @from_gitaly
+ each_gitaly_patch(&block)
+ else
+ each_rugged_patch(&block)
+ end
end
+
+ @populated = true
+
+ # Allow iterator to be garbage-collected. It cannot be reused anyway.
+ @iterator = nil
end
def empty?
@@ -74,23 +100,32 @@ module Gitlab
end
def over_safe_limits?(files)
- files >= @safe_max_files || @line_count > @safe_max_lines || @byte_count >= @safe_max_bytes
+ files >= safe_max_files || @line_count > safe_max_lines || @byte_count >= safe_max_bytes
end
- def each_patch
- i = 0
- @array.each do |diff|
- yield diff
+ def each_gitaly_patch
+ i = @array.length
+
+ @iterator.each do |raw|
+ diff = Gitlab::Git::Diff.new(raw, expanded: !@enforce_limits || @expanded)
+
+ if raw.overflow_marker
+ @overflow = true
+ break
+ end
+
+ yield @array[i] = diff
i += 1
end
+ end
- return if @overflow
- return if @iterator.nil?
+ def each_rugged_patch
+ i = @array.length
@iterator.each do |raw|
@empty = false
- if @enforce_limits && i >= @max_files
+ if @enforce_limits && i >= max_files
@overflow = true
break
end
@@ -106,7 +141,7 @@ module Gitlab
@line_count += diff.line_count
@byte_count += diff.diff.bytesize
- if @enforce_limits && (@line_count >= @max_lines || @byte_count >= @max_bytes)
+ if @enforce_limits && (@line_count >= max_lines || @byte_count >= max_bytes)
# This last Diff instance pushes us over the lines limit. We stop and
# discard it.
@overflow = true
@@ -116,11 +151,6 @@ module Gitlab
yield @array[i] = diff
i += 1
end
-
- @populated = true
-
- # Allow iterator to be garbage-collected. It cannot be reused anyway.
- @iterator = nil
end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 63eebadff2e..3e27fd7b682 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -45,6 +45,8 @@ module Gitlab
:bare?,
to: :rugged
+ delegate :exists?, to: :gitaly_repository_client
+
# Default branch in the repository
def root_ref
@root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
@@ -208,10 +210,6 @@ module Gitlab
!empty?
end
- def repo_exists?
- !!rugged
- end
-
# Discovers the default branch based on the repository's available branches
#
# - If no branches are present, returns nil
@@ -815,6 +813,10 @@ module Gitlab
@gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self)
end
+ def gitaly_repository_client
+ @gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
+ end
+
private
# Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index 8122ff0e81f..8e959c57c7c 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -17,30 +17,13 @@ module Gitlab
def where(repository, sha, path = nil)
path = nil if path == '' || path == '/'
- commit = repository.lookup(sha)
- root_tree = commit.tree
-
- tree = if path
- id = find_id_by_path(repository, root_tree.oid, path)
- if id
- repository.lookup(id)
- else
- []
- end
- else
- root_tree
- end
-
- tree.map do |entry|
- new(
- id: entry[:oid],
- root_id: root_tree.oid,
- name: entry[:name],
- type: entry[:type],
- mode: entry[:filemode].to_s(8),
- path: path ? File.join(path, entry[:name]) : entry[:name],
- commit_id: sha
- )
+ Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
+ if is_enabled
+ client = Gitlab::GitalyClient::CommitService.new(repository)
+ client.tree_entries(repository, sha, path)
+ else
+ tree_entries_from_rugged(repository, sha, path)
+ end
end
end
@@ -74,6 +57,34 @@ module Gitlab
entry[:oid]
end
end
+
+ def tree_entries_from_rugged(repository, sha, path)
+ commit = repository.lookup(sha)
+ root_tree = commit.tree
+
+ tree = if path
+ id = find_id_by_path(repository, root_tree.oid, path)
+ if id
+ repository.lookup(id)
+ else
+ []
+ end
+ else
+ root_tree
+ end
+
+ tree.map do |entry|
+ new(
+ id: entry[:oid],
+ root_id: root_tree.oid,
+ name: entry[:name],
+ type: entry[:type],
+ mode: entry[:filemode].to_s(8),
+ path: path ? File.join(path, entry[:name]) : entry[:name],
+ commit_id: sha
+ )
+ end
+ end
end
def initialize(options)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 197a94487eb..c90ef282fdd 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -57,7 +57,7 @@ module Gitlab
metadata = yield(metadata) if block_given?
stub(service, storage).send(rpc, request, metadata)
end
-
+
def self.request_metadata(storage)
encoded_token = Base64.strict_encode64(token(storage).to_s)
{ metadata: { 'authorization' => "Bearer #{encoded_token}" } }
@@ -86,8 +86,8 @@ module Gitlab
feature.enabled?
end
- def self.migrate(feature)
- is_enabled = feature_enabled?(feature)
+ def self.migrate(feature, status: MigrationStatus::OPT_IN)
+ is_enabled = feature_enabled?(feature, status: status)
metric_name = feature.to_s
metric_name += "_gitaly" if is_enabled
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
new file mode 100644
index 00000000000..61fe462d762
--- /dev/null
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module GitalyClient
+ class Commit
+ attr_reader :repository, :gitaly_commit
+
+ delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit
+
+ def initialize(repository, gitaly_commit)
+ @repository = repository
+ @gitaly_commit = gitaly_commit
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 8f5738fed06..c6e52b530b3 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -23,9 +23,13 @@ module Gitlab
def diff_from_parent(commit, options = {})
request_params = commit_diff_request_params(commit, options)
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
+ request_params[:enforce_limits] = options.fetch(:limits, true)
+ request_params[:collapse_diffs] = request_params[:enforce_limits] || !options.fetch(:expanded, true)
+ request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
+
request = Gitaly::CommitDiffRequest.new(request_params)
response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
- Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options)
+ Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options.merge(from_gitaly: true))
end
def commit_deltas(commit)
@@ -56,6 +60,31 @@ module Gitlab
entry
end
+ def tree_entries(repository, revision, path)
+ request = Gitaly::GetTreeEntriesRequest.new(
+ repository: @gitaly_repo,
+ revision: revision,
+ path: path.presence || '.'
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request)
+
+ response.flat_map do |message|
+ message.entries.map do |gitaly_tree_entry|
+ entry_path = gitaly_tree_entry.path.dup
+ Gitlab::Git::Tree.new(
+ id: gitaly_tree_entry.oid,
+ root_id: gitaly_tree_entry.root_oid,
+ type: gitaly_tree_entry.type.downcase,
+ mode: gitaly_tree_entry.mode.to_s(8),
+ name: File.basename(entry_path),
+ path: entry_path,
+ commit_id: gitaly_tree_entry.commit_oid
+ )
+ end
+ end
+ end
+
def commit_count(ref)
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
@@ -76,6 +105,19 @@ module Gitlab
consume_commits_response(response)
end
+ def find_all_commits(opts = {})
+ request = Gitaly::FindAllCommitsRequest.new(
+ repository: @gitaly_repo,
+ revision: opts[:ref].to_s,
+ max_count: opts[:max_count].to_i,
+ skip: opts[:skip].to_i
+ )
+ request.order = opts[:order].upcase if opts[:order].present?
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request)
+ consume_commits_response(response)
+ end
+
private
def commit_diff_request_params(commit, options = {})
@@ -90,7 +132,12 @@ module Gitlab
end
def consume_commits_response(response)
- response.flat_map { |r| r.commits }
+ response.flat_map do |message|
+ message.commits.map do |gitaly_commit|
+ commit = GitalyClient::Commit.new(@repository, gitaly_commit)
+ Gitlab::Git::Commit.new(commit)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/diff.rb b/lib/gitlab/gitaly_client/diff.rb
index 1e117b7e74a..d459c9a88fb 100644
--- a/lib/gitlab/gitaly_client/diff.rb
+++ b/lib/gitlab/gitaly_client/diff.rb
@@ -1,7 +1,7 @@
module Gitlab
module GitalyClient
class Diff
- FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch).freeze
+ FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed).freeze
attr_accessor(*FIELDS)
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
new file mode 100644
index 00000000000..f5d84ea8762
--- /dev/null
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -0,0 +1,16 @@
+module Gitlab
+ module GitalyClient
+ class RepositoryService
+ def initialize(repository)
+ @repository = repository
+ @gitaly_repo = repository.gitaly_repository
+ end
+
+ def exists?
+ request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
+
+ GitalyClient.call(@repository.storage, :repository_service, :exists, request).exists
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index a1b896c9511..cc282d1415b 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -7,10 +7,10 @@ module Gitlab
'es' => 'Español',
'de' => 'Deutsch',
'fr' => 'Français',
- 'pt_BR' => 'Português(Brasil)',
+ 'pt_BR' => 'Português (Brasil)',
'zh_CN' => '简体中文',
- 'zh_HK' => 'ç¹é«”中文(香港)',
- 'zh_TW' => 'ç¹é«”中文(臺ç£)',
+ 'zh_HK' => 'ç¹é«”中文 (香港)',
+ 'zh_TW' => 'ç¹é«”中文 (臺ç£)',
'bg' => 'българÑки',
'ru' => 'РуÑÑкий',
'eo' => 'Esperanto',
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index 6fdf68641e2..8eda3ea03f9 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -2,6 +2,12 @@
module Gitlab
module LDAP
class Config
+ NET_LDAP_ENCRYPTION_METHOD = {
+ simple_tls: :simple_tls,
+ start_tls: :start_tls,
+ plain: nil
+ }.freeze
+
attr_accessor :provider, :options
def self.enabled?
@@ -39,7 +45,7 @@ module Gitlab
def adapter_options
opts = base_options.merge(
- encryption: encryption
+ encryption: encryption_options
)
opts.merge!(auth_options) if has_auth?
@@ -50,9 +56,10 @@ module Gitlab
def omniauth_options
opts = base_options.merge(
base: base,
- method: options['method'],
+ encryption: options['encryption'],
filter: omniauth_user_filter,
- name_proc: name_proc
+ name_proc: name_proc,
+ disable_verify_certificates: !options['verify_certificates']
)
if has_auth?
@@ -62,6 +69,9 @@ module Gitlab
)
end
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
opts
end
@@ -157,15 +167,37 @@ module Gitlab
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
- def encryption
- case options['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
+ def encryption_options
+ method = translate_method(options['encryption'])
+ return nil unless method
+
+ {
+ method: method,
+ tls_options: tls_options(method)
+ }
+ end
+
+ def translate_method(method_from_config)
+ NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
+ end
+
+ def tls_options(method)
+ return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
+
+ opts = if options['verify_certificates']
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
+ else
+ # It is important to explicitly set verify_mode for two reasons:
+ # 1. The behavior of OpenSSL is undefined when verify_mode is not set.
+ # 2. The net-ldap gem implementation verifies the certificate hostname
+ # unless verify_mode is set to VERIFY_NONE.
+ { verify_mode: OpenSSL::SSL::VERIFY_NONE }
+ end
+
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
+ opts
end
def auth_options
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 60a32d5d5ea..894bd5efae5 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -14,42 +14,42 @@ module Gitlab
TOP_LEVEL_ROUTES = %w[
-
.well-known
+ 404.html
+ 422.html
+ 500.html
+ 502.html
+ 503.html
abuse_reports
admin
- all
api
+ apple-touch-icon-precomposed.png
+ apple-touch-icon.png
assets
autocomplete
ci
dashboard
+ deploy.html
explore
+ favicon.ico
files
groups
health_check
help
- hooks
import
invites
- issues
jwt
koding
- member
- merge_requests
- new
- notes
notification_settings
oauth
profile
projects
public
- repository
robots.txt
s
search
sent_notifications
- services
+ slash-command-logo.png
snippets
- teams
u
unicorn_test
unsubscribes
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 7668ecacc4b..f5b757ace77 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -33,7 +33,12 @@ module Gitlab
def issues
if project && project.jira_tracker?
- @references[:external_issue] ||= references(:external_issue)
+ if project.issues_enabled?
+ @references[:all_issues] ||= references(:external_issue) + references(:issue)
+ else
+ @references[:external_issue] ||= references(:external_issue) +
+ references(:issue).select { |i| i.project_id != project.id }
+ end
else
@references[:issue] ||= references(:issue)
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index c1ee20b6977..1adc5ec952a 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -19,17 +19,23 @@ module Gitlab
"It must start with letter, digit, emoji or '_'."
end
- def container_registry_reference_regex
- Gitlab::PathRegex.git_reference_regex
- end
-
##
- # Docker Distribution Registry 2.4.1 repository name rules
+ # Docker Distribution Registry repository / tag name rules
+ #
+ # See https://github.com/docker/distribution/blob/master/reference/regexp.go.
#
def container_repository_name_regex
@container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z}
end
+ ##
+ # We do not use regexp anchors here because these are not allowed when
+ # used as a routing constraint.
+ #
+ def container_registry_tag_regex
+ @container_registry_tag_regex ||= /[\w][\w.-]{0,127}/
+ end
+
def environment_name_regex_chars
'a-zA-Z0-9_/\\$\\{\\}\\. -'
end
diff --git a/lib/gitlab/slash_commands/issue_command.rb b/lib/gitlab/slash_commands/issue_command.rb
index 87ea19b8806..3d96982b820 100644
--- a/lib/gitlab/slash_commands/issue_command.rb
+++ b/lib/gitlab/slash_commands/issue_command.rb
@@ -2,7 +2,7 @@ module Gitlab
module SlashCommands
class IssueCommand < BaseCommand
def self.available?(project)
- project.issues_enabled? && project.default_issues_tracker?
+ project.issues_enabled?
end
def collection
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
index 8b43f0053d6..7ce2e9d636e 100644
--- a/lib/gitlab/untrusted_regexp.rb
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -22,13 +22,9 @@ module Gitlab
end
def scan(text)
- scan_regexp.scan(text).map do |match|
- if regexp.number_of_capturing_groups == 0
- match.first
- else
- match
- end
- end
+ matches = scan_regexp.scan(text).to_a
+ matches.map!(&:first) if regexp.number_of_capturing_groups.zero?
+ matches
end
def replace(text, rewrite)
@@ -43,7 +39,7 @@ module Gitlab
# groups, so work around it
def scan_regexp
@scan_regexp ||=
- if regexp.number_of_capturing_groups == 0
+ if regexp.number_of_capturing_groups.zero?
RE2::Regexp.new('(' + regexp.source + ')')
else
regexp
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index dba071d7e47..e0ac21305a5 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -40,14 +40,13 @@ module Gitlab
pages_domains: PagesDomain.count,
projects: Project.count,
projects_imported_from_github: Project.where(import_type: 'github').count,
- projects_prometheus_active: PrometheusService.active.count,
protected_branches: ProtectedBranch.count,
releases: Release.count,
snippets: Snippet.count,
todos: Todo.count,
uploads: Upload.count,
web_hooks: WebHook.count
- }
+ }.merge(services_usage)
}
end
@@ -64,6 +63,18 @@ module Gitlab
usage_data
end
+
+ def services_usage
+ types = {
+ JiraService: :projects_jira_active,
+ SlackService: :projects_slack_notifications_active,
+ SlackSlashCommandsService: :projects_slack_slash_active,
+ PrometheusService: :projects_prometheus_active
+ }
+
+ results = Service.unscoped.where(type: types.keys, active: true).group(:type).count
+ results.each_with_object({}) { |(key, value), response| response[types[key.to_sym]] = value }
+ end
end
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 8e91ee7287c..d9a5af09f08 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -37,8 +37,8 @@ module Gitlab
request_cache def can_create_tag?(ref)
return false unless can_access_git?
- if ProtectedTag.protected?(project, ref)
- project.protected_tags.protected_ref_accessible_to?(ref, user, action: :create)
+ if protected?(ProtectedTag, project, ref)
+ protected_tag_accessible_to?(ref, action: :create)
else
user.can?(:push_code, project)
end
@@ -47,20 +47,24 @@ module Gitlab
request_cache def can_delete_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
+ if protected?(ProtectedBranch, project, ref)
user.can?(:delete_protected_branch, project)
else
user.can?(:push_code, project)
end
end
+ def can_update_branch?(ref)
+ can_push_to_branch?(ref) || can_merge_to_branch?(ref)
+ end
+
request_cache def can_push_to_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
+ if protected?(ProtectedBranch, project, ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
- project.protected_branches.protected_ref_accessible_to?(ref, user, action: :push)
+ protected_branch_accessible_to?(ref, action: :push)
else
user.can?(:push_code, project)
end
@@ -69,8 +73,8 @@ module Gitlab
request_cache def can_merge_to_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
- project.protected_branches.protected_ref_accessible_to?(ref, user, action: :merge)
+ if protected?(ProtectedBranch, project, ref)
+ protected_branch_accessible_to?(ref, action: :merge)
else
user.can?(:push_code, project)
end
@@ -87,5 +91,23 @@ module Gitlab
def can_access_git?
user && user.can?(:access_git)
end
+
+ def protected_branch_accessible_to?(ref, action:)
+ ProtectedBranch.protected_ref_accessible_to?(
+ ref, user,
+ action: action,
+ protected_refs: project.protected_branches)
+ end
+
+ def protected_tag_accessible_to?(ref, action:)
+ ProtectedTag.protected_ref_accessible_to?(
+ ref, user,
+ action: action,
+ protected_refs: project.protected_tags)
+ end
+
+ request_cache def protected?(kind, project, ref)
+ kind.protected?(project, ref)
+ end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 916ef365d78..3f25e463412 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -35,7 +35,10 @@ module Gitlab
when 'git_receive_pack'
Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
when 'git_upload_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
+ Gitlab::GitalyClient.feature_enabled?(
+ :post_upload_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
when 'info_refs'
true
else
@@ -62,7 +65,7 @@ module Gitlab
end
def send_git_blob(repository, blob)
- params = if Gitlab::GitalyClient.feature_enabled?(:project_raw_show)
+ params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_raw_show)
{
'GitalyServer' => gitaly_server_hash(repository),
'GetBlobRequest' => {
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index 964aa0fe1bc..28b2d86eed2 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -153,7 +153,6 @@ module Gitlab
clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
checkout_version(version, target_dir)
- reset_to_version(version, target_dir)
end
def clone_repo(repo, target_dir)
@@ -161,12 +160,8 @@ module Gitlab
end
def checkout_version(version, target_dir)
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet])
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}])
- end
-
- def reset_to_version(version, target_dir)
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --])
end
end
end
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index 4108cee08b4..9cc986535e1 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -4,6 +4,7 @@ require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lowe
require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes')
require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
+require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
desc 'GitLab | Sets up PostgreSQL'
@@ -12,5 +13,6 @@ task setup_postgresql: :environment do
AddUsersLowerUsernameEmailIndexes.new.up
AddLowerPathIndexToRoutes.new.up
IndexRoutesPathForLike.new.up
+ AddLowerPathIndexToRedirectRoutes.new.up
IndexRedirectRoutesPathForLike.new.up
end
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 2000fa433b4..959654c7849 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -1,32 +1,308 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Dremor <egeorget@opmbx.org>, 2017. #zanata
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Rémy Coutable <remy@rymai.me>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-14 04:21-0400\n"
+"PO-Revision-Date: 2017-07-19 09:45-0400\n"
"Last-Translator: Dremor <egeorget@opmbx.org>\n"
-"Language-Team: French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)\n"
+"Language-Team: French (https://translate.zanata.org/project/view/GitLab)\n"
"Language: fr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s validation supplémentaire a été masquée afin d'éviter de créer de "
+"problèmes de performances."
+msgstr[1] ""
+"%s validations supplémentaires ont été masquées afin d'éviter de créer de "
+"problèmes de performances."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d validation"
+msgstr[1] "%d validations"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} a validé %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 pipeline"
+msgstr[1] "%d pipelines"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Un ensemble de graphiques concernant l’Intégration Continue (CI)"
+
+msgid "About auto deploy"
+msgstr "A propos de l'auto-déploiement"
+
+msgid "Active"
+msgstr "Actif"
+
+msgid "Activity"
+msgstr "Activité"
+
+msgid "Add Changelog"
+msgstr "Ajouter un journal des modifications"
+
+msgid "Add Contribution guide"
+msgstr "Ajouter un guide de contribution"
+
+msgid "Add License"
+msgstr "Ajouter une licence"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par "
+"SSH."
+
+msgid "Add new directory"
+msgstr "Ajouter un nouveau dossier"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Projet archivé ! Le dépôt est en lecture seule"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Êtes-vous sûr de vouloir supprimer ce pipeline programmé"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Attachez un fichier par glisser &amp; déposer ou %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branche"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branche <strong>%{branch_name}</strong> a été crée. Pour mettre en place "
+"le déploiement automatisé, sélectionnez un modèle de fichier Yaml pour "
+"l'intégration continue (CI) de GitLab, et validez les modifications. "
+"%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Rechercher la branche"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Changer de branche"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Parcourir le dossier"
+
+msgid "Browse File"
+msgstr "Parcourir le fichier"
+
+msgid "Browse Files"
+msgstr "Parcourir les fichiers"
+
+msgid "Browse files"
+msgstr "Parcourir les fichiers"
msgid "ByAuthor|by"
msgstr "par"
+msgid "CI configuration"
+msgstr "Configuration de l'intégration continue (CI)"
+
+msgid "Cancel"
+msgstr "Annuler"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Sélectionner dans la branche"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Annuler dans la branche"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Sélectionner"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Annuler"
+
+msgid "Changelog"
+msgstr "Journal des modifications"
+
+msgid "Charts"
+msgstr "Graphiques"
+
+msgid "Cherry-pick this commit"
+msgstr "Sélectionner cette validation"
+
+msgid "Cherry-pick this merge request"
+msgstr "Sélectionner cette demande de fusion"
+
+msgid "CiStatusLabel|canceled"
+msgstr "annulé"
+
+msgid "CiStatusLabel|created"
+msgstr "créé"
+
+msgid "CiStatusLabel|failed"
+msgstr "échoué"
+
+msgid "CiStatusLabel|manual action"
+msgstr "action manuelle"
+
+msgid "CiStatusLabel|passed"
+msgstr "passé"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "passé avec des avertissements"
+
+msgid "CiStatusLabel|pending"
+msgstr "en attente"
+
+msgid "CiStatusLabel|skipped"
+msgstr "ignoré"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "en attente d'action manuelle"
+
+msgid "CiStatusText|blocked"
+msgstr "bloqué"
+
+msgid "CiStatusText|canceled"
+msgstr "annulé "
+
+msgid "CiStatusText|created"
+msgstr "créé"
+
+msgid "CiStatusText|failed"
+msgstr "échoué"
+
+msgid "CiStatusText|manual"
+msgstr "manuel"
+
+msgid "CiStatusText|passed"
+msgstr "passé"
+
+msgid "CiStatusText|pending"
+msgstr "en attente"
+
+msgid "CiStatusText|skipped"
+msgstr "ignoré"
+
+msgid "CiStatus|running"
+msgstr "en cours"
+
msgid "Commit"
msgid_plural "Commits"
msgstr[0] "Validation"
msgstr[1] "Validations"
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Durée des 30 derniers pipelines en minutes"
+
+msgid "Commit message"
+msgstr "Message de validation"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Validation"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Ajout de %{file_name}"
+
+msgid "Commits"
+msgstr "Validations"
+
+msgid "Commits feed"
+msgstr "Flux de validations"
+
+msgid "Commits|History"
+msgstr "Historique"
+
+msgid "Committed by"
+msgstr "Validé par"
+
+msgid "Compare"
+msgstr "Comparer"
+
+msgid "Contribution guide"
+msgstr "Guilde de contribution"
+
+msgid "Contributors"
+msgstr "Contributeurs"
+
+msgid "Copy URL to clipboard"
+msgstr "Copier l'URL dans le presse-papier"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copier le SHA de la validation"
+
+msgid "Create New Directory"
+msgstr "Créer un nouveau dossier"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Créer un jeton d’accès personnel pour votre compte afin de récupérer ou "
+"pousser par %{protocol}."
+
+msgid "Create directory"
+msgstr "Créer un dossier"
+
+msgid "Create empty bare repository"
+msgstr "Créer un dépôt vide"
+
+msgid "Create merge request"
+msgstr "Créer une demande de fusion"
+
+msgid "Create new..."
+msgstr "Créer nouveau..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fourcher"
+
+msgid "CreateTag|Tag"
+msgstr "Étiquette"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "Créer un jeton d'accès personnel"
+
+msgid "Cron Timezone"
+msgstr "Fuseau horaire de Cron"
+
+msgid "Cron syntax"
+msgstr "Syntaxe Cron"
+
+msgid "Custom notification events"
+msgstr "Événements de notification personnalisés"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"Le niveau de notification Personnalisé est similaire au niveau Participation."
+" Cependant, il permet également de recevoir des notifications pour des "
+"événements sélectionnés. Pour plus d’information, vous pouvez consulter "
+"%{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Analyseur de cycle"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire "
+"pour aller d’une idée à sa mise en production pour votre projet."
msgid "CycleAnalyticsStage|Code"
msgstr "Code"
@@ -49,31 +325,169 @@ msgstr "Pré-production"
msgid "CycleAnalyticsStage|Test"
msgstr "Test"
+msgid "Define a custom pattern with cron syntax"
+msgstr "Définir un schéma personnalisé avec une syntaxe Cron"
+
+msgid "Delete"
+msgstr "Supprimer"
+
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "Déploiement"
msgstr[1] "Déploiements"
+msgid "Description"
+msgstr "Description"
+
+msgid "Directory name"
+msgstr "Nom du dossier"
+
+msgid "Don't show again"
+msgstr "Ne plus montrer"
+
+msgid "Download"
+msgstr "Télécharger"
+
+msgid "Download tar"
+msgstr "Télécharger tar"
+
+msgid "Download tar.bz2"
+msgstr "Télécharger tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Télécharger tar.gz"
+
+msgid "Download zip"
+msgstr "Télécharger zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Télécharger"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Patch email"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Diff simple"
+
+msgid "DownloadSource|Download"
+msgstr "Télécharger"
+
+msgid "Edit"
+msgstr "Éditer"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Éditer le pipeline programmé %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Chaque jour (à 4:00 du matin)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Chaque mois (le 1er à 4:00 du matin)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Chaque semaine (dimanche à 4:00 du matin)"
+
+msgid "Failed to change the owner"
+msgstr "Échec du changement de propriétaire"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Échec de la suppression du pipeline programmé"
+
+msgid "Files"
+msgstr "Fichiers"
+
+msgid "Filter by commit message"
+msgstr "Filtrer par message de validation"
+
+msgid "Find by path"
+msgstr "Rechercher par chemin"
+
+msgid "Find file"
+msgstr "Rechercher un fichier"
+
msgid "FirstPushedBy|First"
msgstr "En premier"
msgid "FirstPushedBy|pushed by"
msgstr "poussé par"
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fourche"
+msgstr[1] "Fourches"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Fouché depuis"
+
msgid "From issue creation until deploy to production"
msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
msgid "From merge request merge until deploy to production"
-msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
+msgstr ""
+"Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
+
+msgid "Go to your fork"
+msgstr "Aller à votre fourche"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fourche"
+
+msgid "Home"
+msgstr "Accueil"
+
+msgid "Housekeeping successfully started"
+msgstr "Maintenance démarrée avec succès"
+
+msgid "Import repository"
+msgstr "Importer un dépôt"
+
+msgid "Interval Pattern"
+msgstr "Schéma d’intervalle"
msgid "Introducing Cycle Analytics"
msgstr "Introduction à l'analyseur de cycle"
+msgid "Jobs for last month"
+msgstr "Tâches pour le mois dernier"
+
+msgid "Jobs for last week"
+msgstr "Tâches pour la semaine dernière"
+
+msgid "Jobs for last year"
+msgstr "Tâches pour l'année dernière"
+
+msgid "LFSStatus|Disabled"
+msgstr "Désactivé"
+
+msgid "LFSStatus|Enabled"
+msgstr "Activé"
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Le dernier %d jour"
msgstr[1] "Les derniers %d jours"
+msgid "Last Pipeline"
+msgstr "Dernier pipeline"
+
+msgid "Last Update"
+msgstr "Dernière mise à jour"
+
+msgid "Last commit"
+msgstr "Dernière validation"
+
+msgid "Learn more in the"
+msgstr "En apprendre plus dans le"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentation concernant la programmation de pipeline"
+
+msgid "Leave group"
+msgstr "Quitter le groupe"
+
+msgid "Leave project"
+msgstr "Quitter le projet"
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limiter l'affichage au plus à %d évènement"
@@ -82,29 +496,276 @@ msgstr[1] "Limiter l'affichage au plus à %d évènements"
msgid "Median"
msgstr "Médian"
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "ajouter une clef SSH"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "Nouvel incident"
msgstr[1] "Nouveaux incidents"
+msgid "New Pipeline Schedule"
+msgstr "Nouveau pipeline programmé"
+
+msgid "New branch"
+msgstr "Nouvelle branche"
+
+msgid "New directory"
+msgstr "Nouveau dossier"
+
+msgid "New file"
+msgstr "Nouveau Fichier"
+
+msgid "New issue"
+msgstr "Nouvel incident"
+
+msgid "New merge request"
+msgstr "Nouvelle demande de fusion"
+
+msgid "New schedule"
+msgstr "Nouveau programme"
+
+msgid "New snippet"
+msgstr "Nouvel extrait de code"
+
+msgid "New tag"
+msgstr "Nouvelle étiquette"
+
+msgid "No repository"
+msgstr "Pas de dépôt"
+
+msgid "No schedules"
+msgstr "Aucun programme"
+
msgid "Not available"
msgstr "Indisponible"
msgid "Not enough data"
msgstr "Données insuffisantes"
+msgid "Notification events"
+msgstr "Événement de notifications"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Clore l'incident"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Clore la demande de fusion"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Pipeline échoué"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Fusionner le demande de fusion"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nouvel incident"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nouvelle demande de fusion"
+
+msgid "NotificationEvent|New note"
+msgstr "Nouvelle note"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Réassigner l'incident"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Réassigner la demande de fusion"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Ré-ouvrir l'incident"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline réussi"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personnalisé"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Désactivé"
+
+msgid "NotificationLevel|Global"
+msgstr "Global"
+
+msgid "NotificationLevel|On mention"
+msgstr "En cas de mention"
+
+msgid "NotificationLevel|Participate"
+msgstr "Participation"
+
+msgid "NotificationLevel|Watch"
+msgstr "Surveillé"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtre"
+
msgid "OpenedNDaysAgo|Opened"
msgstr "Ouvert"
+msgid "Options"
+msgstr "Options"
+
+msgid "Owner"
+msgstr "Propriétaire"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
msgid "Pipeline Health"
msgstr "Santé du Pipeline"
+msgid "Pipeline Schedule"
+msgstr "Programmation de pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Programmations de pipeline"
+
+msgid "PipelineCharts|Failed:"
+msgstr "Échecs : "
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Statistiques générales"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Ratio de succès : "
+
+msgid "PipelineCharts|Successful:"
+msgstr "Succès :"
+
+msgid "PipelineCharts|Total:"
+msgstr "Total :"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Activé"
+
+msgid "PipelineSchedules|Active"
+msgstr "Actif"
+
+msgid "PipelineSchedules|All"
+msgstr "Tous"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inactif"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "Nom de la variable"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Valeur de la variable"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Prochaine exécution"
+
+msgid "PipelineSchedules|None"
+msgstr "Aucune"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Indiquez une courte description"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Supprimer la variable"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "S’approprier"
+
+msgid "PipelineSchedules|Target"
+msgstr "Cible"
+
+msgid "PipelineSchedules|Variables"
+msgstr "Variables"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personnalisé"
+
+msgid "Pipelines"
+msgstr "Pipelines"
+
+msgid "Pipelines charts"
+msgstr "Graphique des pipelines"
+
+msgid "Pipeline|all"
+msgstr "Tous"
+
+msgid "Pipeline|success"
+msgstr "Succès"
+
+msgid "Pipeline|with stage"
+msgstr "avec l'étape"
+
+msgid "Pipeline|with stages"
+msgstr "avec les étapes"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Projet '%{project_name}' en attente de suppression."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Projet '%{project_name}' créé avec succès."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Projet '%{project_name}' mis à jour avec succès."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Le projet '%{project_name}' sera supprimé."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+"L’accès au projet doit être explicitement accordé à chaque utilisateur."
+
+msgid "Project export could not be deleted."
+msgstr "L'export du projet n'a pas pu être supprimé."
+
+msgid "Project export has been deleted."
+msgstr "L'export du projet a été supprimé."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Le lien de l’export du projet a expiré. Merci de générer un nouvel export "
+"depuis les paramètres du projet."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"L'export du projet a débuté. Un lien de téléchargement sera envoyé par "
+"courriel."
+
+msgid "Project home"
+msgstr "Accueil du projet"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Désactivé"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Toute personne ayant accès"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Seulement les membres de l'équipe"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nom"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Jamais"
+
msgid "ProjectLifecycle|Stage"
msgstr "Étape"
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Graphique "
+
msgid "Read more"
msgstr "Lire plus"
+msgid "Readme"
+msgstr "LisezMoi"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Étiquettes"
+
msgid "Related Commits"
msgstr "Validations liés"
@@ -123,43 +784,201 @@ msgstr "Demandes de fusion liées"
msgid "Related Merged Requests"
msgstr "Demandes fusionnées liées"
+msgid "Remind later"
+msgstr "Me le rappeler ultérieurement"
+
+msgid "Remove project"
+msgstr "Supprimer le projet"
+
+msgid "Request Access"
+msgstr "Demander l'accès"
+
+msgid "Revert this commit"
+msgstr "Annuler cette validation"
+
+msgid "Revert this merge request"
+msgstr "Annuler cette demande de fusion"
+
+msgid "Save pipeline schedule"
+msgstr "Sauvegarder le pipeline programmé"
+
+msgid "Schedule a new pipeline"
+msgstr "Programmer un nouveau pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Programmer des pipelines"
+
+msgid "Search branches and tags"
+msgstr "Rechercher dans les branches et les étiquettes"
+
+msgid "Select Archive Format"
+msgstr "Sélectionnez le format de l'archive"
+
+msgid "Select a timezone"
+msgstr "Sélectionnez un fuseau horaire"
+
+msgid "Select target branch"
+msgstr "Sélectionnez une branche cible"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser "
+"par %{protocol}."
+
+msgid "Set up CI"
+msgstr "Mettre en place l'intégration continue (CI)"
+
+msgid "Set up Koding"
+msgstr "Mettre en place Koding"
+
+msgid "Set up auto deploy"
+msgstr "Mettre en place l’auto-déploiement"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "définir un mot de passe"
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Affichage de %d évènement"
msgstr[1] "Affichage de %d évènements"
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."
+msgid "Source code"
+msgstr "Code source"
+
+msgid "StarProject|Star"
+msgstr "S'abonner"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Créer une %{new_merge_request} avec ces changements"
+
+msgid "Switch branch/tag"
+msgstr "Changer de branche / d'étiquette"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Étiquette"
+msgstr[1] "Étiquettes"
+
+msgid "Tags"
+msgstr "Étiquettes"
+
+msgid "Target Branch"
+msgstr "Branche cible"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"L’étape de développement montre le temps entre la première validation et la "
+"création de la demande de fusion. Les données seront automatiquement "
+"ajoutées ici une fois que vous aurez créé votre première demande de fusion."
msgid "The collection of events added to the data gathered for that stage."
-msgstr "L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
+msgstr ""
+"L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."
+msgid "The fork relationship has been removed."
+msgstr "La relation de fourche a été supprimée."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"L'étape des incidents montre le temps nécessaire entre la création d'un "
+"incident et son assignation à un jalon, ou son ajout à une liste d'un "
+"tableau d'incidents. Débutez à créer des incidents pour voir des données "
+"pour cette étape."
msgid "The phase of the development lifecycle."
msgstr "Les étapes du cycle de développement."
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Les pipelines programmés exécutent des pipelines dans le futur, de façon "
+"répétée, pour les branches et étiquettes spécifiées. Ces pipelines "
+"programmés héritent d’un accès partiel au projet basé sur l’utilisateur qui "
+"leurs est associé."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"L’étape de planification montre le temps entre l’étape précédente et l’envoi "
+"de votre première validation. Ce temps sera automatiquement ajouté quand "
+"vous pousserez votre première validation."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"L’étape de mise en production montre le temps nécessaire entre la création "
+"d’un incident et le déploiement du code en production. Les données seront "
+"automatiquement ajoutées une fois que vous aurez complété le cycle complet, "
+"depuis l’idée jusqu’à la mise en production."
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+"Votre projet peut être accédé par n’importe quel utilisateur authentifié"
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."
+msgid "The project can be accessed without any authentication."
+msgstr "Votre projet peut être accédé sans aucune authentification."
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."
+msgid "The repository for this project does not exist."
+msgstr "Le dépôt pour ce projet n'existe pas."
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"L’étape d’évaluation montre le temps entre la création de la demande de "
+"fusion et la fusion effective de celle-ci. Ces données seront "
+"automatiquement ajoutées après que vous ayez fusionné votre première demande "
+"de fusion."
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"L’étape de pré-production indique le temps entre la fusion de la DF et le "
+"déploiement du code dans l’environnent de production. Les données seront "
+"automatiquement ajoutées une fois que vous déploierez en production pour la "
+"première fois."
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque "
+"pipeline liés à la demande de fusion. Les données seront automatiquement "
+"ajoutées après que votre premier pipeline s’achèvera."
msgid "The time taken by each data entry gathered by that stage."
msgstr "Le temps pris par chaque entrée récoltée durant cette étape."
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
-msgstr "La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"La valeur située au point médian d’une série de valeur observée. C.à.d., "
+"entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez "
+"pas un dépôt vide, ou importez une dépôt existant."
msgid "Time before an issue gets scheduled"
msgstr "Temps avant qu’un incident ne soit planifié"
@@ -173,6 +992,129 @@ msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture"
msgid "Time until first merge request"
msgstr "Temps jusqu’à la première demande de fusion"
+msgid "Timeago|%s days ago"
+msgstr "Il y a %s jours"
+
+msgid "Timeago|%s days remaining"
+msgstr "Il reste %s jours"
+
+msgid "Timeago|%s hours remaining"
+msgstr "Il reste %s heures"
+
+msgid "Timeago|%s minutes ago"
+msgstr "Il y a %s minutes"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "Il reste %s minutes"
+
+msgid "Timeago|%s months ago"
+msgstr "Il y a %s mois"
+
+msgid "Timeago|%s months remaining"
+msgstr "Il reste %s mois"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "Il reste %s secondes"
+
+msgid "Timeago|%s weeks ago"
+msgstr "Il y a %s semaines"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "Il reste %s semaines"
+
+msgid "Timeago|%s years ago"
+msgstr "Il y a %s ans"
+
+msgid "Timeago|%s years remaining"
+msgstr "Il reste %s ans"
+
+msgid "Timeago|1 day remaining"
+msgstr "Il reste un jour"
+
+msgid "Timeago|1 hour remaining"
+msgstr "Il reste une heure"
+
+msgid "Timeago|1 minute remaining"
+msgstr "Il reste une minute"
+
+msgid "Timeago|1 month remaining"
+msgstr "Il reste un mois"
+
+msgid "Timeago|1 week remaining"
+msgstr "Il reste une semaine"
+
+msgid "Timeago|1 year remaining"
+msgstr "Il reste un an"
+
+msgid "Timeago|Past due"
+msgstr "En retard"
+
+msgid "Timeago|a day ago"
+msgstr "Il y a un jour"
+
+msgid "Timeago|a month ago"
+msgstr "Il y a un mois"
+
+msgid "Timeago|a week ago"
+msgstr "Il y a une semaine"
+
+msgid "Timeago|a while"
+msgstr "Il y a un moment"
+
+msgid "Timeago|a year ago"
+msgstr "Il y a un an"
+
+msgid "Timeago|about %s hours ago"
+msgstr "Il y a environ %s heures"
+
+msgid "Timeago|about a minute ago"
+msgstr "Il y a environ une minute"
+
+msgid "Timeago|about an hour ago"
+msgstr "Il y a environ une heure"
+
+msgid "Timeago|in %s days"
+msgstr "Dans %s jours"
+
+msgid "Timeago|in %s hours"
+msgstr "Dans %s heures"
+
+msgid "Timeago|in %s minutes"
+msgstr "Dans %s minutes"
+
+msgid "Timeago|in %s months"
+msgstr "Dans %s mois"
+
+msgid "Timeago|in %s seconds"
+msgstr "Dans %s secondes"
+
+msgid "Timeago|in %s weeks"
+msgstr "Dans %s semaines"
+
+msgid "Timeago|in %s years"
+msgstr "Dans %s années"
+
+msgid "Timeago|in 1 day"
+msgstr "Dans 1 jour"
+
+msgid "Timeago|in 1 hour"
+msgstr "Dans 1 heure"
+
+msgid "Timeago|in 1 minute"
+msgstr "Dans 1 minute"
+
+msgid "Timeago|in 1 month"
+msgstr "Dans 1 mois"
+
+msgid "Timeago|in 1 week"
+msgstr "Dans 1 semaine"
+
+msgid "Timeago|in 1 year"
+msgstr "Dans 1 an"
+
+msgid "Timeago|less than a minute ago"
+msgstr "il y a moins d'une minute"
+
msgid "Time|hr"
msgid_plural "Time|hrs"
msgstr[0] "hr"
@@ -192,16 +1134,141 @@ msgstr "Temps total"
msgid "Total test time for all commits/merges"
msgstr "Temps total de test pour toutes les validations/fusions"
+msgid "Unstar"
+msgstr "Se désabonner"
+
+msgid "Upload New File"
+msgstr "Téléverser un nouveau fichier"
+
+msgid "Upload file"
+msgstr "Téléverser un fichier"
+
+msgid "UploadLink|click to upload"
+msgstr "Cliquez pour envoyer"
+
+msgid "Use your global notification setting"
+msgstr "Utiliser vos paramètres de notification globaux"
+
+msgid "View open merge request"
+msgstr "Afficher la demande de fusion"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interne"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privé"
+
+msgid "VisibilityLevel|Public"
+msgstr "Public"
+
msgid "Want to see the data? Please ask an administrator for access."
-msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."
+msgstr ""
+"Vous voulez voir les données ? Merci de contacter un administrateur pour en "
+"obtenir l’accès."
msgid "We don't have enough data to show this stage."
msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape."
+msgid "Withdraw Access Request"
+msgstr "Retirer la demande d'accès"
+
+msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE "
+"PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\n"
+"Les projets supprimés NE PEUVENT PAS être restaurés !\n"
+"Êtes vous ABSOLUMENT sûr ? "
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous allez supprimer la relation de fourche avec le projet source "
+"%{forked_from_project}. Êtes-vous VRAIMENT sûr."
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous allez transférer %{project_name_with_namespace} à un nouveau "
+"propriétaire. Êtes vous VRAIMENT sûr ?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
+
+msgid "You have reached your project limit"
+msgstr "Vous avez atteint votre limite de projet"
+
+msgid "You must sign in to star a project"
+msgstr "Vous devez vous identifier pour vous abonner à un projet"
+
msgid "You need permission."
msgstr "Vous avez besoin d’une autorisation."
+msgid "You will not get any notifications via email"
+msgstr "Vous ne recevrez aucune notification par courriel"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+"Vous ne recevrez de notification que pour les évènements que vous aurez "
+"choisis"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr ""
+"Vous ne recevrez de notification que pour les sujets auxquels vous avez "
+"participé"
+
+msgid "You will receive notifications for any activity"
+msgstr "Vous recevrez des notifications pour n’importe quelles activités"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr ""
+"Vous ne recevrez de notifications que pour les commentaires où vous êtes "
+"@mentionné"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Vous ne pourrez pas récupérer ou pousser de code par %{protocol} tant que "
+"vous n'aurez pas %{set_password_link} pour votre compte"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous "
+"n’aurez pas %{add_ssh_key_link} dans votre profil"
+
+msgid "Your name"
+msgstr "Votre nom"
+
msgid "day"
msgid_plural "days"
msgstr[0] "jour"
msgstr[1] "jours"
+
+msgid "new merge request"
+msgstr "nouvelle demande de fusion"
+
+msgid "notification emails"
+msgstr "courriels de notification"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "parent"
+msgstr[1] "parents"
+
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index b880fc703ec..04c61906c73 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -3,7 +3,7 @@
# Kohei Ota <inductor@kela.jp>, 2017. #zanata
# Taisuke Inoue <taisuke.inoue.jp@gmail.com>, 2017. #zanata
# Takuya Noguchi <takninnovationresearch@gmail.com>, 2017. #zanata
-# YANO TETTER <tetuyano+zana@gmail.com>, 2017. #zanata
+# YANO Tethurou <tetuyano+zana@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
@@ -12,9 +12,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Language-Team: Japanese (https://translate.zanata.org/project/view/GitLab)\n"
-"PO-Revision-Date: 2017-07-18 09:27-0400\n"
"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n"
+"PO-Revision-Date: 2017-07-19 09:45-0400\n"
+"Last-Translator: YANO Tethurou <tetuyano+zana@gmail.com>\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
@@ -33,7 +33,8 @@ msgstr "%{commit_author_link}ã¯%{commit_timeago}å‰ã€ã‚³ãƒŸãƒƒãƒˆã—ã¾ã—ãŸã
msgid "1 pipeline"
msgid_plural "%d pipelines"
-msgstr[0] "%d 個ã®ãƒ‘イプライン"
+msgstr[0] "1 個ã®ãƒ‘イプライン"
+msgstr[1] "%d 個ã®ãƒ‘イプライン"
msgid "A collection of graphs regarding Continuous Integration"
msgstr "CIã«ã¤ã„ã¦ã®ã‚°ãƒ©ãƒ•"
@@ -628,6 +629,12 @@ msgstr "全件"
msgid "PipelineSchedules|Inactive"
msgstr "無効"
+msgid "PipelineSchedules|Input variable key"
+msgstr "変数ã®åå‰ã‚’入力"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "変数ã®å€¤ã‚’入力"
+
msgid "PipelineSchedules|Next Run"
msgstr "次ã®å®Ÿè¡Œ"
@@ -637,12 +644,18 @@ msgstr "ãªã—"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "ã“ã®ãƒ‘イプラインã«ã¤ã„ã¦ç°¡å˜ã«è¨˜è¿°ã—ã¦ãã ã•ã„。"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "変数を削除"
+
msgid "PipelineSchedules|Take ownership"
msgstr "権é™ã‚’å–å¾—ã™ã‚‹"
msgid "PipelineSchedules|Target"
msgstr "ターゲット"
+msgid "PipelineSchedules|Variables"
+msgstr "変数"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "カスタム"
@@ -1104,6 +1117,14 @@ msgid "Withdraw Access Request"
msgstr "アクセスリクエストをå–り消ã™"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "%{group_name} グループを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
+"削除ã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ã¯çµ¶å¯¾ã«å…ƒã«æˆ»ã›ã¾ã›ã‚“ï¼\n"
+"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
@@ -1115,8 +1136,7 @@ msgstr ""
msgid ""
"You are going to remove the fork relationship to source project "
"%{forked_from_project}. Are you ABSOLUTELY sure?"
-msgstr "å…ƒã®ãƒ—ロジェクト (%{forked_from_project}) ã¨ã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
-"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+msgstr "å…ƒã®ãƒ—ロジェクト (%{forked_from_project}) ã¨ã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
@@ -1181,4 +1201,3 @@ msgstr "メール通知"
msgid "parent"
msgid_plural "parents"
msgstr[0] "親"
-
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index c4918a4c920..78cf6d2dacc 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -6,13 +6,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-12 09:05-0400\n"
-"Last-Translator: Leandro Nunes dos Santos <leandronunes@gmail.com>\n"
"Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-14 01:17-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language: pt-BR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
@@ -644,6 +644,12 @@ msgstr "Todos"
msgid "PipelineSchedules|Inactive"
msgstr "Inativo"
+msgid "PipelineSchedules|Input variable key"
+msgstr "PipelineSchedules|Chave da variável de entrada"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "PipelineSchedules|Valor da variável de entrada"
+
msgid "PipelineSchedules|Next Run"
msgstr "Próxima Execução"
@@ -653,12 +659,18 @@ msgstr "Nenhum"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Digite uma descrição curta para esta pipeline"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "PipelineSchedules|Remova a linha da variável"
+
msgid "PipelineSchedules|Take ownership"
msgstr "Tornar-se proprietário"
msgid "PipelineSchedules|Target"
msgstr "Destino"
+msgid "PipelineSchedules|Variables"
+msgstr "PipelineSchedules|Variáveis"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizado"
@@ -1151,6 +1163,15 @@ msgid "Withdraw Access Request"
msgstr "Remover Requisição de Acesso"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Você vai remover %{group_name}.\n"
+"Grupos removidos NÃO PODEM ser restaurados!\n"
+"Você está ABSOLUTAMENTE certo?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 59a7eb6e1b3..2ded61f40d7 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -1,16 +1,16 @@
-# Ðндрей Витюк <andruwa13@gmail.com>, 2017. #zanata
# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Ðндрей Витюк <andruwa13@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-12 09:05-0400\n"
-"Last-Translator: Ðндрей Витюк <andruwa13@gmail.com>\n"
"Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-25 03:27-0400\n"
+"Last-Translator: Ðндрей Витюк <andruwa13@gmail.com>\n"
"Language: uk\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
@@ -57,7 +57,7 @@ msgid "Add Changelog"
msgstr "Додати ÑпиÑок змін (Changelog)"
msgid "Add Contribution guide"
-msgstr "Додати керівництво Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¸Ð±ÑƒÑ‚Ð¾Ñ€Ñ–Ð²"
+msgstr "Додати керівництво Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¸Ð±â€™ÑŽÑ‚Ð¾Ñ€Ñ–Ð²"
msgid "Add License"
msgstr "Додати ліцензію"
@@ -209,7 +209,9 @@ msgstr[1] "Комміта"
msgstr[2] "Коммітів"
msgid "Commit duration in minutes for last 30 commits"
-msgstr "Комміт триваліÑÑ‚ÑŒ у хвилинах за оÑтанні 30 коммітів"
+msgstr ""
+"ТриваліÑÑ‚ÑŒ коммітів протÑгом декількох хвилин на протÑзі 30 оÑтанніх "
+"коммітів"
msgid "Commit message"
msgstr "Комміт повідомленнÑ"
@@ -236,10 +238,10 @@ msgid "Compare"
msgstr "ПорівнÑти"
msgid "Contribution guide"
-msgstr "Керівництво контрибуторів"
+msgstr "Керівництво контриб’юторів"
msgid "Contributors"
-msgstr "Контрибутори"
+msgstr "Контриб’ютори"
msgid "Copy URL to clipboard"
msgstr "Скопіювати URL в буфер обміну"
@@ -352,16 +354,16 @@ msgid "Download"
msgstr "Завантажити"
msgid "Download tar"
-msgstr "Завантажити в форматі tar"
+msgstr "Завантажити tar"
msgid "Download tar.bz2"
-msgstr "Завантажити в форматі tar.bz2"
+msgstr "Завантажити tar.bz2"
msgid "Download tar.gz"
-msgstr "Завантажити в форматі tar.gz"
+msgstr "Завантажити tar.gz"
msgid "Download zip"
-msgstr "Завантажити в форматі zip"
+msgstr "Завантажити zip"
msgid "DownloadArtifacts|Download"
msgstr "Завантажити"
@@ -379,7 +381,7 @@ msgid "Edit"
msgstr "Редагувати"
msgid "Edit Pipeline Schedule %{id}"
-msgstr "Редагувати Розклад Конвеєра % {id}"
+msgstr "Редагувати Розклад Конвеєра %{id}"
msgid "Every day (at 4:00am)"
msgstr "Кожен день (в 4:00 ранку)"
@@ -397,7 +399,7 @@ msgid "Failed to remove the pipeline schedule"
msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ розклад Конвеєра"
msgid "Files"
-msgstr "Файли"
+msgstr "Файлів"
msgid "Filter by commit message"
msgstr "Фільтрувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð¼Ñ–Ñ‚Ñ–Ð²"
@@ -436,7 +438,7 @@ msgid "GoToYourFork|Fork"
msgstr "Форк"
msgid "Home"
-msgstr "Початок"
+msgstr "Головна"
msgid "Housekeeping successfully started"
msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ ÑƒÑпішно розпочато"
@@ -451,13 +453,13 @@ msgid "Introducing Cycle Analytics"
msgstr "ПредÑтавлÑємо аналітику циклу"
msgid "Jobs for last month"
-msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð° оÑтанній міÑÑць"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній міÑÑць"
msgid "Jobs for last week"
-msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð° оÑтанній тиждень"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній тиждень"
msgid "Jobs for last year"
-msgstr "Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð·Ð° оÑтанній рік"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній рік"
msgid "LFSStatus|Disabled"
msgstr "Вимкнено"
@@ -508,7 +510,7 @@ msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "Ðова проблема"
msgstr[1] "Ðові проблеми"
-msgstr[2] "Ðовах проблем"
+msgstr[2] "Ðових проблем"
msgid "New Pipeline Schedule"
msgstr "Ðовий розклад Конвеєра"
@@ -654,6 +656,12 @@ msgstr "Ð’ÑÑ–"
msgid "PipelineSchedules|Inactive"
msgstr "Ðеактивні"
+msgid "PipelineSchedules|Input variable key"
+msgstr "Введіть ім'Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ—"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Вхідні Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¸Ñ…"
+
msgid "PipelineSchedules|Next Run"
msgstr "ÐаÑтупний запуÑк"
@@ -663,12 +671,18 @@ msgstr "Ðемає"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Задайте короткий Ð¾Ð¿Ð¸Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Конвеєру"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Видалити змінні"
+
msgid "PipelineSchedules|Take ownership"
msgstr "Стати влаÑником"
msgid "PipelineSchedules|Target"
msgstr "Ціль"
+msgid "PipelineSchedules|Variables"
+msgstr "Змінні"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "ВлаÑні"
@@ -745,7 +759,7 @@ msgid "ProjectLifecycle|Stage"
msgstr "Етап"
msgid "ProjectNetworkGraph|Graph"
-msgstr "Графік"
+msgstr "ІÑторіÑ"
msgid "Read more"
msgstr "Докладніше"
@@ -840,7 +854,7 @@ msgid "Source code"
msgstr "Код"
msgid "StarProject|Star"
-msgstr "Старт"
+msgstr "ПідпиÑатиÑÑ"
msgid "Start a %{new_merge_request} with these changes"
msgstr "Почати %{new_merge_request} з цих змін"
@@ -976,19 +990,19 @@ msgid "Timeago|%s minutes remaining"
msgstr "%s хвилини залишитиÑÑ"
msgid "Timeago|%s months ago"
-msgstr "%s міÑÑців тому"
+msgstr "%s міÑÑці(в) тому"
msgid "Timeago|%s months remaining"
-msgstr "%s міÑÑці, що залишилиÑÑ"
+msgstr "%s міÑÑці(в), що залишилиÑÑ"
msgid "Timeago|%s seconds remaining"
msgstr "%s Ñекунд, що залишаютьÑÑ"
msgid "Timeago|%s weeks ago"
-msgstr "%s тижнів тому"
+msgstr "%s тижні(в) тому"
msgid "Timeago|%s weeks remaining"
-msgstr "%s тижнів залишилиÑÑ"
+msgstr "%s тижні(в) залишилиÑÑ"
msgid "Timeago|%s years ago"
msgstr "%s років тому"
@@ -1018,7 +1032,7 @@ msgid "Timeago|Past due"
msgstr "ПроÑтрочені"
msgid "Timeago|a day ago"
-msgstr "годин тому"
+msgstr "День тому"
msgid "Timeago|a month ago"
msgstr "міÑÑць тому"
@@ -1042,28 +1056,28 @@ msgid "Timeago|about an hour ago"
msgstr "Близько години тому"
msgid "Timeago|in %s days"
-msgstr "через %s днїв"
+msgstr "через %s дні(в)"
msgid "Timeago|in %s hours"
-msgstr "через %s години"
+msgstr "через %s годин(и)"
msgid "Timeago|in %s minutes"
-msgstr "через %s хвилини"
+msgstr "через %s хвилин(и)"
msgid "Timeago|in %s months"
-msgstr "через %s міÑÑців"
+msgstr "через %s міÑÑці(в)"
msgid "Timeago|in %s seconds"
-msgstr "через %s Ñекунд"
+msgstr "через %s Ñекунд(и)"
msgid "Timeago|in %s weeks"
-msgstr "через %s тижні"
+msgstr "через %s тижні(в)"
msgid "Timeago|in %s years"
-msgstr "через %s років"
+msgstr "через %s роки(ів)"
msgid "Timeago|in 1 day"
-msgstr "через день"
+msgstr "через 1 день"
msgid "Timeago|in 1 hour"
msgstr "через годину"
@@ -1081,22 +1095,22 @@ msgid "Timeago|in 1 year"
msgstr "через рік"
msgid "Timeago|less than a minute ago"
-msgstr "менш хвилини тому"
+msgstr "менше хвилини тому"
msgid "Time|hr"
msgid_plural "Time|hrs"
-msgstr[0] "Година"
-msgstr[1] "Годині"
-msgstr[2] "Годин"
+msgstr[0] "година"
+msgstr[1] "години"
+msgstr[2] "годин"
msgid "Time|min"
msgid_plural "Time|mins"
msgstr[0] "хвилина"
-msgstr[1] "хвилині"
+msgstr[1] "хвилини"
msgstr[2] "хвилин"
msgid "Time|s"
-msgstr "Ñекунда"
+msgstr "Ñекунд(а)"
msgid "Total Time"
msgstr "Загальний чаÑ"
@@ -1105,7 +1119,7 @@ msgid "Total test time for all commits/merges"
msgstr "Загальний чаÑ, щоб перевірити вÑÑ– фікÑації/злиттÑ"
msgid "Unstar"
-msgstr "ЗнÑти позначку"
+msgstr "ВідпиÑатиÑÑŒ"
msgid "Upload New File"
msgstr "Завантажити новий файл"
@@ -1117,7 +1131,7 @@ msgid "UploadLink|click to upload"
msgstr "ÐатиÑніть, щоб завантажити"
msgid "Use your global notification setting"
-msgstr "ВикориÑтовуютьÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð¸Ð¹ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ"
+msgstr "ВикориÑтовуютьÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ"
msgid "View open merge request"
msgstr "ПереглÑд відкритих запитів на злиттÑ"
@@ -1141,6 +1155,15 @@ msgid "Withdraw Access Request"
msgstr "СкаÑувати запит доÑтупу"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ви хочете видалити %{group_name}.\n"
+"Видалені групи ÐЕ МОЖÐРбуду відновити!\n"
+"Ви ÐБСОЛЮТÐО впевнені?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
@@ -1231,4 +1254,3 @@ msgid_plural "parents"
msgstr[0] "джерело"
msgstr[1] "джерела"
msgstr[2] "джерел"
-
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 47b72d7be1a..f471e7def25 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -29,7 +29,8 @@ msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}"
msgid "1 pipeline"
msgid_plural "%d pipelines"
-msgstr[0] "%d æ¡æµæ°´çº¿"
+msgstr[0] "1 æ¡æµæ°´çº¿"
+msgstr[1] "%d æ¡æµæ°´çº¿"
msgid "A collection of graphs regarding Continuous Integration"
msgstr "æŒç»­é›†æˆæ•°æ®å›¾"
@@ -236,7 +237,7 @@ msgstr "创建新目录"
msgid ""
"Create a personal access token on your account to pull or push via "
"%{protocol}."
-msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ï¼…{protocol}æ¥æ‹‰å–或推é€ã€‚"
+msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ %{protocol} æ¥æ‹‰å–或推é€ã€‚"
msgid "Create directory"
msgstr "创建目录"
@@ -1109,9 +1110,7 @@ msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
-msgstr "å³å°†è¦åˆ é™¤ %{project_name_with_namespace}。\n"
-"已删除的项目无法æ¢å¤ï¼\n"
-"确定继续å—?"
+msgstr "å³å°†è¦åˆ é™¤ %{project_name_with_namespace}。已删除的项目无法æ¢å¤ï¼ç¡®å®šç»§ç»­å—?"
msgid ""
"You are going to remove the fork relationship to source project "
@@ -1179,4 +1178,3 @@ msgstr "通知邮件"
msgid "parent"
msgid_plural "parents"
msgstr[0] "父级"
-
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 8a4e6da4ea9..1b7c39f8f62 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -28,7 +28,8 @@ msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}"
msgid "1 pipeline"
msgid_plural "%d pipelines"
-msgstr[0] "%d æ¢æµæ°´ç·š"
+msgstr[0] "1 æ¢æµæ°´ç·š"
+msgstr[1] "%d æ¢æµæ°´ç·š"
msgid "A collection of graphs regarding Continuous Integration"
msgstr "相關æŒçºŒé›†æˆçš„圖åƒé›†åˆ"
@@ -1108,9 +1109,7 @@ msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
-msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。\n"
-"已刪除的項目無法æ¢è¤‡ï¼\n"
-"確定繼續嗎?"
+msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。已刪除的項目無法æ¢è¤‡ï¼ç¢ºå®šç¹¼çºŒå—Žï¼Ÿ"
msgid ""
"You are going to remove the fork relationship to source project "
@@ -1178,4 +1177,3 @@ msgstr "通知郵件"
msgid "parent"
msgid_plural "parents"
msgstr[0] "父級"
-
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index e61cf0e5152..8d30a78145d 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -7,13 +7,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-07-11 09:10-0400\n"
-"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-20 09:50-0400\n"
+"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language: zh-TW\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
@@ -32,7 +32,8 @@ msgstr "%{commit_author_link} 在 %{commit_timeago} é€äº¤"
msgid "1 pipeline"
msgid_plural "%d pipelines"
-msgstr[0] "%d æ¢æµæ°´ç·š"
+msgstr[0] "1 æ¢æµæ°´ç·š"
+msgstr[1] "%d æ¢æµæ°´ç·š"
msgid "A collection of graphs regarding Continuous Integration"
msgstr "æŒçºŒæ•´åˆ (CI) 相關的圖表"
@@ -624,6 +625,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未啟用"
+msgid "PipelineSchedules|Input variable key"
+msgstr "變數å稱"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "變數值"
+
msgid "PipelineSchedules|Next Run"
msgstr "下次執行時間"
@@ -633,12 +640,18 @@ msgstr "ç„¡"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "請簡單說明此æµæ°´ç·š (pipeline) "
+msgid "PipelineSchedules|Remove variable row"
+msgstr "刪除變數"
+
msgid "PipelineSchedules|Take ownership"
msgstr "å–得所有權"
msgid "PipelineSchedules|Target"
msgstr "目標"
+msgid "PipelineSchedules|Variables"
+msgstr "變數"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自訂"
@@ -759,13 +772,13 @@ msgid "Revert this merge request"
msgstr "還原此åˆä½µè«‹æ±‚ (merge request) "
msgid "Save pipeline schedule"
-msgstr "ä¿å­˜æµæ°´ç·š (pipeline) 排程"
+msgstr "儲存æµæ°´ç·š (pipeline) 排程"
msgid "Schedule a new pipeline"
msgstr "建立æµæ°´ç·š (pipeline) 排程"
msgid "Scheduling Pipelines"
-msgstr "æµæ°´ç·š (pipeline) 計劃"
+msgstr "æµæ°´ç·š (pipeline) 排程"
msgid "Search branches and tags"
msgstr "æœå°‹åˆ†æ”¯ (branch) 和標籤"
@@ -849,8 +862,7 @@ msgid ""
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
-"在指定了特定分支 (branch) 或標籤後,此處的æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚\n"
-"æµæ°´ç·šæŽ’程的存å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚"
+"在指定了特定分支 (branch) 或標籤後,此處的æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚æµæ°´ç·šæŽ’程的存å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚"
msgid ""
"The planning stage shows the time from the previous step to pushing your "
@@ -1097,6 +1109,14 @@ msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è«‹"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "å³å°‡è¦åˆªé™¤ %{group_name}。\n"
+"被刪除的群組完全無法救回來喔ï¼\n"
+"真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
@@ -1174,4 +1194,3 @@ msgstr "通知信"
msgid "parent"
msgid_plural "parents"
msgstr[0] "上層"
-
diff --git a/qa/qa.rb b/qa/qa.rb
index bdfb8237995..db9d8c42fde 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -48,7 +48,14 @@ module QA
module Main
autoload :Entry, 'qa/page/main/entry'
autoload :Menu, 'qa/page/main/menu'
- autoload :Groups, 'qa/page/main/groups'
+ end
+
+ module Dashboard
+ autoload :Groups, 'qa/page/dashboard/groups'
+ end
+
+ module Group
+ autoload :Show, 'qa/page/group/show'
end
module Project
diff --git a/qa/qa/page/main/groups.rb b/qa/qa/page/dashboard/groups.rb
index 169c5ebc967..3690f40dcfe 100644
--- a/qa/qa/page/main/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -1,9 +1,11 @@
module QA
module Page
- module Main
+ module Dashboard
class Groups < Page::Base
def prepare_test_namespace
- return if page.has_content?(Runtime::Namespace.name)
+ if page.has_content?(Runtime::Namespace.name)
+ return click_link(Runtime::Namespace.name)
+ end
click_on 'New group'
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
new file mode 100644
index 00000000000..296c311d7c6
--- /dev/null
+++ b/qa/qa/page/group/show.rb
@@ -0,0 +1,11 @@
+module QA
+ module Page
+ module Group
+ class Show < Page::Base
+ def go_to_new_project
+ click_link 'New Project'
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index f7c2086d0dd..7ce4e9009f5 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -14,13 +14,6 @@ module QA
within_user_menu { click_link 'Admin area' }
end
- def go_to_new_project
- within_user_menu do
- find('.header-new-dropdown-toggle').click
- click_link('New project')
- end
- end
-
def sign_out
within_user_menu do
find('.header-user-dropdown-toggle').click
diff --git a/qa/qa/scenario/gitlab/project/create.rb b/qa/qa/scenario/gitlab/project/create.rb
index 99d0fc42a94..b860701c304 100644
--- a/qa/qa/scenario/gitlab/project/create.rb
+++ b/qa/qa/scenario/gitlab/project/create.rb
@@ -13,8 +13,8 @@ module QA
def perform
Page::Main::Menu.act { go_to_groups }
- Page::Main::Groups.act { prepare_test_namespace }
- Page::Main::Menu.act { go_to_new_project }
+ Page::Dashboard::Groups.act { prepare_test_namespace }
+ Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
new file mode 100644
index 00000000000..6eb9f7867d5
--- /dev/null
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Admin::DashboardController do
+ describe '#index' do
+ context 'with pending_delete projects' do
+ render_views
+
+ it 'does not retrieve projects that are pending deletion' do
+ sign_in(create(:admin))
+
+ project = create(:project)
+ pending_delete_project = create(:project, pending_delete: true)
+
+ get :index
+
+ expect(response.body).to match(project.name)
+ expect(response.body).not_to match(pending_delete_project.name)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
new file mode 100644
index 00000000000..d68200164e4
--- /dev/null
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BadgesController do
+ let(:project) { pipeline.project }
+ let!(:pipeline) { create(:ci_empty_pipeline) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'requests the pipeline badge successfully' do
+ get_badge(:pipeline)
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'requests the coverage badge successfully' do
+ get_badge(:coverage)
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ def get_badge(badge)
+ get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg
+ end
+end
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
new file mode 100644
index 00000000000..b93ab220f4d
--- /dev/null
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Projects::HooksController do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe '#index' do
+ it 'redirects to settings/integrations page' do
+ get(:index, namespace_id: project.namespace, project_id: project)
+
+ expect(response).to redirect_to(
+ project_settings_integrations_path(project)
+ )
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 18d0be3c103..2c57f3bcf8d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -7,16 +7,30 @@ describe Projects::IssuesController do
describe "GET #index" do
context 'external issue tracker' do
- let!(:service) do
- create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', project_url: 'http://test.com')
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ create(:jira_service, project: project)
end
- it 'redirects to the external issue tracker' do
- controller.instance_variable_set(:@project, project)
+ context 'when GitLab issues disabled' do
+ it 'returns 404 status' do
+ project.issues_enabled = false
+ project.save!
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when GitLab issues enabled' do
+ it 'renders the "index" template' do
+ get :index, namespace_id: project.namespace, project_id: project
- expect(response).to redirect_to(service.issue_tracker_path)
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:index)
+ end
end
end
@@ -42,15 +56,7 @@ describe Projects::IssuesController do
it "returns 404 when issues are disabled" do
project.issues_enabled = false
- project.save
-
- get :index, namespace_id: project.namespace, project_id: project
- expect(response).to have_http_status(404)
- end
-
- it "returns 404 when external issue tracker is enabled" do
- controller.instance_variable_set(:@project, project)
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ project.save!
get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
@@ -148,14 +154,29 @@ describe Projects::IssuesController do
before do
sign_in(user)
project.team << [user, :developer]
+
+ external = double
+ allow(project).to receive(:external_issue_tracker).and_return(external)
end
- it 'redirects to the external issue tracker' do
- controller.instance_variable_set(:@project, project)
+ context 'when GitLab issues disabled' do
+ it 'returns 404 status' do
+ project.issues_enabled = false
+ project.save!
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
- expect(response).to redirect_to('http://test.com')
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when GitLab issues enabled' do
+ it 'renders the "new" template' do
+ get :new, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:new)
+ end
end
end
end
@@ -806,7 +827,7 @@ describe Projects::IssuesController do
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(302)
- expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
end
it 'delegates the update of the todos count cache to TodoService' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 472e5fc51a0..5a295ae47a6 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -7,6 +7,10 @@ describe Projects::JobsController do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:user) { create(:user) }
+ before do
+ stub_not_protect_default_branch
+ end
+
describe 'GET index' do
context 'when scope is pending' do
before do
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index c193babead0..2fce4b7a85f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -439,7 +439,7 @@ describe Projects::MergeRequestsController do
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(302)
- expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
+ expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
end
it 'delegates the update of the todos count cache to TodoService' do
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 734532668d3..c8de275ca3e 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -8,6 +8,7 @@ describe Projects::PipelinesController do
let(:feature) { ProjectFeature::DISABLED }
before do
+ stub_not_protect_default_branch
project.add_developer(user)
project.project_feature.update(
builds_access_level: feature)
@@ -158,7 +159,7 @@ describe Projects::PipelinesController do
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
-
+
it 'retries a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(build.reload).to be_retried
@@ -175,7 +176,7 @@ describe Projects::PipelinesController do
describe 'POST cancel.json' do
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
-
+
before do
post :cancel, namespace_id: project.namespace,
project_id: project,
@@ -185,7 +186,7 @@ describe Projects::PipelinesController do
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
-
+
it 'cancels a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(pipeline.reload).to be_canceled
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
new file mode 100644
index 00000000000..a823516830e
--- /dev/null
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Projects::Registry::TagsController do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :private) }
+
+ before do
+ sign_in(user)
+ stub_container_registry_config(enabled: true)
+ end
+
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'POST destroy' do
+ context 'when there is matching tag present' do
+ before do
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.])
+ end
+
+ let(:repository) do
+ create(:container_repository, name: 'image', project: project)
+ end
+
+ it 'makes it possible to delete regular tag' do
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
+
+ destroy_tag('rc1')
+ end
+
+ it 'makes it possible to delete a tag that ends with a dot' do
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
+
+ destroy_tag('test.')
+ end
+ end
+ end
+ end
+
+ def destroy_tag(name)
+ post :destroy, namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ id: name
+ end
+end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 7340a4e16c0..c8771eda313 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -23,7 +23,7 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the login page' do
@@ -83,7 +83,7 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the issue page' do
@@ -109,7 +109,7 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the merge request page' do
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 2b4e8723b48..a22fd8eaf9b 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
describe SessionsController do
+ include DeviseHelpers
+
describe '#new' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
context 'when auto sign-in is enabled' do
@@ -34,7 +36,7 @@ describe SessionsController do
describe '#create' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
context 'when using standard authentications' do
@@ -257,7 +259,7 @@ describe SessionsController do
describe '#new' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
it 'redirects correctly for referer on same host with params' do
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index a77f01ecb00..5bba1dec7db 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -84,6 +84,10 @@ FactoryGirl.define do
success
end
+ trait :retried do
+ retried true
+ end
+
trait :cancelable do
pending
end
@@ -106,7 +110,7 @@ FactoryGirl.define do
end
after(:build) do |build, evaluator|
- build.project = build.pipeline.project
+ build.project ||= build.pipeline.project
end
factory :ci_not_started_build do
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index cd754ea235f..d754e980931 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :project_hook do
url { generate(:url) }
enable_ssl_verification false
+ project factory: :empty_project
trait :token do
token { SecureRandom.hex(10) }
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index b9e361328df..2f90f668e89 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do
end
def logo_selector
- '//img[@src^="/uploads/-/system/appearance/logo"]'
+ '//img[data-src^="/uploads/-/system/appearance/logo"]'
end
def header_logo_selector
- '//img[@src^="/uploads/-/system/appearance/header_logo"]'
+ '//img[data-src^="/uploads/-/system/appearance/header_logo"]'
end
def logo_fixture
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 1e675fc0ce7..9a438b65e68 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -74,11 +74,13 @@ describe 'Admin::Hooks', feature: true do
end
end
- describe 'Test' do
+ describe 'Test', js: true do
before do
WebMock.stub_request(:post, @system_hook.url)
visit admin_hooks_path
- click_link 'Test hook'
+
+ find('.hook-test-button.dropdown').click
+ click_link 'Push events'
end
it { expect(current_path).to eq(admin_hooks_path) }
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index b06e7e5037c..46bab3763cc 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -19,7 +19,7 @@ describe "Admin Runners" do
end
it 'has all necessary texts' do
- expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 1"
end
@@ -54,7 +54,7 @@ describe "Admin Runners" do
end
it 'has all necessary texts including no runner message' do
- expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 0"
expect(page).to have_text 'No runners found'
end
@@ -163,12 +163,11 @@ describe "Admin Runners" do
end
it 'has a registration token' do
- expect(page).to have_content("Registration token is #{token}")
- expect(page).to have_selector('#runners-token', text: token)
+ expect(page.find('#registration_token')).to have_content(token)
end
describe 'reload registration token' do
- let(:page_token) { find('#runners-token').text }
+ let(:page_token) { find('#registration_token').text }
before do
click_button 'Reset runners registration token'
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 69c1a2ed89a..2a5ef08da60 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -78,5 +78,23 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
end
end
+
+ it 'shows the new issue page', js: true do
+ Gitlab::Application.routes.default_url_options = {
+ host: Capybara.current_session.server.host,
+ port: Capybara.current_session.server.port,
+ protocol: 'http'
+ }
+
+ find('.new-project-item-select-button').trigger('click')
+ wait_for_requests
+ find('.select2-results li').click
+
+ expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new")
+
+ page.within('#content-body') do
+ expect(page).to have_selector('.issue-form')
+ end
+ end
end
end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 7dd69f550ac..e51d527bdf9 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -1,17 +1,13 @@
require 'spec_helper'
feature 'Top Plus Menu', feature: true, js: true do
- let(:user) { create :user }
- let(:guest_user) { create :user}
+ let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let(:public_project) { create(:project, :public) }
before do
group.add_owner(user)
- group.add_guest(guest_user)
-
- project.add_guest(guest_user)
end
context 'used by full user' do
@@ -39,7 +35,7 @@ feature 'Top Plus Menu', feature: true, js: true do
scenario 'click on New snippet shows new snippet page' do
visit root_dashboard_path
-
+
click_topmenuitem("New snippet")
expect(page).to have_content('New Snippet')
@@ -102,7 +98,12 @@ feature 'Top Plus Menu', feature: true, js: true do
end
context 'used by guest user' do
+ let(:guest_user) { create(:user) }
+
before do
+ group.add_guest(guest_user)
+ project.add_guest(guest_user)
+
sign_in(guest_user)
end
@@ -153,7 +154,7 @@ feature 'Top Plus Menu', feature: true, js: true do
scenario 'has no New project for group menu item' do
visit group_path(group)
-
+
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project')
end
end
@@ -168,5 +169,5 @@ feature 'Top Plus Menu', feature: true, js: true do
def hasnot_topmenuitem(item_name)
expect(find('.header-new.dropdown')).not_to have_content(item_name)
- end
+ end
end
diff --git a/spec/features/issuables/markdown_references_spec.rb b/spec/features/issuables/markdown_references_spec.rb
new file mode 100644
index 00000000000..f51b2e4001a
--- /dev/null
+++ b/spec/features/issuables/markdown_references_spec.rb
@@ -0,0 +1,193 @@
+require 'rails_helper'
+
+describe 'Markdown References', :feature, :js do
+ let(:user) { create(:user) }
+ let(:actual_project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project)}
+ let(:issue_actual_project) { create(:issue, project: actual_project) }
+ let!(:other_project) { create(:empty_project, :public) }
+ let!(:issue_other_project) { create(:issue, project: other_project) }
+ let(:issues) { [issue_actual_project, issue_other_project] }
+
+ def build_note
+ markdown = "Referencing internal issue #{issue_actual_project.to_reference}, " +
+ "cross-project #{issue_other_project.to_reference(actual_project)} external JIRA-5 " +
+ "and non existing #999"
+
+ page.within('#diff-notes-app') do
+ fill_in 'note_note', with: markdown
+ end
+ end
+
+ shared_examples 'correct references' do
+ before do
+ remotelink = double(:remotelink, all: [], build: double(save!: true))
+
+ stub_request(:get, "https://jira.example.com/rest/api/2/issue/JIRA-5")
+ stub_request(:post, "https://jira.example.com/rest/api/2/issue/JIRA-5/comment")
+ allow_any_instance_of(JIRA::Resource::Issue).to receive(:remotelink).and_return(remotelink)
+
+ sign_in(user)
+ visit merge_request_path(merge_request)
+ build_note
+ end
+
+ def links_expectations
+ issues.each do |issue|
+ if referenced_issues.include?(issue)
+ expect(page).to have_link(issue.to_reference, href: issue_path(issue))
+ else
+ expect(page).not_to have_link(issue.to_reference, href: issue_path(issue))
+ end
+ end
+
+ if jira_referenced
+ expect(page).to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5')
+ else
+ expect(page).not_to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5')
+ end
+
+ expect(page).not_to have_link('#999')
+ end
+
+ it 'creates a link to the referenced issue on the preview' do
+ find('.js-md-preview-button').click
+ wait_for_requests
+
+ page.within('.md-preview-holder') do
+ links_expectations
+ end
+ end
+
+ it 'creates a link to the referenced issue after submit' do
+ click_button 'Comment'
+ wait_for_requests
+
+ page.within('#diff-notes-app') do
+ links_expectations
+ end
+ end
+
+ it 'creates a note on the referenced issues' do
+ click_button 'Comment'
+ wait_for_requests
+
+ if referenced_issues.include?(issue_actual_project)
+ visit issue_path(issue_actual_project)
+
+ page.within('#notes') do
+ expect(page).to have_content(
+ "#{user.to_reference} mentioned in merge request #{merge_request.to_reference}"
+ )
+ end
+ end
+
+ if referenced_issues.include?(issue_other_project)
+ visit issue_path(issue_other_project)
+
+ page.within('#notes') do
+ expect(page).to have_content(
+ "#{user.to_reference} mentioned in merge request #{merge_request.to_reference(other_project)}"
+ )
+ end
+ end
+ end
+ end
+
+ context 'when internal issues tracker is enabled for the other project' do
+ context 'when only internal issues tracker is enabled for the actual project' do
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project, issue_other_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+
+ context 'when both external and internal issues trackers are enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project, issue_other_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when only external issues tracker is enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_other_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when no tracker is enabled for the actual project' do
+ before do
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_other_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+ end
+
+ context 'when internal issues tracker is disabled for the other project' do
+ before do
+ other_project.issues_enabled = false
+ other_project.save!
+ end
+
+ context 'when only internal issues tracker is enabled for the actual project' do
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+
+ context 'when both external and internal issues trackers are enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when only external issues tracker is enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when no issues tracker is enabled for the actual project' do
+ before do
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [] }
+ let(:jira_referenced) { false }
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 1cd1f016674..0c3c27e3e45 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -21,6 +21,16 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
wait_for_requests
end
+ describe 'time tracking' do
+ let(:issue) { create(:issue, project: project) }
+
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) }
@@ -99,39 +109,50 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
end
end
- describe 'Issuable time tracking' do
+ describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) }
- before do
- project.team << [user, :developer]
+ it 'does not recognize the command nor create a note' do
+ write_note("/wip")
+
+ expect(page).not_to have_content '/wip'
end
+ end
- context 'Issue' do
- before do
- visit project_issue_path(project, issue)
- end
+ describe 'mark issue as duplicate' do
+ let(:issue) { create(:issue, project: project) }
+ let(:original_issue) { create(:issue, project: project) }
- it_behaves_like 'issuable time tracker'
- end
+ context 'when the current user can update issues' do
+ it 'does not create a note, and marks the issue as a duplicate' do
+ write_note("/duplicate ##{original_issue.to_reference}")
- context 'Merge Request' do
- let(:merge_request) { create(:merge_request, source_project: project) }
+ expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
- before do
- visit project_merge_request_path(project, merge_request)
+ expect(issue.reload).to be_closed
end
-
- it_behaves_like 'issuable time tracker'
end
- end
- describe 'toggling the WIP prefix from the title from note' do
- let(:issue) { create(:issue, project: project) }
+ context 'when the current user cannot update the issue' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
+ end
- it 'does not recognize the command nor create a note' do
- write_note("/wip")
+ it 'does not create a note, and does not mark the issue as a duplicate' do
+ write_note("/duplicate ##{original_issue.to_reference}")
- expect(page).not_to have_content '/wip'
+ expect(page).to have_content "/duplicate ##{original_issue.to_reference}"
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
+
+ expect(issue.reload).to be_open
+ end
end
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 534be3ab5a7..1aca3e3a9fd 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -100,7 +100,7 @@ describe 'GitLab Markdown', feature: true do
end
it 'permits img elements' do
- expect(doc).to have_selector('img[src*="smile.png"]')
+ expect(doc).to have_selector('img[data-src*="smile.png"]')
end
it 'permits br elements' do
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index 434f5a7c0ac..b2187e01bdb 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -24,6 +24,14 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
wait_for_requests
end
+ describe 'time tracking' do
+ before do
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do
it 'adds the WIP: prefix to the title' do
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 42764e808e6..0064c9ef25e 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'OAuth Login', js: true do
+ include DeviseHelpers
+
def enter_code(code)
fill_in 'user_otp_attempt', with: code
click_button 'Verify code'
@@ -8,7 +10,7 @@ feature 'OAuth Login', js: true do
def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
- Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: Rails.application)
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
end
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 161d731f524..fd8e9232b02 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -10,16 +10,16 @@ feature 'list of badges' do
end
scenario 'user wants to see build status badge' do
- page.within('.build-status') do
- expect(page).to have_content 'build status'
+ page.within('.pipeline-status') do
+ expect(page).to have_content 'pipeline status'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
expect(page).to have_content 'AsciiDoc'
expect(page).to have_css('.highlight', count: 3)
- expect(page).to have_xpath("//img[@alt='build status']")
+ expect(page).to have_xpath("//img[@alt='pipeline status']")
page.within('.highlight', match: :first) do
- expect(page).to have_content 'badges/master/build.svg'
+ expect(page).to have_content 'badges/master/pipeline.svg'
end
end
end
@@ -40,14 +40,14 @@ feature 'list of badges' do
end
scenario 'user changes current ref of build status badge', js: true do
- page.within('.build-status') do
+ page.within('.pipeline-status') do
first('.js-project-refs-dropdown').click
page.within '.project-refs-form' do
click_link 'improve/awesome'
end
- expect(page).to have_content 'badges/improve/awesome/build.svg'
+ expect(page).to have_content 'badges/improve/awesome/pipeline.svg'
end
end
end
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
new file mode 100644
index 00000000000..b83ea8f4eaa
--- /dev/null
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+feature 'Pipeline Badge' do
+ set(:project) { create(:project, :repository, :public) }
+ let(:ref) { project.default_branch }
+
+ # this can't be tested in the controller, as it bypasses the rails router
+ # and constructs a route based on the controller being tested
+ # Keep around until 10.0, see gitlab-org/gitlab-ce#35307
+ context 'when the deprecated badge is requested' do
+ it 'displays the badge' do
+ visit build_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ end
+ end
+
+ context 'when the project has a pipeline' do
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: ref, sha: project.commit(ref).sha) }
+ let!(:job) { create(:ci_build, pipeline: pipeline) }
+
+ context 'when the pipeline was successfull' do
+ it 'displays so on the badge' do
+ job.success
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('passed')
+ end
+ end
+
+ context 'when the pipeline failed' do
+ it 'shows displays so on the badge' do
+ job.drop
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('failed')
+ end
+ end
+
+ context 'when the pipeline is running' do
+ it 'shows displays so on the badge' do
+ create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run')
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('running')
+ end
+ end
+
+ context 'when a new pipeline is created' do
+ it 'shows a fresh badge' do
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect(page.response_headers['Cache-Control']).to include 'no-cache'
+ end
+ end
+
+ def expect_badge(status)
+ svg = Nokogiri::XML.parse(page.body)
+ expect(page.response_headers['Content-Type']).to include('image/svg+xml')
+ expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
+ end
+ end
+end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 827e02a58d0..1588f8a828a 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -39,14 +39,25 @@ describe 'Edit Project Settings', feature: true do
end
end
- context "When external issue tracker is enabled" do
- it "does not hide issues tab" do
- project.project_feature.update(issues_access_level: ProjectFeature::DISABLED)
+ context 'When external issue tracker is enabled and issues enabled on project settings' do
+ it 'does not hide issues tab' do
allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
visit project_path(project)
- expect(page).to have_selector(".shortcuts-issues")
+ expect(page).to have_selector('.shortcuts-issues')
+ end
+ end
+
+ context 'When external issue tracker is enabled and issues disabled on project settings' do
+ it 'hides issues tab' do
+ project.issues_enabled = false
+ project.save!
+ allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
+
+ visit project_path(project)
+
+ expect(page).not_to have_selector('.shortcuts-issues')
end
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 033ccf06124..de6dd8fc8a6 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -70,6 +70,17 @@ feature 'Pipeline Schedules', :feature, js: true do
expect(first('.branch-name-cell').text).to eq('')
end
end
+
+ context 'when ref is empty' do
+ before do
+ pipeline_schedule.update_attribute(:ref, '')
+ visit_pipelines_schedules
+ end
+
+ it 'shows a list of the pipeline schedules with empty ref column' do
+ expect(first('.branch-name-cell').text).to eq('')
+ end
+ end
end
describe 'POST /projects/pipeline_schedules/new' do
@@ -128,6 +139,19 @@ feature 'Pipeline Schedules', :feature, js: true do
end
end
end
+
+ context 'when ref is empty' do
+ before do
+ pipeline_schedule.update_attribute(:ref, '')
+ edit_pipeline_schedule
+ end
+
+ it 'shows the pipeline schedule with default ref' do
+ page.within('.js-target-branch-dropdown') do
+ expect(first('.dropdown-toggle-text').text).to eq('master')
+ end
+ end
+ end
end
context 'when user creates a new pipeline schedule with variables' do
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
index 7c29af247d6..b71eec0ecfd 100644
--- a/spec/features/projects/services/jira_service_spec.rb
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -6,17 +6,12 @@ feature 'Setup Jira service', :feature, :js do
let(:service) { project.create_jira_service }
let(:url) { 'http://jira.example.com' }
-
- def stub_project_url
- WebMock.stub_request(:get, 'http://jira.example.com/rest/api/2/project/GitLabProject')
- .with(basic_auth: %w(username password))
- end
+ let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
def fill_form(active = true)
check 'Active' if active
fill_in 'service_url', with: url
- fill_in 'service_project_key', with: 'GitLabProject'
fill_in 'service_username', with: 'username'
fill_in 'service_password', with: 'password'
fill_in 'service_jira_issue_transition_id', with: '25'
@@ -31,11 +26,10 @@ feature 'Setup Jira service', :feature, :js do
describe 'user sets and activates Jira Service' do
context 'when Jira connection test succeeds' do
- before do
- stub_project_url
- end
-
it 'activates the JIRA service' do
+ server_info = { key: 'value' }.to_json
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info)
+
click_link('JIRA')
fill_form
click_button('Test settings and save changes')
@@ -47,10 +41,6 @@ feature 'Setup Jira service', :feature, :js do
end
context 'when Jira connection test fails' do
- before do
- stub_project_url.to_return(status: 401)
- end
-
it 'shows errors when some required fields are not filled in' do
click_link('JIRA')
@@ -64,6 +54,9 @@ feature 'Setup Jira service', :feature, :js do
end
it 'activates the JIRA service' do
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password))
+ .to_raise(JIRA::HTTPError.new(double(message: 'message')))
+
click_link('JIRA')
fill_form
click_button('Test settings and save changes')
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index 584d3ed8f42..3319b0fedf3 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -159,7 +159,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
it 'shows the correct trigger url' do
value = find_field('request_url').value
- expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+ expect(value).to match("api/v4/projects/#{project.id}/services/mattermost_slash_commands/trigger")
end
it 'shows a token placeholder' do
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index 4efe484262a..71f5a8d7a4e 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -40,6 +40,6 @@ feature 'Slack slash commands', feature: true do
it 'shows the correct trigger url' do
value = find_field('url').value
- expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger")
+ expect(value).to match("api/v4/projects/#{project.id}/services/slack_slash_commands/trigger")
end
end
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index 13313bfde24..6ae242af87f 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -36,14 +36,14 @@ feature 'Integration settings', feature: true do
expect(page.status_code).to eq(200)
expect(page).to have_content(hook.url)
expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Push Events')
- expect(page).to have_content('Tag Push Events')
- expect(page).to have_content('Issues Events')
- expect(page).to have_content('Confidential Issues Events')
- expect(page).to have_content('Note Events')
- expect(page).to have_content('Merge Requests Events')
- expect(page).to have_content('Pipeline Events')
- expect(page).to have_content('Wiki Page Events')
+ expect(page).to have_content('Push events')
+ expect(page).to have_content('Tag push events')
+ expect(page).to have_content('Issues events')
+ expect(page).to have_content('Confidential issues events')
+ expect(page).to have_content('Note events')
+ expect(page).to have_content('Merge requests events')
+ expect(page).to have_content('Pipeline events')
+ expect(page).to have_content('Wiki page events')
end
scenario 'create webhook' do
@@ -58,8 +58,8 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Push Events')
- expect(page).to have_content('Tag Push Events')
+ expect(page).to have_content('Push events')
+ expect(page).to have_content('Tag push events')
expect(page).to have_content('Job events')
end
@@ -76,11 +76,12 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
end
- scenario 'test existing webhook' do
+ scenario 'test existing webhook', js: true do
WebMock.stub_request(:post, hook.url)
visit integrations_path
- click_link 'Test'
+ find('.hook-test-button.dropdown').click
+ click_link 'Push events'
expect(current_path).to eq(integrations_path)
end
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 5843f18d89f..8188d4c79f4 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
visit group_path(group)
- expect(page).to have_selector(%Q(img[src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"]))
+ expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(group.reload.avatar.file).to exist
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index e8171dcaeb0..2628508afe8 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
visit user_path(user)
- expect(page).to have_selector(%Q(img[src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"]))
+ expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(user.reload.avatar.file).to exist
diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb
new file mode 100644
index 00000000000..296d6c51d04
--- /dev/null
+++ b/spec/finders/admin/projects_finder_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe Admin::ProjectsFinder do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :public) }
+
+ let!(:private_project) do
+ create(:empty_project, :private, name: 'A', path: 'A')
+ end
+
+ let!(:internal_project) do
+ create(:empty_project, :internal, group: group, name: 'B', path: 'B')
+ end
+
+ let!(:public_project) do
+ create(:empty_project, :public, group: group, name: 'C', path: 'C')
+ end
+
+ let!(:shared_project) do
+ create(:empty_project, :private, name: 'D', path: 'D')
+ end
+
+ let(:params) { {} }
+ let(:current_user) { user }
+ let(:project_ids_relation) { nil }
+ let(:finder) { described_class.new(params: params, current_user: current_user) }
+
+ subject { finder.execute.to_a }
+
+ context 'without a user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'with a user' do
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'filter by namespace_id' do
+ let(:namespace) { create(:namespace) }
+ let!(:project_in_namespace) { create(:empty_project, namespace: namespace) }
+ let(:params) { { namespace_id: namespace.id } }
+
+ it { is_expected.to eq([project_in_namespace]) }
+ end
+
+ context 'filter by visibility_level' do
+ before do
+ private_project.add_master(user)
+ end
+
+ context 'private' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
+
+ it { is_expected.to match_array([shared_project, private_project]) }
+ end
+
+ context 'internal' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } }
+
+ it { is_expected.to eq([internal_project]) }
+ end
+
+ context 'public' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+ end
+
+ context 'filter by push' do
+ let(:pushed_event) { create(:event, :pushed) }
+ let!(:project_with_push) { pushed_event.project }
+ let(:params) { { with_push: true } }
+
+ it { is_expected.to eq([project_with_push]) }
+ end
+
+ context 'filter by abandoned' do
+ before do
+ private_project.update(last_activity_at: Time.zone.now - 6.months - 1.minute)
+ end
+
+ let(:params) { { abandoned: true } }
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ context 'filter by last_repository_check_failed' do
+ before do
+ private_project.update(last_repository_check_failed: true)
+ end
+
+ let(:params) { { last_repository_check_failed: true } }
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ context 'filter by archived' do
+ let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') }
+
+ context 'archived=false' do
+ let(:params) { { archived: false } }
+
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'archived=true' do
+ let(:params) { { archived: true } }
+
+ it { is_expected.to match_array([archived_project, shared_project, public_project, internal_project, private_project]) }
+ end
+ end
+
+ context 'filter by personal' do
+ let!(:personal_project) { create(:empty_project, namespace: user.namespace) }
+ let(:params) { { personal: true } }
+
+ it { is_expected.to eq([personal_project]) }
+ end
+
+ context 'filter by name' do
+ let(:params) { { name: 'C' } }
+
+ it { is_expected.to match_array([shared_project, public_project, private_project]) }
+ end
+
+ context 'sorting' do
+ let(:params) { { sort: 'name_asc' } }
+
+ it { is_expected.to eq([private_project, internal_project, public_project, shared_project]) }
+ end
+ end
+end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f5e139685e8..ac5a58ac189 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -62,13 +62,13 @@ describe ApplicationHelper do
avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
end
it 'gives uploaded icon when present' do
@@ -77,7 +77,8 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
avatar_url = "#{gitlab_host}#{project_avatar_path(project)}"
- expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url))
+ expect(helper.project_icon(project.full_path).to_s)
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
end
end
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 049475a5408..d16fcf21e45 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -27,11 +27,11 @@ describe AvatarsHelper do
it 'displays user avatar' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, 16) }
)
end
@@ -40,22 +40,8 @@ describe AvatarsHelper do
it 'uses provided css_class' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: "avatar has-tooltip s16 #{options[:css_class]}",
- alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body' }
- )
- end
- end
-
- context 'with lazy parameter' do
- let(:options) { { user: user, lazy: true } }
-
- it 'uses data-src instead of src' do
- is_expected.to eq image_tag(
- '',
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: "avatar has-tooltip s16 #{options[:css_class]} lazy",
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body', src: avatar_icon(user, 16) }
@@ -68,11 +54,11 @@ describe AvatarsHelper do
it 'uses provided size' do
is_expected.to eq image_tag(
- avatar_icon(user, options[:size]),
- class: "avatar has-tooltip s#{options[:size]} ",
+ LazyImageTagHelper.placeholder_image,
+ class: "avatar has-tooltip s#{options[:size]} lazy",
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, options[:size]) }
)
end
end
@@ -82,11 +68,11 @@ describe AvatarsHelper do
it 'uses provided url' do
is_expected.to eq image_tag(
- options[:url],
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: options[:url] }
)
end
end
@@ -99,22 +85,22 @@ describe AvatarsHelper do
it 'prefers user parameter' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, 16) }
)
end
end
it 'uses user_name and user_email parameter if user is not present' do
is_expected.to eq image_tag(
- avatar_icon(options[:user_email], 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{options[:user_name]}'s avatar",
title: options[:user_name],
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(options[:user_email], 16) }
)
end
end
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
new file mode 100644
index 00000000000..9f0004bf8cf
--- /dev/null
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe HooksHelper do
+ let(:project) { create(:empty_project) }
+ let(:project_hook) { create(:project_hook, project: project) }
+ let(:system_hook) { create(:system_hook) }
+ let(:trigger) { 'push_events' }
+
+ describe '#link_to_test_hook' do
+ it 'returns project namespaced link' do
+ expect(helper.link_to_test_hook(project_hook, trigger))
+ .to include("href=\"#{test_project_hook_path(project, project_hook, trigger: trigger)}\"")
+ end
+
+ it 'returns admin namespaced link' do
+ expect(helper.link_to_test_hook(system_hook, trigger))
+ .to include("href=\"#{test_admin_hook_path(system_hook, trigger: trigger)}\"")
+ end
+ end
+end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 8f7f17a484f..9524a101e74 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -8,7 +8,7 @@ describe IssuesHelper do
describe "url_for_issue" do
let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
- let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
+ let(:int_expected) { polymorphic_path([@project.namespace, @project, issue]) }
it "returns internal path if used internal tracker" do
@project = project
@@ -22,6 +22,12 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to match(ext_expected)
end
+ it "returns path to internal issue when internal option passed" do
+ @project = ext_project
+
+ expect(url_for_issue(issue.iid, ext_project, internal: true)).to match(int_expected)
+ end
+
it "returns empty string if project nil" do
@project = nil
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index 0132f4b7c93..b3c9bca64cc 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -12,7 +12,9 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
- dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys);
+ dropdownUser = new gl.DropdownUser({
+ tokenKeys: gl.FilteredSearchTokenKeys,
+ });
});
it('should not return the double quote found in value', () => {
@@ -78,7 +80,10 @@ describe('Dropdown User', () => {
loadFixtures(fixtureTemplate);
authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
const dummyInput = document.createElement('div');
- dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput);
+ dropdown = new gl.DropdownUser({
+ dropdown: authorFilterDropdownElement,
+ input: dummyInput,
+ });
});
const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user');
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index c9c0b891237..e3d7986f2cf 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -10,10 +10,12 @@ context 'U2F' do
end
describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ include DeviseHelpers
+
render_views
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
it 'u2f/authenticate.html.raw' do |example|
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
new file mode 100644
index 00000000000..1d81e4e2d1a
--- /dev/null
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -0,0 +1,57 @@
+import LazyLoader from '~/lazy_loader';
+
+let lazyLoader = null;
+
+describe('LazyLoader', function () {
+ preloadFixtures('issues/issue_with_comment.html.raw');
+
+ beforeEach(function () {
+ loadFixtures('issues/issue_with_comment.html.raw');
+ lazyLoader = new LazyLoader({
+ observerNode: 'body',
+ });
+ // Doing everything that happens normally in onload
+ lazyLoader.loadCheck();
+ });
+ describe('behavior', function () {
+ it('should copy value from data-src to src for img 1', function (done) {
+ const img = document.querySelectorAll('img[data-src]')[0];
+ const originalDataSrc = img.getAttribute('data-src');
+ img.scrollIntoView();
+
+ setTimeout(() => {
+ expect(img.getAttribute('src')).toBe(originalDataSrc);
+ expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0);
+ done();
+ }, 100);
+ });
+
+ it('should lazy load dynamically added data-src images', function (done) {
+ const newImg = document.createElement('img');
+ const testPath = '/img/testimg.png';
+ newImg.className = 'lazy';
+ newImg.setAttribute('data-src', testPath);
+ document.body.appendChild(newImg);
+ newImg.scrollIntoView();
+
+ setTimeout(() => {
+ expect(newImg.getAttribute('src')).toBe(testPath);
+ expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0);
+ done();
+ }, 100);
+ });
+
+ it('should not alter normal images', function (done) {
+ const newImg = document.createElement('img');
+ const testPath = '/img/testimg.png';
+ newImg.setAttribute('src', testPath);
+ document.body.appendChild(newImg);
+ newImg.scrollIntoView();
+
+ setTimeout(() => {
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ }, 100);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
index d4b200875df..ab8a3f6c64c 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
@@ -10,6 +10,7 @@ const deploymentMockData = [
url: '/root/acets-review-apps/environments/15',
stop_url: '/root/acets-review-apps/environments/15/stop',
metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
external_url: 'http://diplo.',
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 2c3d0ddff28..6adcbc73ed7 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -3,6 +3,7 @@ import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widge
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
+const monitoringUrl = '/root/acets-review-apps/environments/15/metrics';
const metricsMockData = {
success: true,
@@ -39,6 +40,7 @@ const createComponent = () => {
el: document.createElement('div'),
propsData: {
metricsUrl: url,
+ metricsMonitoringUrl: monitoringUrl,
memoryMetrics: [],
deploymentTime: 0,
hasMetrics: false,
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index b7d82c36ddd..fb320e0148a 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -108,6 +108,11 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
let(:issue) { ExternalIssue.new("#123", project) }
let(:reference) { issue.to_reference }
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
it_behaves_like "external issue tracker"
end
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 082c0d4dd0d..cbb2808c6bb 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -22,7 +22,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag = '[[images/image.jpg]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
- expect(doc.at_css('img')['src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg"
+ expect(doc.at_css('img')['data-src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg"
end
it 'does not creates img tag if image does not exist' do
@@ -40,7 +40,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag = '[[http://example.com/image.jpg]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
- expect(doc.at_css('img')['src']).to eq "http://example.com/image.jpg"
+ expect(doc.at_css('img')['data-src']).to eq "http://example.com/image.jpg"
end
it 'does not creates img tag for invalid URL' do
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
new file mode 100644
index 00000000000..c19de7b784a
--- /dev/null
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ImageLazyLoadFilter, lib: true do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'transforms the image src to a data-src' do
+ doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('img')['data-src']).to eq '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'
+ end
+
+ it 'works with external images' do
+ doc = filter(image('https://i.imgur.com/DfssX9C.jpg'))
+ expect(doc.at_css('img')['data-src']).to eq 'https://i.imgur.com/DfssX9C.jpg'
+ end
+end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 1eb90dc1847..601ffbb5456 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -4,26 +4,87 @@ describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
let(:project) { create(:redmine_project, :public) }
- it 'allows to use shorthand external reference syntax for Redmine' do
- markdown = '#12'
+ context 'when internal issue tracker is enabled' do
+ context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issue if it exists' do
+ issue = create(:issue, project: project)
+ markdown = issue.to_reference(project, full: true)
- result = described_class.call(markdown, project: project)[:output]
- link = result.css('a').first
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(project, issue)
+ )
+ end
+
+ it 'does not link any issue if it does not exist on GitLab' do
+ markdown = '#12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ expect(result.css('a')).to be_empty
+ end
+ end
+
+ it 'allows to use long external reference syntax for Redmine' do
+ markdown = 'API_32-12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'parses cross-project references to regular issues' do
+ other_project = create(:empty_project, :public)
+ issue = create(:issue, project: other_project)
+ markdown = issue.to_reference(project, full: true)
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
+ )
+ end
end
- it 'parses cross-project references to regular issues' do
- other_project = create(:empty_project, :public)
- issue = create(:issue, project: other_project)
- markdown = issue.to_reference(project, full: true)
+ context 'when internal issue tracker is disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'allows to use shorthand external reference syntax for Redmine' do
+ markdown = '#12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'allows to use long external reference syntax for Redmine' do
+ markdown = 'API_32-12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'parses cross-project references to regular issues' do
+ other_project = create(:empty_project, :public)
+ issue = create(:issue, project: other_project)
+ markdown = issue.to_reference(project, full: true)
- result = described_class.call(markdown, project: project)[:output]
- link = result.css('a').first
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
- expect(link['href']).to eq(
- Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
- )
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
+ )
+ end
end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index ea79389e67e..ed571a2ba05 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -32,6 +32,28 @@ module Ci
end
end
+ describe 'retry entry' do
+ context 'when retry count is specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec', retry: 1 })
+ end
+
+ it 'includes retry count in build options attribute' do
+ expect(subject[:options]).to include(retry: 1)
+ end
+ end
+
+ context 'when retry count is not specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec' })
+ end
+
+ it 'does not persist retry count in the database' do
+ expect(subject[:options]).not_to have_key(:retry)
+ end
+ end
+ end
+
describe 'allow failure entry' do
context 'when job is a manual action' do
context 'when allow_failure is defined' do
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index cfa59280139..4ad69aeba43 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::BackgroundMigration do
expect(queue[0]).to receive(:delete).and_return(true)
expect(described_class).to receive(:perform)
- .with('Foo', [10, 20], anything)
+ .with('Foo', [10, 20])
described_class.steal('Foo')
end
@@ -93,9 +93,9 @@ describe Gitlab::BackgroundMigration do
it 'steals from the scheduled sets queue first' do
Sidekiq::Testing.disable! do
expect(described_class).to receive(:perform)
- .with('Object', [1], anything).ordered
+ .with('Object', [1]).ordered
expect(described_class).to receive(:perform)
- .with('Object', [2], anything).ordered
+ .with('Object', [2]).ordered
BackgroundMigrationWorker.perform_async('Object', [2])
BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1])
diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index 9df96ea04eb..d537ce8803c 100644
--- a/spec/lib/gitlab/badge/build/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Build::Metadata do
+describe Gitlab::Badge::Pipeline::Metadata do
let(:badge) { double(project: create(:empty_project), ref: 'feature') }
let(:metadata) { described_class.new(badge) }
@@ -9,13 +9,13 @@ describe Gitlab::Badge::Build::Metadata do
describe '#title' do
it 'returns build status title' do
- expect(metadata.title).to eq 'build status'
+ expect(metadata.title).to eq 'pipeline status'
end
end
describe '#image_url' do
it 'returns valid url' do
- expect(metadata.image_url).to include 'badges/feature/build.svg'
+ expect(metadata.image_url).to include 'badges/feature/pipeline.svg'
end
end
diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index 6abf4ca46a9..dc835375c66 100644
--- a/spec/lib/gitlab/badge/build/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -1,36 +1,35 @@
require 'spec_helper'
-describe Gitlab::Badge::Build::Status do
+describe Gitlab::Badge::Pipeline::Status do
let(:project) { create(:project, :repository) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) }
describe '#entity' do
- it 'always says build' do
- expect(badge.entity).to eq 'build'
+ it 'always says pipeline' do
+ expect(badge.entity).to eq 'pipeline'
end
end
describe '#template' do
it 'returns badge template' do
- expect(badge.template.key_text).to eq 'build'
+ expect(badge.template.key_text).to eq 'pipeline'
end
end
describe '#metadata' do
it 'returns badge metadata' do
- expect(badge.metadata.image_url)
- .to include 'badges/master/build.svg'
+ expect(badge.metadata.image_url).to include 'badges/master/pipeline.svg'
end
end
- context 'build exists' do
- let!(:build) { create_build(project, sha, branch) }
+ context 'pipeline exists' do
+ let!(:pipeline) { create_pipeline(project, sha, branch) }
- context 'build success' do
+ context 'pipeline success' do
before do
- build.success!
+ pipeline.success!
end
describe '#status' do
@@ -40,9 +39,9 @@ describe Gitlab::Badge::Build::Status do
end
end
- context 'build failed' do
+ context 'pipeline failed' do
before do
- build.drop!
+ pipeline.drop!
end
describe '#status' do
@@ -54,10 +53,10 @@ describe Gitlab::Badge::Build::Status do
context 'when outdated pipeline for given ref exists' do
before do
- build.success!
+ pipeline.success!
- old_build = create_build(project, '11eeffdd', branch)
- old_build.drop!
+ old_pipeline = create_pipeline(project, '11eeffdd', branch)
+ old_pipeline.drop!
end
it 'does not take outdated pipeline into account' do
@@ -67,10 +66,10 @@ describe Gitlab::Badge::Build::Status do
context 'when multiple pipelines exist for given sha' do
before do
- build.drop!
+ pipeline.drop!
- new_build = create_build(project, sha, branch)
- new_build.success!
+ new_pipeline = create_pipeline(project, sha, branch)
+ new_pipeline.success!
end
it 'does not take outdated pipeline into account' do
@@ -87,7 +86,7 @@ describe Gitlab::Badge::Build::Status do
end
end
- def create_build(project, sha, branch)
+ def create_pipeline(project, sha, branch)
pipeline = create(:ci_empty_pipeline,
project: project,
sha: sha,
diff --git a/spec/lib/gitlab/badge/build/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index a7e21fb8bb1..20fa4f879c3 100644
--- a/spec/lib/gitlab/badge/build/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -1,28 +1,28 @@
require 'spec_helper'
-describe Gitlab::Badge::Build::Template do
- let(:badge) { double(entity: 'build', status: 'success') }
+describe Gitlab::Badge::Pipeline::Template do
+ let(:badge) { double(entity: 'pipeline', status: 'success') }
let(:template) { described_class.new(badge) }
describe '#key_text' do
- it 'is always says build' do
- expect(template.key_text).to eq 'build'
+ it 'is always says pipeline' do
+ expect(template.key_text).to eq 'pipeline'
end
end
describe '#value_text' do
it 'is status value' do
- expect(template.value_text).to eq 'success'
+ expect(template.value_text).to eq 'passed'
end
end
describe 'widths and text anchors' do
it 'has fixed width and text anchors' do
- expect(template.width).to eq 92
- expect(template.key_width).to eq 38
+ expect(template.width).to eq 116
+ expect(template.key_width).to eq 62
expect(template.value_width).to eq 54
- expect(template.key_text_anchor).to eq 19
- expect(template.value_text_anchor).to eq 65
+ expect(template.key_text_anchor).to eq 31
+ expect(template.value_text_anchor).to eq 89
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index c5cad887b64..6769f64f950 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -80,6 +80,45 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include "job script can't be blank"
end
end
+
+ context 'when retry value is not correct' do
+ context 'when it is not a numeric value' do
+ let(:config) { { retry: true } }
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry is not a number'
+ end
+ end
+
+ context 'when it is lower than zero' do
+ let(:config) { { retry: -1 } }
+
+ it 'returns error about value too low' do
+ expect(entry).not_to be_valid
+ expect(entry.errors)
+ .to include 'job retry must be greater than or equal to 0'
+ end
+ end
+
+ context 'when it is not an integer' do
+ let(:config) { { retry: 1.5 } }
+
+ it 'returns error about wrong value' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry must be an integer'
+ end
+ end
+
+ context 'when the value is too high' do
+ let(:config) { { retry: 10 } }
+
+ it 'returns error about value too high' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry must be less than or equal to 2'
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 114d2490490..5a7a42d84c0 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -48,7 +48,9 @@ describe Gitlab::Ci::Status::Build::Cancelable do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index c8a97016f20..8768302eda1 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -7,7 +7,9 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:factory) { described_class.new(build, user) }
before do
- project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ project.add_developer(user)
end
context 'when build is successful' do
@@ -225,19 +227,19 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when user has ability to play action' do
- before do
- project.add_developer(user)
-
- create(:protected_branch, :developers_can_merge,
- name: build.ref, project: project)
- end
-
it 'fabricates status that has action' do
expect(status).to have_action
end
end
context 'when user does not have ability to play action' do
+ before do
+ allow(build.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: build.project)
+ end
+
it 'fabricates status that has no action' do
expect(status).not_to have_action
end
@@ -262,6 +264,13 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when user is not allowed to execute manual action' do
+ before do
+ allow(build.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: build.project)
+ end
+
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 099d873fc01..21026f2c968 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -48,7 +48,9 @@ describe Gitlab::Ci::Status::Build::Retryable do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 23902f26b1a..e0425103f41 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -20,7 +20,9 @@ describe Gitlab::Ci::Status::Build::Stop do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 13f0338b6aa..ebe5af56160 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -300,5 +300,48 @@ describe Gitlab::Ci::Trace::Stream do
include_examples 'malicious regexp'
end
+
+ context 'multi-line data with rooted regexp' do
+ let(:data) { "\n65%\n" }
+ let(:regex) { '^(\d+)\%$' }
+
+ it { is_expected.to eq('65') }
+ end
+
+ context 'long line' do
+ let(:data) { 'a' * 80000 + '100%' + 'a' * 80000 }
+ let(:regex) { '\d+\%' }
+
+ it { is_expected.to eq('100') }
+ end
+
+ context 'many lines' do
+ let(:data) { "foo\n" * 80000 + "100%\n" + "foo\n" * 80000 }
+ let(:regex) { '\d+\%' }
+
+ it { is_expected.to eq('100') }
+ end
+
+ context 'empty regex' do
+ let(:data) { 'foo' }
+ let(:regex) { '' }
+
+ it 'skips processing' do
+ expect(stream).not_to receive(:read)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'nil regex' do
+ let(:data) { 'foo' }
+ let(:regex) { nil }
+
+ it 'skips processing' do
+ expect(stream).not_to receive(:read)
+
+ is_expected.to be_nil
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
new file mode 100644
index 00000000000..a776d888c47
--- /dev/null
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::DataBuilder::WikiPage do
+ let(:project) { create(:project, :repository) }
+ let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
+ let(:user) { create(:user) }
+
+ describe '.build' do
+ let(:data) { described_class.build(wiki_page, user, 'create') }
+
+ it { expect(data).to be_a(Hash) }
+ it { expect(data[:object_kind]).to eq('wiki_page') }
+ it { expect(data[:user]).to eq(user.hook_attrs) }
+ it { expect(data[:project]).to eq(project.hook_attrs) }
+ it { expect(data[:wiki]).to eq(project.wiki.hook_attrs) }
+
+ it { expect(data[:object_attributes]).to include(wiki_page.hook_attrs) }
+ it { expect(data[:object_attributes]).to include(url: Gitlab::UrlBuilder.build(wiki_page)) }
+ it { expect(data[:object_attributes]).to include(action: 'create') }
+ end
+end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 60de91324f0..730fdb112d9 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
committer: committer
)
end
- let(:commit) { described_class.new(gitaly_commit) }
+ let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) }
it { expect(commit.short_id).to eq(id[0..10]) }
it { expect(commit.id).to eq(id) }
@@ -290,69 +290,85 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '.find_all' do
- it 'should return a return a collection of commits' do
- commits = described_class.find_all(repository)
+ shared_examples 'finding all commits' do
+ it 'should return a return a collection of commits' do
+ commits = described_class.find_all(repository)
- expect(commits).not_to be_empty
- expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
- end
-
- context 'while applying a sort order based on the `order` option' do
- it "allows ordering topologically (no parents shown before their children)" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
-
- described_class.find_all(repository, order: :topo)
+ expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
end
- it "allows ordering by date" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
-
- described_class.find_all(repository, order: :date)
+ context 'max_count' do
+ subject do
+ commits = Gitlab::Git::Commit.find_all(
+ repository,
+ max_count: 50
+ )
+
+ commits.map(&:id)
+ end
+
+ it 'has 33 elements' do
+ expect(subject.size).to eq(33)
+ end
+
+ it 'includes the expected commits' do
+ expect(subject).to include(
+ SeedRepo::Commit::ID,
+ SeedRepo::Commit::PARENT_ID,
+ SeedRepo::FirstCommit::ID
+ )
+ end
end
- it "applies no sorting by default" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
-
- described_class.find_all(repository)
+ context 'ref + max_count + skip' do
+ subject do
+ commits = Gitlab::Git::Commit.find_all(
+ repository,
+ ref: 'master',
+ max_count: 50,
+ skip: 1
+ )
+
+ commits.map(&:id)
+ end
+
+ it 'has 24 elements' do
+ expect(subject.size).to eq(24)
+ end
+
+ it 'includes the expected commits' do
+ expect(subject).to include(SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID)
+ expect(subject).not_to include(SeedRepo::LastCommit::ID)
+ end
end
end
- context 'max_count' do
- subject do
- commits = Gitlab::Git::Commit.find_all(
- repository,
- max_count: 50
- )
+ context 'when Gitaly find_all_commits feature is enabled' do
+ it_behaves_like 'finding all commits'
+ end
- commits.map { |c| c.id }
- end
+ context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding all commits'
- it 'has 31 elements' do
- expect(subject.size).to eq(33)
- end
- it { is_expected.to include(SeedRepo::Commit::ID) }
- it { is_expected.to include(SeedRepo::Commit::PARENT_ID) }
- it { is_expected.to include(SeedRepo::FirstCommit::ID) }
- end
+ context 'while applying a sort order based on the `order` option' do
+ it "allows ordering topologically (no parents shown before their children)" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
- context 'ref + max_count + skip' do
- subject do
- commits = Gitlab::Git::Commit.find_all(
- repository,
- ref: 'master',
- max_count: 50,
- skip: 1
- )
+ described_class.find_all(repository, order: :topo)
+ end
- commits.map { |c| c.id }
- end
+ it "allows ordering by date" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
+
+ described_class.find_all(repository, order: :date)
+ end
+
+ it "applies no sorting by default" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
- it 'has 23 elements' do
- expect(subject.size).to eq(24)
+ described_class.find_all(repository)
+ end
end
- it { is_expected.to include(SeedRepo::Commit::ID) }
- it { is_expected.to include(SeedRepo::FirstCommit::ID) }
- it { is_expected.not_to include(SeedRepo::LastCommit::ID) }
end
end
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index d20298fa139..0cfb210e390 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -484,6 +484,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
def each
+ return enum_for(:each) unless block_given?
+
loop do
break if @count.zero?
# It is critical to decrement before yielding. We may never reach the lines after 'yield'.
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 4b76a43e6b5..98ddd3c3664 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -1,8 +1,9 @@
require "spec_helper"
describe Gitlab::Git::Tree, seed_helper: true do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+
context :repo do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
it { expect(tree).to be_kind_of Array }
@@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(submodule.name).to eq('gitlab-shell') }
end
end
+
+ describe '#where' do
+ context 'with gitaly disabled' do
+ before do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
+ end
+
+ it 'calls #tree_entries_from_rugged' do
+ expect(described_class).to receive(:tree_entries_from_rugged)
+
+ described_class.where(repository, SeedRepo::Commit::ID, '/')
+ end
+ end
+
+ it 'gets the tree entries from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries)
+
+ described_class.where(repository, SeedRepo::Commit::ID, '/')
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index 93affb12f2b..0868c793a33 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -2,9 +2,13 @@ require 'spec_helper'
describe Gitlab::GitalyClient::CommitService do
let(:project) { create(:project, :repository) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.path_with_namespace + '.git' }
let(:repository) { project.repository }
let(:repository_message) { repository.gitaly_repository }
- let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
+ let(:revision) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
+ let(:commit) { project.commit(revision) }
+ let(:client) { described_class.new(repository) }
describe '#diff_from_parent' do
context 'when a commit has a parent' do
@@ -12,12 +16,15 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
- right_commit_id: commit.id
+ right_commit_id: commit.id,
+ collapse_diffs: true,
+ enforce_limits: true,
+ **Gitlab::Git::DiffCollection.collection_limits.to_h
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
- described_class.new(repository).diff_from_parent(commit)
+ client.diff_from_parent(commit)
end
end
@@ -27,27 +34,30 @@ describe Gitlab::GitalyClient::CommitService do
request = Gitaly::CommitDiffRequest.new(
repository: repository_message,
left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
- right_commit_id: initial_commit.id
+ right_commit_id: initial_commit.id,
+ collapse_diffs: true,
+ enforce_limits: true,
+ **Gitlab::Git::DiffCollection.collection_limits.to_h
)
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
- described_class.new(repository).diff_from_parent(initial_commit)
+ client.diff_from_parent(initial_commit)
end
end
it 'returns a Gitlab::Git::DiffCollection' do
- ret = described_class.new(repository).diff_from_parent(commit)
+ ret = client.diff_from_parent(commit)
expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
end
it 'passes options to Gitlab::Git::DiffCollection' do
- options = { max_files: 31, max_lines: 13 }
+ options = { max_files: 31, max_lines: 13, from_gitaly: true }
expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
- described_class.new(repository).diff_from_parent(commit, options)
+ client.diff_from_parent(commit, options)
end
end
@@ -62,7 +72,7 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
- described_class.new(repository).commit_deltas(commit)
+ client.commit_deltas(commit)
end
end
@@ -77,7 +87,7 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
- described_class.new(repository).commit_deltas(initial_commit)
+ client.commit_deltas(initial_commit)
end
end
end
@@ -85,6 +95,7 @@ describe Gitlab::GitalyClient::CommitService do
describe '#between' do
let(:from) { 'master' }
let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+
it 'sends an RPC request' do
request = Gitaly::CommitsBetweenRequest.new(
repository: repository_message, from: from, to: to
@@ -96,4 +107,17 @@ describe Gitlab::GitalyClient::CommitService do
described_class.new(repository).between(from, to)
end
end
+
+ describe '#tree_entries' do
+ let(:path) { '/' }
+
+ it 'sends a get_tree_entries message' do
+ expect_any_instance_of(Gitaly::CommitService::Stub)
+ .to receive(:get_tree_entries)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([])
+
+ client.tree_entries(repository, revision, path)
+ end
+ end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
new file mode 100644
index 00000000000..5a9f3fc130c
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::RepositoryService do
+ set(:project) { create(:empty_project) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.path_with_namespace + '.git' }
+ let(:client) { described_class.new(project.repository) }
+
+ describe '#exists?' do
+ it 'sends an exists message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:exists)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_call_original
+
+ client.exists?
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index cab2e9908ff..3a56797d68b 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::LDAP::Config, lib: true do
let(:config) { Gitlab::LDAP::Config.new('ldapmain') }
- describe '#initalize' do
+ describe '#initialize' do
it 'requires a provider' do
expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
end
@@ -23,9 +23,9 @@ describe Gitlab::LDAP::Config, lib: true do
it 'constructs basic options' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 386,
- 'method' => 'plain'
+ 'host' => 'ldap.example.com',
+ 'port' => 386,
+ 'encryption' => 'plain'
}
)
@@ -39,24 +39,140 @@ describe Gitlab::LDAP::Config, lib: true do
it 'includes authentication options when auth is configured' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 686,
- 'method' => 'ssl',
- 'bind_dn' => 'uid=admin,dc=example,dc=com',
- 'password' => 'super_secret'
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'bind_dn' => 'uid=admin,dc=example,dc=com',
+ 'password' => 'super_secret'
}
)
- expect(config.adapter_options).to eq(
- host: 'ldap.example.com',
- port: 686,
- encryption: :simple_tls,
+ expect(config.adapter_options).to include({
auth: {
method: :simple,
username: 'uid=admin,dc=example,dc=com',
password: 'super_secret'
}
+ })
+ end
+
+ it 'sets encryption method to simple_tls when configured as simple_tls' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls'
+ }
)
+
+ expect(config.adapter_options[:encryption]).to include({ method: :simple_tls })
+ end
+
+ it 'sets encryption method to start_tls when configured as start_tls' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'start_tls'
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({ method: :start_tls })
+ end
+
+ context 'when verify_certificates is enabled' do
+ it 'sets tls_options to OpenSSL defaults' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({ tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS })
+ end
+ end
+
+ context 'when verify_certificates is disabled' do
+ it 'sets verify_mode to OpenSSL VERIFY_NONE' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => false
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({
+ tls_options: {
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
+ }
+ })
+ end
+ end
+
+ context 'when ca_file is specified' do
+ it 'passes it through in tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ca_file' => '/etc/ca.pem'
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).to include({ ca_file: '/etc/ca.pem' })
+ end
+ end
+
+ context 'when ca_file is a blank string' do
+ it 'does not add the ca_file key to tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ca_file' => ' '
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ca_file)
+ end
+ end
+
+ context 'when ssl_version is specified' do
+ it 'passes it through in tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ssl_version' => 'TLSv1_2'
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).to include({ ssl_version: 'TLSv1_2' })
+ end
+ end
+
+ context 'when ssl_version is a blank string' do
+ it 'does not add the ssl_version key to tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ssl_version' => ' '
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ssl_version)
+ end
end
end
@@ -64,11 +180,11 @@ describe Gitlab::LDAP::Config, lib: true do
it 'constructs basic options' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 386,
- 'base' => 'ou=users,dc=example,dc=com',
- 'method' => 'plain',
- 'uid' => 'uid'
+ 'host' => 'ldap.example.com',
+ 'port' => 386,
+ 'base' => 'ou=users,dc=example,dc=com',
+ 'encryption' => 'plain',
+ 'uid' => 'uid'
}
)
@@ -76,7 +192,7 @@ describe Gitlab::LDAP::Config, lib: true do
host: 'ldap.example.com',
port: 386,
base: 'ou=users,dc=example,dc=com',
- method: 'plain',
+ encryption: 'plain',
filter: '(uid=%{username})'
)
expect(config.omniauth_options.keys).not_to include(:bind_dn, :password)
@@ -98,6 +214,100 @@ describe Gitlab::LDAP::Config, lib: true do
password: 'super_secret'
)
end
+
+ context 'when verify_certificates is enabled' do
+ it 'specifies disable_verify_certificates as false' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true
+ }
+ )
+
+ expect(config.omniauth_options).to include({ disable_verify_certificates: false })
+ end
+ end
+
+ context 'when verify_certificates is disabled' do
+ it 'specifies disable_verify_certificates as true' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => false
+ }
+ )
+
+ expect(config.omniauth_options).to include({ disable_verify_certificates: true })
+ end
+ end
+
+ context 'when ca_file is present' do
+ it 'passes it through' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ca_file' => '/etc/ca.pem'
+ }
+ )
+
+ expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' })
+ end
+ end
+
+ context 'when ca_file is blank' do
+ it 'does not include the ca_file option' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ca_file' => ' '
+ }
+ )
+
+ expect(config.omniauth_options).not_to have_key(:ca_file)
+ end
+ end
+
+ context 'when ssl_version is present' do
+ it 'passes it through' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ssl_version' => 'TLSv1_2'
+ }
+ )
+
+ expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' })
+ end
+ end
+
+ context 'when ssl_version is blank' do
+ it 'does not include the ssl_version option' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ssl_version' => ' '
+ }
+ )
+
+ expect(config.omniauth_options).not_to have_key(:ssl_version)
+ end
+ end
end
describe '#has_auth?' do
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 1eea710c80b..20be743d224 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -36,9 +36,12 @@ describe Gitlab::PathRegex, lib: true do
described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
end
- def failure_message(missing_words, constant_name, migration_helper)
+ def failure_message(constant_name, migration_helper, missing_words: [], additional_words: [])
missing_words = Array(missing_words)
- <<-MSG
+ additional_words = Array(additional_words)
+ message = ""
+ if missing_words.any?
+ message += <<-MISSING
Found new routes that could cause conflicts with existing namespaced routes
for groups or projects.
@@ -51,7 +54,18 @@ describe Gitlab::PathRegex, lib: true do
Make sure to make a note of the renamed records in the release blog post.
- MSG
+ MISSING
+ end
+
+ if additional_words.any?
+ message += <<-ADDITIONAL
+ Why are <#{additional_words.join(', ')}> in `#{constant_name}`?
+ If they are really required, update these specs to reflect that.
+
+ ADDITIONAL
+ end
+
+ message
end
let(:all_routes) do
@@ -68,9 +82,23 @@ describe Gitlab::PathRegex, lib: true do
let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
let(:top_level_words) do
- routes_not_starting_in_wildcard.map do |route|
+ words = routes_not_starting_in_wildcard.map do |route|
route.split('/')[1]
end.compact.uniq
+
+ words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s)
+ end
+
+ let(:ee_top_level_words) do
+ ['unsubscribes']
+ end
+
+ let(:files_in_public) do
+ git = Gitlab.config.git.bin_path
+ `cd #{Rails.root} && #{git} ls-files public`
+ .split("\n")
+ .map { |entry| entry.gsub('public/', '') }
+ .uniq
end
# All routes that start with a namespaced path, that have 1 or more
@@ -115,18 +143,29 @@ describe Gitlab::PathRegex, lib: true do
let(:paths_after_group_id) do
group_routes.map do |route|
route.gsub(STARTING_WITH_GROUP, '').split('/').first
- end.uniq
+ end.uniq + ee_paths_after_group_id
+ end
+
+ let(:ee_paths_after_group_id) do
+ %w(analytics
+ ldap
+ ldap_group_links
+ notification_setting
+ audit_events
+ pipeline_quota hooks)
end
describe 'TOP_LEVEL_ROUTES' do
it 'includes all the top level namespaces' do
failure_block = lambda do
missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
- failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+ additional_words = described_class::TOP_LEVEL_ROUTES - top_level_words
+ failure_message('TOP_LEVEL_ROUTES', 'rename_root_paths',
+ missing_words: missing_words, additional_words: additional_words)
end
expect(described_class::TOP_LEVEL_ROUTES)
- .to include(*top_level_words), failure_block
+ .to contain_exactly(*top_level_words), failure_block
end
end
@@ -134,11 +173,13 @@ describe Gitlab::PathRegex, lib: true do
it "don't contain a second wildcard" do
failure_block = lambda do
missing_words = paths_after_group_id - described_class::GROUP_ROUTES
- failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
+ additional_words = described_class::GROUP_ROUTES - paths_after_group_id
+ failure_message('GROUP_ROUTES', 'rename_child_paths',
+ missing_words: missing_words, additional_words: additional_words)
end
expect(described_class::GROUP_ROUTES)
- .to include(*paths_after_group_id), failure_block
+ .to contain_exactly(*paths_after_group_id), failure_block
end
end
@@ -147,7 +188,7 @@ describe Gitlab::PathRegex, lib: true do
aggregate_failures do
all_wildcard_paths.each do |path|
expect(wildcards_include?(path))
- .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
+ .to be(true), failure_message('PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths', missing_words: path)
end
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 84cfd934fa0..917692e9c6c 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -183,11 +183,34 @@ describe Gitlab::ReferenceExtractor, lib: true do
context 'with an external issue tracker' do
let(:project) { create(:jira_project) }
+ let(:issue) { create(:issue, project: project) }
+
+ context 'when GitLab issues are enabled' do
+ it 'returns both JIRA and internal issues' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project),
+ issue]
+ end
+
+ it 'returns only JIRA issues if the internal one does not exists' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #999")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project)]
+ end
+ end
- it 'returns JIRA issues for a JIRA-integrated project' do
- subject.analyze('JIRA-123 and FOOBAR-4567')
- expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
- ExternalIssue.new('FOOBAR-4567', project)]
+ context 'when GitLab issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'returns only JIRA issues' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project)]
+ end
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 51e2c3c38c6..251f82849bf 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -38,4 +38,15 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') }
end
+
+ describe '.container_repository_name_regex' do
+ subject { described_class.container_repository_name_regex }
+
+ it { is_expected.to match('image') }
+ it { is_expected.to match('my/image') }
+ it { is_expected.to match('my/awesome/image-1') }
+ it { is_expected.to match('my/awesome/image.test') }
+ it { is_expected.not_to match('.my/image') }
+ it { is_expected.not_to match('my/image.') }
+ end
end
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 66045917cb3..bed58d407ef 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -46,10 +46,28 @@ describe Gitlab::UntrustedRegexp do
context 'malicious regexp' do
let(:text) { malicious_text }
let(:regexp) { malicious_regexp }
-
+
include_examples 'malicious regexp'
end
+ context 'empty regexp' do
+ let(:regexp) { '' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches' do
+ is_expected.to eq([nil, nil, nil, nil])
+ end
+ end
+
+ context 'empty capture group regexp' do
+ let(:regexp) { '()' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches in an array' do
+ is_expected.to eq([[nil], [nil], [nil], [nil]])
+ end
+ end
+
context 'no capture group' do
let(:regexp) { '.+' }
let(:text) { 'foo' }
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index daf097f8d51..68429d792f2 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1,11 +1,19 @@
require 'spec_helper'
describe Gitlab::UsageData do
- let!(:project) { create(:empty_project) }
- let!(:project2) { create(:empty_project) }
- let!(:board) { create(:board, project: project) }
+ let(:projects) { create_list(:project, 3) }
+ let!(:board) { create(:board, project: projects[0]) }
describe '#data' do
+ before do
+ create(:jira_service, project: projects[0])
+ create(:jira_service, project: projects[1])
+ create(:prometheus_service, project: projects[1])
+ create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
+ create(:service, project: projects[1], type: 'SlackService', active: true)
+ create(:service, project: projects[2], type: 'SlackService', active: true)
+ end
+
subject { described_class.data }
it "gathers usage data" do
@@ -25,7 +33,7 @@ describe Gitlab::UsageData do
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
- expect(count_data[:projects]).to eq(2)
+ expect(count_data[:projects]).to eq(3)
expect(count_data.keys).to match_array(%i(
boards
@@ -49,6 +57,9 @@ describe Gitlab::UsageData do
notes
projects
projects_imported_from_github
+ projects_jira_active
+ projects_slack_notifications_active
+ projects_slack_slash_active
projects_prometheus_active
pages_domains
protected_branches
@@ -59,6 +70,16 @@ describe Gitlab::UsageData do
web_hooks
))
end
+
+ it 'gathers projects data correctly' do
+ count_data = subject[:counts]
+
+ expect(count_data[:projects]).to eq(3)
+ expect(count_data[:projects_prometheus_active]).to eq(1)
+ expect(count_data[:projects_jira_active]).to eq(2)
+ expect(count_data[:projects_slack_notifications_active]).to eq(2)
+ expect(count_data[:projects_slack_slash_active]).to eq(1)
+ end
end
describe '#license_usage_data' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 124f66a6e0e..6ca1edb01b9 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -237,7 +237,8 @@ describe Gitlab::Workhorse, lib: true do
context 'when action is not enabled by feature flag' do
it 'does not include Gitaly params in the returned value' do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag).and_return(false)
+ status_opt_out = Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag, status: status_opt_out).and_return(false)
expect(subject).not_to include(gitaly_params)
end
@@ -325,7 +326,7 @@ describe Gitlab::Workhorse, lib: true do
subject { described_class.send_git_blob(repository, blob) }
- context 'when Gitaly project_raw_show feature is enabled' do
+ context 'when Gitaly workhorse_raw_show feature is enabled' do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
@@ -345,7 +346,7 @@ describe Gitlab::Workhorse, lib: true do
end
end
- context 'when Gitaly project_raw_show feature is disabled', skip_gitaly_mock: true do
+ context 'when Gitaly workhorse_raw_show feature is disabled', skip_gitaly_mock: true do
it 'sets the header correctly' do
key, command, params = decode_workhorse_header(subject)
diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb
new file mode 100644
index 00000000000..9a581df28a2
--- /dev/null
+++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb')
+
+describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do
+ let(:migration_class) { 'MigrateBuildStageIdReference' }
+ let(:migration) { spy('migration') }
+
+ before do
+ allow(Gitlab::BackgroundMigration.const_get(migration_class))
+ .to receive(:new).and_return(migration)
+ end
+
+ context 'when there are pending background migrations' do
+ it 'processes pending jobs synchronously' do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1])
+ BackgroundMigrationWorker.perform_async(migration_class, [1, 1])
+
+ migrate!
+
+ expect(migration).to have_received(:perform).with(1, 1).twice
+ end
+ end
+ end
+ context 'when there are no background migrations pending' do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ migrate!
+
+ expect(migration).not_to have_received(:perform)
+ end
+ end
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index dc7a0d80752..58f1a620ab4 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -98,7 +98,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project).to receive(:owner).twice.and_return(user1)
+ expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -109,7 +109,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project.team).to receive(:members).twice.and_return([user1])
+ expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -140,7 +140,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project).to receive(:owner).twice.and_return(user1)
+ expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -151,7 +151,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project.team).to receive(:members).twice.and_return([user1])
+ expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 154b6759f46..0b521d720f3 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -802,6 +802,47 @@ describe Ci::Build, :models do
end
end
+ describe 'build auto retry feature' do
+ describe '#retries_count' do
+ subject { create(:ci_build, name: 'test', pipeline: pipeline) }
+
+ context 'when build has been retried several times' do
+ before do
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ end
+
+ it 'reports a correct retry count value' do
+ expect(subject.retries_count).to eq 2
+ end
+ end
+
+ context 'when build has not been retried' do
+ it 'returns zero' do
+ expect(subject.retries_count).to eq 0
+ end
+ end
+ end
+
+ describe '#retries_max' do
+ context 'when max retries value is defined' do
+ subject { create(:ci_build, options: { retry: 1 }) }
+
+ it 'returns a number of configured max retries' do
+ expect(subject.retries_max).to eq 1
+ end
+ end
+
+ context 'when max retries value is not defined' do
+ subject { create(:ci_build) }
+
+ it 'returns zero' do
+ expect(subject.retries_max).to eq 0
+ end
+ end
+ end
+ end
+
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
@@ -1583,7 +1624,7 @@ describe Ci::Build, :models do
end
end
- describe 'State transition: any => [:pending]' do
+ describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
it 'queues BuildQueueWorker' do
@@ -1592,4 +1633,35 @@ describe Ci::Build, :models do
build.enqueue
end
end
+
+ describe 'state transition when build fails' do
+ context 'when build is configured to be retried' do
+ subject { create(:ci_build, :running, options: { retry: 3 }) }
+
+ it 'retries builds and assigns a same user to it' do
+ expect(described_class).to receive(:retry)
+ .with(subject, subject.user)
+
+ subject.drop!
+ end
+ end
+
+ context 'when build is not configured to be retried' do
+ subject { create(:ci_build, :running) }
+
+ it 'does not retry build' do
+ expect(described_class).not_to receive(:retry)
+
+ subject.drop!
+ end
+
+ it 'does not count retries when not necessary' do
+ expect(described_class).not_to receive(:retry)
+ expect_any_instance_of(described_class)
+ .not_to receive(:retries_count)
+
+ subject.drop!
+ end
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index ba0696fa210..bbd45f10b1b 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -734,6 +734,8 @@ describe Ci::Pipeline, models: true do
context 'on failure and build retry' do
before do
+ stub_not_protect_default_branch
+
build.drop
project.add_developer(user)
@@ -999,6 +1001,8 @@ describe Ci::Pipeline, models: true do
let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index e2a29e0ae70..1ad811736af 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -174,25 +174,25 @@ describe Commit, 'Mentionable' do
it "is false when message doesn't reference anything" do
allow(commit.raw).to receive(:message).and_return "WIP: Do something"
- expect(commit.matches_cross_reference_regex?).to be false
+ expect(commit.matches_cross_reference_regex?).to be_falsey
end
it 'is true if issue #number mentioned in title' do
allow(commit.raw).to receive(:message).and_return "#1"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if references an MR' do
allow(commit.raw).to receive(:message).and_return "See merge request !12"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if references a commit' do
allow(commit.raw).to receive(:message).and_return "a1b2c3d4"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if issue referenced by url' do
@@ -200,7 +200,7 @@ describe Commit, 'Mentionable' do
allow(commit.raw).to receive(:message).and_return Gitlab::UrlBuilder.build(issue)
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
context 'with external issue tracker' do
@@ -209,7 +209,13 @@ describe Commit, 'Mentionable' do
it 'is true if external issues referenced' do
allow(commit.raw).to receive(:message).and_return 'JIRA-123'
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
+ end
+
+ it 'is true if internal issues referenced' do
+ allow(commit.raw).to receive(:message).and_return '#123'
+
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 770176451fe..d8e868265ed 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -236,6 +236,7 @@ describe Group, models: true do
describe '#has_owner?' do
before do
@members = setup_group_members(group)
+ create(:group_member, :invited, :owner, group: group)
end
it { expect(group.has_owner?(@members[:owner])).to be_truthy }
@@ -244,11 +245,13 @@ describe Group, models: true do
it { expect(group.has_owner?(@members[:reporter])).to be_falsey }
it { expect(group.has_owner?(@members[:guest])).to be_falsey }
it { expect(group.has_owner?(@members[:requester])).to be_falsey }
+ it { expect(group.has_owner?(nil)).to be_falsey }
end
describe '#has_master?' do
before do
@members = setup_group_members(group)
+ create(:group_member, :invited, :master, group: group)
end
it { expect(group.has_master?(@members[:owner])).to be_falsey }
@@ -257,6 +260,7 @@ describe Group, models: true do
it { expect(group.has_master?(@members[:reporter])).to be_falsey }
it { expect(group.has_master?(@members[:guest])).to be_falsey }
it { expect(group.has_master?(@members[:requester])).to be_falsey }
+ it { expect(group.has_master?(nil)).to be_falsey }
end
describe '#lfs_enabled?' do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 474ae62ccec..0af270014b5 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,10 +1,14 @@
require 'spec_helper'
describe ProjectHook, models: true do
- describe "Associations" do
+ describe 'associations' do
it { is_expected.to belong_to :project }
end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ end
+
describe '.push_hooks' do
it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true)
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 57454d2a773..8e871a41a8c 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -5,6 +5,10 @@ describe ServiceHook, models: true do
it { is_expected.to belong_to :service }
end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:service) }
+ end
+
describe 'execute' do
let(:hook) { build(:service_hook) }
let(:data) { { key: 'value' } }
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 0d2b622132e..559778257fa 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -7,8 +7,7 @@ describe SystemHook, models: true do
it 'sets defined default parameters' do
attrs = {
push_events: false,
- repository_update_events: true,
- enable_ssl_verification: true
+ repository_update_events: true
}
expect(system_hook).to have_attributes(attrs)
end
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index db2c2619968..a6cc01bea5f 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -13,12 +13,6 @@ describe List do
it { is_expected.to validate_presence_of(:position) }
it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) }
- it 'validates uniqueness of label scoped to board_id' do
- create(:list)
-
- expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:board_id)
- end
-
context 'when list_type is set to closed' do
subject { described_class.new(list_type: :closed) }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 1eadc28869f..6f6a8ac91b8 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -155,13 +155,53 @@ describe MergeRequest, models: true do
expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
end
- it 'does not cache issues from external trackers' do
- subject.project.update_attribute(:has_external_issue_tracker, true)
- issue = ExternalIssue.new('JIRA-123', subject.project)
- commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
- allow(subject).to receive(:commits).and_return([commit])
+ context 'when both internal and external issue trackers are enabled' do
+ before do
+ subject.project.has_external_issue_tracker = true
+ subject.project.save!
+ end
+
+ it 'does not cache issues from external trackers' do
+ issue = ExternalIssue.new('JIRA-123', subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ end
+
+ it 'caches an internal issue' do
+ issue = create(:issue, project: subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }
+ .to change(subject.merge_requests_closing_issues, :count).by(1)
+ end
+ end
+
+ context 'when only external issue tracker enabled' do
+ before do
+ subject.project.has_external_issue_tracker = true
+ subject.project.issues_enabled = false
+ subject.project.save!
+ end
+
+ it 'does not cache issues from external trackers' do
+ issue = ExternalIssue.new('JIRA-123', subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ end
+
+ it 'does not cache an internal issue' do
+ issue = create(:issue, project: subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }
+ .not_to change(subject.merge_requests_closing_issues, :count)
+ end
end
end
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index cc235ad467e..76a7b07949f 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -11,7 +11,14 @@ RSpec.describe NotificationSetting, type: :model do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:level) }
- it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) }
+
+ describe 'user_id' do
+ before do
+ subject.user = create(:user)
+ end
+
+ it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_type, :source_id]).with_message(/already exists in source/) }
+ end
context "events" do
let(:user) { create(:user) }
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index f9d060d4e0e..d4a777a9bd9 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -11,7 +11,7 @@ describe PagesDomain, models: true do
context 'is unique' do
let(:domain) { 'my.domain.com' }
- it { is_expected.to validate_uniqueness_of(:domain) }
+ it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
end
{
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 105afed1337..d7d09808a98 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -15,7 +15,6 @@ describe JiraService, models: true do
end
it { is_expected.to validate_presence_of(:url) }
- it { is_expected.to validate_presence_of(:project_key) }
it_behaves_like 'issue tracker service URL attribute', :url
end
@@ -34,7 +33,6 @@ describe JiraService, models: true do
active: true,
username: 'username',
password: 'test',
- project_key: 'TEST',
jira_issue_transition_id: 24,
url: 'http://jira.test.com'
)
@@ -88,7 +86,6 @@ describe JiraService, models: true do
url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
- project_key: 'GitLabProject',
jira_issue_transition_id: "custom-id"
)
@@ -196,15 +193,14 @@ describe JiraService, models: true do
project: create(:project),
url: 'http://jira.example.com',
username: 'jira_username',
- password: 'jira_password',
- project_key: 'GitLabProject'
+ password: 'jira_password'
)
end
def test_settings(api_url)
- project_url = "http://#{api_url}/rest/api/2/project/GitLabProject"
+ test_url = "http://#{api_url}/rest/api/2/serverInfo"
- WebMock.stub_request(:get, project_url).with(basic_auth: %w(jira_username jira_password))
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(jira_username jira_password)).to_return(body: { url: 'http://url' }.to_json )
jira_service.test_settings
end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index bd50a2d1470..fb95c4cda35 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -112,7 +112,7 @@ describe MicrosoftTeamsService, models: true do
let(:wiki_page_sample_data) do
service = WikiPages::CreateService.new(project, user, opts)
wiki_page = service.execute
- service.hook_data(wiki_page, 'create')
+ Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create')
end
it "calls Microsoft Teams API" do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 90769b580cd..8d916b79b13 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -309,10 +309,14 @@ describe Project, models: true do
end
describe 'delegation' do
- it { is_expected.to delegate_method(:add_guest).to(:team) }
- it { is_expected.to delegate_method(:add_reporter).to(:team) }
- it { is_expected.to delegate_method(:add_developer).to(:team) }
- it { is_expected.to delegate_method(:add_master).to(:team) }
+ [:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method|
+ it { is_expected.to delegate_method(method).to(:team) }
+ end
+
+ it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
+ it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
+ it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) }
+ it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
describe '#to_reference' do
@@ -529,15 +533,48 @@ describe Project, models: true do
end
context 'with external issues tracker' do
+ let!(:internal_issue) { create(:issue, project: project) }
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ allow(project).to receive(:external_issue_tracker).and_return(true)
end
- it 'returns an ExternalIssue' do
- issue = project.get_issue('FOO-1234', user)
- expect(issue).to be_kind_of(ExternalIssue)
- expect(issue.iid).to eq 'FOO-1234'
- expect(issue.project).to eq project
+ context 'when internal issues are enabled' do
+ it 'returns interlan issue' do
+ issue = project.get_issue(internal_issue.iid, user)
+
+ expect(issue).to be_kind_of(Issue)
+ expect(issue.iid).to eq(internal_issue.iid)
+ expect(issue.project).to eq(project)
+ end
+
+ it 'returns an ExternalIssue when internal issue does not exists' do
+ issue = project.get_issue('FOO-1234', user)
+
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq('FOO-1234')
+ expect(issue.project).to eq(project)
+ end
+ end
+
+ context 'when internal issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'returns always an External issues' do
+ issue = project.get_issue(internal_issue.iid, user)
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq(internal_issue.iid.to_s)
+ expect(issue.project).to eq(project)
+ end
+
+ it 'returns an ExternalIssue when internal issue does not exists' do
+ issue = project.get_issue('FOO-1234', user)
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq('FOO-1234')
+ expect(issue.project).to eq(project)
+ end
end
end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 1f314791479..79ab50c1234 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -21,7 +21,7 @@ describe ProjectWiki, models: true do
describe '#web_url' do
it 'returns the full web URL to the wiki' do
- expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/wikis/home")
+ expect(subject.web_url).to match("https?://[^\/]+/#{project.path_with_namespace}/wikis/home")
end
end
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 71827421dd7..a97af28cb8e 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -11,7 +11,7 @@ describe RedirectRoute, models: true do
describe 'validations' do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
end
describe '.matching_path_and_descendants' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 7635b0868e7..fcda4248446 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -956,21 +956,25 @@ describe Repository, models: true do
end
end
- describe '#exists?' do
+ shared_examples 'repo exists check' do
it 'returns true when a repository exists' do
expect(repository.exists?).to eq(true)
end
- it 'returns false when a repository does not exist' do
- allow(repository).to receive(:refs_directory_exists?).and_return(false)
+ it 'returns false if no full path can be constructed' do
+ allow(repository).to receive(:path_with_namespace).and_return(nil)
expect(repository.exists?).to eq(false)
end
+ end
- it 'returns false when there is no namespace' do
- allow(repository).to receive(:path_with_namespace).and_return(nil)
+ describe '#exists?' do
+ context 'when repository_exists is disabled' do
+ it_behaves_like 'repo exists check'
+ end
- expect(repository.exists?).to eq(false)
+ context 'when repository_exists is enabled', skip_gitaly_mock: true do
+ it_behaves_like 'repo exists check'
end
end
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 1754253e0f2..12f7611fb28 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -15,7 +15,7 @@ describe Route, models: true do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
end
describe 'callbacks' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a1d6d7e6e0b..20bdb7e37da 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -114,7 +114,9 @@ describe User, models: true do
end
it 'validates uniqueness' do
- expect(subject).to validate_uniqueness_of(:username).case_insensitive
+ user = build(:user)
+
+ expect(user).to validate_uniqueness_of(:username).case_insensitive
end
end
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index 9f3212b1a63..e3ea3c960a4 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -96,87 +96,57 @@ describe Ci::BuildPolicy, :models do
end
end
- describe 'rules for manual actions' do
+ describe 'rules for protected ref' do
let(:project) { create(:project) }
+ let(:build) { create(:ci_build, ref: 'some-ref', pipeline: pipeline) }
before do
project.add_developer(user)
end
- shared_examples 'protected ref' do
- context 'when build is a manual action' do
- let(:build) do
- create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
- end
-
- it 'does not include ability to update build' do
- expect(policy).to be_disallowed :update_build
- end
+ context 'when no one can push or merge to the branch' do
+ before do
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: project)
end
- context 'when build is not a manual action' do
- let(:build) do
- create(:ci_build, ref: 'some-ref', pipeline: pipeline)
- end
-
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
+ it 'does not include ability to update build' do
+ expect(policy).to be_disallowed :update_build
end
end
- context 'when build is against a protected branch' do
+ context 'when developers can push to the branch' do
before do
- create(:protected_branch, :no_one_can_push,
- name: 'some-ref', project: project)
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
end
- it_behaves_like 'protected ref'
+ it 'includes ability to update build' do
+ expect(policy).to be_allowed :update_build
+ end
end
- context 'when build is against a protected tag' do
+ context 'when no one can create the tag' do
before do
create(:protected_tag, :no_one_can_create,
- name: 'some-ref', project: project)
+ name: build.ref, project: project)
build.update(tag: true)
end
- it_behaves_like 'protected ref'
+ it 'does not include ability to update build' do
+ expect(policy).to be_disallowed :update_build
+ end
end
- context 'when build is against a protected tag but it is not a tag' do
+ context 'when no one can create the tag but it is not a tag' do
before do
create(:protected_tag, :no_one_can_create,
- name: 'some-ref', project: project)
+ name: build.ref, project: project)
end
- context 'when build is a manual action' do
- let(:build) do
- create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
- end
-
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
- end
- end
-
- context 'when branch build is assigned to is not protected' do
- context 'when build is a manual action' do
- let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
-
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
- end
-
- context 'when build is not a manual action' do
- let(:build) { create(:ci_build, pipeline: pipeline) }
-
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
+ it 'includes ability to update build' do
+ expect(policy).to be_allowed :update_build
end
end
end
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
new file mode 100644
index 00000000000..b11b06d301f
--- /dev/null
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Ci::PipelinePolicy, :models do
+ let(:user) { create(:user) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+ let(:policy) do
+ described_class.new(user, pipeline)
+ end
+
+ describe 'rules' do
+ describe 'rules for protected ref' do
+ let(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when no one can push or merge to the branch' do
+ before do
+ create(:protected_branch, :no_one_can_push,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'does not include ability to update pipeline' do
+ expect(policy).to be_disallowed :update_pipeline
+ end
+ end
+
+ context 'when developers can push to the branch' do
+ before do
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'includes ability to update pipeline' do
+ expect(policy).to be_allowed :update_pipeline
+ end
+ end
+
+ context 'when no one can create the tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: pipeline.ref, project: project)
+
+ pipeline.update(tag: true)
+ end
+
+ it 'does not include ability to update pipeline' do
+ expect(policy).to be_disallowed :update_pipeline
+ end
+ end
+
+ context 'when no one can create the tag but it is not a tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'includes ability to update pipeline' do
+ expect(policy).to be_allowed :update_pipeline
+ end
+ end
+ end
+ end
+end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index bb0fa0c0e9c..c3e2b603c4b 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -30,5 +30,25 @@ describe GlobalPolicy, models: true do
it { is_expected.to be_allowed(:read_users_list) }
end
end
+
+ context "for an admin" do
+ let(:current_user) { create(:admin) }
+
+ context "when the public level is restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+
+ context "when the public level is not restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [])
+ end
+
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+ end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index ca435dd0218..f244975e597 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -103,6 +103,48 @@ describe ProjectPolicy, models: true do
end
end
+ context 'issues feature' do
+ subject { described_class.new(owner, project) }
+
+ context 'when the feature is disabled' do
+ it 'does not include the issues permissions' do
+ project.issues_enabled = false
+ project.save!
+
+ expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+ end
+ end
+
+ context 'when the feature is disabled and external tracker configured' do
+ it 'does not include the issues permissions' do
+ create(:jira_service, project: project)
+
+ project.issues_enabled = false
+ project.save!
+
+ expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+ end
+ end
+ end
+
+ context 'when a project has pending invites, and the current user is anonymous' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
+ let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
+
+ subject { described_class.new(nil, project) }
+
+ before do
+ create(:group_member, :invited, group: group)
+ end
+
+ it 'does not grant owner access' do
+ expect_allowed(*anonymous_permissions)
+ expect_disallowed(*user_permissions)
+ end
+ end
+
context 'abilities for non-public projects' do
let(:project) { create(:empty_project, namespace: owner.namespace) }
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
new file mode 100644
index 00000000000..9b24658771f
--- /dev/null
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe API::GroupMilestones do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :private) }
+ let(:project) { create(:empty_project, namespace: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') }
+ let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') }
+
+ it_behaves_like 'group and project milestones', "/groups/:id/milestones" do
+ let(:route) { "/groups/#{group.id}/milestones" }
+ end
+
+ def setup_for_group
+ context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ context_group.add_developer(user)
+ public_project.update(namespace: context_group)
+ context_group.reload
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 9837fedb522..ff4fc802176 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -693,6 +693,19 @@ describe API::Issues do
expect(json_response['confidential']).to be_falsy
end
+ context 'links exposure' do
+ it 'exposes related resources full URIs' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
+
+ links = json_response['_links']
+
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}")
+ expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes")
+ expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji")
+ expect(links['project']).to end_with("/api/v4/projects/#{project.id}")
+ end
+ end
+
it "returns a project issue by internal id" do
get api("/projects/#{project.id}/issues/#{issue.iid}", user)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 9098ae6bcda..35b6522ea98 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -794,18 +794,24 @@ describe API::MergeRequests do
it 'handles external issues' do
jira_project = create(:jira_project, :public, name: 'JIR_EXT1')
- issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
- merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
- merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
+ ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
+ issue = create(:issue, project: jira_project)
+ description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}"
+ merge_request = create(:merge_request,
+ :simple, author: user, assignee: user, source_project: jira_project, description: description)
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
+ expect(json_response.second['title']).to eq(ext_issue.title)
+ expect(json_response.second['id']).to eq(ext_issue.id)
+ expect(json_response.second['confidential']).to be_nil
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.first['id']).to eq(issue.id)
+ expect(json_response.first['confidential']).not_to be_nil
end
it 'returns 403 if the user has no access to the merge request' do
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
new file mode 100644
index 00000000000..fe8fdbfd7e4
--- /dev/null
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe API::ProjectMilestones do
+ let(:user) { create(:user) }
+ let!(:project) { create(:empty_project, namespace: user.namespace ) }
+ let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
+ let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ it_behaves_like 'group and project milestones', "/projects/:id/milestones" do
+ let(:route) { "/projects/#{project.id}/milestones" }
+ end
+
+ describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
+ it 'creates an activity event when an milestone is closed' do
+ expect(Event).to receive(:create)
+
+ put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ state_event: 'close'
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 6dbde8bad31..79e7e1a95df 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -159,6 +159,31 @@ describe API::Projects do
expect(json_response.first).to include 'statistics'
end
+ context 'when external issue tracker is enabled' do
+ let!(:jira_service) { create(:jira_service, project: project) }
+
+ it 'includes open_issues_count' do
+ get api('/projects', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to include('open_issues_count')
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count')
+ end
+
+ it 'does not include open_issues_count if issues are disabled' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+
+ get api('/projects', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
+ end
+ end
+
context 'and with simple=true' do
it 'returns a simplified version of all the projects' do
expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
@@ -790,6 +815,38 @@ describe API::Projects do
expect(json_response).not_to include("import_error")
end
+ context 'links exposure' do
+ it 'exposes related resources full URIs' do
+ get api("/projects/#{project.id}", user)
+
+ links = json_response['_links']
+
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
+ expect(links['issues']).to end_with("/api/v4/projects/#{project.id}/issues")
+ expect(links['merge_requests']).to end_with("/api/v4/projects/#{project.id}/merge_requests")
+ expect(links['repo_branches']).to end_with("/api/v4/projects/#{project.id}/repository/branches")
+ expect(links['labels']).to end_with("/api/v4/projects/#{project.id}/labels")
+ expect(links['events']).to end_with("/api/v4/projects/#{project.id}/events")
+ expect(links['members']).to end_with("/api/v4/projects/#{project.id}/members")
+ end
+
+ it 'filters related URIs when their feature is not enabled' do
+ project = create(:empty_project, :public,
+ :merge_requests_disabled,
+ :issues_disabled,
+ creator_id: user.id,
+ namespace: user.namespace)
+
+ get api("/projects/#{project.id}", user)
+
+ links = json_response['_links']
+
+ expect(links.has_key?('merge_requests')).to be_falsy
+ expect(links.has_key?('issues')).to be_falsy
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
+ end
+ end
+
describe 'permissions' do
context 'all projects' do
before do
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 16ddade27d9..c2636b6614e 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -61,7 +61,8 @@ describe API::Triggers do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No pipeline created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 877bde3b9a6..66b165b438b 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -55,17 +55,22 @@ describe API::Users do
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
- allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true)
end
- it "renders 403" do
- get api("/users")
- expect(response).to have_http_status(403)
+ context 'when authenticate as a regular user' do
+ it "renders 403" do
+ get api("/users", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
- it "renders 404" do
- get api("/users/#{user.id}")
- expect(response).to have_http_status(404)
+ context 'when authenticate as an admin' do
+ it "renders 200" do
+ get api("/users", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
end
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index d3de6bf13bc..60212660fb6 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -52,7 +52,8 @@ describe API::V3::Triggers do
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No builds created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c969d08d0dd..49e815ee16c 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -69,6 +69,72 @@ describe Ci::API::Builds do
end
end
+ context 'when an old image syntax is used' do
+ before do
+ build.update!(options: { image: 'codeclimate' })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "image" => "codeclimate" })
+ end
+ end
+
+ context 'when a new image syntax is used' do
+ before do
+ build.update!(options: { image: { name: 'codeclimate' } })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "image" => "codeclimate" })
+ end
+ end
+
+ context 'when an old service syntax is used' do
+ before do
+ build.update!(options: { services: ['mysql'] })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "services" => ["mysql"] })
+ end
+ end
+
+ context 'when a new service syntax is used' do
+ before do
+ build.update!(options: { services: [name: 'mysql'] })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "services" => ["mysql"] })
+ end
+ end
+
+ context 'when no image or service is defined' do
+ before do
+ build.update!(options: {})
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+
+ expect(json_response["options"]).to be_empty
+ end
+ end
+
context 'when there is a pending build' do
it 'starts a build' do
register_builds info: { platform: :darwin }
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index 26b03c0f148..e481ca916ab 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -5,7 +5,14 @@ describe Ci::API::Triggers do
let!(:trigger_token) { 'secure token' }
let!(:project) { create(:project, :repository, ci_id: 10) }
let!(:project2) { create(:empty_project, ci_id: 11) }
- let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+
+ let!(:trigger) do
+ create(:ci_trigger,
+ project: project,
+ token: trigger_token,
+ owner: create(:user))
+ end
+
let(:options) do
{
token: trigger_token
@@ -14,6 +21,8 @@ describe Ci::API::Triggers do
before do
stub_ci_pipeline_to_return_yaml_file
+
+ project.add_developer(trigger.owner)
end
context 'Handles errors' do
@@ -47,7 +56,8 @@ describe Ci::API::Triggers do
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No builds created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 2f1c3c95e59..65314b688a4 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -609,4 +609,26 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com')
end
end
+
+ describe Projects::Registry::TagsController, :routing do
+ describe '#destroy' do
+ it 'correctly routes to a destroy action' do
+ expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/rc1'))
+ .to route_to('projects/registry/tags#destroy',
+ namespace_id: 'gitlab',
+ project_id: 'gitlabhq',
+ repository_id: '1',
+ id: 'rc1')
+ end
+
+ it 'takes registry tag name constrains into account' do
+ expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/-rc1'))
+ .not_to route_to('projects/registry/tags#destroy',
+ namespace_id: 'gitlab',
+ project_id: 'gitlabhq',
+ repository_id: '1',
+ id: '-rc1')
+ end
+ end
+ end
end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index b92c1c28ba8..1332572fffc 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -9,47 +9,96 @@ describe BuildDetailsEntity do
describe '#as_json' do
let(:project) { create(:project, :repository) }
- let!(:build) { create(:ci_build, :failed, project: project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, :failed, pipeline: pipeline) }
let(:request) { double('request') }
- let(:entity) { described_class.new(build, request: request, current_user: user, project: project) }
+
+ let(:entity) do
+ described_class.new(build, request: request,
+ current_user: user,
+ project: project)
+ end
+
subject { entity.as_json }
before do
allow(request).to receive(:current_user).and_return(user)
end
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:coverage, :erased_at, :duration)
+ expect(subject).to include(:runner, :pipeline)
+ expect(subject).to include(:raw_path, :new_issue_path)
+ end
+
context 'when the user has access to issues and merge requests' do
- let!(:merge_request) do
- create(:merge_request, source_project: project, source_branch: build.ref)
- end
+ context 'when merge request orginates from the same project' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: build.ref)
+ end
- before do
- allow(build).to receive(:merge_request).and_return(merge_request)
- end
+ before do
+ allow(build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:merge_request)
+ expect(subject).to include(:new_issue_path)
+ end
- it 'contains the needed key value pairs' do
- expect(subject).to include(:coverage, :erased_at, :duration)
- expect(subject).to include(:runner, :pipeline)
- expect(subject).to include(:raw_path, :merge_request)
- expect(subject).to include(:new_issue_path)
+ it 'exposes correct details of the merge request' do
+ expect(subject[:merge_request][:iid]).to eq merge_request.iid
+ end
+
+ it 'has a correct merge request path' do
+ expect(subject[:merge_request][:path]).to include project.full_path
+ end
end
- it 'exposes details of the merge request' do
- expect(subject[:merge_request]).to include(:iid, :path)
+ context 'when merge request is from a fork' do
+ let(:fork_project) do
+ create(:empty_project, forked_from_project: project)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, project: fork_project) }
+
+ before do
+ allow(build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, source_project: fork_project,
+ target_project: project,
+ source_branch: build.ref)
+ end
+
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:merge_request)
+ expect(subject).to include(:new_issue_path)
+ end
+
+ it 'exposes details of the merge request' do
+ expect(subject[:merge_request][:iid]).to eq merge_request.iid
+ end
+
+ it 'has a merge request path to a target project' do
+ expect(subject[:merge_request][:path])
+ .to include project.full_path
+ end
end
- context 'when the build has been erased' do
- let!(:build) { create(:ci_build, :erasable, project: project) }
+ context 'when the build has not been erased' do
+ let(:build) { create(:ci_build, :erasable, project: project) }
- it 'exposes the user whom erased the build' do
+ it 'exposes a build erase path' do
expect(subject).to include(:erase_path)
end
end
context 'when the build has been erased' do
- let!(:build) { create(:ci_build, erased_at: Time.now, project: project, erased_by: user) }
+ let(:build) { create(:ci_build, :erased, project: project) }
- it 'exposes the user whom erased the build' do
+ it 'exposes the user who erased the build' do
expect(subject).to include(:erased_by)
end
end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index 9620f9665cf..8149de869f1 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -2,13 +2,15 @@ require 'spec_helper'
describe DeployKeyEntity do
include RequestAwareEntity
-
+
let(:user) { create(:user) }
let(:project) { create(:empty_project, :internal)}
let(:project_private) { create(:empty_project, :private)}
+ let!(:project_pending_delete) { create(:empty_project, :internal, pending_delete: true) }
let(:deploy_key) { create(:deploy_key) }
let!(:deploy_key_internal) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
let!(:deploy_key_private) { create(:deploy_keys_project, project: project_private, deploy_key: deploy_key) }
+ let!(:deploy_key_pending_delete) { create(:deploy_keys_project, project: project_pending_delete, deploy_key: deploy_key) }
let(:entity) { described_class.new(deploy_key, user: user) }
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 5ca7bf2fcaf..026360e91a3 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -7,7 +7,9 @@ describe JobEntity do
let(:request) { double('request') }
before do
+ stub_not_protect_default_branch
allow(request).to receive(:current_user).and_return(user)
+
project.add_developer(user)
end
@@ -77,7 +79,7 @@ describe JobEntity do
project.add_developer(user)
create(:protected_branch, :developers_can_merge,
- name: 'master', project: project)
+ name: job.ref, project: job.project)
end
it 'contains path to play action' do
@@ -90,6 +92,13 @@ describe JobEntity do
end
context 'when user is not allowed to trigger action' do
+ before do
+ allow(job.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: job.ref, project: job.project)
+ end
+
it 'does not contain path to play action' do
expect(subject).not_to include(:play_path)
end
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index d28dec9592a..b990370a271 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -9,6 +9,8 @@ describe PipelineDetailsEntity do
end
before do
+ stub_not_protect_default_branch
+
allow(request).to receive(:current_user).and_return(user)
end
@@ -52,7 +54,7 @@ describe PipelineDetailsEntity do
context 'user has ability to retry pipeline' do
before do
- project.team << [user, :developer]
+ project.add_developer(user)
end
it 'retryable flag is true' do
@@ -97,7 +99,7 @@ describe PipelineDetailsEntity do
context 'when pipeline has commit statuses' do
let(:pipeline) { create(:ci_empty_pipeline) }
-
+
before do
create(:generic_commit_status, pipeline: pipeline)
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 46650f3a80d..5b01cc4fc9e 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -5,6 +5,8 @@ describe PipelineEntity do
let(:request) { double('request') }
before do
+ stub_not_protect_default_branch
+
allow(request).to receive(:current_user).and_return(user)
end
@@ -52,7 +54,7 @@ describe PipelineEntity do
context 'user has ability to retry pipeline' do
before do
- project.team << [user, :developer]
+ project.add_developer(user)
end
it 'contains retry path' do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 44813656aff..262bc4acb69 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -108,14 +108,35 @@ describe PipelineSerializer do
end
end
- it 'verifies number of queries', :request_store do
- recorded = ActiveRecord::QueryRecorder.new { subject }
- expect(recorded.count).to be_within(1).of(57)
- expect(recorded.cached_count).to eq(0)
+ shared_examples 'no N+1 queries' do
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expect(recorded.count).to be_within(1).of(59)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
+
+ context 'with the same ref' do
+ let(:ref) { 'feature' }
+
+ it_behaves_like 'no N+1 queries'
+ end
+
+ context 'with different refs' do
+ def ref
+ @sequence ||= 0
+ @sequence += 1
+ "feature-#{@sequence}"
+ end
+
+ it_behaves_like 'no N+1 queries'
end
def create_pipeline(status)
- create(:ci_empty_pipeline, project: project, status: status).tap do |pipeline|
+ create(:ci_empty_pipeline,
+ project: project,
+ status: status,
+ ref: ref).tap do |pipeline|
Ci::Build::AVAILABLE_STATUSES.each do |status|
create_build(pipeline, status, status)
end
@@ -125,7 +146,7 @@ describe PipelineSerializer do
def create_build(pipeline, stage, status)
create(:ci_build, :tags, :triggered, :artifacts,
pipeline: pipeline, stage: stage,
- name: stage, status: status)
+ name: stage, status: status, ref: pipeline.ref)
end
end
end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index e71c462b99a..146d25daba3 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -3,19 +3,26 @@ require 'spec_helper'
describe Ci::CreatePipelineService, :services do
let(:project) { create(:project, :repository) }
let(:user) { create(:admin) }
+ let(:ref_name) { 'refs/heads/master' }
before do
stub_ci_pipeline_to_return_yaml_file
end
describe '#execute' do
- def execute_service(source: :push, after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
+ def execute_service(
+ source: :push,
+ after: project.commit.id,
+ message: 'Message',
+ ref: ref_name,
+ trigger_request: nil)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }] }
- described_class.new(project, user, params).execute(source)
+ described_class.new(project, user, params).execute(
+ source, trigger_request: trigger_request)
end
context 'valid params' do
@@ -320,5 +327,223 @@ describe Ci::CreatePipelineService, :services do
end.not_to change { Environment.count }
end
end
+
+ context 'when builds with auto-retries are configured' do
+ before do
+ config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'correctly creates builds with auto-retry value configured' do
+ pipeline = execute_service
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
+ end
+ end
+
+ shared_examples 'when ref is protected' do
+ let(:user) { create(:user) }
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service).not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when user is master' do
+ before do
+ project.add_master(user)
+ end
+
+ it 'creates a pipeline' do
+ expect(execute_service).to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+
+ context 'when trigger belongs to no one' do
+ let(:user) {}
+ let(:trigger_request) { create(:ci_trigger_request) }
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when trigger belongs to a developer' do
+ let(:user) {}
+
+ let(:trigger_request) do
+ create(:ci_trigger_request).tap do |request|
+ user = create(:user)
+ project.add_developer(user)
+ request.trigger.update(owner: user)
+ end
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when trigger belongs to a master' do
+ let(:user) {}
+
+ let(:trigger_request) do
+ create(:ci_trigger_request).tap do |request|
+ user = create(:user)
+ project.add_master(user)
+ request.trigger.update(owner: user)
+ end
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+ end
+
+ context 'when ref is a protected branch' do
+ before do
+ create(:protected_branch, project: project, name: 'master')
+ end
+
+ it_behaves_like 'when ref is protected'
+ end
+
+ context 'when ref is a protected tag' do
+ let(:ref_name) { 'refs/tags/v1.0.0' }
+
+ before do
+ create(:protected_tag, project: project, name: '*')
+ end
+
+ it_behaves_like 'when ref is protected'
+ end
+
+ context 'when ref is not protected' do
+ context 'when trigger belongs to no one' do
+ let(:user) {}
+ let(:trigger_request) { create(:ci_trigger_request) }
+
+ it 'creates a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+ end
+ end
+
+ describe '#allowed_to_create?' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:ref) { 'master' }
+
+ subject do
+ described_class.new(project, user, ref: ref)
+ .send(:allowed_to_create?, user)
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to merge' do
+ let!(:protected_branch) do
+ create(:protected_branch,
+ :developers_can_merge,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :developers_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when no one can create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :no_one_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ context 'when owner cannot create pipeline' do
+ it { is_expected.to be_falsey }
+ end
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index f2956262f4b..37ca9804f56 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -1,12 +1,15 @@
require 'spec_helper'
describe Ci::CreateTriggerRequestService, services: true do
- let(:service) { described_class.new }
+ let(:service) { described_class }
let(:project) { create(:project, :repository) }
- let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+ let(:owner) { create(:user) }
before do
stub_ci_pipeline_to_return_yaml_file
+
+ project.add_developer(owner)
end
describe '#execute' do
@@ -14,29 +17,26 @@ describe Ci::CreateTriggerRequestService, services: true do
subject { service.execute(project, trigger, 'master') }
context 'without owner' do
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline).to be_trigger }
- it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'with owner' do
- let(:owner) { create(:user) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
-
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
+ it { expect(subject.trigger_request.builds.first.user).to eq(owner) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline).to be_trigger }
it { expect(subject.pipeline.user).to eq(owner) }
- it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.builds.first.user).to eq(owner) }
end
end
context 'no commit for ref' do
subject { service.execute(project, trigger, 'other-branch') }
- it { expect(subject).to be_nil }
+ it { expect(subject.pipeline).not_to be_persisted }
end
context 'no builds created' do
@@ -46,7 +46,7 @@ describe Ci::CreateTriggerRequestService, services: true do
stub_ci_pipeline_yaml_file('script: { only: [develop], script: hello World }')
end
- it { expect(subject).to be_nil }
+ it { expect(subject.pipeline).not_to be_persisted }
end
end
end
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index efcaccc254e..6346f311696 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -9,6 +9,8 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
@@ -463,6 +465,35 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
+ context 'when builds with auto-retries are configured' do
+ before do
+ create_build('build:1', stage_idx: 0, user: user, options: { retry: 2 })
+ create_build('test:1', stage_idx: 1, user: user, when: :on_failure)
+ create_build('test:2', stage_idx: 1, user: user, options: { retry: 1 })
+ end
+
+ it 'automatically retries builds in a valid order' do
+ expect(process_pipeline).to be_truthy
+
+ fail_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1]
+ expect(builds_statuses).to eq %w[failed pending]
+
+ succeed_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1 test:2]
+ expect(builds_statuses).to eq %w[failed success pending]
+
+ succeed_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1 test:2]
+ expect(builds_statuses).to eq %w[failed success success]
+
+ expect(pipeline.reload).to be_success
+ end
+ end
+
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index ef9927c5969..2cf62b54666 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -85,6 +85,8 @@ describe Ci::RetryBuildService, :services do
context 'when user has ability to execute build' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
@@ -131,6 +133,8 @@ describe Ci::RetryBuildService, :services do
context 'when user has ability to execute build' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 3e860203063..7798db3f3b9 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -244,13 +244,9 @@ describe Ci::RetryPipelineService, '#execute', :services do
create_build('verify', :canceled, 1)
end
- it 'does not reprocess manual action' do
- service.execute(pipeline)
-
- expect(build('test')).to be_pending
- expect(build('deploy')).to be_failed
- expect(build('verify')).to be_created
- expect(pipeline.reload).to be_running
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
end
end
@@ -261,13 +257,9 @@ describe Ci::RetryPipelineService, '#execute', :services do
create_build('verify', :canceled, 2)
end
- it 'does not reprocess manual action' do
- service.execute(pipeline)
-
- expect(build('test')).to be_pending
- expect(build('deploy')).to be_failed
- expect(build('verify')).to be_created
- expect(pipeline.reload).to be_running
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
end
end
end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index dfab6ebf372..2794721e157 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -244,6 +244,8 @@ describe CreateDeploymentService, services: true do
context 'when job is retried' do
it_behaves_like 'creates deployment' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index c493c08a7ae..f801506f1b6 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -488,21 +488,57 @@ describe GitPushService, services: true do
end
end
- context "using wrong markdown" do
- let(:message) { "this is some work.\n\ncloses #1" }
+ context "using internal issue reference" do
+ context 'when internal issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+ let(:message) { "this is some work.\n\ncloses #1" }
+
+ it "does not initiates one api call to jira server to close the issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
+ end
+
+ it "does not initiates one api call to jira server to comment on the issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+ end
- it "does not initiates one api call to jira server to close the issue" do
- execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ context 'when internal issues are enabled' do
+ let(:issue) { create(:issue, project: project) }
+ let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" }
- expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
- end
+ it "initiates one api call to jira server to close the jira issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
- it "does not initiates one api call to jira server to comment on the issue" do
- execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
+ end
- expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: comment_body
- ).once
+ it "initiates one api call to jira server to comment on the jira issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+
+ it "closes the internal issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ expect(issue.reload).to be_closed
+ end
+
+ it "adds a note indicating that the issue is now closed" do
+ expect(SystemNoteService).to receive(:change_status)
+ .with(issue, project, commit_author, "closed", closing_commit)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ end
end
end
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index d6f4c694069..da8b60f1337 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -98,13 +98,13 @@ describe Issues::CloseService, services: true do
end
end
- context 'external issue tracker' do
+ context 'internal issues disabled' do
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
- described_class.new(project, user).close_issue(issue)
+ project.issues_enabled = false
+ project.save!
end
- it 'closes the issue' do
+ it 'does not close the issue' do
expect(issue).to be_valid
expect(issue).to be_opened
expect(todo.reload).to be_pending
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
new file mode 100644
index 00000000000..82daf53b173
--- /dev/null
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Issues::DuplicateService, services: true do
+ let(:user) { create(:user) }
+ let(:canonical_project) { create(:empty_project) }
+ let(:duplicate_project) { create(:empty_project) }
+
+ let(:canonical_issue) { create(:issue, project: canonical_project) }
+ let(:duplicate_issue) { create(:issue, project: duplicate_project) }
+
+ subject { described_class.new(duplicate_project, user, {}) }
+
+ describe '#execute' do
+ context 'when the issues passed are the same' do
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, duplicate_issue)
+ end
+ end
+
+ context 'when the user cannot update the duplicate issue' do
+ before do
+ canonical_project.add_reporter(user)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+
+ context 'when the user cannot comment on the canonical issue' do
+ before do
+ duplicate_project.add_reporter(user)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+
+ context 'when the user can mark the issue as a duplicate' do
+ before do
+ canonical_project.add_reporter(user)
+ duplicate_project.add_reporter(user)
+ end
+
+ it 'closes the duplicate issue' do
+ subject.execute(duplicate_issue, canonical_issue)
+
+ expect(duplicate_issue.reload).to be_closed
+ expect(canonical_issue.reload).to be_open
+ end
+
+ it 'adds a system note to the duplicate issue' do
+ expect(SystemNoteService)
+ .to receive(:mark_duplicate_issue).with(duplicate_issue, duplicate_project, user, canonical_issue)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+
+ it 'adds a system note to the canonical issue' do
+ expect(SystemNoteService)
+ .to receive(:mark_canonical_issue_of_duplicate).with(canonical_issue, canonical_project, user, duplicate_issue)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index d0b991f19ab..064be940a1c 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -491,6 +491,27 @@ describe Issues::UpdateService, services: true do
include_examples 'updating mentions', Issues::UpdateService
end
+ context 'duplicate issue' do
+ let(:canonical_issue) { create(:issue, project: project) }
+
+ context 'invalid canonical_issue_id' do
+ it 'does not call the duplicate service' do
+ expect(Issues::DuplicateService).not_to receive(:new)
+
+ update_issue(canonical_issue_id: 123456789)
+ end
+ end
+
+ context 'valid canonical_issue_id' do
+ it 'calls the duplicate service with both issues' do
+ expect_any_instance_of(Issues::DuplicateService)
+ .to receive(:execute).with(issue, canonical_issue)
+
+ update_issue(canonical_issue_id: canonical_issue.id)
+ end
+ end
+ end
+
include_examples 'issuable update service' do
let(:open_issuable) { issue }
let(:closed_issuable) { create(:closed_issue, project: project) }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 01ef52396d7..a40d4c877bc 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -207,7 +207,7 @@ describe MergeRequests::BuildService, services: true do
let(:source_branch) { '12345-fix-issue' }
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ allow(project).to receive(:external_issue_tracker).and_return(true)
end
it 'sets the title to: Resolves External Issue $issue-iid' do
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index fd4011ad606..3ee834748df 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -103,7 +103,7 @@ describe Projects::UpdateService, '#execute', :services do
end
end
- context 'when renaming project that contains container images' do
+ context 'when updating a project that contains container images' do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
@@ -116,6 +116,13 @@ describe Projects::UpdateService, '#execute', :services do
expect(result).to include(status: :error)
expect(result[:message]).to match(/contains container registry tags/)
end
+
+ it 'allows to update other settings' do
+ result = update_project(project, admin, public_builds: true)
+
+ expect(result[:status]).to eq :success
+ expect(project.reload.public_builds).to be true
+ end
end
context 'when passing invalid parameters' do
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index a2db3f68ff7..2a2a5c38e4b 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -261,6 +261,15 @@ describe QuickActions::InterpretService, services: true do
end
end
+ shared_examples 'duplicate command' do
+ it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do
+ issue_duplicate # populate the issue
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(canonical_issue_id: issue_duplicate.id)
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -644,6 +653,41 @@ describe QuickActions::InterpretService, services: true do
let(:issuable) { issue }
end
+ context '/duplicate command' do
+ it_behaves_like 'duplicate command' do
+ let(:issue_duplicate) { create(:issue, project: project) }
+ let(:content) { "/duplicate #{issue_duplicate.to_reference}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/duplicate' }
+ let(:issuable) { issue }
+ end
+
+ context 'cross project references' do
+ it_behaves_like 'duplicate command' do
+ let(:other_project) { create(:empty_project, :public) }
+ let(:issue_duplicate) { create(:issue, project: other_project) }
+ let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/duplicate imaginary#1234" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:other_project) { create(:empty_project, :private) }
+ let(:issue_duplicate) { create(:issue, project: other_project) }
+
+ let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" }
+ let(:issuable) { issue }
+ end
+ end
+ end
+
context 'when current_user cannot :admin_issue' do
let(:visitor) { create(:user) }
let(:issue) { create(:issue, project: project, author: visitor) }
@@ -693,6 +737,11 @@ describe QuickActions::InterpretService, services: true do
let(:content) { '/remove_due_date' }
let(:issuable) { issue }
end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/duplicate #{issue.to_reference}' }
+ let(:issuable) { issue }
+ end
end
context '/award command' do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 60477b8e9ba..681b419aedf 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1101,4 +1101,54 @@ describe SystemNoteService, services: true do
expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
end
end
+
+ describe '.mark_duplicate_issue' do
+ subject { described_class.mark_duplicate_issue(noteable, project, author, canonical_issue) }
+
+ context 'within the same project' do
+ let(:canonical_issue) { create(:issue, project: project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference}" }
+ end
+
+ context 'across different projects' do
+ let(:other_project) { create(:empty_project) }
+ let(:canonical_issue) { create(:issue, project: other_project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}" }
+ end
+ end
+
+ describe '.mark_canonical_issue_of_duplicate' do
+ subject { described_class.mark_canonical_issue_of_duplicate(noteable, project, author, duplicate_issue) }
+
+ context 'within the same project' do
+ let(:duplicate_issue) { create(:issue, project: project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference} as a duplicate of this issue" }
+ end
+
+ context 'across different projects' do
+ let(:other_project) { create(:empty_project) }
+ let(:duplicate_issue) { create(:issue, project: other_project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue" }
+ end
+ end
end
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
deleted file mode 100644
index f99fd8434c2..00000000000
--- a/spec/services/test_hook_service_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe TestHookService, services: true do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:hook) { create(:project_hook, project: project) }
-
- describe '#execute' do
- it "executes successfully" do
- stub_request(:post, hook.url).to_return(status: 200)
- expect(TestHookService.new.execute(hook, user)).to be_truthy
- end
- end
-end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
new file mode 100644
index 00000000000..4218c15a3ce
--- /dev/null
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -0,0 +1,188 @@
+require 'spec_helper'
+
+describe TestHooks::ProjectService do
+ let(:current_user) { create(:user) }
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:hook) { create(:project_hook, project: project) }
+ let(:service) { described_class.new(hook, current_user, trigger) }
+ let(:sample_data) { { data: 'sample' } }
+ let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
+
+ context 'hook with not implemented test' do
+ let(:trigger) { 'not_implemented_events' }
+
+ it 'returns error message' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
+ end
+ end
+
+ context 'push_events' do
+ let(:trigger) { 'push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'tag_push_events' do
+ let(:trigger) { 'tag_push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'note_events' do
+ let(:trigger) { 'note_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has notes.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:notes).and_return([Note.new])
+ allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'issues_events' do
+ let(:trigger) { 'issues_events' }
+ let(:issue) { build(:issue) }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:issues).and_return([issue])
+ allow(issue).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'confidential_issues_events' do
+ let(:trigger) { 'confidential_issues_events' }
+ let(:issue) { build(:issue) }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:issues).and_return([issue])
+ allow(issue).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'merge_requests_events' do
+ let(:trigger) { 'merge_requests_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has merge requests.' })
+ end
+
+ it 'executes hook' do
+ create(:merge_request, source_project: project)
+ allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'job_events' do
+ let(:trigger) { 'job_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI jobs.' })
+ end
+
+ it 'executes hook' do
+ create(:ci_build, project: project)
+ allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'pipeline_events' do
+ let(:trigger) { 'pipeline_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI pipelines.' })
+ end
+
+ it 'executes hook' do
+ create(:ci_empty_pipeline, project: project)
+ allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'wiki_page_events' do
+ let(:trigger) { 'wiki_page_events' }
+
+ it 'returns error message if wiki disabled' do
+ allow(project).to receive(:wiki_enabled?).and_return(false)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
+ end
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
+ end
+
+ it 'executes hook' do
+ create(:wiki_page, wiki: project.wiki)
+ allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
new file mode 100644
index 00000000000..00d89924766
--- /dev/null
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe TestHooks::SystemService do
+ let(:current_user) { create(:user) }
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:hook) { create(:system_hook) }
+ let(:service) { described_class.new(hook, current_user, trigger) }
+ let(:sample_data) { { data: 'sample' }}
+ let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
+
+ before do
+ allow(Project).to receive(:first).and_return(project)
+ end
+
+ context 'hook with not implemented test' do
+ let(:trigger) { 'not_implemented_events' }
+
+ it 'returns error message' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
+ end
+ end
+
+ context 'push_events' do
+ let(:trigger) { 'push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'tag_push_events' do
+ let(:trigger) { 'tag_push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project.repository).to receive(:tags).and_return([])
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has tags." })
+ end
+
+ it 'executes hook' do
+ allow(project.repository).to receive(:tags).and_return(['tag'])
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'repository_update_events' do
+ let(:trigger) { 'repository_update_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:commit).and_return(nil)
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index b5abc46e80c..7ff37c22963 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -58,7 +58,7 @@ describe WebHookService, services: true do
exception = exception_class.new('Exception message')
WebMock.stub_request(:post, project_hook.url).to_raise(exception)
- expect(service_instance.execute).to eq([nil, exception.message])
+ expect(service_instance.execute).to eq({ status: :error, message: exception.message })
expect { service_instance.execute }.not_to raise_error
end
end
@@ -66,13 +66,13 @@ describe WebHookService, services: true do
it 'handles 200 status code' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
- expect(service_instance.execute).to eq([200, 'Success'])
+ expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
end
it 'handles 2xx status codes' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
- expect(service_instance.execute).to eq([201, 'Success'])
+ expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
end
context 'execution logging' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 5d5715b10ff..e7329210896 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -148,3 +148,10 @@ FactoryGirl::SyntaxRunner.class_eval do
end
ActiveRecord::Migration.maintain_test_schema!
+
+Shoulda::Matchers.configure do |config|
+ config.integrate do |with|
+ with.test_framework :rspec
+ with.library :rails
+ end
+end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/support/api/milestones_shared_examples.rb
index ab5ea3e8f2c..480e7d5151f 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -1,21 +1,14 @@
-require 'spec_helper'
-
-describe API::Milestones do
- let(:user) { create(:user) }
- let!(:project) { create(:empty_project, namespace: user.namespace ) }
- let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
- let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
+shared_examples_for 'group and project milestones' do |route_definition|
+ let(:resource_route) { "#{route}/#{milestone.id}" }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) }
let(:label_3) { create(:label, title: 'label_3', project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:another_merge_request) { create(:merge_request, :simple, source_project: project) }
- before do
- project.team << [user, :developer]
- end
-
- describe 'GET /projects/:id/milestones' do
- it 'returns project milestones' do
- get api("/projects/#{project.id}/milestones", user)
+ describe "GET #{route_definition}" do
+ it 'returns milestones list' do
+ get api(route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -24,13 +17,13 @@ describe API::Milestones do
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones")
+ get api(route)
expect(response).to have_http_status(401)
end
it 'returns an array of active milestones' do
- get api("/projects/#{project.id}/milestones?state=active", user)
+ get api("#{route}/?state=active", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -40,7 +33,7 @@ describe API::Milestones do
end
it 'returns an array of closed milestones' do
- get api("/projects/#{project.id}/milestones?state=closed", user)
+ get api("#{route}/?state=closed", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -50,9 +43,9 @@ describe API::Milestones do
end
it 'returns an array of milestones specified by iids' do
- other_milestone = create(:milestone, project: project)
+ other_milestone = create(:milestone, project: try(:project), group: try(:group))
- get api("/projects/#{project.id}/milestones", user), iids: [closed_milestone.iid, other_milestone.iid]
+ get api(route, user), iids: [closed_milestone.iid, other_milestone.iid]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -61,25 +54,15 @@ describe API::Milestones do
end
it 'does not return any milestone if none found' do
- get api("/projects/#{project.id}/milestones", user), iids: [Milestone.maximum(:iid).succ]
+ get api(route, user), iids: [Milestone.maximum(:iid).succ]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- end
-
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
-
- expect(response).to have_http_status(200)
- expect(json_response['title']).to eq(milestone.title)
- expect(json_response['iid']).to eq(milestone.iid)
- end
- it 'returns a project milestone by iids array' do
- get api("/projects/#{project.id}/milestones?iids=#{closed_milestone.iid}", user)
+ it 'returns a milestone by iids array' do
+ get api("#{route}?iids=#{closed_milestone.iid}", user)
expect(response.status).to eq 200
expect(response).to include_pagination_headers
@@ -89,8 +72,8 @@ describe API::Milestones do
expect(json_response.first['id']).to eq closed_milestone.id
end
- it 'returns a project milestone by searching for title' do
- get api("/projects/#{project.id}/milestones", user), search: 'version2'
+ it 'returns a milestone by searching for title' do
+ get api(route, user), search: 'version2'
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -99,8 +82,8 @@ describe API::Milestones do
expect(json_response.first['id']).to eq milestone.id
end
- it 'returns a project milestones by searching for description' do
- get api("/projects/#{project.id}/milestones", user), search: 'open'
+ it 'returns a milestones by searching for description' do
+ get api(route, user), search: 'open'
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -110,9 +93,17 @@ describe API::Milestones do
end
end
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+ describe "GET #{route_definition}/:milestone_id" do
+ it 'returns a milestone by id' do
+ get api(resource_route, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['title']).to eq(milestone.title)
+ expect(json_response['iid']).to eq(milestone.iid)
+ end
+
+ it 'returns a milestone by id' do
+ get api(resource_route, user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(milestone.title)
@@ -120,29 +111,29 @@ describe API::Milestones do
end
it 'returns 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}")
+ get api(resource_route)
expect(response).to have_http_status(401)
end
it 'returns a 404 error if milestone id not found' do
- get api("/projects/#{project.id}/milestones/1234", user)
+ get api("#{route}/1234", user)
expect(response).to have_http_status(404)
end
end
- describe 'POST /projects/:id/milestones' do
- it 'creates a new project milestone' do
- post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
+ describe "POST #{route_definition}" do
+ it 'creates a new milestone' do
+ post api(route, user), title: 'new milestone'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new milestone')
expect(json_response['description']).to be_nil
end
- it 'creates a new project milestone with description and dates' do
- post api("/projects/#{project.id}/milestones", user),
+ it 'creates a new milestone with description and dates' do
+ post api(route, user),
title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02'
expect(response).to have_http_status(201)
@@ -152,20 +143,20 @@ describe API::Milestones do
end
it 'returns a 400 error if title is missing' do
- post api("/projects/#{project.id}/milestones", user)
+ post api(route, user)
expect(response).to have_http_status(400)
end
it 'returns a 400 error if params are invalid (duplicate title)' do
- post api("/projects/#{project.id}/milestones", user),
+ post api(route, user),
title: milestone.title, description: 'release', due_date: '2013-03-02'
expect(response).to have_http_status(400)
end
- it 'creates a new project with reserved html characters' do
- post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2'
+ it 'creates a new milestone with reserved html characters' do
+ post api(route, user), title: 'foo & bar 1.1 -> 2.2'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2')
@@ -173,9 +164,9 @@ describe API::Milestones do
end
end
- describe 'PUT /projects/:id/milestones/:milestone_id' do
- it 'updates a project milestone' do
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ describe "PUT #{route_definition}/:milestone_id" do
+ it 'updates a milestone' do
+ put api(resource_route, user),
title: 'updated title'
expect(response).to have_http_status(200)
@@ -185,23 +176,21 @@ describe API::Milestones do
it 'removes a due date if nil is passed' do
milestone.update!(due_date: "2016-08-05")
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil
+ put api(resource_route, user), due_date: nil
expect(response).to have_http_status(200)
expect(json_response['due_date']).to be_nil
end
it 'returns a 404 error if milestone id not found' do
- put api("/projects/#{project.id}/milestones/1234", user),
+ put api("#{route}/1234", user),
title: 'updated title'
expect(response).to have_http_status(404)
end
- end
- describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do
- it 'updates a project milestone' do
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ it 'closes milestone' do
+ put api(resource_route, user),
state_event: 'close'
expect(response).to have_http_status(200)
@@ -209,21 +198,14 @@ describe API::Milestones do
end
end
- describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
- it 'creates an activity event when an milestone is closed' do
- expect(Event).to receive(:create)
-
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
- state_event: 'close'
- end
- end
+ describe "GET #{route_definition}/:milestone_id/issues" do
+ let(:issues_route) { "#{route}/#{milestone.id}/issues" }
- describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
milestone.issues << create(:issue, project: project)
end
- it 'returns project issues for a particular milestone' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ it 'returns issues for a particular milestone' do
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -231,12 +213,12 @@ describe API::Milestones do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
- it 'returns project issues sorted by label priority' do
+ it 'returns issues sorted by label priority' do
issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3])
issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1])
issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2])
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(json_response.first['id']).to eq(issue_2.id)
expect(json_response.second['id']).to eq(issue_3.id)
@@ -244,44 +226,58 @@ describe API::Milestones do
end
it 'matches V4 response schema for a list of issues' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to match_response_schema('public_api/v4/issues')
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
+ get api(issues_route)
expect(response).to have_http_status(401)
end
describe 'confidential issues' do
- let(:public_project) { create(:empty_project, :public) }
- let(:milestone) { create(:milestone, project: public_project) }
- let(:issue) { create(:issue, project: public_project) }
- let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ let!(:public_project) { create(:empty_project, :public) }
+ let!(:context_group) { try(:group) }
+ let!(:milestone) do
+ context_group ? create(:milestone, group: context_group) : create(:milestone, project: public_project)
+ end
+ let!(:issue) { create(:issue, project: public_project) }
+ let!(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ let!(:issues_route) do
+ if context_group
+ "#{route}/#{milestone.id}/issues"
+ else
+ "/projects/#{public_project.id}/milestones/#{milestone.id}/issues"
+ end
+ end
before do
+ # Add public project to the group in context
+ setup_for_group if context_group
+
public_project.team << [user, :developer]
milestone.issues << issue << confidential_issue
end
it 'returns confidential issues to team members' do
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(2)
+ # 2 for projects, 3 for group(which has another project with an issue)
+ expect(json_response.size).to be_between(2, 3)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
end
it 'does not return confidential issues to team members with guest role' do
member = create(:user)
- project.team << [member, :guest]
+ public_project.team << [member, :guest]
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
+ get api(issues_route, member)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -291,7 +287,7 @@ describe API::Milestones do
end
it 'does not return confidential issues to regular users' do
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+ get api(issues_route, create(:user))
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -304,30 +300,30 @@ describe API::Milestones do
issue.labels << label_2
confidential_issue.labels << label_1
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(2)
+ # 2 for projects, 3 for group(which has another project with an issue)
+ expect(json_response.size).to be_between(2, 3)
expect(json_response.first['id']).to eq(confidential_issue.id)
expect(json_response.second['id']).to eq(issue.id)
end
end
end
- describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
- let(:merge_request) { create(:merge_request, source_project: project) }
- let(:another_merge_request) { create(:merge_request, :simple, source_project: project) }
+ describe "GET #{route_definition}/:milestone_id/merge_requests" do
+ let(:merge_requests_route) { "#{route}/#{milestone.id}/merge_requests" }
before do
milestone.merge_requests << merge_request
end
- it 'returns project merge_requests for a particular milestone' do
+ it 'returns merge_requests for a particular milestone' do
# eager-load another_merge_request
another_merge_request
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -336,12 +332,12 @@ describe API::Milestones do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
- it 'returns project merge_requests sorted by label priority' do
+ it 'returns merge_requests sorted by label priority' do
merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2])
merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1])
merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3])
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(json_response.first['id']).to eq(merge_request_2.id)
expect(json_response.second['id']).to eq(merge_request_1.id)
@@ -349,20 +345,22 @@ describe API::Milestones do
end
it 'returns a 404 error if milestone id not found' do
- get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
+ not_found_route = "#{route}/1234/merge_requests"
+
+ get api(not_found_route, user)
expect(response).to have_http_status(404)
end
it 'returns a 404 if the user has no access to the milestone' do
new_user = create :user
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user)
+ get api(merge_requests_route, new_user)
expect(response).to have_http_status(404)
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests")
+ get api(merge_requests_route)
expect(response).to have_http_status(401)
end
@@ -372,7 +370,7 @@ describe API::Milestones do
another_merge_request.labels << label_1
merge_request.labels << label_2
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb
new file mode 100644
index 00000000000..890a2d9d287
--- /dev/null
+++ b/spec/support/devise_helpers.rb
@@ -0,0 +1,14 @@
+module DeviseHelpers
+ # explicitly tells Devise which mapping to use
+ # this is needed when we are testing a Devise controller bypassing the router
+ def set_devise_mapping(context:)
+ env =
+ if context.respond_to?(:env_config)
+ context.env_config
+ elsif context.respond_to?(:env)
+ context.env
+ end
+
+ env['devise.mapping'] = Devise.mappings[:user] if env
+ end
+end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 033e338fe61..035428a7d9b 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -5,8 +5,6 @@ shared_examples 'issuable record that supports quick actions in its description
include QuickActionsHelpers
let(:master) { create(:user) }
- let(:assignee) { create(:user, username: 'bob') }
- let(:guest) { create(:user) }
let(:project) { create(:project, :public) }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
@@ -15,8 +13,6 @@ shared_examples 'issuable record that supports quick actions in its description
before do
project.team << [master, :master]
- project.team << [assignee, :developer]
- project.team << [guest, :guest]
sign_in(master)
end
@@ -57,6 +53,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing commands' do
it 'creates a note without the commands and interpret the commands accordingly' do
+ assignee = create(:user, username: 'bob')
write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
expect(page).to have_content 'Awesome!'
@@ -77,6 +74,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing only commands' do
it 'does not create a note but interpret the commands accordingly' do
+ assignee = create(:user, username: 'bob')
write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
expect(page).not_to have_content '/assign @bob'
@@ -111,8 +109,12 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot close #{issuable_type}" do
before do
+ guest = create(:user)
+ project.add_guest(guest)
+
sign_out(:user)
sign_in(guest)
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -146,8 +148,12 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot reopen #{issuable_type}" do
before do
+ guest = create(:user)
+ project.add_guest(guest)
+
sign_out(:user)
sign_in(guest)
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -176,6 +182,9 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot change title of #{issuable_type}" do
before do
+ guest = create(:user)
+ project.add_guest(guest)
+
sign_out(:user)
sign_in(guest)
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
@@ -267,6 +276,8 @@ shared_examples 'issuable record that supports quick actions in its description
describe "preview of note on #{issuable_type}" do
it 'removes quick actions from note and explains them' do
+ create(:user, username: 'bob')
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
page.within('.js-main-target-form') do
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
index 97ae0b6afc5..0b5f66597fd 100644
--- a/spec/support/jira_service_helper.rb
+++ b/spec/support/jira_service_helper.rb
@@ -51,7 +51,7 @@ module JiraServiceHelper
end
def jira_project_url
- JIRA_API + "/project/#{jira_tracker.project_key}"
+ JIRA_API + "/project"
end
def jira_api_comment_url(issue_id)
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index b410a652126..c714d1b08a6 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -1,4 +1,6 @@
module LoginHelpers
+ include DeviseHelpers
+
# Internal: Log in as a specific user or a new user of a specific role
#
# user_or_role - User object, or a role to create (e.g., :admin, :user)
@@ -62,7 +64,7 @@ module LoginHelpers
visit new_user_session_path
expect(page).to have_content('Sign in with')
- check 'Remember Me' if remember_me
+ check 'remember_me' if remember_me
click_link "oauth-login-#{provider}"
end
@@ -106,7 +108,7 @@ module LoginHelpers
end
def stub_omniauth_saml_config(messages)
- Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index bbbbaf4c5e8..7afa57fb76b 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -17,7 +17,7 @@ module MarkdownMatchers
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
- expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
+ expect(image['data-src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
end
end
@@ -70,7 +70,7 @@ module MarkdownMatchers
# GollumTagsFilter
matcher :parse_gollum_tags do
def have_image(src)
- have_css("img[src$='#{src}']")
+ have_css("img[data-src$='#{src}']")
end
prefix = '/namespace1/gitlabhq/wikis'
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index 66e598e2691..d5bc12f3bc5 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -5,7 +5,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
- within('.new_protected_branch') do
+ within('.js-new-protected-branch') do
allowed_to_push_button = find(".js-allowed-to-push")
unless allowed_to_push_button.text == access_type_name
@@ -50,7 +50,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
- within('.new_protected_branch') do
+ within('.js-new-protected-branch') do
allowed_to_merge_button = find(".js-allowed-to-merge")
unless allowed_to_merge_button.text == access_type_name
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 044c09d5fde..6accf16bea4 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -78,7 +78,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
wiki_page_service = WikiPages::CreateService.new(project, user, opts)
@wiki_page = wiki_page_service.execute
- @wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
+ @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create')
end
it "calls Slack/Mattermost API for push events" do
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 48f454c7187..80ecce92dc1 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -9,6 +9,11 @@ module StubConfiguration
.to receive_messages(messages)
end
+ def stub_not_protect_default_branch
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
def stub_config_setting(messages)
allow(Gitlab.config.gitlab).to receive_messages(messages)
end
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 91cc684d032..d34617be474 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it' do
expect(subject).to receive(:checkout_version).with(tag, clone_path)
- expect(subject).to receive(:reset_to_version).with(tag, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
@@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it with a branch name' do
expect(subject).to receive(:checkout_version).with(branch, clone_path)
- expect(subject).to receive(:reset_to_version).with(branch, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
@@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do
describe '#checkout_version' do
it 'clones the repo in the target dir' do
expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
+ .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet origin #{tag}])
expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
+ .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout -f --quiet FETCH_HEAD --])
subject.checkout_version(tag, clone_path)
end
end
-
- describe '#reset_to_version' do
- it 'resets --hard to the given version' do
- expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
-
- subject.reset_to_version(tag, clone_path)
- end
- end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index a8f4bb72acf..74a9f90195c 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -74,6 +74,7 @@ describe PostReceive do
OpenStruct.new(id: '123456')
end
allow_any_instance_of(Ci::CreatePipelineService).to receive(:branch?).and_return(true)
+ allow_any_instance_of(Repository).to receive(:ref_exists?).and_return(true)
stub_ci_pipeline_to_return_yaml_file
end