summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flayignore35
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml9
-rw-r--r--.haml-lint.yml1
-rw-r--r--CHANGELOG.md8
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile7
-rw-r--r--Gemfile.lock34
-rw-r--r--Gemfile.rails5.lock33
-rw-r--r--app/assets/javascripts/badges/components/badge_form.vue105
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue2
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue10
-rw-r--r--app/assets/javascripts/flash.js1
-rw-r--r--app/assets/javascripts/ide/components/ide.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue8
-rw-r--r--app/assets/javascripts/ide/components/repo_file.vue12
-rw-r--r--app/assets/javascripts/jobs/store/actions.js175
-rw-r--r--app/assets/javascripts/jobs/store/index.js13
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js29
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js94
-rw-r--r--app/assets/javascripts/jobs/store/state.js40
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js5
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/account_and_limits.js25
-rw-r--r--app/assets/javascripts/pages/admin/index.js6
-rw-r--r--app/assets/javascripts/pages/admin/users/new/index.js49
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/settings/badges/index/index.js10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue42
-rw-r--r--app/assets/stylesheets/framework/emojis.scss4
-rw-r--r--app/assets/stylesheets/framework/layout.scss39
-rw-r--r--app/assets/stylesheets/framework/mobile.scss5
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss70
-rw-r--r--app/assets/stylesheets/pages/diff.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/controllers/concerns/issuable_collections.rb12
-rw-r--r--app/controllers/concerns/sends_blob.rb69
-rw-r--r--app/controllers/groups/settings/badges_controller.rb13
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/ide_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/avatars_controller.rb12
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb37
-rw-r--r--app/controllers/projects/settings/badges_controller.rb13
-rw-r--r--app/controllers/projects/tags_controller.rb5
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/blob_helper.rb22
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/cookies_helper.rb9
-rw-r--r--app/helpers/namespaces_helper.rb2
-rw-r--r--app/helpers/submodule_helper.rb37
-rw-r--r--app/helpers/users_helper.rb11
-rw-r--r--app/helpers/wiki_helper.rb6
-rw-r--r--app/models/application_setting.rb11
-rw-r--r--app/models/ci/job_artifact.rb20
-rw-r--r--app/models/concerns/atomic_internal_id.rb2
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/case_sensitivity.rb2
-rw-r--r--app/models/concerns/each_batch.rb2
-rw-r--r--app/models/concerns/ignorable_column.rb2
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/concerns/loaded_in_group_list.rb2
-rw-r--r--app/models/concerns/manual_inverse_association.rb2
-rw-r--r--app/models/concerns/mentionable.rb2
-rw-r--r--app/models/concerns/optionally_search.rb2
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/referable.rb2
-rw-r--r--app/models/concerns/resolvable_note.rb2
-rw-r--r--app/models/concerns/select_for_project_authorization.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/sortable.rb2
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/concerns/strip_attribute.rb2
-rw-r--r--app/models/concerns/triggerable_hooks.rb1
-rw-r--r--app/policies/issuable_policy.rb1
-rw-r--r--app/policies/issue_policy.rb4
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/serializers/build_details_entity.rb22
-rw-r--r--app/serializers/diff_file_entity.rb5
-rw-r--r--app/serializers/diff_line_entity.rb14
-rw-r--r--app/serializers/diff_line_parallel_entity.rb6
-rw-r--r--app/serializers/diff_line_serializer.rb5
-rw-r--r--app/serializers/discussion_entity.rb2
-rw-r--r--app/services/files/base_service.rb4
-rw-r--r--app/services/issues/reopen_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb5
-rw-r--r--app/services/users/build_service.rb12
-rw-r--r--app/services/wikis/create_attachment_service.rb71
-rw-r--r--app/uploaders/file_uploader.rb10
-rw-r--r--app/uploaders/job_artifact_uploader.rb17
-rw-r--r--app/uploaders/uploader_helper.rb27
-rw-r--r--app/validators/js_regex_validator.rb15
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml7
-rw-r--r--app/views/admin/users/_access_levels.html.haml4
-rw-r--r--app/views/award_emoji/_awards_block.html.haml1
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml2
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.haml1
-rw-r--r--app/views/groups/edit.html.haml12
-rw-r--r--app/views/groups/labels/index.html.haml1
-rw-r--r--app/views/ide/index.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml4
-rw-r--r--app/views/layouts/explore.html.haml4
-rw-r--r--app/views/layouts/fullscreen.html.haml (renamed from app/views/layouts/nav_only.html.haml)4
-rw-r--r--app/views/layouts/group_settings.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml5
-rw-r--r--app/views/projects/_home_panel.html.haml1
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml1
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml1
-rw-r--r--app/views/projects/diffs/_single_image_diff.html.haml2
-rw-r--r--app/views/projects/edit.html.haml12
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/labels/index.html.haml1
-rw-r--r--app/views/projects/merge_requests/_how_to_merge.html.haml6
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml2
-rw-r--r--app/views/projects/milestones/_deprecation_message.html.haml7
-rw-r--r--app/views/projects/milestones/show.html.haml1
-rw-r--r--app/views/projects/new.html.haml1
-rw-r--r--app/views/projects/notes/_actions.html.haml1
-rw-r--r--app/views/projects/pipelines/new.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml1
-rw-r--r--app/views/projects/tags/_tag.atom.builder19
-rw-r--r--app/views/projects/tags/index.atom.builder7
-rw-r--r--app/views/projects/tags/index.html.haml4
-rw-r--r--app/views/projects/wikis/edit.html.haml5
-rw-r--r--app/views/shared/_label.html.haml2
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml1
-rw-r--r--app/views/shared/_ref_switcher.html.haml2
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml16
-rw-r--r--app/views/shared/issuable/_filter.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml1
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml1
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/snippets/notes/_actions.html.haml1
-rw-r--r--app/workers/background_migration_worker.rb14
-rw-r--r--app/workers/concerns/application_worker.rb2
-rw-r--r--app/workers/concerns/waitable_worker.rb2
-rw-r--r--changelogs/unreleased/37356-relative-submodule-link.yml5
-rw-r--r--changelogs/unreleased/39665-restrict-issue-reopen.yml5
-rw-r--r--changelogs/unreleased/41292-users-stuck-on-a-redirect-loop-after-transferring-project.yml5
-rw-r--r--changelogs/unreleased/46591-fix-ide-height-issues.yml5
-rw-r--r--changelogs/unreleased/47765-group-visibility-error-due-to-string-conversion.yml6
-rw-r--r--changelogs/unreleased/47943-project-milestone-page-deprecation-message.yml5
-rw-r--r--changelogs/unreleased/50101-add-artifact-information-to-job-api.yml5
-rw-r--r--changelogs/unreleased/50414-rubocop-rule-to-enforce-class-methods-over-module.yml5
-rw-r--r--changelogs/unreleased/50564-chat-service-refactoring.yml5
-rw-r--r--changelogs/unreleased/50853-vendor-auto-devops-gitlab-ci-yml-to-resolve-redeploying-deleted-app-gives-helm-error.yml5
-rw-r--r--changelogs/unreleased/50879-unused-css-container-fluid.yml5
-rw-r--r--changelogs/unreleased/50936-docs-run-review-cleanup-only-for-gitlab-org-repos.yml5
-rw-r--r--changelogs/unreleased/_acet-disable-ide-button.yml5
-rw-r--r--changelogs/unreleased/add-background-migration-for-legacy-traces.yml5
-rw-r--r--changelogs/unreleased/feature--32877-add-default-field-branch-api.yml5
-rw-r--r--changelogs/unreleased/feature-whitelist-new-users-as-internal.yml5
-rw-r--r--changelogs/unreleased/fix-download-dropdown-link.yml5
-rw-r--r--changelogs/unreleased/fix-junit-parser.yml5
-rw-r--r--changelogs/unreleased/fix_emojis_cutting_and_regressions.yml5
-rw-r--r--changelogs/unreleased/fj-2635-enable-rss-for-tags.yml5
-rw-r--r--changelogs/unreleased/fj-33475-files-inside-wiki-repo.yml5
-rw-r--r--changelogs/unreleased/fj-47229-fix-logo-lfs-tracked.yml5
-rw-r--r--changelogs/unreleased/ide-multiple-file-uploads.yml5
-rw-r--r--changelogs/unreleased/ide-row-hover-scroll.yml5
-rw-r--r--changelogs/unreleased/rails5-mysql-binary-column-index-length.yml5
-rw-r--r--changelogs/unreleased/remove-background-migration-worker-feature-flag.yml5
-rw-r--r--changelogs/unreleased/schema-changed-ee-backport.yml5
-rw-r--r--changelogs/unreleased/sh-bump-gitlab-pages-v1-1-0.yml5
-rw-r--r--changelogs/unreleased/sh-bump-unauth-expiration.yml5
-rw-r--r--changelogs/unreleased/sh-disable-sidekiq-session.yml5
-rw-r--r--changelogs/unreleased/sh-fix-confidential-note-option.yml5
-rw-r--r--changelogs/unreleased/sh-fix-dedupe-group-importer.yml5
-rw-r--r--changelogs/unreleased/sh-fix-error-500-updating-wikis.yml5
-rw-r--r--changelogs/unreleased/sh-improve-bitbucket-server-logging.yml5
-rw-r--r--changelogs/unreleased/sh-send-put-headers-object-storage.yml5
-rw-r--r--changelogs/unreleased/sh-set-secure-cookies.yml5
-rw-r--r--changelogs/unreleased/update-padding-markdown.yml5
-rw-r--r--changelogs/unreleased/winh-move-badge-settings.yml5
-rw-r--r--config/dependency_decisions.yml7
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/mysql_set_length_for_binary_indexes.rb28
-rw-r--r--config/initializers/sidekiq.rb6
-rw-r--r--config/routes/group.rb1
-rw-r--r--config/routes/project.rb1
-rw-r--r--danger/metadata/Dangerfile2
-rw-r--r--db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb13
-rw-r--r--db/migrate/20180815160409_add_file_location_to_ci_job_artifacts.rb9
-rw-r--r--db/migrate/20180815170510_add_partial_index_to_ci_builds_artifacts_file.rb16
-rw-r--r--db/post_migrate/20180816161409_migrate_legacy_artifacts_to_job_artifacts.rb32
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/compliance.md18
-rw-r--r--doc/administration/gitaly/index.md2
-rw-r--r--doc/administration/high_availability/nfs.md2
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/job_artifacts.md2
-rw-r--r--doc/administration/job_traces.md7
-rw-r--r--doc/administration/logs.md9
-rw-r--r--doc/administration/pages/index.md5
-rw-r--r--doc/api/branches.md5
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/api/settings.md236
-rw-r--r--doc/api/tags.md22
-rw-r--r--doc/api/wikis.md44
-rw-r--r--doc/ci/caching/index.md109
-rw-r--r--doc/ci/junit_test_reports.md47
-rw-r--r--doc/ci/yaml/README.md22
-rw-r--r--doc/development/licensing.md2
-rw-r--r--doc/install/installation.md8
-rw-r--r--doc/install/kubernetes/gitlab_chart.md4
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md4
-rw-r--r--doc/raketasks/backup_restore.md5
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/reset_root_password.md2
-rw-r--r--doc/security/unlock_user.md31
-rw-r--r--doc/security/user_email_confirmation.md2
-rw-r--r--doc/ssh/README.md26
-rw-r--r--doc/topics/autodevops/index.md6
-rw-r--r--doc/topics/autodevops/quick_start_guide.md10
-rw-r--r--doc/update/11.2-to-11-3.md378
-rw-r--r--doc/update/README.md2
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/gitlab_com/index.md11
-rw-r--r--doc/user/group/img/groups.pngbin62070 -> 61507 bytes
-rw-r--r--doc/user/permissions.md17
-rw-r--r--doc/user/project/badges.md4
-rw-r--r--doc/user/project/import/svn.md2
-rw-r--r--doc/user/project/integrations/microsoft_teams.md2
-rw-r--r--doc/user/project/issues/automatic_issue_closing.md6
-rw-r--r--doc/user/project/pages/getting_started_part_two.md2
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md2
-rw-r--r--doc/workflow/lfs/lfs_administration.md2
-rw-r--r--doc/workflow/timezone.md1
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/entities.rb26
-rw-r--r--lib/api/projects_relation_builder.rb2
-rw-r--r--lib/api/wikis.rb31
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb10
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb21
-rw-r--r--lib/gitlab/background_migration/migrate_legacy_artifacts.rb126
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb63
-rw-r--r--lib/gitlab/ci/parsers/junit.rb45
-rw-r--r--lib/gitlab/data_builder/push.rb8
-rw-r--r--lib/gitlab/diff/line.rb12
-rw-r--r--lib/gitlab/email/handler.rb25
-rw-r--r--lib/gitlab/email/handler/base_handler.rb2
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb2
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb2
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb2
-rw-r--r--lib/gitlab/email/handler/reply_processing.rb2
-rw-r--r--lib/gitlab/email/handler/unsubscribe_handler.rb2
-rw-r--r--lib/gitlab/encoding_helper.rb2
-rw-r--r--lib/gitlab/file_markdown_link_builder.rb21
-rw-r--r--lib/gitlab/file_type_detection.rb43
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/github_import/representation/expose_attribute.rb2
-rw-r--r--lib/gitlab/graphql/mount_mutation.rb2
-rw-r--r--lib/gitlab/import/logger.rb9
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb5
-rw-r--r--lib/object_storage/direct_upload.rb4
-rw-r--r--lib/static_model.rb2
-rw-r--r--lib/tasks/flay.rake9
-rw-r--r--lib/tasks/lint.rake1
-rw-r--r--locale/gitlab.pot69
-rw-r--r--qa/qa.rb9
-rw-r--r--qa/qa/page/component/clone_panel.rb (renamed from qa/qa/page/shared/clone_panel.rb)4
-rw-r--r--qa/qa/page/project/issue/show.rb2
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/page/project/wiki/show.rb4
-rw-r--r--qa/qa/page/view.rb4
-rw-r--r--qa/qa/scenario/test/integration/mattermost.rb2
-rw-r--r--qa/qa/scenario/test/integration/object_storage.rb13
-rw-r--r--qa/qa/scenario/test/sanity/failing.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/sanity/failing_spec.rb13
-rw-r--r--rubocop/cop/prefer_class_methods_over_module.rb73
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/lint-doc.sh2
-rw-r--r--scripts/schema_changed.sh13
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb28
-rw-r--r--spec/controllers/groups_controller_spec.rb10
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb53
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb1
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb56
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb101
-rw-r--r--spec/controllers/projects_controller_spec.rb13
-rw-r--r--spec/factories/ci/job_artifacts.rb6
-rw-r--r--spec/features/admin/admin_settings_spec.rb12
-rw-r--r--spec/features/admin/admin_users_spec.rb46
-rw-r--r--spec/features/groups/settings/group_badges_spec.rb2
-rw-r--r--spec/features/issues/rss_spec.rb (renamed from spec/features/projects/issues/rss_spec.rb)0
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb (renamed from spec/features/projects/issues/user_comments_on_issue_spec.rb)0
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb (renamed from spec/features/projects/issues/user_creates_issue_spec.rb)0
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb (renamed from spec/features/projects/issues/user_edits_issue_spec.rb)0
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb (renamed from spec/features/projects/issues/user_sorts_issues_spec.rb)0
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb (renamed from spec/features/projects/issues/user_toggles_subscription_spec.rb)0
-rw-r--r--spec/features/issues/user_views_issue_spec.rb (renamed from spec/features/projects/issues/user_views_issue_spec.rb)0
-rw-r--r--spec/features/issues/user_views_issues_spec.rb (renamed from spec/features/projects/issues/user_views_issues_spec.rb)0
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_closes_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_comments_on_commit_spec.rb (renamed from spec/features/projects/merge_requests/user_comments_on_commit_spec.rb)0
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb (renamed from spec/features/projects/merge_requests/user_comments_on_diff_spec.rb)0
-rw-r--r--spec/features/merge_request/user_comments_on_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_creates_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_edits_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb (renamed from spec/features/projects/merge_requests/user_manages_subscription_spec.rb)0
-rw-r--r--spec/features/merge_request/user_merges_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_merges_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_rebases_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_rebases_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_reverts_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb3
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb (renamed from spec/features/projects/merge_requests/user_views_diffs_spec.rb)0
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb (renamed from spec/features/projects/merge_requests/user_views_user_status_on_merge_request_spec.rb)0
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb (renamed from spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb)0
-rw-r--r--spec/features/merge_requests/user_views_all_merge_requests_spec.rb (renamed from spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb)0
-rw-r--r--spec/features/merge_requests/user_views_closed_merge_requests_spec.rb (renamed from spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb)0
-rw-r--r--spec/features/merge_requests/user_views_merged_merge_requests_spec.rb (renamed from spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb)0
-rw-r--r--spec/features/merge_requests/user_views_open_merge_requests_spec.rb (renamed from spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb)0
-rw-r--r--spec/features/projects/settings/project_badges_spec.rb2
-rw-r--r--spec/features/projects/tags/user_views_tags_spec.rb69
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb26
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/ci_detailed_status.json24
-rw-r--r--spec/fixtures/api/schemas/entities/diff_line.json14
-rw-r--r--spec/fixtures/api/schemas/entities/diff_line_parallel.json11
-rw-r--r--spec/fixtures/api/schemas/http_method.json5
-rw-r--r--spec/fixtures/api/schemas/job/artifact.json11
-rw-r--r--spec/fixtures/api/schemas/job/job.json (renamed from spec/fixtures/api/schemas/job.json)13
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json7
-rw-r--r--spec/fixtures/api/schemas/pipeline_stage.json2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/branch.json2
-rw-r--r--spec/helpers/button_helper_spec.rb14
-rw-r--r--spec/helpers/namespaces_helper_spec.rb5
-rw-r--r--spec/helpers/submodule_helper_spec.rb75
-rw-r--r--spec/helpers/users_helper_spec.rb24
-rw-r--r--spec/javascripts/badges/components/badge_form_spec.js150
-rw-r--r--spec/javascripts/fixtures/admin_users.rb29
-rw-r--r--spec/javascripts/fixtures/application_settings.rb34
-rw-r--r--spec/javascripts/ide/components/new_dropdown/upload_spec.js17
-rw-r--r--spec/javascripts/ide/components/repo_file_spec.js21
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js625
-rw-r--r--spec/javascripts/jobs/store/mutations_spec.js228
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js10
-rw-r--r--spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js33
-rw-r--r--spec/javascripts/pages/admin/users/new/index_spec.js43
-rw-r--r--spec/javascripts/pdf/page_spec.js62
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js45
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb40
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb156
-rw-r--r--spec/lib/gitlab/ci/parsers/junit_spec.rb64
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler_spec.rb4
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb12
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb80
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb82
-rw-r--r--spec/lib/gitlab/import_export/importer_spec.rb10
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb2
-rw-r--r--spec/migrations/migrate_legacy_artifacts_to_job_artifacts_spec.rb73
-rw-r--r--spec/models/application_setting_spec.rb24
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb50
-rw-r--r--spec/policies/issue_policy_spec.rb42
-rw-r--r--spec/requests/api/wikis_spec.rb124
-rw-r--r--spec/rubocop/cop/prefer_class_methods_over_module_spec.rb98
-rw-r--r--spec/serializers/diff_file_entity_spec.rb17
-rw-r--r--spec/serializers/diff_line_serializer_spec.rb25
-rw-r--r--spec/services/projects/transfer_service_spec.rb29
-rw-r--r--spec/services/users/build_service_spec.rb106
-rw-r--r--spec/services/wikis/create_attachment_service_spec.rb202
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb89
-rw-r--r--spec/support/shared_examples/wiki_file_attachments_examples.rb88
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb47
-rw-r--r--spec/uploaders/uploader_helper_spec.rb25
-rw-r--r--spec/validators/js_regex_validator_spec.rb27
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml2
384 files changed, 6107 insertions, 1120 deletions
diff --git a/.flayignore b/.flayignore
deleted file mode 100644
index 87411516a2a..00000000000
--- a/.flayignore
+++ /dev/null
@@ -1,35 +0,0 @@
-*.erb
-lib/gitlab/sanitizers/svg/whitelist.rb
-lib/gitlab/diff/position_tracer.rb
-app/controllers/projects/approver_groups_controller.rb
-app/controllers/projects/approvers_controller.rb
-app/controllers/projects/protected_branches/merge_access_levels_controller.rb
-app/controllers/projects/protected_branches/push_access_levels_controller.rb
-app/controllers/projects/protected_tags/create_access_levels_controller.rb
-app/helpers/system_note_helper.rb
-app/policies/project_policy.rb
-app/models/concerns/relative_positioning.rb
-app/workers/stuck_merge_jobs_worker.rb
-lib/gitlab/redis/*.rb
-lib/gitlab/gitaly_client/operation_service.rb
-app/models/project_services/packagist_service.rb
-lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
-lib/gitlab/background_migration/*
-app/models/project_services/kubernetes_service.rb
-lib/gitlab/workhorse.rb
-lib/gitlab/ci/trace/chunked_io.rb
-lib/gitlab/gitaly_client/ref_service.rb
-lib/gitlab/gitaly_client/commit_service.rb
-lib/gitlab/git/commit.rb
-lib/gitlab/git/tag.rb
-
-ee/db/**/*
-ee/app/serializers/ee/merge_request_widget_entity.rb
-ee/lib/api/epics.rb
-ee/lib/api/geo_nodes.rb
-ee/lib/ee/api/group_boards.rb
-ee/lib/ee/api/boards.rb
-ee/lib/ee/gitlab/ldap/sync/admin_users.rb
-ee/app/workers/geo/file_download_dispatch_worker/job_artifact_job_finder.rb
-ee/app/workers/geo/file_download_dispatch_worker/lfs_object_job_finder.rb
-ee/spec/**/*
diff --git a/.gitignore b/.gitignore
index 9a42a663fb4..eb0875a977f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,3 +77,4 @@ eslint-report.html
/plugins/*
/.gitlab_pages_secret
package-lock.json
+/junit_rspec.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 23d71675ae4..1b4134282c9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -171,7 +171,7 @@ stages:
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
- scripts/gitaly-test-spawn
- - knapsack rspec "--color --format documentation"
+ - knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml"
artifacts:
expire_in: 31d
when: always
@@ -180,6 +180,8 @@ stages:
- knapsack/
- rspec_flaky/
- tmp/capybara/
+ reports:
+ junit: junit_rspec.xml
.rspec-metadata-pg: &rspec-metadata-pg
<<: *rspec-metadata
@@ -311,10 +313,13 @@ review-docs-cleanup:
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
action: stop
- when: manual
script:
- gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME cleanup
+ when: manual
+ only:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
##
# Trigger a docker image build in CNG (Cloud Native GitLab) repository
diff --git a/.haml-lint.yml b/.haml-lint.yml
index fcdc47af60f..bad918ef35d 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -113,7 +113,6 @@ linters:
- Lint/ParenthesesAsGroupedExpression
- Lint/RedundantWithIndex
- Lint/Syntax
- - Lint/UselessAssignment
- Metrics/BlockNesting
- Naming/VariableName
- Performance/RedundantMatch
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e022b7e52b..c1d5a638cd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,9 @@ entry.
## 11.2.3 (2018-08-28)
-- No changes.
+### Fixed (1 change)
+
+- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.2.2 (2018-08-27)
@@ -269,7 +271,9 @@ entry.
## 11.1.6 (2018-08-28)
-- No changes.
+### Fixed (1 change)
+
+- Fixed cache invalidation issue with diff lines from 11.2.2.
## 11.1.5 (2018-08-27)
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 3eefcb9dd5b..9084fa2f716 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.0.0
+1.1.0
diff --git a/Gemfile b/Gemfile
index 208289cb7fb..7b83c6d1178 100644
--- a/Gemfile
+++ b/Gemfile
@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
+# Export Ruby Regex to Javascript
+gem 'js_regex', '~> 2.2.1'
+
# User agent parsing
gem 'device_detector'
@@ -360,12 +363,11 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
- gem 'flay', '~> 2.10.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
- gem 'license_finder', '~> 3.1', require: false
+ gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.16'
gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
@@ -387,6 +389,7 @@ group :test do
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.0.5'
gem 'test-prof', '~> 0.2.5'
+ gem 'rspec_junit_formatter'
end
gem 'octokit', '~> 4.9'
diff --git a/Gemfile.lock b/Gemfile.lock
index 77effb63d2e..b9fa9c74919 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -86,7 +86,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
@@ -210,11 +209,6 @@ GEM
fast_gettext (1.6.0)
ffaker (2.4.0)
ffi (1.9.18)
- flay (2.10.0)
- erubis (~> 2.7.0)
- path_expander (~> 1.0)
- ruby_parser (~> 3.0)
- sexp_processor (~> 4.0)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -428,6 +422,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
+ js_regex (2.2.1)
+ regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
@@ -463,13 +459,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
- license_finder (3.1.1)
+ license_finder (5.4.0)
bundler
- httparty
rubyzip
thor
- toml (= 0.1.2)
- with_env (> 1.0)
+ toml (= 0.2.0)
+ with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
@@ -587,9 +582,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
- parslet (1.5.0)
- blankslate (~> 2.0)
- path_expander (1.0.2)
+ parslet (1.8.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
@@ -726,6 +719,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
+ regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -776,6 +770,9 @@ GEM
rspec-core
rspec-set (0.1.3)
rspec-support (3.7.1)
+ rspec_junit_formatter (0.2.3)
+ builder (< 4)
+ rspec-core (>= 2, < 4, != 2.12.0)
rspec_profiling (0.0.5)
activerecord
pg
@@ -907,8 +904,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
- toml (0.1.2)
- parslet (~> 1.5.0)
+ toml (0.2.0)
+ parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
@@ -1024,7 +1021,6 @@ DEPENDENCIES
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
- flay (~> 2.10.0)
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
@@ -1074,13 +1070,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
+ js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
- license_finder (~> 3.1)
+ license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
@@ -1149,6 +1146,7 @@ DEPENDENCIES
rspec-rails (~> 3.7.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
+ rspec_junit_formatter
rspec_profiling (~> 0.0.5)
rubocop (~> 0.54.0)
rubocop-rspec (~> 1.22.1)
@@ -1201,4 +1199,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.3
+ 1.16.4
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 63b450d3f62..0171c3564e3 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -89,7 +89,6 @@ GEM
bindata (2.4.3)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- blankslate (2.1.2.4)
bootsnap (1.3.1)
msgpack (~> 1.0)
bootstrap_form (2.7.0)
@@ -213,11 +212,6 @@ GEM
fast_gettext (1.6.0)
ffaker (2.4.0)
ffi (1.9.18)
- flay (2.10.0)
- erubis (~> 2.7.0)
- path_expander (~> 1.0)
- ruby_parser (~> 3.0)
- sexp_processor (~> 4.0)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -431,6 +425,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
+ js_regex (2.2.1)
+ regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
@@ -466,13 +462,12 @@ GEM
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
- license_finder (3.1.1)
+ license_finder (5.4.0)
bundler
- httparty
rubyzip
thor
- toml (= 0.1.2)
- with_env (> 1.0)
+ toml (= 0.2.0)
+ with_env (= 1.1.0)
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
@@ -591,9 +586,7 @@ GEM
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
- parslet (1.5.0)
- blankslate (~> 2.0)
- path_expander (1.0.2)
+ parslet (1.8.2)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
@@ -735,6 +728,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
+ regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -785,6 +779,8 @@ GEM
rspec-core
rspec-set (0.1.3)
rspec-support (3.7.1)
+ rspec_junit_formatter (0.4.1)
+ rspec-core (>= 2, < 4, != 2.12.0)
rspec_profiling (0.0.5)
activerecord
pg
@@ -914,8 +910,8 @@ GEM
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
- toml (0.1.2)
- parslet (~> 1.5.0)
+ toml (0.2.0)
+ parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
trollop (2.1.3)
@@ -1034,7 +1030,6 @@ DEPENDENCIES
faraday (~> 0.12)
fast_blank
ffaker (~> 2.4)
- flay (~> 2.10.0)
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
@@ -1084,13 +1079,14 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
+ js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.16)
kubeclient (~> 3.1.0)
letter_opener_web (~> 1.3.0)
- license_finder (~> 3.1)
+ license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
loofah (~> 2.2)
@@ -1160,6 +1156,7 @@ DEPENDENCIES
rspec-rails (~> 3.7.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
+ rspec_junit_formatter
rspec_profiling (~> 0.0.5)
rubocop (~> 0.54.0)
rubocop-rspec (~> 1.22.1)
@@ -1211,4 +1208,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.3
+ 1.16.4
diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue
index 7a13f74c570..b3f25da87ce 100644
--- a/app/assets/javascripts/badges/components/badge_form.vue
+++ b/app/assets/javascripts/badges/components/badge_form.vue
@@ -23,6 +23,11 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ wasValidated: false,
+ };
+ },
computed: {
...mapState([
'badgeInAddForm',
@@ -39,16 +44,6 @@ export default {
return this.badgeInAddForm;
},
- canSubmit() {
- return (
- this.badge !== null &&
- this.badge.imageUrl &&
- this.badge.imageUrl.trim() !== '' &&
- this.badge.linkUrl &&
- this.badge.linkUrl.trim() !== '' &&
- !this.isSaving
- );
- },
helpText() {
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
.map(placeholder => `<code>%{${placeholder}}</code>`)
@@ -93,11 +88,18 @@ export default {
});
},
},
- submitButtonLabel() {
- if (this.isEditing) {
- return s__('Badges|Save changes');
- }
- return s__('Badges|Add badge');
+ badgeImageUrlExample() {
+ const exampleUrl =
+ 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg';
+ return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
+ exampleUrl,
+ });
+ },
+ badgeLinkUrlExample() {
+ const exampleUrl = 'https://example.gitlab.com/%{project_path}';
+ return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
+ exampleUrl,
+ });
},
},
methods: {
@@ -109,7 +111,9 @@ export default {
this.stopEditing();
},
onSubmit() {
- if (!this.canSubmit) {
+ const form = this.$el;
+ if (!form.checkValidity()) {
+ this.wasValidated = true;
return Promise.resolve();
}
@@ -117,6 +121,7 @@ export default {
return this.saveBadge()
.then(() => {
createFlash(s__('Badges|The badge was saved.'), 'notice');
+ this.wasValidated = false;
})
.catch(error => {
createFlash(
@@ -129,6 +134,7 @@ export default {
return this.addBadge()
.then(() => {
createFlash(s__('Badges|A new badge was added.'), 'notice');
+ this.wasValidated = false;
})
.catch(error => {
createFlash(
@@ -138,47 +144,58 @@ export default {
});
},
},
- badgeImageUrlPlaceholder:
- 'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
- badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
};
</script>
<template>
<form
- class="prepend-top-default append-bottom-default"
+ :class="{ 'was-validated': wasValidated }"
+ class="prepend-top-default append-bottom-default needs-validation"
+ novalidate
@submit.prevent.stop="onSubmit"
>
<div class="form-group">
- <label for="badge-link-url">{{ s__('Badges|Link') }}</label>
+ <label
+ for="badge-link-url"
+ class="label-bold"
+ >{{ s__('Badges|Link') }}</label>
+ <p v-html="helpText"></p>
<input
id="badge-link-url"
v-model="linkUrl"
- :placeholder="$options.badgeLinkUrlPlaceholder"
- type="text"
+ type="URL"
class="form-control"
+ required
@input="debouncedPreview"
/>
- <span
- class="form-text text-muted"
- v-html="helpText"
- ></span>
+ <div class="invalid-feedback">
+ {{ s__('Badges|Please fill in a valid URL') }}
+ </div>
+ <span class="form-text text-muted">
+ {{ badgeLinkUrlExample }}
+ </span>
</div>
<div class="form-group">
- <label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
+ <label
+ for="badge-image-url"
+ class="label-bold"
+ >{{ s__('Badges|Badge image URL') }}</label>
+ <p v-html="helpText"></p>
<input
id="badge-image-url"
v-model="imageUrl"
- :placeholder="$options.badgeImageUrlPlaceholder"
- type="text"
+ type="URL"
class="form-control"
+ required
@input="debouncedPreview"
/>
- <span
- class="form-text text-muted"
- v-html="helpText"
- ></span>
+ <div class="invalid-feedback">
+ {{ s__('Badges|Please fill in a valid URL') }}
+ </div>
+ <span class="form-text text-muted">
+ {{ badgeImageUrlExample }}
+ </span>
</div>
<div class="form-group">
@@ -200,20 +217,32 @@ export default {
>{{ s__('Badges|No image to preview') }}</p>
</div>
- <div class="row-content-block">
+ <div
+ v-if="isEditing"
+ class="row-content-block"
+ >
<loading-button
- :disabled="!canSubmit"
:loading="isSaving"
- :label="submitButtonLabel"
+ :label="s__('Badges|Save changes')"
type="submit"
container-class="btn btn-success"
/>
<button
- v-if="isEditing"
class="btn btn-cancel"
type="button"
@click="onCancel"
>{{ __('Cancel') }}</button>
</div>
+ <div
+ v-else
+ class="form-group"
+ >
+ <loading-button
+ :loading="isSaving"
+ :label="s__('Badges|Add badge')"
+ type="submit"
+ container-class="btn btn-success"
+ />
+ </div>
</form>
</template>
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
index 268968b63b3..d2ec0fbb2c0 100644
--- a/app/assets/javascripts/badges/components/badge_list.vue
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -28,7 +28,7 @@ export default {
{{ s__('Badges|Your badges') }}
<span
v-show="!isLoading"
- class="badge"
+ class="badge badge-pill"
>{{ badges.length }}</span>
</div>
<loading-icon
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
index 98aa00af0d7..712d81d0430 100644
--- a/app/assets/javascripts/badges/components/badge_list_row.vue
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -43,13 +43,13 @@ export default {
<badge
:image-url="badge.renderedImageUrl"
:link-url="badge.renderedLinkUrl"
- class="table-section section-30"
+ class="table-section section-40"
/>
- <span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
- <div class="table-section section-10">
- <span class="badge">{{ badgeKindText }}</span>
+ <span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span>
+ <div class="table-section section-15">
+ <span class="badge badge-pill">{{ badgeKindText }}</span>
</div>
- <div class="table-section section-10 table-button-footer">
+ <div class="table-section section-15 table-button-footer">
<div
v-if="canEditBadge"
class="table-action-buttons">
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index a0af2875ab5..a29de9ae899 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
flashEl.addEventListener('transitionend', () => {
flashEl.remove();
+ window.dispatchEvent(new Event('resize'));
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
}, {
once: true,
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index 2c8305aa0cc..6a5ab35a16a 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -78,13 +78,13 @@ export default {
</script>
<template>
- <article class="ide">
+ <article class="ide position-relative d-flex flex-column align-items-stretch">
<error-message
v-if="errorMessage"
:message="errorMessage"
/>
<div
- class="ide-view"
+ class="ide-view flex-grow d-flex"
>
<find-file
v-show="fileFindVisible"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index 5b1743bb30e..e2be805ed22 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -24,12 +24,6 @@ export default {
default: null,
},
},
- mounted() {
- this.$refs.fileUpload.addEventListener('change', this.openFile);
- },
- beforeDestroy() {
- this.$refs.fileUpload.removeEventListener('change', this.openFile);
- },
methods: {
createFile(target, file, isText) {
const { name } = file;
@@ -85,6 +79,8 @@ export default {
ref="fileUpload"
type="file"
class="hidden"
+ multiple
+ @change="openFile"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue
index dbdf0be2809..110eda83bb4 100644
--- a/app/assets/javascripts/ide/components/repo_file.vue
+++ b/app/assets/javascripts/ide/components/repo_file.vue
@@ -95,16 +95,18 @@ export default {
return this.file.changed || this.file.tempFile || this.file.staged;
},
},
+ watch: {
+ 'file.active': function fileActiveWatch(active) {
+ if (this.file.type === 'blob' && active) {
+ this.scrollIntoView();
+ }
+ },
+ },
mounted() {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
},
- updated() {
- if (this.file.type === 'blob' && this.file.active) {
- this.scrollIntoView();
- }
- },
methods: {
...mapActions(['toggleTreeOpen']),
clickFile() {
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
new file mode 100644
index 00000000000..7f5406d6f43
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -0,0 +1,175 @@
+import Visibility from 'visibilityjs';
+import * as types from './mutation_types';
+import axios from '../../lib/utils/axios_utils';
+import Poll from '../../lib/utils/poll';
+import { setCiStatusFavicon } from '../../lib/utils/common_utils';
+import flash from '../../flash';
+import { __ } from '../../locale';
+
+export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint);
+export const setTraceEndpoint = ({ commit }, endpoint) =>
+ commit(types.SET_TRACE_ENDPOINT, endpoint);
+export const setStagesEndpoint = ({ commit }, endpoint) =>
+ commit(types.SET_STAGES_ENDPOINT, endpoint);
+export const setJobsEndpoint = ({ commit }, endpoint) => commit(types.SET_JOBS_ENDPOINT, endpoint);
+
+let eTagPoll;
+
+export const clearEtagPoll = () => {
+ eTagPoll = null;
+};
+
+export const stopPolling = () => {
+ if (eTagPoll) eTagPoll.stop();
+};
+
+export const restartPolling = () => {
+ if (eTagPoll) eTagPoll.restart();
+};
+
+export const requestJob = ({ commit }) => commit(types.REQUEST_JOB);
+
+export const fetchJob = ({ state, dispatch }) => {
+ dispatch('requestJob');
+
+ eTagPoll = new Poll({
+ resource: {
+ getJob(endpoint) {
+ return axios.get(endpoint);
+ },
+ },
+ data: state.jobEndpoint,
+ method: 'getJob',
+ successCallback: ({ data }) => dispatch('receiveJobSuccess', data),
+ errorCallback: () => dispatch('receiveJobError'),
+ });
+
+ if (!Visibility.hidden()) {
+ eTagPoll.makeRequest();
+ } else {
+ axios
+ .get(state.jobEndpoint)
+ .then(({ data }) => dispatch('receiveJobSuccess', data))
+ .catch(() => dispatch('receiveJobError'));
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ dispatch('restartPolling');
+ } else {
+ dispatch('stopPolling');
+ }
+ });
+};
+
+export const receiveJobSuccess = ({ commit }, data) => commit(types.RECEIVE_JOB_SUCCESS, data);
+export const receiveJobError = ({ commit }) => {
+ commit(types.RECEIVE_JOB_ERROR);
+ flash(__('An error occurred while fetching the job.'));
+};
+
+/**
+ * Job's Trace
+ */
+export const scrollTop = ({ commit }) => {
+ commit(types.SCROLL_TO_TOP);
+ window.scrollTo({ top: 0 });
+};
+
+export const scrollBottom = ({ commit }) => {
+ commit(types.SCROLL_TO_BOTTOM);
+ window.scrollTo({ top: document.height });
+};
+
+export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE);
+
+let traceTimeout;
+export const fetchTrace = ({ dispatch, state }) => {
+ dispatch('requestTrace');
+
+ axios
+ .get(`${state.traceEndpoint}/trace.json`, {
+ params: { state: state.traceState },
+ })
+ .then(({ data }) => {
+ if (!state.fetchingStatusFavicon) {
+ dispatch('fetchFavicon');
+ }
+ dispatch('receiveTraceSuccess', data);
+
+ if (!data.complete) {
+ traceTimeout = setTimeout(() => {
+ dispatch('fetchTrace');
+ }, 4000);
+ } else {
+ dispatch('stopPollingTrace');
+ }
+ })
+ .catch(() => dispatch('receiveTraceError'));
+};
+export const stopPollingTrace = ({ commit }) => {
+ commit(types.STOP_POLLING_TRACE);
+ clearTimeout(traceTimeout);
+};
+export const receiveTraceSuccess = ({ commit }, log) => commit(types.RECEIVE_TRACE_SUCCESS, log);
+export const receiveTraceError = ({ commit }) => {
+ commit(types.RECEIVE_TRACE_ERROR);
+ clearTimeout(traceTimeout);
+ flash(__('An error occurred while fetching the job log.'));
+};
+
+export const fetchFavicon = ({ state, dispatch }) => {
+ dispatch('requestStatusFavicon');
+ setCiStatusFavicon(`${state.pagePath}/status.json`)
+ .then(() => dispatch('receiveStatusFaviconSuccess'))
+ .catch(() => dispatch('requestStatusFaviconError'));
+};
+export const requestStatusFavicon = ({ commit }) => commit(types.REQUEST_STATUS_FAVICON);
+export const receiveStatusFaviconSuccess = ({ commit }) =>
+ commit(types.RECEIVE_STATUS_FAVICON_SUCCESS);
+export const requestStatusFaviconError = ({ commit }) => commit(types.RECEIVE_STATUS_FAVICON_ERROR);
+
+/**
+ * Stages dropdown on sidebar
+ */
+export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
+export const fetchStages = ({ state, dispatch }) => {
+ dispatch('requestStages');
+
+ axios
+ .get(state.stagesEndpoint)
+ .then(({ data }) => dispatch('receiveStagesSuccess', data))
+ .catch(() => dispatch('receiveStagesError'));
+};
+export const receiveStagesSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_STAGES_SUCCESS, data);
+export const receiveStagesError = ({ commit }) => {
+ commit(types.RECEIVE_STAGES_ERROR);
+ flash(__('An error occurred while fetching stages.'));
+};
+
+/**
+ * Jobs list on sidebar - depend on stages dropdown
+ */
+export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
+export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
+
+// On stage click, set selected stage + fetch job
+export const fetchJobsForStage = ({ state, dispatch }, stage) => {
+ dispatch('setSelectedStage', stage);
+ dispatch('requestJobsForStage');
+
+ axios
+ .get(state.stageJobsEndpoint)
+ .then(({ data }) => dispatch('receiveJobsForStageSuccess', data))
+ .catch(() => dispatch('receiveJobsForStageError'));
+};
+export const receiveJobsForStageSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, data);
+export const receiveJobsForStageError = ({ commit }) => {
+ commit(types.RECEIVE_JOBS_FOR_STAGE_ERROR);
+ flash(__('An error occurred while fetching the jobs.'));
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/jobs/store/index.js b/app/assets/javascripts/jobs/store/index.js
new file mode 100644
index 00000000000..d8f6f56ce61
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/index.js
@@ -0,0 +1,13 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default () => new Vuex.Store({
+ actions,
+ mutations,
+ state: state(),
+});
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
new file mode 100644
index 00000000000..e66e1d4f116
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -0,0 +1,29 @@
+export const SET_JOB_ENDPOINT = 'SET_JOB_ENDPOINT';
+export const SET_TRACE_ENDPOINT = 'SET_TRACE_ENDPOINT';
+export const SET_STAGES_ENDPOINT = 'SET_STAGES_ENDPOINT';
+export const SET_JOBS_ENDPOINT = 'SET_JOBS_ENDPOINT';
+
+export const SCROLL_TO_TOP = 'SCROLL_TO_TOP';
+export const SCROLL_TO_BOTTOM = 'SCROLL_TO_BOTTOM';
+
+export const REQUEST_JOB = 'REQUEST_JOB';
+export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS';
+export const RECEIVE_JOB_ERROR = 'RECEIVE_JOB_ERROR';
+
+export const REQUEST_TRACE = 'REQUEST_TRACE';
+export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
+export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
+export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
+
+export const REQUEST_STATUS_FAVICON = 'REQUEST_STATUS_FAVICON';
+export const RECEIVE_STATUS_FAVICON_SUCCESS = 'RECEIVE_STATUS_FAVICON_SUCCESS';
+export const RECEIVE_STATUS_FAVICON_ERROR = 'RECEIVE_STATUS_FAVICON_ERROR';
+
+export const REQUEST_STAGES = 'REQUEST_STAGES';
+export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
+export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
+
+export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
+export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE';
+export const RECEIVE_JOBS_FOR_STAGE_SUCCESS = 'RECEIVE_JOBS_FOR_STAGE_SUCCESS';
+export const RECEIVE_JOBS_FOR_STAGE_ERROR = 'RECEIVE_JOBS_FOR_STAGE_ERROR';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
new file mode 100644
index 00000000000..2a451ef0cd1
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -0,0 +1,94 @@
+/* eslint-disable no-param-reassign */
+
+import * as types from './mutation_types';
+
+export default {
+ [types.REQUEST_STATUS_FAVICON](state) {
+ state.fetchingStatusFavicon = true;
+ },
+ [types.RECEIVE_STATUS_FAVICON_SUCCESS](state) {
+ state.fetchingStatusFavicon = false;
+ },
+ [types.RECEIVE_STATUS_FAVICON_ERROR](state) {
+ state.fetchingStatusFavicon = false;
+ },
+
+ [types.RECEIVE_TRACE_SUCCESS](state, log) {
+ if (log.state) {
+ state.traceState = log.state;
+ }
+
+ if (log.append) {
+ state.trace += log.html;
+ state.traceSize += log.size;
+ } else {
+ state.trace = log.html;
+ state.traceSize = log.size;
+ }
+
+ if (state.traceSize < log.total) {
+ state.isTraceSizeVisible = true;
+ } else {
+ state.isTraceSizeVisible = false;
+ }
+
+ state.isTraceComplete = log.complete;
+ state.hasTraceError = false;
+ },
+ [types.STOP_POLLING_TRACE](state) {
+ state.isTraceComplete = true;
+ },
+ // todo_fl: check this.
+ [types.RECEIVE_TRACE_ERROR](state) {
+ state.isLoadingTrace = false;
+ state.isTraceComplete = true;
+ state.hasTraceError = true;
+ },
+
+ [types.REQUEST_JOB](state) {
+ state.isLoading = true;
+ },
+ [types.RECEIVE_JOB_SUCCESS](state, job) {
+ state.isLoading = false;
+ state.hasError = false;
+ state.job = job;
+ },
+ [types.RECEIVE_JOB_ERROR](state) {
+ state.isLoading = false;
+ state.hasError = true;
+ state.job = {};
+ },
+
+ [types.SCROLL_TO_TOP](state) {
+ state.isTraceScrolledToBottom = false;
+ state.hasBeenScrolled = true;
+ },
+ [types.SCROLL_TO_BOTTOM](state) {
+ state.isTraceScrolledToBottom = true;
+ state.hasBeenScrolled = true;
+ },
+
+ [types.REQUEST_STAGES](state) {
+ state.isLoadingStages = true;
+ },
+ [types.RECEIVE_STAGES_SUCCESS](state, stages) {
+ state.isLoadingStages = false;
+ state.stages = stages;
+ },
+ [types.RECEIVE_STAGES_ERROR](state) {
+ state.isLoadingStages = false;
+ state.stages = [];
+ },
+
+ [types.REQUEST_JOBS_FOR_STAGE](state) {
+ state.isLoadingJobs = true;
+ },
+ [types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
+ state.isLoadingJobs = false;
+ state.jobs = jobs;
+ },
+ [types.RECEIVE_JOBS_FOR_STAGE_ERROR](state) {
+ state.isLoadingJobs = false;
+ state.jobs = [];
+ },
+};
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
new file mode 100644
index 00000000000..509cb69a5d3
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -0,0 +1,40 @@
+export default () => ({
+ jobEndpoint: null,
+ traceEndpoint: null,
+
+ // dropdown options
+ stagesEndpoint: null,
+ // list of jobs on sidebard
+ stageJobsEndpoint: null,
+
+ // job log
+ isLoading: false,
+ hasError: false,
+ job: {},
+
+ // trace
+ isLoadingTrace: false,
+ hasTraceError: false,
+
+ trace: '',
+
+ isTraceScrolledToBottom: false,
+ hasBeenScrolled: false,
+
+ isTraceComplete: false,
+ traceSize: 0, // todo_fl: needs to be converted into human readable format in components
+ isTraceSizeVisible: false,
+
+ fetchingStatusFavicon: false,
+ // used as a query parameter
+ traceState: null,
+ // used to check if we need to redirect the user - todo_fl: check if actually needed
+ traceStatus: null,
+
+ // sidebar dropdown
+ isLoadingStages: false,
+ isLoadingJobs: false,
+ selectedStage: null,
+ stages: [],
+ jobs: [],
+});
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 2f3dd6f6cbc..3e208764b3e 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -491,7 +491,10 @@ export const setCiStatusFavicon = pageUrl =>
}
return resetFavicon();
})
- .catch(resetFavicon);
+ .catch((error) => {
+ resetFavicon();
+ throw error;
+ });
export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';
diff --git a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
new file mode 100644
index 00000000000..7281f907ec7
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
@@ -0,0 +1,25 @@
+import { __ } from '~/locale';
+
+export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern');
+export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external');
+
+function setUserInternalRegexPlaceholder(checkbox) {
+ const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex');
+ if (checkbox && userInternalRegex) {
+ if (checkbox.checked) {
+ userInternalRegex.readOnly = false;
+ userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE;
+ } else {
+ userInternalRegex.readOnly = true;
+ userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE;
+ }
+ }
+}
+
+export default function initUserInternalRegexPlaceholder() {
+ const checkbox = document.getElementById('application_setting_user_default_external');
+ setUserInternalRegexPlaceholder(checkbox);
+ checkbox.addEventListener('change', () => {
+ setUserInternalRegexPlaceholder(checkbox);
+ });
+}
diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js
index e50b61f09e2..3aa793e47b9 100644
--- a/app/assets/javascripts/pages/admin/index.js
+++ b/app/assets/javascripts/pages/admin/index.js
@@ -1,3 +1,7 @@
import initAdmin from './admin';
+import initUserInternalRegexPlaceholder from './application_settings/account_and_limits';
-document.addEventListener('DOMContentLoaded', initAdmin);
+document.addEventListener('DOMContentLoaded', () => {
+ initAdmin();
+ initUserInternalRegexPlaceholder();
+});
diff --git a/app/assets/javascripts/pages/admin/users/new/index.js b/app/assets/javascripts/pages/admin/users/new/index.js
new file mode 100644
index 00000000000..58bfa8d64e7
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/users/new/index.js
@@ -0,0 +1,49 @@
+import $ from 'jquery';
+
+export default class UserInternalRegexHandler {
+ constructor() {
+ this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
+ if (this.regexPattern && this.regexPattern !== '') {
+ this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options');
+ this.external = $('#user_external');
+ this.warningMessage = $('#warning_external_automatically_set');
+ this.addListenerToEmailField();
+ this.addListenerToUserExternalCheckbox();
+ }
+ }
+
+ addListenerToEmailField() {
+ $('#user_email').on('input', (event) => {
+ this.setExternalCheckbox(event.currentTarget.value);
+ });
+ }
+
+ addListenerToUserExternalCheckbox() {
+ this.external.on('click', () => {
+ this.warningMessage.addClass('hidden');
+ });
+ }
+
+ isEmailInternal(email) {
+ const regex = new RegExp(this.regexPattern, this.regexOptions);
+ return regex.test(email);
+ }
+
+ setExternalCheckbox(email) {
+ const isChecked = this.external.prop('checked');
+ if (this.isEmailInternal(email)) {
+ if (isChecked) {
+ this.external.prop('checked', false);
+ this.warningMessage.removeClass('hidden');
+ }
+ } else if (!isChecked) {
+ this.external.prop('checked', true);
+ this.warningMessage.addClass('hidden');
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ // eslint-disable-next-line
+ new UserInternalRegexHandler();
+});
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 8737f537296..002b2279fcc 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -2,14 +2,13 @@ import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+import { GROUP_BADGE } from '~/badges/constants';
document.addEventListener('DOMContentLoaded', () => {
groupAvatar();
new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal();
-});
-
-document.addEventListener('DOMContentLoaded', () => {
- // Initialize expandable settings panels
initSettingsPanels();
+ mountBadgeSettings(GROUP_BADGE);
});
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 628913483c6..f5b1cf85e68 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -1,6 +1,8 @@
+import { PROJECT_BADGE } from '~/badges/constants';
import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
+import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions';
@@ -13,4 +15,5 @@ document.addEventListener('DOMContentLoaded', () => {
projectAvatar();
initProjectPermissionsSettings();
initConfirmDangerModal();
+ mountBadgeSettings(PROJECT_BADGE);
});
diff --git a/app/assets/javascripts/pages/projects/settings/badges/index/index.js b/app/assets/javascripts/pages/projects/settings/badges/index/index.js
deleted file mode 100644
index 30469550866..00000000000
--- a/app/assets/javascripts/pages/projects/settings/badges/index/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Vue from 'vue';
-import Translate from '~/vue_shared/translate';
-import { PROJECT_BADGE } from '~/badges/constants';
-import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
-
-Vue.use(Translate);
-
-document.addEventListener('DOMContentLoaded', () => {
- mountBadgeSettings(PROJECT_BADGE);
-});
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 72bd28ae03f..4c3f8dff3c4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -4,6 +4,7 @@ import { n__, s__, sprintf } from '~/locale';
import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
export default {
@@ -13,6 +14,9 @@ export default {
clipboardButton,
TooltipOnTruncate,
},
+ directives: {
+ tooltip,
+ },
props: {
mr: {
type: Object,
@@ -40,10 +44,19 @@ export default {
});
},
webIdePath() {
- return mergeUrlParams({
- target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ?
- this.mr.targetProjectFullPath : '',
- }, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`));
+ if (this.mr.canPushToSourceBranch) {
+ return mergeUrlParams({
+ target_project: this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath ?
+ this.mr.targetProjectFullPath : '',
+ }, webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`));
+ }
+
+ return null;
+ },
+ ideButtonTitle() {
+ return !this.mr.canPushToSourceBranch
+ ? s__('mrWidget|You are not allowed to edit this project directly. Please fork to make changes.')
+ : '';
},
},
};
@@ -93,13 +106,22 @@ export default {
v-if="mr.isOpen"
class="branch-actions"
>
- <a
- v-if="!mr.sourceBranchRemoved"
- :href="webIdePath"
- class="btn btn-default inline js-web-ide d-none d-md-inline-block"
+ <span
+ v-tooltip
+ :title="ideButtonTitle"
+ data-placement="bottom"
+ tabindex="0"
>
- {{ s__("mrWidget|Open in Web IDE") }}
- </a>
+ <a
+ v-if="!mr.sourceBranchRemoved"
+ :href="webIdePath"
+ :class="{ disabled: !mr.canPushToSourceBranch }"
+ class="btn btn-default inline js-web-ide d-none d-md-inline-block"
+ role="button"
+ >
+ {{ s__("mrWidget|Open in Web IDE") }}
+ </a>
+ </span>
<button
:disabled="mr.sourceBranchRemoved"
data-target="#modal_merge_info"
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index a8ec1e1145a..6c50ea719d3 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -3,6 +3,6 @@ gl-emoji {
display: inline-flex;
vertical-align: middle;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
- font-size: 1.5em;
- line-height: 0.9;
+ font-size: 1.4em;
+ line-height: 1em;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 52b5f059f20..d4bae4cb137 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -111,3 +111,42 @@ body {
.with-performance-bar .layout-page {
margin-top: $header-height + $performance-bar-height;
}
+
+.fullscreen-layout {
+ padding-top: 0;
+ height: 100vh;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ overflow: hidden;
+
+ > #js-peek,
+ > .navbar-gitlab {
+ position: static;
+ top: auto;
+ }
+
+ .flash-container {
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+
+ .alert-wrapper .flash-container .flash-alert:last-child,
+ .alert-wrapper .flash-container .flash-notice:last-child {
+ margin-bottom: 0;
+ }
+
+ .content-wrapper {
+ margin-top: 0;
+ padding-bottom: 0;
+ flex: 1;
+ min-height: 0;
+ }
+
+ &.flash-shown {
+ .content-wrapper {
+ margin-top: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 6244fb86fea..033e5e57177 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -4,11 +4,6 @@
margin-top: 20px;
}
- .container-fluid {
- padding-left: 5px;
- padding-right: 5px;
- }
-
.nav-links > li > a {
padding: 10px;
font-size: 12px;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 5c6110737a4..9929f1bdebf 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -327,7 +327,7 @@ h6 {
pre {
font-family: $monospace-font;
display: block;
- padding: $gl-padding-8;
+ padding: $gl-padding-8 $input-horizontal-padding;
margin: 0 0 $gl-padding-8;
font-size: 13px;
word-break: break-all;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 2781d910b8d..d76f5cbd9ff 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -236,6 +236,7 @@ $gl-vert-padding: 6px;
$gl-padding-top: 10px;
$gl-sidebar-padding: 22px;
$gl-bar-padding: 3px;
+$input-horizontal-padding: 12px;
/*
* Misc
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index eac1345742d..5ff4e487d04 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -28,11 +28,10 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-view {
position: relative;
- display: flex;
- height: calc(100vh - #{$header-height});
margin-top: 0;
padding-bottom: $ide-statusbar-height;
color: $gl-text-color;
+ min-height: 0; // firefox fix
&.is-collapsed {
.ide-file-list {
@@ -50,7 +49,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
display: flex;
flex-direction: column;
flex: 1;
- min-height: 0;
+ min-height: 0; // firefox fix
.file {
height: 32px;
@@ -357,7 +356,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.multi-file-editor-holder {
height: 100%;
- min-height: 0;
+ min-height: 0; // firefox fix
&.is-readonly,
.editor.original {
@@ -546,7 +545,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
border-left: 1px solid $white-dark;
border-top: 1px solid $white-dark;
border-top-left-radius: $border-radius-small;
- min-height: 0;
+ min-height: 0; // firefox fix
}
}
@@ -758,7 +757,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide-loading {
display: flex;
- height: 100vh;
+ height: 100%;
align-items: center;
justify-content: center;
}
@@ -772,60 +771,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
.ide {
overflow: hidden;
-
- &.nav-only {
- padding-top: $header-height;
-
- .with-performance-bar & {
- padding-top: $header-height + $performance-bar-height;
- }
-
- .flash-container {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .alert-wrapper .flash-container .flash-alert:last-child,
- .alert-wrapper .flash-container .flash-notice:last-child {
- margin-bottom: 0;
- }
-
- .content-wrapper {
- margin-top: 0;
- padding-bottom: 0;
- }
-
- &.flash-shown {
- .content-wrapper {
- margin-top: 0;
- }
-
- .ide-view {
- height: calc(100vh - #{$header-height + $flash-height});
- }
- }
- }
-}
-
-.with-performance-bar .ide.nav-only {
- .flash-container {
- margin-top: 0;
- }
-
- .content-wrapper {
- margin-top: 0;
- padding-bottom: 0;
- }
-
- .ide-view {
- height: calc(100vh - #{$header-height + $performance-bar-height});
- }
-
- &.flash-shown {
- .ide-view {
- height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
- }
- }
+ flex: 1;
}
.drag-handle {
@@ -1199,7 +1145,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.avatar-container {
- flex: initial;
+ flex: 0 0 auto;
margin-right: 0;
}
@@ -1209,7 +1155,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
}
.ide-context-body {
- min-height: 0;
+ min-height: 0; // firefox fix
}
.ide-sidebar-project-title {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index a999a70693e..7d7143631f2 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -25,10 +25,6 @@
color: $gl-text-color;
border-radius: 0 0 3px 3px;
- .code {
- padding: 0;
- }
-
.unfold {
cursor: pointer;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index fce04c58c24..dbe9f0c03fb 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -141,6 +141,9 @@ ul.notes {
}
.note-body {
+ overflow-x: auto;
+ overflow-y: hidden;
+
.note-text {
@include md-typography;
// Reset ul style types since we're nested inside a ul already
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 22b39f47bf0..a2c96f5d635 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -1,5 +1,6 @@
module IssuableCollections
extend ActiveSupport::Concern
+ include CookiesHelper
include SortingHelper
include Gitlab::IssuableMetadata
include Gitlab::Utils::StrongMemoize
@@ -107,11 +108,14 @@ module IssuableCollections
end
def set_sort_order_from_cookie
- cookies[remember_sorting_key] = params[:sort] if params[:sort].present?
+ sort_param = params[:sort] if params[:sort].present?
# fallback to legacy cookie value for backward compatibility
- cookies[remember_sorting_key] ||= cookies['issuable_sort']
- cookies[remember_sorting_key] = update_cookie_value(cookies[remember_sorting_key])
- params[:sort] = cookies[remember_sorting_key]
+ sort_param ||= cookies['issuable_sort']
+ sort_param ||= cookies[remember_sorting_key]
+
+ sort_value = update_cookie_value(sort_param)
+ set_secure_cookie(remember_sorting_key, sort_value)
+ params[:sort] = sort_value
end
def remember_sorting_key
diff --git a/app/controllers/concerns/sends_blob.rb b/app/controllers/concerns/sends_blob.rb
new file mode 100644
index 00000000000..971390d9118
--- /dev/null
+++ b/app/controllers/concerns/sends_blob.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module SendsBlob
+ extend ActiveSupport::Concern
+
+ included do
+ include BlobHelper
+ include SendFileUpload
+ end
+
+ def send_blob(blob, params = {})
+ if blob
+ headers['X-Content-Type-Options'] = 'nosniff'
+
+ return if cached_blob?(blob)
+
+ if blob.stored_externally?
+ send_lfs_object(blob)
+ else
+ send_git_blob(repository, blob, params)
+ end
+ else
+ render_404
+ end
+ end
+
+ private
+
+ def cached_blob?(blob)
+ stale = stale?(etag: blob.id) # The #stale? method sets cache headers.
+
+ # Because we are opinionated we set the cache headers ourselves.
+ response.cache_control[:public] = project.public?
+
+ response.cache_control[:max_age] =
+ if @ref && @commit && @ref == @commit.id # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ # This is a link to a commit by its commit SHA. That means that the blob
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ Blob::CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this blob. That means that the expected blob
+ # value may change over time.
+ Blob::CACHE_TIME
+ end
+
+ response.etag = blob.id
+ !stale
+ end
+
+ def send_lfs_object(blob)
+ lfs_object = find_lfs_object(blob)
+
+ if lfs_object && lfs_object.project_allowed_access?(project)
+ send_upload(lfs_object.file, attachment: blob.name)
+ else
+ render_404
+ end
+ end
+
+ def find_lfs_object(blob)
+ lfs_object = LfsObject.find_by_oid(blob.lfs_oid)
+ if lfs_object && lfs_object.file.exists?
+ lfs_object
+ else
+ nil
+ end
+ end
+end
diff --git a/app/controllers/groups/settings/badges_controller.rb b/app/controllers/groups/settings/badges_controller.rb
deleted file mode 100644
index ccbd0a3bc02..00000000000
--- a/app/controllers/groups/settings/badges_controller.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module Groups
- module Settings
- class BadgesController < Groups::ApplicationController
- include API::Helpers::RelatedResourcesHelpers
-
- before_action :authorize_admin_group!
-
- def index
- @badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id))
- end
- end
- end
-end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 83169636ccf..e57b9ff23a7 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -1,4 +1,5 @@
class GroupsController < Groups::ApplicationController
+ include API::Helpers::RelatedResourcesHelpers
include IssuesAction
include MergeRequestsAction
include ParamsBackwardCompatibility
@@ -77,6 +78,7 @@ class GroupsController < Groups::ApplicationController
end
def edit
+ @badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id))
end
def projects
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index 1ff25a45398..96bb2237d90 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -1,5 +1,5 @@
class IdeController < ApplicationController
- layout 'nav_only'
+ layout 'fullscreen'
def index
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index b4f814fd3a4..695ffd90a85 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,4 +1,5 @@
class Projects::ApplicationController < ApplicationController
+ include CookiesHelper
include RoutableActions
include ChecksCollaboration
@@ -74,7 +75,7 @@ class Projects::ApplicationController < ApplicationController
end
def apply_diff_view_cookie!
- cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
+ set_secure_cookie(:diff_view, params.delete(:view), permanent: true) if params[:view].present?
end
def require_pages_enabled!
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 53fdc5843b5..878c82cd183 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -1,24 +1,16 @@
class Projects::AvatarsController < Projects::ApplicationController
- include BlobHelper
+ include SendsBlob
before_action :authorize_admin_project!, only: [:destroy]
def show
@blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
- if @blob
- headers['X-Content-Type-Options'] = 'nosniff'
- return if cached_blob?
-
- send_git_blob @repository, @blob
- else
- render_404
- end
+ send_blob(@blob)
end
def destroy
@project.remove_avatar!
-
@project.save
redirect_to edit_project_path(@project, anchor: 'js-general-project-settings'), status: :found
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index ebc61264b39..56dafa31332 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -127,7 +127,7 @@ class Projects::BlobController < Projects::ApplicationController
add_match_line
- render json: @lines
+ render json: DiffLineSerializer.new.represent(@lines)
end
def add_match_line
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 1cba0011304..91cf35bc70b 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -1,8 +1,7 @@
# Controller for viewing a file's raw
class Projects::RawController < Projects::ApplicationController
include ExtractsPath
- include BlobHelper
- include SendFileUpload
+ include SendsBlob
before_action :require_non_empty_project
before_action :assign_ref_vars
@@ -10,39 +9,7 @@ class Projects::RawController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
- if @blob
- headers['X-Content-Type-Options'] = 'nosniff'
- return if cached_blob?
-
- if @blob.stored_externally?
- send_lfs_object
- else
- send_git_blob @repository, @blob, inline: (params[:inline] != 'false')
- end
- else
- render_404
- end
- end
-
- private
-
- def send_lfs_object
- lfs_object = find_lfs_object
-
- if lfs_object && lfs_object.project_allowed_access?(@project)
- send_upload(lfs_object.file, attachment: @blob.name)
- else
- render_404
- end
- end
-
- def find_lfs_object
- lfs_object = LfsObject.find_by_oid(@blob.lfs_oid)
- if lfs_object && lfs_object.file.exists?
- lfs_object
- else
- nil
- end
+ send_blob(@blob, inline: (params[:inline] != 'false'))
end
end
diff --git a/app/controllers/projects/settings/badges_controller.rb b/app/controllers/projects/settings/badges_controller.rb
deleted file mode 100644
index 7887bee49c5..00000000000
--- a/app/controllers/projects/settings/badges_controller.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module Projects
- module Settings
- class BadgesController < Projects::ApplicationController
- include API::Helpers::RelatedResourcesHelpers
-
- before_action :authorize_admin_project!
-
- def index
- @badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
- end
- end
- end
-end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index b17753222a0..7f2c3ca38ad 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -17,6 +17,11 @@ class Projects::TagsController < Projects::ApplicationController
tag_names = @tags.map(&:name)
@tags_pipelines = @project.pipelines.latest_successful_for_refs(tag_names)
@releases = project.releases.where(tag: tag_names)
+
+ respond_to do |format|
+ format.html
+ format.atom { render layout: 'xml.atom' }
+ end
end
def show
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index e9ae8c13142..0eaf9f94e37 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,4 +1,5 @@
class ProjectsController < Projects::ApplicationController
+ include API::Helpers::RelatedResourcesHelpers
include IssuableCollections
include ExtractsPath
include PreviewMarkdown
@@ -32,6 +33,7 @@ class ProjectsController < Projects::ApplicationController
end
def edit
+ @badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
render 'edit'
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 1e05f07e676..684c84c3006 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -255,6 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private,
:user_default_external,
:user_show_add_ssh_key_message,
+ :user_default_internal_regex,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index b61cbd5418a..00ebafd177b 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -157,28 +157,6 @@ module BlobHelper
end
end
- def cached_blob?
- stale = stale?(etag: @blob.id) # The #stale? method sets cache headers.
-
- # Because we are opionated we set the cache headers ourselves.
- response.cache_control[:public] = @project.public?
-
- response.cache_control[:max_age] =
- if @ref && @commit && @ref == @commit.id
- # This is a link to a commit by its commit SHA. That means that the blob
- # is immutable. The only reason to invalidate the cache is if the commit
- # was deleted or if the user lost access to the repository.
- Blob::CACHE_TIME_IMMUTABLE
- else
- # A branch or tag points at this blob. That means that the expected blob
- # value may change over time.
- Blob::CACHE_TIME
- end
-
- response.etag = @blob.id
- !stale
- end
-
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 7adc882bc47..26e3850a540 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -67,7 +67,7 @@ module ButtonHelper
def http_dropdown_description(protocol)
if current_user.try(:require_password_creation_for_git?)
_("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
- else
+ elsif current_user.try(:require_personal_access_token_creation_for_git_auth?)
_("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
end
end
diff --git a/app/helpers/cookies_helper.rb b/app/helpers/cookies_helper.rb
new file mode 100644
index 00000000000..3a7e9987190
--- /dev/null
+++ b/app/helpers/cookies_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module CookiesHelper
+ def set_secure_cookie(key, value, httponly: false, permanent: false)
+ cookie_jar = permanent ? cookies.permanent : cookies
+
+ cookie_jar[key] = { value: value, secure: Gitlab.config.gitlab.https, httponly: httponly }
+ end
+end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 30585cb403d..6535afb6425 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -55,7 +55,7 @@ module NamespacesHelper
# group if one exists by that name to prevent duplicates.
def dedup_extra_group(extra_group)
unless extra_group.persisted?
- existing_group = Group.find_by(name: extra_group.name)
+ existing_group = Group.find_by(path: extra_group.path)
extra_group = existing_group if existing_group&.persisted?
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index ebfde993456..ec2cf2b16c0 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -64,8 +64,7 @@ module SubmoduleHelper
end
def relative_self_url?(url)
- # (./)?(../repo.git) || (./)?(../../project/repo.git) )
- url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z}
+ url.start_with?('../', './')
end
def standard_links(host, namespace, project, commit)
@@ -73,25 +72,29 @@ module SubmoduleHelper
[base, [base, '/tree/', commit].join('')]
end
- def relative_self_links(url, commit, project)
- url.rstrip!
- # Map relative links to a namespace and project
- # For example:
- # ../bar.git -> same namespace, repo bar
- # ../foo/bar.git -> namespace foo, repo bar
- # ../../foo/bar/baz.git -> namespace bar, repo baz
- components = url.split('/')
- base = components.pop.gsub(/.git$/, '')
- namespace = components.pop.gsub(/^\.\.$/, '')
-
- if namespace.empty?
- namespace = project.namespace.full_path
+ def relative_self_links(relative_path, commit, project)
+ relative_path.rstrip!
+ absolute_project_path = "/" + project.full_path
+
+ # Resolve `relative_path` to target path
+ # Assuming `absolute_project_path` is `/g1/p1`:
+ # ../p2.git -> /g1/p2
+ # ../g2/p3.git -> /g1/g2/p3
+ # ../../g3/g4/p4.git -> /g3/g4/p4
+ submodule_project_path = File.absolute_path(relative_path, absolute_project_path)
+ target_namespace_path = File.dirname(submodule_project_path)
+
+ if target_namespace_path == '/' || target_namespace_path.start_with?(absolute_project_path)
+ return [nil, nil]
end
+ target_namespace_path.sub!(%r{^/}, '')
+ submodule_base = File.basename(submodule_project_path, '.git')
+
begin
[
- namespace_project_path(namespace, base),
- namespace_project_tree_path(namespace, base, commit)
+ namespace_project_path(target_namespace_path, submodule_base),
+ namespace_project_tree_path(target_namespace_path, submodule_base, commit)
]
rescue ActionController::UrlGenerationError
[nil, nil]
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index ceea4384f91..2c0c4254a0c 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -23,6 +23,17 @@ module UsersHelper
profile_tabs.include?(tab)
end
+ def user_internal_regex_data
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ pattern, options = if settings.user_default_internal_regex_enabled?
+ regex = settings.user_default_internal_regex_instance
+ JsRegex.new(regex).to_h.slice(:source, :options).values
+ end
+
+ { user_internal_regex_pattern: pattern, user_internal_regex_options: options }
+ end
+
def current_user_menu_items
@current_user_menu_items ||= get_current_user_menu_items
end
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index 41f9eedd4bd..17940aeb900 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -1,4 +1,6 @@
module WikiHelper
+ include API::Helpers::RelatedResourcesHelpers
+
# Produces a pure text breadcrumb for a given page.
#
# page_slug - The slug of a WikiPage object.
@@ -39,4 +41,8 @@ module WikiHelper
end
end
end
+
+ def wiki_attachment_upload_url
+ expose_url(api_v4_projects_wikis_attachments_path(id: @project.id))
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c77faa4b71d..03bd7fa016e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default
+ validates :user_default_internal_regex, js_regex: true, allow_nil: true
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
user_default_external: false,
+ user_default_internal_regex: nil,
user_show_add_ssh_key_message: true
}
end
@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end
+ def user_default_internal_regex_enabled?
+ user_default_external? && user_default_internal_regex.present?
+ end
+
+ def user_default_internal_regex_instance
+ Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
+ end
+
delegate :terms, to: :latest_terms, allow_nil: true
def latest_terms
@latest_terms ||= Term.latest
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 17b7ee4f07e..32d7cb3424e 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -48,6 +48,20 @@ module Ci
gzip: 3
}
+ # `file_location` indicates where actual files are stored.
+ # Ideally, actual files should be stored in the same directory, and use the same
+ # convention to generate its path. However, sometimes we can't do so due to backward-compatibility.
+ #
+ # legacy_path ... The actual file is stored at a path consists of a timestamp
+ # and raw project/model IDs. Those rows were migrated from
+ # `ci_builds.artifacts_file` and `ci_builds.artifacts_metadata`
+ # hashed_path ... The actual file is stored at a path consists of a SHA2 based on the project ID.
+ # This is the default value.
+ enum file_location: {
+ legacy_path: 1,
+ hashed_path: 2
+ }
+
FILE_FORMAT_ADAPTERS = {
gzip: Gitlab::Ci::Build::Artifacts::GzipFileAdapter
}.freeze
@@ -72,6 +86,10 @@ module Ci
[nil, ::JobArtifactUploader::Store::LOCAL].include?(self.file_store)
end
+ def hashed_path?
+ super || self.file_location.nil?
+ end
+
def expire_in
expire_at - Time.now if expire_at
end
@@ -108,7 +126,7 @@ module Ci
end
def update_project_statistics_after_destroy
- update_project_statistics(-self.size)
+ update_project_statistics(-self.size.to_i)
end
def update_project_statistics(difference)
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 7f6d48d972c..4e15b60ccd1 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -26,7 +26,7 @@
module AtomicInternalId
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example).
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 4200253053a..6f29c92d176 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -12,7 +12,7 @@ module Awardable
end
end
- module ClassMethods
+ class_methods do
def awarded(user, name)
sql = <<~EOL
EXISTS (
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index 0ba542b75ab..6e80365ee5b 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -4,7 +4,7 @@
module CaseSensitivity
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Queries the given columns regardless of the casing used.
#
# Unlike other ActiveRecord methods this method only operates on a Hash.
diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb
index a9e14cb55eb..8cf0b8b154d 100644
--- a/app/models/concerns/each_batch.rb
+++ b/app/models/concerns/each_batch.rb
@@ -3,7 +3,7 @@
module EachBatch
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Iterates over the rows in a relation in batches, similar to Rails'
# `in_batches` but in a more efficient way.
#
diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb
index 2b074c1921c..5c1f7dfcd2a 100644
--- a/app/models/concerns/ignorable_column.rb
+++ b/app/models/concerns/ignorable_column.rb
@@ -14,7 +14,7 @@
module IgnorableColumn
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def columns
super.reject { |column| ignored_columns.include?(column.name) }
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index e8072145551..f881ce2321c 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -118,7 +118,7 @@ module Issuable
end
end
- module ClassMethods
+ class_methods do
# Searches for records with a matching title.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
diff --git a/app/models/concerns/loaded_in_group_list.rb b/app/models/concerns/loaded_in_group_list.rb
index a2233eb2997..fc15c6d55ed 100644
--- a/app/models/concerns/loaded_in_group_list.rb
+++ b/app/models/concerns/loaded_in_group_list.rb
@@ -3,7 +3,7 @@
module LoadedInGroupList
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def with_counts(archived:)
selects_including_counts = [
'namespaces.*',
diff --git a/app/models/concerns/manual_inverse_association.rb b/app/models/concerns/manual_inverse_association.rb
index d0d781dc15f..e18edd33ba7 100644
--- a/app/models/concerns/manual_inverse_association.rb
+++ b/app/models/concerns/manual_inverse_association.rb
@@ -3,7 +3,7 @@
module ManualInverseAssociation
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def manual_inverse_association(association, inverse)
define_method(association) do |*args|
super(*args).tap do |value|
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 7e7eccb1c27..393607e82c4 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -10,7 +10,7 @@
module Mentionable
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable(attr, options = {})
attr = attr.to_s
diff --git a/app/models/concerns/optionally_search.rb b/app/models/concerns/optionally_search.rb
index dec97b7dee8..4093429e372 100644
--- a/app/models/concerns/optionally_search.rb
+++ b/app/models/concerns/optionally_search.rb
@@ -3,7 +3,7 @@
module OptionallySearch
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def search(*)
raise(
NotImplementedError,
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 1f6c42f3b3a..614c3242874 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -26,7 +26,7 @@
module Participable
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Adds a list of participant attributes. Attributes can either be symbols or
# Procs.
#
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 468eaf68883..58143a32fdc 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -40,7 +40,7 @@ module Referable
end
end
- module ClassMethods
+ class_methods do
# The character that prefixes the actual reference identifier
#
# This should be overridden by the including class.
diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb
index f47e20229f1..16ea330701d 100644
--- a/app/models/concerns/resolvable_note.rb
+++ b/app/models/concerns/resolvable_note.rb
@@ -20,7 +20,7 @@ module ResolvableNote
scope :unresolved, -> { resolvable.where(resolved_at: nil) }
end
- module ClassMethods
+ class_methods do
# This method must be kept in sync with `#resolve!`
def resolve!(current_user)
unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id)
diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb
index 39306179eb8..333c9118aa5 100644
--- a/app/models/concerns/select_for_project_authorization.rb
+++ b/app/models/concerns/select_for_project_authorization.rb
@@ -3,7 +3,7 @@
module SelectForProjectAuthorization
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def select_for_project_authorization
select("projects.id AS project_id, members.access_level")
end
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index c322c356db2..e51b4e22c96 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -3,7 +3,7 @@
module ShaAttribute
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def sha_attribute(name)
return if ENV['STATIC_VERIFICATION']
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index 501bd1bb83c..29e48f0c5f7 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -19,7 +19,7 @@ module Sortable
scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end
- module ClassMethods
+ class_methods do
def order_by(method)
case method.to_s
when 'created_asc' then order_created_asc
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index c6e3dc385fe..3ff4b4046d3 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -3,7 +3,7 @@
module Spammable
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options]
end
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index 344f677a3f3..c9f5ba7793d 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -14,7 +14,7 @@
module StripAttribute
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def strip_attributes(*attrs)
strip_attrs.concat(attrs)
end
diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb
index f55ab2fcaf3..223a61119e5 100644
--- a/app/models/concerns/triggerable_hooks.rb
+++ b/app/models/concerns/triggerable_hooks.rb
@@ -6,6 +6,7 @@ module TriggerableHooks
push_hooks: :push_events,
tag_push_hooks: :tag_push_events,
issue_hooks: :issues_events,
+ confidential_note_hooks: :confidential_note_events,
confidential_issue_hooks: :confidential_issues_events,
note_hooks: :note_events,
merge_request_hooks: :merge_requests_events,
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 198bb168d85..6d8b575102e 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -14,6 +14,7 @@ class IssuablePolicy < BasePolicy
rule { assignee_or_author }.policy do
enable :read_issue
enable :update_issue
+ enable :reopen_issue
enable :read_merge_request
enable :update_merge_request
end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 94b5f37c682..a0706eaa46c 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -19,4 +19,8 @@ class IssuePolicy < IssuablePolicy
prevent :update_issue
prevent :admin_issue
end
+
+ rule { locked }.policy do
+ prevent :reopen_issue
+ end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index fd6cc504a3b..273a93a1423 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -180,6 +180,7 @@ class ProjectPolicy < BasePolicy
enable :fork_project
enable :create_project_snippet
enable :update_issue
+ enable :reopen_issue
enable :admin_issue
enable :admin_label
enable :admin_list
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index b887b99d31c..271ff668eda 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -9,6 +9,28 @@ class BuildDetailsEntity < JobEntity
expose :metadata, using: BuildMetadataEntity
+ expose :artifact, if: -> (*) { can?(current_user, :read_build, build) } do
+ expose :download_path, if: -> (*) { build.artifacts? } do |build|
+ download_project_job_artifacts_path(project, build)
+ end
+
+ expose :browse_path, if: -> (*) { build.browsable_artifacts? } do |build|
+ browse_project_job_artifacts_path(project, build)
+ end
+
+ expose :keep_path, if: -> (*) { build.has_expiring_artifacts? && can?(current_user, :update_build, build) } do |build|
+ keep_project_job_artifacts_path(project, build)
+ end
+
+ expose :expire_at, if: -> (*) { build.artifacts_expire_at.present? } do |build|
+ build.artifacts_expire_at
+ end
+
+ expose :expired, if: -> (*) { build.artifacts_expire_at.present? } do |build|
+ build.artifacts_expired?
+ end
+ end
+
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build|
erase_project_job_path(project, build)
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 79844c9210a..cbe6f200b86 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -2,7 +2,6 @@
class DiffFileEntity < Grape::Entity
include RequestAwareEntity
- include BlobHelper
include CommitsHelper
include DiffHelper
include SubmoduleHelper
@@ -136,12 +135,12 @@ class DiffFileEntity < Grape::Entity
end
# Used for inline diffs
- expose :highlighted_diff_lines, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
+ expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
diff_file.diff_lines_for_serializer
end
# Used for parallel diffs
- expose :parallel_diff_lines, if: -> (diff_file, _) { diff_file.text? }
+ expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? }
def current_user
request.current_user
diff --git a/app/serializers/diff_line_entity.rb b/app/serializers/diff_line_entity.rb
new file mode 100644
index 00000000000..2119a1017d3
--- /dev/null
+++ b/app/serializers/diff_line_entity.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class DiffLineEntity < Grape::Entity
+ expose :line_code
+ expose :type
+ expose :old_line
+ expose :new_line
+ expose :text
+ expose :meta_positions, as: :meta_data
+
+ expose :rich_text do |line|
+ line.rich_text || CGI.escapeHTML(line.text)
+ end
+end
diff --git a/app/serializers/diff_line_parallel_entity.rb b/app/serializers/diff_line_parallel_entity.rb
new file mode 100644
index 00000000000..0438a67d51b
--- /dev/null
+++ b/app/serializers/diff_line_parallel_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class DiffLineParallelEntity < Grape::Entity
+ expose :left, using: DiffLineEntity
+ expose :right, using: DiffLineEntity
+end
diff --git a/app/serializers/diff_line_serializer.rb b/app/serializers/diff_line_serializer.rb
new file mode 100644
index 00000000000..7f1f2d9aa7c
--- /dev/null
+++ b/app/serializers/diff_line_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class DiffLineSerializer < BaseSerializer
+ entity DiffLineEntity
+end
diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb
index b8321037fa5..ed09db0f3f4 100644
--- a/app/serializers/discussion_entity.rb
+++ b/app/serializers/discussion_entity.rb
@@ -43,7 +43,7 @@ class DiscussionEntity < Grape::Entity
project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion)
end
- expose :truncated_diff_lines, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) }
+ expose :truncated_diff_lines, using: DiffLineEntity, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) }
expose :image_diff_html, if: -> (d, _) { d.diff_discussion? && d.on_image? } do |discussion|
diff_file = discussion.diff_file
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 025f093a428..fc7b236f7da 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -7,8 +7,8 @@ module Files
def initialize(*args)
super
- @author_email = params[:author_email]
- @author_name = params[:author_name]
+ @author_email = params[:author_email] || current_user&.email
+ @author_name = params[:author_name] || current_user&.name
@commit_message = params[:commit_message]
@last_commit_sha = params[:last_commit_sha]
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 3bd53f9ccdc..56d59b235a7 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -3,7 +3,7 @@
module Issues
class ReopenService < Issues::BaseService
def execute(issue)
- return issue unless can?(current_user, :update_issue, issue)
+ return issue unless can?(current_user, :reopen_issue, issue)
if issue.reopen
event_service.reopen_issue(issue, current_user)
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index c2a0c5fa7f3..3746cfef702 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -43,8 +43,8 @@ module Projects
@new_path = File.join(@new_namespace.try(:full_path) || '', project.path)
@old_namespace = project.namespace
- if Project.where(path: project.path, namespace_id: @new_namespace.try(:id)).exists?
- raise TransferError.new("Project with same path in target namespace already exists")
+ if Project.where(namespace_id: @new_namespace.try(:id)).where('path = ? or name = ?', project.path, project.name).exists?
+ raise TransferError.new("Project with same name or path in target namespace already exists")
end
if project.has_container_registry_tags?
@@ -118,6 +118,7 @@ module Projects
def rollback_side_effects
rollback_folder_move
+ project.reload
update_namespace_and_visibility(@old_namespace)
write_repository_config(@old_path)
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index acc2fa153ae..9417c63c43a 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -2,6 +2,10 @@
module Users
class BuildService < BaseService
+ delegate :user_default_internal_regex_enabled?,
+ :user_default_internal_regex_instance,
+ to: :'Gitlab::CurrentSettings.current_application_settings'
+
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
@@ -89,6 +93,10 @@ module Users
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
+
+ if user_default_internal_regex_enabled? && !user_params.key?(:external)
+ user_params[:external] = user_external?
+ end
else
allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization
@@ -105,5 +113,9 @@ module Users
def skip_user_confirmation_email_from_setting
!Gitlab::CurrentSettings.send_user_confirmation_email
end
+
+ def user_external?
+ user_default_internal_regex_instance.match(params[:email]).nil?
+ end
end
end
diff --git a/app/services/wikis/create_attachment_service.rb b/app/services/wikis/create_attachment_service.rb
new file mode 100644
index 00000000000..30fe0e371a6
--- /dev/null
+++ b/app/services/wikis/create_attachment_service.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Wikis
+ class CreateAttachmentService < Files::CreateService
+ ATTACHMENT_PATH = 'uploads'.freeze
+ MAX_FILENAME_LENGTH = 255
+
+ delegate :wiki, to: :project
+ delegate :repository, to: :wiki
+
+ def initialize(*args)
+ super
+
+ @file_name = truncate_file_name(params[:file_name])
+ @file_path = File.join(ATTACHMENT_PATH, SecureRandom.hex, @file_name) if @file_name
+ @commit_message ||= "Upload attachment #{@file_name}"
+ @branch_name ||= wiki.default_branch
+ end
+
+ def create_commit!
+ commit_result(create_transformed_commit(@file_content))
+ end
+
+ private
+
+ def truncate_file_name(file_name)
+ return unless file_name.present?
+ return file_name if file_name.length <= MAX_FILENAME_LENGTH
+
+ extension = File.extname(file_name)
+ truncate_at = MAX_FILENAME_LENGTH - extension.length - 1
+ base_name = File.basename(file_name, extension)[0..truncate_at]
+ base_name + extension
+ end
+
+ def validate!
+ validate_file_name!
+ validate_permissions!
+ end
+
+ def validate_file_name!
+ raise_error('The file name cannot be empty') unless @file_name
+ end
+
+ def validate_permissions!
+ unless can?(current_user, :create_wiki, project)
+ raise_error('You are not allowed to push to the wiki')
+ end
+ end
+
+ def create_transformed_commit(content)
+ repository.create_file(
+ current_user,
+ @file_path,
+ content,
+ message: @commit_message,
+ branch_name: @branch_name,
+ author_email: @author_email,
+ author_name: @author_name)
+ end
+
+ def commit_result(commit_id)
+ {
+ file_name: @file_name,
+ file_path: @file_path,
+ branch: @branch_name,
+ commit: commit_id
+ }
+ end
+ end
+end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index b1365659834..ffc1e5f75ca 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -122,12 +122,6 @@ class FileUploader < GitlabUploader
}
end
- def markdown_link
- markdown = +"[#{markdown_name}](#{secure_url})"
- markdown.prepend("!") if image_or_video? || dangerous?
- markdown
- end
-
def to_h
{
alt: markdown_name,
@@ -192,10 +186,6 @@ class FileUploader < GitlabUploader
storage.delete_dir!(store_dir) # only remove when empty
end
- def markdown_name
- (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
- end
-
def identifier
@identifier ||= filename
end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index f6af023e0f9..557b13a8bd6 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -5,6 +5,7 @@ class JobArtifactUploader < GitlabUploader
include ObjectStorage::Concern
ObjectNotReadyError = Class.new(StandardError)
+ UnknownFileLocationError = Class.new(StandardError)
storage_options Gitlab.config.artifacts
@@ -23,10 +24,22 @@ class JobArtifactUploader < GitlabUploader
def dynamic_segment
raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id
- creation_date = model.created_at.utc.strftime('%Y_%m_%d')
+ if model.hashed_path?
+ hashed_path
+ elsif model.legacy_path?
+ legacy_path
+ else
+ raise UnknownFileLocationError
+ end
+ end
+ def hashed_path
File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
- creation_date, model.job_id.to_s, model.id.to_s)
+ model.created_at.utc.strftime('%Y_%m_%d'), model.job_id.to_s, model.id.to_s)
+ end
+
+ def legacy_path
+ File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s)
end
def disk_hash
diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb
index 2a2b54a9270..e8a2dce7755 100644
--- a/app/uploaders/uploader_helper.rb
+++ b/app/uploaders/uploader_helper.rb
@@ -2,32 +2,7 @@
# Extra methods for uploader
module UploaderHelper
- IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
- # We recommend using the .mp4 format over .mov. Videos in .mov format can
- # still be used but you really need to make sure they are served with the
- # proper MIME type video/mp4 and not video/quicktime or your videos won't play
- # on IE >= 9.
- # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
- VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
- # These extension types can contain dangerous code and should only be embedded inline with
- # proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
- DANGEROUS_EXT = %w[svg].freeze
-
- def image?
- extension_match?(IMAGE_EXT)
- end
-
- def video?
- extension_match?(VIDEO_EXT)
- end
-
- def image_or_video?
- image? || video?
- end
-
- def dangerous?
- extension_match?(DANGEROUS_EXT)
- end
+ include Gitlab::FileMarkdownLinkBuilder
private
diff --git a/app/validators/js_regex_validator.rb b/app/validators/js_regex_validator.rb
new file mode 100644
index 00000000000..a515af7b919
--- /dev/null
+++ b/app/validators/js_regex_validator.rb
@@ -0,0 +1,15 @@
+class JsRegexValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return true if value.blank?
+
+ parsed_regex = JsRegex.new(Regexp.new(value, Regexp::IGNORECASE))
+
+ if parsed_regex.source.empty?
+ record.errors.add(attribute, "Regex Pattern #{value} can not be expressed in Javascript")
+ else
+ parsed_regex.warnings.each { |warning| record.errors.add(attribute, warning) }
+ end
+ rescue RegexpError => regex_error
+ record.errors.add(attribute, regex_error.to_s)
+ end
+end
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 622cb11010e..9121e44d31b 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -29,6 +29,13 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
+ .prepend-top-10
+ = _('Internal users')
+ = f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
+ .help-block
+ = _('Specify an e-mail address regex pattern to identify default internal users.')
+ = link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
+ target: '_blank'
.form-group
= f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
.form-check
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 5f68163163e..12e24ddef02 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -34,8 +34,12 @@
.form-group.row
.col-sm-2.text-right
= f.label :external, class: 'col-form-label'
+ .hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
External
%p.light
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups.
+ %row.hidden#warning_external_automatically_set.hidden
+ .badge.badge-warning.text-white
+ = _('Automatically marked as default internal user')
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 30d7b21b1b8..a758a63dfb3 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -1,5 +1,4 @@
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
-- user_authored = awardable.user_authored?(current_user)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 646e89e9bd1..44c898e0fac 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -1,6 +1,4 @@
- diff_file = discussion.diff_file
-- blob = discussion.blob
-- discussions = { discussion.original_line_code => [discussion] }
- diff_file_class = diff_file.text? ? 'text-file' : 'js-image-file'
- diff_data = {}
- expanded = discussion.expanded? || local_assigns.fetch(:expanded, nil)
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
index 08f2442f025..69cc510e9c1 100644
--- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -1,4 +1,3 @@
-- submit_btn_css ||= 'btn btn-link btn-remove'
- if defined?(token)
- path = oauth_authorized_application_path(0, token_id: token)
- else
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index cae2df4699e..fc17dd2d310 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -25,6 +25,18 @@
.settings-content
= render 'groups/settings/permissions'
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = s_('GroupSettings|Badges')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = s_('GroupSettings|Customize your group badges.')
+ = link_to s_('GroupSettings|Learn more about badges.'), help_page_path('user/project/badges')
+ .settings-content
+ = render 'shared/badges/badge_settings'
+
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index db7eaff6658..e1e38a7e82f 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @group)
-- hide_class = ''
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
- issuables = ['issues', 'merge requests']
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
index 4cae9c51acc..d8bd37fe986 100644
--- a/app/views/ide/index.html.haml
+++ b/app/views/ide/index.html.haml
@@ -1,4 +1,4 @@
-- @body_class = 'ide'
+- @body_class = 'ide-layout'
- page_title 'IDE'
- content_for :page_specific_javascripts do
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 9a7a67cfa83..a86972d8cf3 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,7 +1,3 @@
-- if controller.controller_path =~ /^groups/ && @group.persisted?
- - label = _('This group')
-- if controller.controller_path =~ /^projects/ && @project.persisted?
- - label = _('This project')
- if @group && @group.persisted? && @group.path
- group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
- if @project && @project.persisted?
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index 2ab9e55441b..80bda34a3f5 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -1,5 +1,5 @@
-- page_title = _("Explore")
+- page_title _("Explore")
- unless current_user
- - header_title = _("Explore GitLab"), explore_root_path
+ - header_title _("Explore GitLab"), explore_root_path
= render template: "layouts/application"
diff --git a/app/views/layouts/nav_only.html.haml b/app/views/layouts/fullscreen.html.haml
index 0811211f7b2..95db8313821 100644
--- a/app/views/layouts/nav_only.html.haml
+++ b/app/views/layouts/fullscreen.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
- %body{ class: "#{user_application_theme} #{@body_class} nav-only", data: { page: body_data_page } }
+ %body{ class: "#{user_application_theme} #{@body_class} fullscreen-layout", data: { page: body_data_page } }
= render 'peek/bar'
= render "layouts/header/default"
= render 'shared/outdated_browser'
@@ -10,5 +10,5 @@
= render "layouts/broadcast"
= yield :flash_message
= render "layouts/flash"
- .content{ id: "content-body" }
+ .content-wrapper{ id: "content-body", class: "d-flex flex-column align-items-stretch" }
= yield
diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml
index 14c5f0ce04c..9db78ec58e4 100644
--- a/app/views/layouts/group_settings.html.haml
+++ b/app/views/layouts/group_settings.html.haml
@@ -1,4 +1,4 @@
-- page_title = _("Settings")
-- nav "group"
+- page_title _("Settings")
+- nav "group"
= render template: "layouts/group"
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index d471dd84550..4158bb69452 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -122,12 +122,6 @@
%span
= _('General')
- = nav_link(controller: :badges) do
- = link_to group_settings_badges_path(@group), title: _('Project Badges') do
- %span
- = _('Project Badges')
-
-
= nav_link(path: 'groups#projects') do
= link_to projects_group_path(@group), title: _('Projects') do
%span
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 34f47806205..30e0e9fca27 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -313,11 +313,6 @@
%span
= _('Members')
- if can_edit
- = nav_link(controller: :badges) do
- = link_to project_settings_badges_path(@project), title: _('Badges') do
- %span
- = _('Badges')
- - if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: _('Integrations') do
%span
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index fbe88ec9618..1b6c4193c4d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,5 +1,4 @@
- empty_repo = @project.empty_repo?
-- fork_network = @project.fork_network
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index 540e996e4d8..935581643cd 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -1,5 +1,4 @@
- form = local_assigns.fetch(:form)
-- project = local_assigns.fetch(:project)
.form-group
= label_tag :merge_method_merge, class: 'label-bold' do
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index e47361354f3..4b1d4b3ea17 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -5,7 +5,6 @@
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
- - last_line = right.new_pos if right
- discussions_left, discussions_right = parallel_diff_discussions(left, right, diff_file)
%tr.line_holder.parallel
- if left
diff --git a/app/views/projects/diffs/_single_image_diff.html.haml b/app/views/projects/diffs/_single_image_diff.html.haml
index 12be8beab39..454f814795a 100644
--- a/app/views/projects/diffs/_single_image_diff.html.haml
+++ b/app/views/projects/diffs/_single_image_diff.html.haml
@@ -1,7 +1,5 @@
- blob = diff_file.blob
-- old_blob = diff_file.old_blob
- blob_raw_url = diff_file_blob_raw_url(diff_file)
-- old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = ''
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index e37a444c1c9..fb837b27207 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -102,6 +102,18 @@
= render_if_exists 'projects/service_desk_settings'
+ %section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = s_('ProjectSettings|Badges')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = s_('ProjectSettings|Customize your project badges.')
+ = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
+ .settings-content
+ = render 'shared/badges/badge_settings'
+
= render 'export', project: @project
%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 2d036bd4e3e..b81d1a188f0 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -6,6 +6,7 @@
- page_card_attributes @issue.card_attributes
- can_update_issue = can?(current_user, :update_issue, @issue)
+- can_reopen_issue = can?(current_user, :reopen_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
@@ -40,6 +41,7 @@
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue
%li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close js-btn-issue-action #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ - if can_reopen_issue
%li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam
%li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
@@ -48,7 +50,7 @@
%li.divider
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
+ = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue
- if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam'
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 768ce9bd103..dfac62e7985 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @project)
-- hide_class = ''
- search = params[:search]
- if can_admin_label
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 d3871453b9f..15499c89ffb 100644
--- a/app/views/projects/merge_requests/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
@@ -30,11 +30,13 @@
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
- git checkout #{h @merge_request.target_branch}
+ git fetch origin
+ git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
- else
:preserve
- git checkout #{h @merge_request.target_branch}
+ git fetch origin
+ git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_branch}
%p
%strong Step 4.
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index a58179091ae..1bf42ded97a 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -39,4 +39,4 @@
- if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn btn-grouped js-issuable-edit"
- = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
+ = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_update_merge_request
diff --git a/app/views/projects/milestones/_deprecation_message.html.haml b/app/views/projects/milestones/_deprecation_message.html.haml
new file mode 100644
index 00000000000..b2cca3690d6
--- /dev/null
+++ b/app/views/projects/milestones/_deprecation_message.html.haml
@@ -0,0 +1,7 @@
+.banner-callout.compact.milestone-deprecation-message.prepend-top-20
+ .banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
+ .banner-body.prepend-left-10.append-right-10
+ %h5.banner-title.prepend-top-0
+ = _('The tabs below will be removed in a future version')
+ %p.milestone-banner-text
+ = _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') }
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 0a684f9016a..5859de61d71 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -67,5 +67,6 @@
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close this milestone now.
+ = render 'deprecation_message'
= render 'shared/milestones/tabs', milestone: @milestone
= render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 6c363345e38..f9b4cddf9b2 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,7 +3,6 @@
- @hide_top_links = true
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
-- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
- active_tab = local_assigns.fetch(:active_tab, 'blank')
.project-edit-container
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index e9008d60098..eb6838cec8d 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -38,7 +38,6 @@
- if can?(current_user, :award_emoji, note)
- if note.emoji_awardable?
- - user_authored = note.user_authored?(current_user)
.note-actions-item
= button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
= icon('spinner spin')
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index c13e3194340..5b6823da1f6 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -1,5 +1,5 @@
- breadcrumb_title "Pipelines"
-- page_title = s_("Pipeline|Run Pipeline")
+- page_title s_("Pipeline|Run Pipeline")
- settings_link = link_to _('CI/CD settings'), project_settings_ci_cd_path(@project)
%h3.page-title
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index ab9ba5c7569..ab92b757836 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -5,7 +5,6 @@
%fieldset.builds-feature.js-auto-devops-settings
.form-group
- message = auto_devops_warning_message(@project)
- - ci_file_formatted = '<code>.gitlab-ci.yml</code>'.html_safe
- if message
%p.auto-devops-warning-message.settings-message.text-center
= message.html_safe
diff --git a/app/views/projects/tags/_tag.atom.builder b/app/views/projects/tags/_tag.atom.builder
new file mode 100644
index 00000000000..60d4b21b9d1
--- /dev/null
+++ b/app/views/projects/tags/_tag.atom.builder
@@ -0,0 +1,19 @@
+commit = @repository.commit(tag.dereferenced_target)
+release = @releases.find { |r| r.tag == tag.name }
+tag_url = project_tag_url(@project, tag.name)
+
+if commit
+ xml.entry do
+ xml.id tag_url
+ xml.link href: tag_url
+ xml.title truncate(tag.name, length: 80)
+ xml.summary strip_gpg_signature(tag.message)
+ xml.content markdown_field(release, :description), type: 'html'
+ xml.updated release.updated_at.xmlschema if release
+ xml.media :thumbnail, width: '40', height: '40', url: image_url(avatar_icon_for_email(commit.author_email))
+ xml.author do |author|
+ xml.name commit.author_name
+ xml.email commit.author_email
+ end
+ end
+end
diff --git a/app/views/projects/tags/index.atom.builder b/app/views/projects/tags/index.atom.builder
new file mode 100644
index 00000000000..b9b58b7beaa
--- /dev/null
+++ b/app/views/projects/tags/index.atom.builder
@@ -0,0 +1,7 @@
+xml.title "#{@project.name} tags"
+xml.link href: project_tags_url(@project, @ref, rss_url_options), rel: 'self', type: 'application/atom+xml'
+xml.link href: project_tags_url(@project, @ref), rel: 'alternate', type: 'text/html'
+xml.id project_tags_url(@project, @ref)
+xml.updated @releases.first.updated_at.xmlschema if @releases.any?
+
+xml << render(partial: 'tag', collection: @tags) if @tags.any?
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index dab95ba09f2..20b4705521c 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,6 +1,8 @@
- @no_container = true
- @sort ||= sort_value_recently_updated
- page_title s_('TagsPage|Tags')
+= content_for :meta_tags do
+ = auto_discovery_link_tag(:atom, project_tags_url(@project, rss_url_options), title: "#{@project.name} tags")
.flex-list{ class: container_class }
.top-area.adjust
@@ -25,6 +27,8 @@
- if can?(current_user, :push_code, @project)
= link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
= s_('TagsPage|New tag')
+ = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn rss-btn has-tooltip' do
+ = icon("rss")
= render_if_exists 'projects/commits/mirror_status'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index d80d2957466..71359708022 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -41,3 +41,8 @@
= render 'sidebar'
#delete-wiki-modal.modal.fade
+
+- content_for :scripts_body do
+ -# haml-lint:disable InlineJavaScript
+ :javascript
+ window.uploads_path = "#{wiki_attachment_upload_url}";
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 2c3cbd0b986..71f34c0d85b 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -4,8 +4,6 @@
- use_label_priority = local_assigns.fetch(:use_label_priority, false)
- force_priority = local_assigns.fetch(:force_priority, use_label_priority ? label.priority.present? : false)
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
-- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
-- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
- tooltip_title = label_status_tooltip(label, status) if status
%li.label-list-item{ id: label_css_id, data: { id: label.id } }
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index ac2164a4a71..28b34e38b15 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -3,7 +3,6 @@
- if stage.status
- detailed_status = stage.detailed_status(current_user)
- icon_status = "#{detailed_status.icon}_borderless"
- - status_klass = "ci-status-icon ci-status-icon-#{detailed_status.group}"
.stage-container.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_ajax_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 4e7061eef1c..7cbc5810c10 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,5 +1,3 @@
-- show_create = local_assigns.fetch(:show_create, false)
-
- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index 933d4b2ea65..70e05eb1c8c 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -2,13 +2,15 @@
- display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable)
-- if can_update && is_current_user
- = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
-- elsif can_update && !is_current_user
- = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
+- if can_update
+ - if is_current_user
+ = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
+ class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
+ - else
+ = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
+ - if can_reopen && is_current_user
+ = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
+ class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse'
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 1cd8ce0826c..c7037335866 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,5 +1,3 @@
-- boards_page = controller.controller_name == 'boards'
-
.issues-filters
.issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 34911fd2712..0b42b33581a 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -7,7 +7,6 @@
- data_options = local_assigns.fetch(:data_options, {})
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
-- selected_toggle = local_assigns.fetch(:selected_toggle, nil)
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options)
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 3b017c62a80..d8580ad8ab4 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -3,7 +3,6 @@
- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
- has_due_date = issuable.has_attribute?(:due_date)
-- has_labels = @labels && @labels.any?
- form = local_assigns.fetch(:form)
%hr
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index be053d481e4..aba790e1217 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,9 +1,7 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
- forks = false unless local_assigns[:forks] == true
-- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
-- user = local_assigns[:user]
- access = max_project_member_access(project)
- css_class = '' unless local_assigns[:css_class]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index e1f7ee80ebb..220ba2b49e6 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -1,6 +1,5 @@
- if current_user
- if note.emoji_awardable?
- - user_authored = note.user_authored?(current_user)
.note-actions-item
= link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin')
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index 7d006cc348e..688b600649a 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -10,17 +10,7 @@ class BackgroundMigrationWorker
# maintenance related tasks have plenty of time to clean up after a migration
# has been performed.
def self.minimum_interval
- if enable_health_check?
- 2.minutes.to_i
- else
- 5.minutes.to_i
- end
- end
-
- def self.enable_health_check?
- Rails.env.development? ||
- Rails.env.test? ||
- Feature.enabled?('background_migration_health_check')
+ 2.minutes.to_i
end
# Performs the background migration.
@@ -86,8 +76,6 @@ class BackgroundMigrationWorker
# class_name - The name of the background migration that we might want to
# run.
def healthy_database?
- return true unless self.class.enable_health_check?
-
return true unless Gitlab::Database.postgresql?
!Postgresql::ReplicationSlot.lag_too_great?
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index bb06e31641d..d64c2f82a09 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -11,7 +11,7 @@ module ApplicationWorker
set_queue
end
- module ClassMethods
+ class_methods do
def inherited(subclass)
subclass.set_queue
end
diff --git a/app/workers/concerns/waitable_worker.rb b/app/workers/concerns/waitable_worker.rb
index d85bc7d1660..27b94a82444 100644
--- a/app/workers/concerns/waitable_worker.rb
+++ b/app/workers/concerns/waitable_worker.rb
@@ -3,7 +3,7 @@
module WaitableWorker
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Schedules multiple jobs and waits for them to be completed.
def bulk_perform_and_wait(args_list, timeout: 10)
# Short-circuit: it's more efficient to do small numbers of jobs inline
diff --git a/changelogs/unreleased/37356-relative-submodule-link.yml b/changelogs/unreleased/37356-relative-submodule-link.yml
new file mode 100644
index 00000000000..99d1577609d
--- /dev/null
+++ b/changelogs/unreleased/37356-relative-submodule-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fix git submodule link for subgroup projects with relative path
+merge_request: 21154
+author:
+type: fixed
diff --git a/changelogs/unreleased/39665-restrict-issue-reopen.yml b/changelogs/unreleased/39665-restrict-issue-reopen.yml
new file mode 100644
index 00000000000..204baafb700
--- /dev/null
+++ b/changelogs/unreleased/39665-restrict-issue-reopen.yml
@@ -0,0 +1,5 @@
+---
+title: Restrict reopening locked issues for non authorized issue authors
+merge_request: 21299
+author:
+type: changed
diff --git a/changelogs/unreleased/41292-users-stuck-on-a-redirect-loop-after-transferring-project.yml b/changelogs/unreleased/41292-users-stuck-on-a-redirect-loop-after-transferring-project.yml
new file mode 100644
index 00000000000..830c02510f2
--- /dev/null
+++ b/changelogs/unreleased/41292-users-stuck-on-a-redirect-loop-after-transferring-project.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project transfer name validation issues causing a redirect loop
+merge_request: 21408
+author:
+type: fixed
diff --git a/changelogs/unreleased/46591-fix-ide-height-issues.yml b/changelogs/unreleased/46591-fix-ide-height-issues.yml
new file mode 100644
index 00000000000..d161bda6ab1
--- /dev/null
+++ b/changelogs/unreleased/46591-fix-ide-height-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Fix IDE issues with persistent banners
+merge_request: 21283
+author:
+type: fixed
diff --git a/changelogs/unreleased/47765-group-visibility-error-due-to-string-conversion.yml b/changelogs/unreleased/47765-group-visibility-error-due-to-string-conversion.yml
new file mode 100644
index 00000000000..ad09527b329
--- /dev/null
+++ b/changelogs/unreleased/47765-group-visibility-error-due-to-string-conversion.yml
@@ -0,0 +1,6 @@
+---
+title: Importing a project no longer fails when visibility level holds a string value
+ type
+merge_request: 21242
+author:
+type: fixed
diff --git a/changelogs/unreleased/47943-project-milestone-page-deprecation-message.yml b/changelogs/unreleased/47943-project-milestone-page-deprecation-message.yml
new file mode 100644
index 00000000000..b9f68e1c46c
--- /dev/null
+++ b/changelogs/unreleased/47943-project-milestone-page-deprecation-message.yml
@@ -0,0 +1,5 @@
+---
+title: Show deprecation message on project milestone page for category tabs
+merge_request: 21236
+author:
+type: changed
diff --git a/changelogs/unreleased/50101-add-artifact-information-to-job-api.yml b/changelogs/unreleased/50101-add-artifact-information-to-job-api.yml
new file mode 100644
index 00000000000..f98d111a337
--- /dev/null
+++ b/changelogs/unreleased/50101-add-artifact-information-to-job-api.yml
@@ -0,0 +1,5 @@
+---
+title: Send artifact information in job API
+merge_request: 50460
+author:
+type: other
diff --git a/changelogs/unreleased/50414-rubocop-rule-to-enforce-class-methods-over-module.yml b/changelogs/unreleased/50414-rubocop-rule-to-enforce-class-methods-over-module.yml
new file mode 100644
index 00000000000..1694fb2376d
--- /dev/null
+++ b/changelogs/unreleased/50414-rubocop-rule-to-enforce-class-methods-over-module.yml
@@ -0,0 +1,5 @@
+---
+title: Adds Rubocop rule to enforce class_methods over module ClassMethods
+merge_request: 21379
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/50564-chat-service-refactoring.yml b/changelogs/unreleased/50564-chat-service-refactoring.yml
new file mode 100644
index 00000000000..aec5e8fab0a
--- /dev/null
+++ b/changelogs/unreleased/50564-chat-service-refactoring.yml
@@ -0,0 +1,5 @@
+---
+title: Use sample data for push event when no commits created
+merge_request: 21440
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/50853-vendor-auto-devops-gitlab-ci-yml-to-resolve-redeploying-deleted-app-gives-helm-error.yml b/changelogs/unreleased/50853-vendor-auto-devops-gitlab-ci-yml-to-resolve-redeploying-deleted-app-gives-helm-error.yml
new file mode 100644
index 00000000000..37922eb82f6
--- /dev/null
+++ b/changelogs/unreleased/50853-vendor-auto-devops-gitlab-ci-yml-to-resolve-redeploying-deleted-app-gives-helm-error.yml
@@ -0,0 +1,5 @@
+---
+title: 'Auto-DevOps.gitlab-ci.yml: fix redeploying deleted app gives helm error'
+merge_request: 21429
+author:
+type: fixed
diff --git a/changelogs/unreleased/50879-unused-css-container-fluid.yml b/changelogs/unreleased/50879-unused-css-container-fluid.yml
new file mode 100644
index 00000000000..3f706472523
--- /dev/null
+++ b/changelogs/unreleased/50879-unused-css-container-fluid.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused CSS part in mobile framework
+merge_request: 21439
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/50936-docs-run-review-cleanup-only-for-gitlab-org-repos.yml b/changelogs/unreleased/50936-docs-run-review-cleanup-only-for-gitlab-org-repos.yml
new file mode 100644
index 00000000000..87265506e24
--- /dev/null
+++ b/changelogs/unreleased/50936-docs-run-review-cleanup-only-for-gitlab-org-repos.yml
@@ -0,0 +1,5 @@
+---
+title: Run review-docs-cleanup job for gitlab-org repos only
+merge_request: 21463
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/_acet-disable-ide-button.yml b/changelogs/unreleased/_acet-disable-ide-button.yml
new file mode 100644
index 00000000000..2fff3847052
--- /dev/null
+++ b/changelogs/unreleased/_acet-disable-ide-button.yml
@@ -0,0 +1,5 @@
+---
+title: Disable Web IDE button if user is not allowed to push the source branch.
+merge_request: 21288
+author:
+type: added
diff --git a/changelogs/unreleased/add-background-migration-for-legacy-traces.yml b/changelogs/unreleased/add-background-migration-for-legacy-traces.yml
new file mode 100644
index 00000000000..3d5a0b4e452
--- /dev/null
+++ b/changelogs/unreleased/add-background-migration-for-legacy-traces.yml
@@ -0,0 +1,5 @@
+---
+title: Add background migrations for legacy artifacts
+merge_request: 18615
+author:
+type: performance
diff --git a/changelogs/unreleased/feature--32877-add-default-field-branch-api.yml b/changelogs/unreleased/feature--32877-add-default-field-branch-api.yml
new file mode 100644
index 00000000000..a99ecc9a67e
--- /dev/null
+++ b/changelogs/unreleased/feature--32877-add-default-field-branch-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add default parameter to branches API
+merge_request: 21294
+author: Riccardo Padovani
+type: changed
diff --git a/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml b/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml
new file mode 100644
index 00000000000..7a3bd11c119
--- /dev/null
+++ b/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml
@@ -0,0 +1,5 @@
+---
+title: Add an option to whitelist users based on email address as internal when the "New user set to external" setting is enabled.
+merge_request: 17711
+author: Roger Rüttimann
+type: added
diff --git a/changelogs/unreleased/fix-download-dropdown-link.yml b/changelogs/unreleased/fix-download-dropdown-link.yml
new file mode 100644
index 00000000000..998476b07bd
--- /dev/null
+++ b/changelogs/unreleased/fix-download-dropdown-link.yml
@@ -0,0 +1,5 @@
+---
+title: Hide PAT creation advice for HTTP clone if PAT exists
+merge_request: 18208
+author: George Thomas @thegeorgeous
+type: fixed
diff --git a/changelogs/unreleased/fix-junit-parser.yml b/changelogs/unreleased/fix-junit-parser.yml
new file mode 100644
index 00000000000..e0a9ad8f210
--- /dev/null
+++ b/changelogs/unreleased/fix-junit-parser.yml
@@ -0,0 +1,5 @@
+---
+title: Fix edge cases of JUnitParser
+merge_request: 21469
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix_emojis_cutting_and_regressions.yml b/changelogs/unreleased/fix_emojis_cutting_and_regressions.yml
new file mode 100644
index 00000000000..a9c1b88a61c
--- /dev/null
+++ b/changelogs/unreleased/fix_emojis_cutting_and_regressions.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Emojis cutting in the right way
+merge_request:
+author: Alexander Popov
+type: fixed
diff --git a/changelogs/unreleased/fj-2635-enable-rss-for-tags.yml b/changelogs/unreleased/fj-2635-enable-rss-for-tags.yml
new file mode 100644
index 00000000000..ee197572385
--- /dev/null
+++ b/changelogs/unreleased/fj-2635-enable-rss-for-tags.yml
@@ -0,0 +1,5 @@
+---
+title: Added atom feed for tags
+merge_request: 21428
+author:
+type: added
diff --git a/changelogs/unreleased/fj-33475-files-inside-wiki-repo.yml b/changelogs/unreleased/fj-33475-files-inside-wiki-repo.yml
new file mode 100644
index 00000000000..8c1f0e3dbf2
--- /dev/null
+++ b/changelogs/unreleased/fj-33475-files-inside-wiki-repo.yml
@@ -0,0 +1,5 @@
+---
+title: Store wiki uploads inside git repository
+merge_request: 21362
+author:
+type: added
diff --git a/changelogs/unreleased/fj-47229-fix-logo-lfs-tracked.yml b/changelogs/unreleased/fj-47229-fix-logo-lfs-tracked.yml
new file mode 100644
index 00000000000..ed2af81f779
--- /dev/null
+++ b/changelogs/unreleased/fj-47229-fix-logo-lfs-tracked.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed bug when the project logo file is stored in LFS
+merge_request: 20948
+author:
+type: fixed
diff --git a/changelogs/unreleased/ide-multiple-file-uploads.yml b/changelogs/unreleased/ide-multiple-file-uploads.yml
new file mode 100644
index 00000000000..6bb73739864
--- /dev/null
+++ b/changelogs/unreleased/ide-multiple-file-uploads.yml
@@ -0,0 +1,5 @@
+---
+title: Enabled multiple file uploads in the Web IDE
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/ide-row-hover-scroll.yml b/changelogs/unreleased/ide-row-hover-scroll.yml
new file mode 100644
index 00000000000..24c273b4f25
--- /dev/null
+++ b/changelogs/unreleased/ide-row-hover-scroll.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed IDE file row scrolling into view when hovering
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/rails5-mysql-binary-column-index-length.yml b/changelogs/unreleased/rails5-mysql-binary-column-index-length.yml
new file mode 100644
index 00000000000..c4eb0ddac4c
--- /dev/null
+++ b/changelogs/unreleased/rails5-mysql-binary-column-index-length.yml
@@ -0,0 +1,5 @@
+---
+title: 'Rails 5: support schema t.index for mysql'
+merge_request: 21485
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/remove-background-migration-worker-feature-flag.yml b/changelogs/unreleased/remove-background-migration-worker-feature-flag.yml
new file mode 100644
index 00000000000..429ab6c59e3
--- /dev/null
+++ b/changelogs/unreleased/remove-background-migration-worker-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove health check feature flag in BackgroundMigrationWorker
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/schema-changed-ee-backport.yml b/changelogs/unreleased/schema-changed-ee-backport.yml
new file mode 100644
index 00000000000..f3b16fc0c27
--- /dev/null
+++ b/changelogs/unreleased/schema-changed-ee-backport.yml
@@ -0,0 +1,5 @@
+---
+title: Backport schema_changed.sh from EE which prints the diff if the schema is different
+merge_request: 21422
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/sh-bump-gitlab-pages-v1-1-0.yml b/changelogs/unreleased/sh-bump-gitlab-pages-v1-1-0.yml
new file mode 100644
index 00000000000..bc5b6b36ac5
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-gitlab-pages-v1-1-0.yml
@@ -0,0 +1,5 @@
+---
+title: Bump GitLab Pages to v1.1.0
+merge_request: 21419
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-bump-unauth-expiration.yml b/changelogs/unreleased/sh-bump-unauth-expiration.yml
new file mode 100644
index 00000000000..107069f3b30
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-unauth-expiration.yml
@@ -0,0 +1,5 @@
+---
+title: Bump unauthenticated session time from 1 hour to 2 hours
+merge_request: 21453
+author:
+type: other
diff --git a/changelogs/unreleased/sh-disable-sidekiq-session.yml b/changelogs/unreleased/sh-disable-sidekiq-session.yml
new file mode 100644
index 00000000000..d018bbed841
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-sidekiq-session.yml
@@ -0,0 +1,5 @@
+---
+title: Disable the Sidekiq Admin Rack session
+merge_request: 21441
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-confidential-note-option.yml b/changelogs/unreleased/sh-fix-confidential-note-option.yml
new file mode 100644
index 00000000000..14d70281760
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-confidential-note-option.yml
@@ -0,0 +1,5 @@
+---
+title: Fix "Confidential comments" button not saving in project hooks
+merge_request: 21289
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-dedupe-group-importer.yml b/changelogs/unreleased/sh-fix-dedupe-group-importer.yml
new file mode 100644
index 00000000000..1b874c64718
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-dedupe-group-importer.yml
@@ -0,0 +1,5 @@
+---
+title: Fix importers not assigning a new default group
+merge_request: 21456
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-error-500-updating-wikis.yml b/changelogs/unreleased/sh-fix-error-500-updating-wikis.yml
new file mode 100644
index 00000000000..d80d4952ba5
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-error-500-updating-wikis.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Error 500s due to encoding issues when Wiki hooks fire
+merge_request: 21414
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-improve-bitbucket-server-logging.yml b/changelogs/unreleased/sh-improve-bitbucket-server-logging.yml
new file mode 100644
index 00000000000..c94ff959f1c
--- /dev/null
+++ b/changelogs/unreleased/sh-improve-bitbucket-server-logging.yml
@@ -0,0 +1,5 @@
+---
+title: Add JSON logging for Bitbucket Server importer
+merge_request: 21378
+author:
+type: other
diff --git a/changelogs/unreleased/sh-send-put-headers-object-storage.yml b/changelogs/unreleased/sh-send-put-headers-object-storage.yml
new file mode 100644
index 00000000000..cbd8b6deb5b
--- /dev/null
+++ b/changelogs/unreleased/sh-send-put-headers-object-storage.yml
@@ -0,0 +1,5 @@
+---
+title: Send back required object storage PUT headers in /uploads/authorize API
+merge_request: 21319
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-set-secure-cookies.yml b/changelogs/unreleased/sh-set-secure-cookies.yml
new file mode 100644
index 00000000000..da741288b42
--- /dev/null
+++ b/changelogs/unreleased/sh-set-secure-cookies.yml
@@ -0,0 +1,5 @@
+---
+title: Set issuable_sort, diff_view, and perf_bar_enabled cookies to secure when possible
+merge_request: 21442
+author:
+type: security
diff --git a/changelogs/unreleased/update-padding-markdown.yml b/changelogs/unreleased/update-padding-markdown.yml
new file mode 100644
index 00000000000..51037200bd1
--- /dev/null
+++ b/changelogs/unreleased/update-padding-markdown.yml
@@ -0,0 +1,5 @@
+---
+title: Increase padding in code blocks
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-move-badge-settings.yml b/changelogs/unreleased/winh-move-badge-settings.yml
new file mode 100644
index 00000000000..9638ba04c1e
--- /dev/null
+++ b/changelogs/unreleased/winh-move-badge-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Move badge settings to general settings
+merge_request: 21333
+author:
+type: changed
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index dce1fc1bc45..16f16f77fb9 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -570,3 +570,10 @@
:why: https://github.com/codesandbox-app/codesandbox-importers/blob/master/packages/import-utils/LICENSE
:versions: []
:when: 2018-08-03 12:23:24.083046000 Z
+- - :ignore_group
+ - devDependencies
+ - :who: Winnie Hellmann
+ :why: NPM packages used for development are not distributed with the final product and are therefore
+ exempt.
+ :versions: []
+ :when: 2018-08-30 12:06:35.668181000 Z
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9ad55e21d11..ab351b86cae 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -141,7 +141,7 @@ Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
Settings.gitlab['session_expire_delay'] ||= 10080
-Settings.gitlab['unauthenticated_session_expire_delay'] ||= 1.hour.to_i
+Settings.gitlab['unauthenticated_session_expire_delay'] ||= 2.hours.to_i
Settings.gitlab.default_projects_features['issues'] = true if Settings.gitlab.default_projects_features['issues'].nil?
Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil?
Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
diff --git a/config/initializers/mysql_set_length_for_binary_indexes.rb b/config/initializers/mysql_set_length_for_binary_indexes.rb
index de0bc5322aa..1b16b39d517 100644
--- a/config/initializers/mysql_set_length_for_binary_indexes.rb
+++ b/config/initializers/mysql_set_length_for_binary_indexes.rb
@@ -2,6 +2,9 @@
# MySQL adapter apply a length of 20. Otherwise MySQL can't create an index on
# binary columns.
+# This module can be removed once a Rails 5 schema is used.
+# It can't be wrapped in a check that checks Gitlab.rails5? because
+# the old Rails 4 schema layout is still used
module MysqlSetLengthForBinaryIndex
def add_index(table_name, column_names, options = {})
Array(column_names).each do |column_name|
@@ -19,3 +22,28 @@ end
if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:prepend, MysqlSetLengthForBinaryIndex)
end
+
+if Gitlab.rails5?
+ module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema
+ # This method is used in Rails 5 schema loading as t.index
+ def index(column_names, options = {})
+ Array(column_names).each do |column_name|
+ column = columns.find { |c| c.name == column_name }
+
+ if column&.type == :binary
+ options[:length] = 20
+ end
+ end
+
+ # Ignore indexes that use opclasses,
+ # also see config/initializers/mysql_ignore_postgresql_options.rb
+ unless options[:opclasses]
+ super(column_names, options)
+ end
+ end
+ end
+
+ if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition)
+ ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema)
+ end
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 6f54bee4713..476eaabfed8 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,3 +1,9 @@
+require 'sidekiq/web'
+
+# Disable the Sidekiq Rack session since GitLab already has its own session store.
+# CSRF protection still works (https://github.com/mperham/sidekiq/commit/315504e766c4fd88a29b7772169060afc4c40329).
+Sidekiq::Web.set :sessions, false
+
# Custom Queues configuration
queues_config_hash = Gitlab::Redis::Queues.params
queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
diff --git a/config/routes/group.rb b/config/routes/group.rb
index d7313e43786..343865cc50c 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -25,7 +25,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd'
- resources :badges, only: [:index]
end
resource :variables, only: [:show, :update]
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 34f49546983..4021d62b931 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -442,7 +442,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :repository, only: [:show], controller: :repository do
post :create_deploy_token, path: 'deploy_token/create'
end
- resources :badges, only: [:index]
end
# Since both wiki and repository routing contains wildcard characters
diff --git a/danger/metadata/Dangerfile b/danger/metadata/Dangerfile
index 3cfaa04e01b..51fc9e6bfca 100644
--- a/danger/metadata/Dangerfile
+++ b/danger/metadata/Dangerfile
@@ -21,5 +21,5 @@ end
has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('Pick into') }
if gitlab.branch_for_base != "master" && !has_pick_into_stable_label
- warn "Most of the time, all merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label."
+ warn "Most of the time, merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label."
end
diff --git a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
new file mode 100644
index 00000000000..fe50e909563
--- /dev/null
+++ b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
@@ -0,0 +1,13 @@
+class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :application_settings, :user_default_internal_regex, :string, null: true
+ end
+
+ def down
+ remove_column :application_settings, :user_default_internal_regex
+ end
+end
diff --git a/db/migrate/20180815160409_add_file_location_to_ci_job_artifacts.rb b/db/migrate/20180815160409_add_file_location_to_ci_job_artifacts.rb
new file mode 100644
index 00000000000..620342005fe
--- /dev/null
+++ b/db/migrate/20180815160409_add_file_location_to_ci_job_artifacts.rb
@@ -0,0 +1,9 @@
+class AddFileLocationToCiJobArtifacts < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_job_artifacts, :file_location, :integer, limit: 2
+ end
+end
diff --git a/db/migrate/20180815170510_add_partial_index_to_ci_builds_artifacts_file.rb b/db/migrate/20180815170510_add_partial_index_to_ci_builds_artifacts_file.rb
new file mode 100644
index 00000000000..5e041ea6559
--- /dev/null
+++ b/db/migrate/20180815170510_add_partial_index_to_ci_builds_artifacts_file.rb
@@ -0,0 +1,16 @@
+class AddPartialIndexToCiBuildsArtifactsFile < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'partial_index_ci_builds_on_id_with_legacy_artifacts'.freeze
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:ci_builds, :id, where: "artifacts_file <> ''", name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:ci_builds, INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20180816161409_migrate_legacy_artifacts_to_job_artifacts.rb b/db/post_migrate/20180816161409_migrate_legacy_artifacts_to_job_artifacts.rb
new file mode 100644
index 00000000000..2dd711e9c10
--- /dev/null
+++ b/db/post_migrate/20180816161409_migrate_legacy_artifacts_to_job_artifacts.rb
@@ -0,0 +1,32 @@
+class MigrateLegacyArtifactsToJobArtifacts < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'MigrateLegacyArtifacts'.freeze
+ BATCH_SIZE = 100
+
+ disable_ddl_transaction!
+
+ class Build < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'ci_builds'
+ self.inheritance_column = :_type_disabled
+
+ scope :with_legacy_artifacts, -> { where("artifacts_file <> ''") }
+ end
+
+ def up
+ MigrateLegacyArtifactsToJobArtifacts::Build
+ .with_legacy_artifacts.tap do |relation|
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ MIGRATION,
+ 5.minutes,
+ batch_size: BATCH_SIZE)
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cb8f90efded..56c7265119d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain"
t.boolean "pages_domain_verification_enabled", default: true, null: false
+ t.string "user_default_internal_regex"
t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.boolean "enforce_terms", default: false
t.boolean "mirror_available", default: true, null: false
@@ -341,6 +342,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
+ add_index "ci_builds", ["id"], name: "partial_index_ci_builds_on_id_with_legacy_artifacts", where: "(artifacts_file <> ''::text)", using: :btree
add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
@@ -396,6 +398,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.string "file"
t.binary "file_sha256"
t.integer "file_format", limit: 2
+ t.integer "file_location", limit: 2
end
add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree
diff --git a/doc/administration/compliance.md b/doc/administration/compliance.md
new file mode 100644
index 00000000000..0414b3ec12e
--- /dev/null
+++ b/doc/administration/compliance.md
@@ -0,0 +1,18 @@
+# Compliance features
+
+You can configure the following GitLab features to help ensure that your GitLab instance meets common compliance standards. Click a feature name for further documentation.
+
+GitLab’s [security features](../security/README.md) may also help you meet relevant compliance standards.
+
+|Feature |GitLab tier |GitLab.com |
+| ---------| :--------: | :-------: |
+|**[Restrict SSH Keys](../README.html#administrator-documentation)**<br>Control the technology and key length of SSH keys used to access GitLab|Core+||
+|**[Granular user roles and flexible permissions](../user/permissions.html)**<br>Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker.|Core+|✓|
+|**[Enforce TOS acceptance](../user/admin_area/settings/terms.html)**<br>Enforce your users accepting new terms of service by blocking GitLab traffic.|Core+||
+|**[Email all users of a project, group, or entire server](../user/admin_area/settings/terms.html)**<br>An admin can email groups of users based on project or group membership, or email everyone using the GitLab instance. This is great for scheduled maintenance or upgrades.|Starter+||
+|**[Omnibus package supports log forwarding](https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-forwarding)**<br>Forward your logs to a central system.|Starter+||
+|**[Lock project membership to group](../workflow/groups.html#lock-project-membership-to-members-of-this-group)**<br>Group owners can prevent new members from being added to projects within a group.|Starter+|✓|
+|**[LDAP group sync](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition gives admins the ability to automatically sync groups and manage SSH keys, permissions, and authentication, so you can focus on building your product, not configuring your tools.|Starter+||
+|**[LDAP group sync filters](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#group-sync)**<br>GitLab Enterprise Edition Premium gives more flexibility to synchronize with LDAP based on filters, meaning you can leverage LDAP attributes to map GitLab permissions.|Premium+||
+|**[Audit logs](https://docs.gitlab.com/ee/administration/audit_events.html)**<br>To maintain the integrity of your code, GitLab Enterprise Edition Premium gives admins the ability to view any modifications made within the GitLab server in an advanced audit log system, so you can control, analyze and track every change.|Premium+||
+|**[Auditor users](https://docs.gitlab.com/ee/administration/auditor_users.html)**<br>Auditor users are users who are given read-only access to all projects, groups, and other resources on the GitLab instance.|Premium+|| \ No newline at end of file
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 6d7e408d41b..36567173125 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -149,7 +149,7 @@ listen_addr = '0.0.0.0:8075'
[auth]
token = 'abc123secret'
-[[storage]
+[[storage]]
name = 'default'
path = '/mnt/gitlab/default/repositories'
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index cd2284f5f2a..95e2caf0cad 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -47,7 +47,7 @@ there because this will also affect performance. We recommend that the log files
stored on a local volume.
For more details on another person's experience with EFS, see
-[Amazon's Elastic File System: Burst Credits](https://www.rawkode.io/2017/04/amazons-elastic-file-system-burst-credits/)
+[Amazon's Elastic File System: Burst Credits](https://rawkode.com/2017/04/16/amazons-elastic-file-system-burst-credits/)
## NFS Client mount options
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 030a2f95e23..837a04f3e88 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -46,6 +46,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Plugins](plugins.md): With custom plugins, GitLab administrators can introduce custom integrations without modifying GitLab's source code.
- [Enforcing Terms of Service](../user/admin_area/settings/terms.md)
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
+- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
#### Customizing GitLab's appearance
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 8c55c8c4298..4b5be8699e9 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -190,7 +190,7 @@ _The artifacts are stored by default in
remote_directory: "artifacts" # The bucket name
connection:
provider: AWS # Only AWS supported at the moment
- aws_access_key_id: AWS_ACESS_KEY_ID
+ aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: eu-central-1
```
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index 6e2f67f61bc..a792a5f2a97 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -12,8 +12,8 @@ In the following table you can see the phases a trace goes through.
| ----- | ----- | --------- | --------- | ----------- |
| 1: patching | Live trace | When a job is running | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
| 2: overwriting | Live trace | When a job is finished | GitLab Runner => Unicorn => file storage |`#{ROOT_PATH}/builds/#{YYYY_mm}/#{project_id}/#{job_id}.log`|
-| 3: archiving | Archived trace | After a job is finished | Sidekiq moves live trace to artifacts folder |`#{ROOT_PATH}/shared/artifacts/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/trace.log`|
-| 4: uploading | Archived trace | After a trace is archived | Sidekiq moves archived trace to [object storage](#uploading-traces-to-object-storage) (if configured) |`#{bucket_name}/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/trace.log`|
+| 3: archiving | Archived trace | After a job is finished | Sidekiq moves live trace to artifacts folder |`#{ROOT_PATH}/shared/artifacts/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/job.log`|
+| 4: uploading | Archived trace | After a trace is archived | Sidekiq moves archived trace to [object storage](#uploading-traces-to-object-storage) (if configured) |`#{bucket_name}/#{disk_hash}/#{YYYY_mm_dd}/#{job_id}/#{job_artifact_id}/job.log`|
The `ROOT_PATH` varies per your environment. For Omnibus GitLab it
would be `/var/opt/gitlab/gitlab-ci`, whereas for installations from source
@@ -88,6 +88,8 @@ To archive those legacy job traces, please follow the instruction below.
## How to migrate archived job traces to object storage
+> [Introduced][ce-21193] in GitLab 11.3.
+
If job traces have already been archived into local storage, and you want to migrate those traces to object storage, please follow the instruction below.
1. Ensure [Object storage integration for Job Artifacts](job_artifacts.md#object-storage-settings) is enabled
@@ -201,4 +203,5 @@ indicate that we have trace chunk. `UPDATE`s with 128KB of data is issued once w
receive multiple chunks.
[ce-18169]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18169
+[ce-21193]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21193
[ce-46097]: https://gitlab.com/gitlab-org/gitlab-ce/issues/46097
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index c8a3ef80e8f..0fbb4481fb8 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -219,6 +219,15 @@ installations from source.
It logs information whenever a [repository check is run][repocheck] on a project.
+## `importer.log`
+
+Introduced in GitLab 11.3. This file lives in `/var/log/gitlab/gitlab-rails/importer.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for
+installations from source.
+
+Currently it logs the progress of project imports from the Bitbucket Server
+importer. Future importers may use this file.
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index eefa86f8e42..f16ba0b297d 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -11,6 +11,7 @@ description: 'Learn how to administer GitLab Pages.'
- This guide is for Omnibus GitLab installations. If you have installed
GitLab from source, follow the [Pages source installation document](source.md).
- To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
+- Does NOT support subgroups. See [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) for more information and status.
This document describes how to set up the _latest_ GitLab Pages feature. Make
sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
@@ -73,8 +74,8 @@ among other things.
Follow [these instructions](https://publicsuffix.org/submit/) to submit your
GitLab Pages subdomain. For instance, if your domain is `example.io`, you should
-request that `*.example.io` is added to the Public Suffix List. GitLab.com
-added `*.gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230).
+request that `example.io` is added to the Public Suffix List. GitLab.com
+added `gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230).
### DNS configuration
diff --git a/doc/api/branches.md b/doc/api/branches.md
index bfb21608d28..4abf0639eb0 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -27,6 +27,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
+ "default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
@@ -75,6 +76,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
+ "default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true,
@@ -141,6 +143,7 @@ Example response:
"name": "master",
"merged": false,
"protected": true,
+ "default": true,
"developers_can_push": true,
"developers_can_merge": true,
"can_push": true
@@ -190,6 +193,7 @@ Example response:
"name": "master",
"merged": false,
"protected": false,
+ "default": true,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true
@@ -234,6 +238,7 @@ Example response:
"name": "newbranch",
"merged": false,
"protected": false,
+ "default": false,
"developers_can_push": false,
"developers_can_merge": false,
"can_push": true
diff --git a/doc/api/commits.md b/doc/api/commits.md
index d07b9d5614a..624ed529009 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -464,7 +464,7 @@ Example response:
},
{
"started_at" : null,
- "name" : "flay",
+ "name" : "test",
"allow_failure" : false,
"status" : "pending",
"created_at" : "2016-01-19T08:40:25.832Z",
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 64e0d78788d..be75c363a40 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -82,7 +82,7 @@ You can filter by [custom attributes](custom_attributes.md) with:
GET /groups?custom_attributes[key]=value&custom_attributes[other_key]=other_value
```
-## List a groups's subgroups
+## List a group's subgroups
> [Introduced][ce-15142] in GitLab 10.3.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0936ff52dae..86acb96357d 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -710,7 +710,7 @@ PUT /projects/:id
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `name` | string | yes | The name of the project |
+| `name` | string | no | The name of the project |
| `path` | string | no | Custom repository name for the project. By default generated based on name |
| `default_branch` | string | no | `master` by default |
| `description` | string | no | Short project description |
diff --git a/doc/api/settings.md b/doc/api/settings.md
index b480d62e16a..83fa9b055d1 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -1,12 +1,14 @@
# Application settings API
-These API calls allow you to read and modify GitLab instance application
-settings as appear in `/admin/application_settings`. You have to be an
+These API calls allow you to read and modify GitLab instance
+[application settings](#list-of-settings-that-can-be-accessed-via-api-calls)
+as appear in `/admin/application_settings`. You have to be an
administrator in order to perform this action.
## Get current application settings
-List the current application settings of the GitLab instance.
+List the current [application settings](#list-of-settings-that-can-be-accessed-via-api-calls)
+of the GitLab instance.
```
GET /application/settings
@@ -63,108 +65,13 @@ Example response:
## Change application settings
+Use an API call to modify GitLab instance
+[application settings](#list-of-settings-that-can-be-accessed-via-api-calls).
+
```
PUT /application/settings
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | :------: | ----------- |
-| `admin_notification_email` | string | no | Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. |
-| `after_sign_out_path` | string | no | Where to redirect users after logout |
-| `after_sign_up_text` | string | no | Text shown to the user after signing up |
-| `akismet_api_key` | string | no | API key for akismet spam protection |
-| `akismet_enabled` | boolean | no | Enable or disable akismet spam protection |
-| `circuitbreaker_access_retries` | integer | no | The number of attempts GitLab will make to access a storage. |
-| `circuitbreaker_check_interval` | integer | no | Number of seconds in between storage checks. |
-| `circuitbreaker_failure_count_threshold` | integer | no | The number of failures of after which GitLab will completely prevent access to the storage. |
-| `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. |
-| `circuitbreaker_storage_timeout` | integer | no | Seconds to wait for a storage access attempt |
-| `clientside_sentry_dsn` | string | no | Required if `clientside_sentry_dsn` is enabled |
-| `clientside_sentry_enabled` | boolean | no | Enable Sentry error reporting for the client side |
-| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes |
-| `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts |
-| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take `0` _(not protected, both developers and maintainers can push new commits, force push, or delete the branch)_, `1` _(partially protected, developers and maintainers can push new commits, but cannot force push or delete the branch)_ or `2` _(fully protected, developers cannot push new commits, but maintainers can; no-one can force push or delete the branch)_ as a parameter. Default is `2`. |
-| `default_group_visibility` | string | no | What visibility level new groups receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
-| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
-| `default_projects_limit` | integer | no | Project limit per user. Default is `100000` |
-| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
-| `disabled_oauth_sign_in_sources` | Array of strings | no | Disabled OAuth sign-in sources |
-| `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` |
-| `domain_blacklist` | array of strings | yes (if `domain_blacklist_enabled` is `true`) | People trying to sign-up with emails from this domain will not be allowed to do so. |
-| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. |
-| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
-| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
-| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
-| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
-| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
-| `gravatar_enabled` | boolean | no | Enable Gravatar |
-| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help |
-| `help_page_support_url` | string | no | Alternate support URL for help page |
-| `home_page_url` | string | no | Redirect to this URL when not logged in |
-| `housekeeping_bitmaps_enabled` | boolean | no | Enable Git pack file bitmap creation |
-| `housekeeping_enabled` | boolean | no | Enable or disable git housekeeping |
-| `housekeeping_full_repack_period` | integer | no | Number of Git pushes after which an incremental 'git repack' is run. |
-| `housekeeping_gc_period` | integer | no | Number of Git pushes after which 'git gc' is run. |
-| `housekeeping_incremental_repack_period` | integer | no | Number of Git pushes after which an incremental 'git repack' is run. |
-| `html_emails_enabled` | boolean | no | Enable HTML emails |
-| `import_sources` | Array of strings | no | Sources to allow project import from, possible values: "github bitbucket gitlab google_code fogbugz git gitlab_project manifest |
-| `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. |
-| `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. |
-| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
-| `max_attachment_size` | integer | no | Limit attachment size in MB |
-| `max_pages_size` | integer | no | Maximum size of pages repositories in MB |
-| `metrics_enabled` | boolean | no | Enable influxDB metrics |
-| `metrics_host` | string | yes (if `metrics_enabled` is `true`) | InfluxDB host |
-| `metrics_method_call_threshold` | integer | yes (if `metrics_enabled` is `true`) | A method call is only tracked when it takes longer than the given amount of milliseconds |
-| `metrics_packet_size` | integer | yes (if `metrics_enabled` is `true`) | The amount of datapoints to send in a single UDP packet. |
-| `metrics_pool_size` | integer | yes (if `metrics_enabled` is `true`) | The amount of InfluxDB connections to keep open |
-| `metrics_port` | integer | no | The UDP port to use for connecting to InfluxDB |
-| `metrics_sample_interval` | integer | yes (if `metrics_enabled` is `true`) | The sampling interval in seconds. |
-| `metrics_timeout` | integer | yes (if `metrics_enabled` is `true`) | The amount of seconds after which InfluxDB will time out. |
-| `password_authentication_enabled_for_web` | boolean | no | Enable authentication for the web interface via a GitLab account password. Default is `true`. |
-| `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. |
-| `performance_bar_allowed_group_path` | string | no | Path of the group that is allowed to toggle the performance bar |
-| `performance_bar_allowed_group_id` | string | no | Deprecated: Use `performance_bar_allowed_group_path` instead. Path of the group that is allowed to toggle the performance bar |
-| `performance_bar_enabled` | boolean | no | Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance bar |
-| `plantuml_enabled` | boolean | no | Enable PlantUML integration. Default is `false`. |
-| `plantuml_url` | string | yes (if `plantuml_enabled` is `true`) | The PlantUML instance URL for integration. |
-| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling. |
-| `project_export_enabled` | boolean | no | Enable project export |
-| `prometheus_metrics_enabled` | boolean | no | Enable prometheus metrics |
-| `recaptcha_enabled` | boolean | no | Enable recaptcha |
-| `recaptcha_private_key` | string | yes (if `recaptcha_enabled` is true) | Private key for recaptcha |
-| `recaptcha_site_key` | string | yes (if `recaptcha_enabled` is true) | Site key for recaptcha |
-| `repository_checks_enabled` | boolean | no | GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues. |
-| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
-| `require_two_factor_authentication` | boolean | no | Require all users to setup Two-factor authentication |
-| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is null which means there is no restriction. |
-| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
-| `send_user_confirmation_email` | boolean | no | Send confirmation email on sign-up |
-| `sentry_dsn` | string | yes (if `sentry_enabled` is true) | Sentry Data Source Name |
-| `sentry_enabled` | boolean | no | Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com |
-| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
-| `shared_runners_enabled` | true | no | Enable shared runners for new projects |
-| `shared_runners_text` | string | no | Shared runners text |
-| `sidekiq_throttling_enabled` | boolean | no | Enable Sidekiq Job Throttling |
-| `sidekiq_throttling_factor` | decimal | yes (if `sidekiq_throttling_enabled` is true) | The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive. |
-| `sidekiq_throttling_queues` | array of strings | yes (if `sidekiq_throttling_enabled` is true) | Choose which queues you wish to throttle |
-| `sign_in_text` | string | no | Text on login page |
-| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
-| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time. |
-| `two_factor_grace_period` | integer | no | Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication |
-| `unique_ips_limit_enabled` | boolean | no | Limit sign in from multiple ips |
-| `unique_ips_limit_per_user` | integer | yes (if `unique_ips_limit_enabled` is true) | Maximum number of ips per user |
-| `unique_ips_limit_time_window` | integer | yes (if `unique_ips_limit_enabled` is true) | How many seconds an IP will be counted towards the limit |
-| `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc. |
-| `user_default_external` | boolean | no | Newly registered users will by default be external |
-| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider |
-| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
-| `enforce_terms` | boolean | no | Enforce application ToS to all users |
-| `terms` | text | yes (if `enforce_terms` is true) | Markdown content for the ToS |
-| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins |
-| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push
-+project code via SSH" warning shown to users with no uploaded SSH key |
-
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/application/settings?signup_enabled=false&default_project_visibility=internal
```
@@ -175,7 +82,7 @@ Example response:
{
"id": 1,
"default_projects_limit": 100000,
- "signup_enabled": true,
+ "signup_enabled": false,
"password_authentication_enabled_for_web": true,
"gravatar_enabled": true,
"sign_in_text": "",
@@ -213,3 +120,128 @@ Example response:
"user_show_add_ssh_key_message": true
}
```
+
+## List of settings that can be accessed via API calls
+
+In general, all settings are optional. Certain settings though, if enabled, will
+require other settings to be set in order to function properly. These requirements
+are listed in the descriptions of the relevant settings.
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | :------: | ----------- |
+| `admin_notification_email` | string | no | Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. |
+| `after_sign_out_path` | string | no | Where to redirect users after logout. |
+| `after_sign_up_text` | string | no | Text shown to the user after signing up |
+| `akismet_api_key` | string | required by: `akismet_enabled` | API key for akismet spam protection. |
+| `akismet_enabled` | boolean | no | (**If enabled, requires:** `akismet_api_key`) Enable or disable akismet spam protection. |
+| `allow_local_requests_from_hooks_and_services` | boolean | no | Allow requests to the local network from hooks and services. |
+| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
+| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
+| `circuitbreaker_access_retries` | integer | no | The number of attempts GitLab will make to access a storage. |
+| `circuitbreaker_check_interval` | integer | no | Number of seconds in between storage checks. |
+| `circuitbreaker_failure_count_threshold` | integer | no | The number of failures after which GitLab will completely prevent access to the storage. |
+| `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. |
+| `circuitbreaker_storage_timeout` | integer | no | Seconds to wait for a storage access attempt. |
+| `clientside_sentry_dsn` | string | required by: `clientside_sentry_enabled` | Clientside Sentry Data Source Name. |
+| `clientside_sentry_enabled` | boolean | no | (**If enabled, requires:** `clientside_sentry_dsn`) Enable Sentry error reporting for the client side. |
+| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes. |
+| `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts. |
+| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take: `0` _(not protected, both developers and maintainers can push new commits, force push, or delete the branch)_, `1` _(partially protected, developers and maintainers can push new commits, but cannot force push or delete the branch)_ or `2` _(fully protected, developers cannot push new commits, but maintainers can; no-one can force push or delete the branch)_ as a parameter. Default is `2`. |
+| `default_group_visibility` | string | no | What visibility level new groups receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
+| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
+| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
+| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
+| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
+| `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
+| `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. |
+| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
+| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
+| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
+| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
+| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
+| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
+| `enforce_terms` | boolean | no | (**If enabled, requires:** `terms`) Enforce application ToS to all users. |
+| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
+| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
+| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
+| `gravatar_enabled` | boolean | no | Enable Gravatar. |
+| `hashed_storage_enabled` | boolean | no | Create new projects using hashed storage paths: Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance. (EXPERIMENTAL) |
+| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help. |
+| `help_page_support_url` | string | no | Alternate support URL for help page. |
+| `help_page_text` | string | no | Custom text displayed on the help page. |
+| `hide_third_party_offers` | boolean | no | Do not display offers from third parties within GitLab. |
+| `home_page_url` | string | no | Redirect to this URL when not logged in. |
+| `housekeeping_bitmaps_enabled` | boolean | required by: `housekeeping_enabled` | Enable Git pack file bitmap creation. |
+| `housekeeping_enabled` | boolean | no | (**If enabled, requires:** `housekeeping_bitmaps_enabled`, `housekeeping_full_repack_period`, `housekeeping_gc_period`, and `housekeeping_incremental_repack_period`) Enable or disable git housekeeping. |
+| `housekeeping_full_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
+| `housekeeping_gc_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which `git gc` is run. |
+| `housekeeping_incremental_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
+| `html_emails_enabled` | boolean | no | Enable HTML emails. |
+| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins. |
+| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `gitlab`, `google_code`, `fogbugz`, `git`, and `gitlab_project`. |
+| `koding_enabled` | boolean | no | (If enabled, requires: `koding_url`) Enable Koding integration. Default is `false`. |
+| `koding_url` | string | required by: `koding_enabled` | The Koding instance URL for integration. |
+| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
+| `max_attachment_size` | integer | no | Limit attachment size in MB |
+| `max_pages_size` | integer | no | Maximum size of pages repositories in MB |
+| `metrics_enabled` | boolean | no | (**If enabled, requires:** `metrics_host`, `metrics_method_call_threshold`, `metrics_packet_size`, `metrics_pool_size`, `metrics_port`, `metrics_sample_interval` and `metrics_timeout`) Enable influxDB metrics. |
+| `metrics_host` | string | required by: `metrics_enabled` | InfluxDB host. |
+| `metrics_method_call_threshold` | integer | required by: `metrics_enabled` | A method call is only tracked when it takes longer than the given amount of milliseconds. |
+| `metrics_packet_size` | integer | required by: `metrics_enabled` | The amount of datapoints to send in a single UDP packet. |
+| `metrics_pool_size` | integer | required by: `metrics_enabled` | The amount of InfluxDB connections to keep open. |
+| `metrics_port` | integer | required by: `metrics_enabled` | The UDP port to use for connecting to InfluxDB. |
+| `metrics_sample_interval` | integer | required by: `metrics_enabled` | The sampling interval in seconds. |
+| `metrics_timeout` | integer | required by: `metrics_enabled` | The amount of seconds after which InfluxDB will time out. |
+| `mirror_available` | boolean | no | Allow mirrors to be setup for projects. If disabled, only admins will be able to setup mirrors in projects. |
+| `pages_domain_verification_enabled` | boolean | no | Require users to prove ownership of custom domains. Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled. |
+| `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. |
+| `password_authentication_enabled_for_web` | boolean | no | Enable authentication for the web interface via a GitLab account password. Default is `true`. |
+| `performance_bar_allowed_group_id` | string | no | (Deprecated: Use `performance_bar_allowed_group_path` instead) Path of the group that is allowed to toggle the performance bar. |
+| `performance_bar_allowed_group_path` | string | no | Path of the group that is allowed to toggle the performance bar. |
+| `performance_bar_enabled` | boolean | no | (Deprecated: Pass `performance_bar_allowed_group_path: nil` instead) Allow enabling the performance bar. |
+| `plantuml_enabled` | boolean | no | (**If enabled, requires:** `plantuml_url`) Enable PlantUML integration. Default is `false`. |
+| `plantuml_url` | string | required by: `plantuml_enabled` | The PlantUML instance URL for integration. |
+| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to `0` to disable polling. |
+| `project_export_enabled` | boolean | no | Enable project export. |
+| `prometheus_metrics_enabled` | boolean | no | Enable prometheus metrics. |
+| `recaptcha_enabled` | boolean | no | (**If enabled, requires:** `recaptcha_private_key` and `recaptcha_site_key`) Enable recaptcha. |
+| `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for recaptcha. |
+| `recaptcha_site_key` | string | required by: `recaptcha_enabled` | Site key for recaptcha. |
+| `repository_checks_enabled` | boolean | no | GitLab will periodically run `git fsck` in all project and wiki repositories to look for silent disk corruption issues. |
+| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
+| `require_two_factor_authentication` | boolean | no | (**If enabled, requires:** `two_factor_grace_period`) Require all users to set up Two-factor authentication. |
+| `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is `null` which means there is no restriction. |
+| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
+| `send_user_confirmation_email` | boolean | no | Send confirmation email on sign-up. |
+| `sentry_dsn` | string | required by: `sentry_enabled` | Sentry Data Source Name. |
+| `sentry_enabled` | boolean | no | (**If enabled, requires:** `sentry_dsn`) Sentry is an error reporting and logging tool which is currently not shipped with GitLab, available at https://getsentry.com. |
+| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes |
+| `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text`) Enable shared runners for new projects. |
+| `shared_runners_text` | string | required by: `shared_runners_enabled` | Shared runners text. |
+| `sidekiq_throttling_enabled` | boolean | no | (**If enabled, requires:** `sidekiq_throttling_factor` and `sidekiq_throttling_queues`) Enable Sidekiq Job Throttling. |
+| `sidekiq_throttling_factor` | decimal | required by: `sidekiq_throttling_enabled` | The factor by which the queues should be throttled. A value between `0.0` and `1.0`, exclusive. |
+| `sidekiq_throttling_queues` | array of strings | required by: `sidekiq_throttling_enabled` | Choose which queues you wish to throttle. |
+| `sign_in_text` | string | no | Text on the login page. |
+| `signin_enabled` | string | no | (Deprecated: Use `password_authentication_enabled_for_web` instead) Flag indicating if password authentication is enabled for the web interface. |
+| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
+| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to `0` for unlimited time. |
+| `terms` | text | required by: `enforce_terms` | (**Required by:** `enforce_terms`) Markdown content for the ToS. |
+| `throttle_authenticated_api_enabled` | boolean | no | (**If enabled, requires:** `throttle_authenticated_api_period_in_seconds` and `throttle_authenticated_api_requests_per_period`) Enable authenticated API request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). |
+| `throttle_authenticated_api_period_in_seconds` | integer | required by: `throttle_authenticated_api_enabled` | Rate limit period in seconds. |
+| `throttle_authenticated_api_requests_per_period` | integer | required by: `throttle_authenticated_api_enabled` | Max requests per period per user. |
+| `throttle_authenticated_web_enabled` | boolean | no | (**If enabled, requires:** `throttle_authenticated_web_period_in_seconds` and `throttle_authenticated_web_requests_per_period`) Enable authenticated web request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). |
+| `throttle_authenticated_web_period_in_seconds` | integer | required by: `throttle_authenticated_web_enabled` | Rate limit period in seconds. |
+| `throttle_authenticated_web_requests_per_period` | integer | required by: `throttle_authenticated_web_enabled` | Max requests per period per user. |
+| `throttle_unauthenticated_enabled` | boolean | no | (**If enabled, requires:** `throttle_unauthenticated_period_in_seconds` and `throttle_unauthenticated_requests_per_period`) Enable unauthenticated request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). |
+| `throttle_unauthenticated_period_in_seconds` | integer | required by: `throttle_unauthenticated_enabled` | Rate limit period in seconds. |
+| `throttle_unauthenticated_requests_per_period` | integer | required by: `throttle_unauthenticated_enabled` | Max requests per period per IP. |
+| `two_factor_grace_period` | integer | required by: `require_two_factor_authentication` | Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication. |
+| `unique_ips_limit_enabled` | boolean | no | (**If enabled, requires:** `unique_ips_limit_per_user` and `unique_ips_limit_time_window`) Limit sign in from multiple ips. |
+| `unique_ips_limit_per_user` | integer | required by: `unique_ips_limit_enabled` | Maximum number of ips per user. |
+| `unique_ips_limit_time_window` | integer | required by: `unique_ips_limit_enabled` | How many seconds an IP will be counted towards the limit. |
+| `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc. |
+| `user_default_external` | boolean | no | Newly registered users will be external by default. |
+| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
+| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
+| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 4af096c3c0c..f2a3f9f28d2 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -174,10 +174,21 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
+
+Request body:
+
- `description` (required) - Release notes with markdown support
```json
{
+ "description": "Amazing release. Wow"
+}
+```
+
+Response:
+
+```json
+{
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
@@ -195,10 +206,21 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `tag_name` (required) - The name of a tag
+
+Request body:
+
- `description` (required) - Release notes with markdown support
```json
{
+ "description": "Amazing release. Wow"
+}
+```
+
+Response:
+
+```json
+{
"tag_name": "1.0.0",
"description": "Amazing release. Wow"
}
diff --git a/doc/api/wikis.md b/doc/api/wikis.md
index 15ce5f96b60..fb0ec773da5 100644
--- a/doc/api/wikis.md
+++ b/doc/api/wikis.md
@@ -97,12 +97,12 @@ curl --data "format=rdoc&title=Hello&content=Hello world" --header "PRIVATE-TOKE
Example response:
```json
-{
+{
"content" : "Hello world",
"format" : "markdown",
"slug" : "Hello",
"title" : "Hello"
-}
+}
```
## Edit an existing wiki page
@@ -154,6 +154,44 @@ DELETE /projects/:id/wikis/:slug
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
-On success the HTTP status code is `204` and no JSON response is expected.
+On success the HTTP status code is `204` and no JSON response is expected.
[ce-13372]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13372
+
+## Upload an attachment to the wiki repository
+
+Uploads a file to the attachment folder inside the wiki's repository. The
+ attachment folder is the `uploads` folder.
+
+```
+POST /projects/:id/wikis/attachments
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
+| `file` | string | yes | The attachment to be uploaded |
+| `branch` | string | no | The name of the branch. Defaults to the wiki repository default branch |
+
+To upload a file from your filesystem, use the `--form` argument. This causes
+cURL to post data using the header `Content-Type: multipart/form-data`.
+The `file=` parameter must point to a file on your filesystem and be preceded
+by `@`. For example:
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "file=@dk.png" https://gitlab.example.com/api/v4/projects/1/wikis/attachments
+```
+
+Example response:
+
+```json
+{
+ "file_name" : "dk.png",
+ "file_path" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+ "branch" : "master",
+ "link" : {
+ "url" : "uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png",
+ "markdown" : "![dk](uploads/6a061c4cf9f1c28cb22c384b4b8d4e3c/dk.png)"
+ }
+}
+```
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index c159198d16b..01e95b54fc4 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -14,6 +14,64 @@ starting from GitLab 9.0.
Make sure you read the [`cache` reference](../yaml/README.md#cache) to learn
how it is defined in `.gitlab-ci.yml`.
+## Cache vs artifacts
+
+NOTE: **Note:**
+Be careful if you use cache and artifacts to store the same path in your jobs
+as **caches are restored before artifacts** and the content would be overwritten.
+
+Don't mix the caching with passing artifacts between stages. Caching is not
+designed to pass artifacts between stages. Cache is for runtime dependencies
+needed to compile the project:
+
+- `cache` - **Use for temporary storage for project dependencies.** Not useful
+ for keeping intermediate build results, like `jar` or `apk` files.
+ Cache was designed to be used to speed up invocations of subsequent runs of a
+ given job, by keeping things like dependencies (e.g., npm packages, Go vendor
+ packages, etc.) so they don't have to be re-fetched from the public internet.
+ While the cache can be abused to pass intermediate build results between stages,
+ there may be cases where artifacts are a better fit.
+- `artifacts` - **Use for stage results that will be passed between stages.**
+ Artifacts were designed to upload some compiled/generated bits of the build,
+ and they can be fetched by any number of concurrent Runners. They are
+ guaranteed to be available and are there to pass data between jobs. They are
+ also exposed to be downloaded from the UI. **Artifacts can only exist in
+ directories relative to the build directory** and specifying paths which don't
+ comply to this rule trigger an unintuitive and illogical error message (an
+ enhancement is discussed at
+ https://gitlab.com/gitlab-org/gitlab-ce/issues/15530). Artifacts need to be
+ uploaded to the GitLab instance (not only the GitLab runner) before the next
+ stage job(s) can start, so you need to evaluate carefully whether your
+ bandwidth allows you to profit from parallelization with stages and shared
+ artifacts before investing time in changes to the setup.
+
+It's sometimes confusing because the name artifact sounds like something that
+is only useful outside of the job, like for downloading a final image. But
+artifacts are also available in between stages within a pipeline. So if you
+build your application by downloading all the required modules, you might want
+to declare them as artifacts so that each subsequent stage can depend on them
+being there. There are some optimizations like declaring an
+[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts
+around too long, and using [dependencies](../yaml/README.md#dependencies) to
+control exactly where artifacts are passed around.
+
+In summary:
+
+- Caches are disabled if not defined globally or per job (using `cache:`)
+- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally
+- Caches can be used by subsequent pipelines of that very same job (a script in
+ a stage) in which the cache was created (if not defined globally).
+- Caches are stored where the Runner is installed **and** uploaded to S3 if
+ [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)
+- Caches defined per job are only used either a) for the next pipeline of that job,
+ or b) if that same cache is also defined in a subsequent job of the same pipeline
+- Artifacts are disabled if not defined per job (using `artifacts:`)
+- Artifacts can only be enabled per job, not globally
+- Artifacts are created during a pipeline and can be used by the subsequent
+ jobs of that currently active pipeline
+- Artifacts are always uploaded to GitLab (known as coordinator)
+- Artifacts can have an expiration value for controlling disk usage (30 days by default).
+
## Good caching practices
We have the cache from the perspective of the developers (who consume a cache
@@ -87,7 +145,7 @@ you can use the same key for all of them:
```yaml
cache:
- key: one-key-to-rull-them-all
+ key: one-key-to-rule-them-all
```
To share the same cache between branches, but separate them by job:
@@ -467,52 +525,3 @@ Behind the scenes, this works by increasing a counter in the database, and the
value of that counter is used to create the key for the cache by appending an
integer to it: `-1`, `-2`, etc. After a push, a new key is generated and the
old cache is not valid anymore.
-
-## Cache vs artifacts
-
-NOTE: **Note:**
-Be careful if you use cache and artifacts to store the same path in your jobs
-as **caches are restored before artifacts** and the content would be overwritten.
-
-Don't mix the caching with passing artifacts between stages. Caching is not
-designed to pass artifacts between stages. Cache is for runtime dependencies
-needed to compile the project:
-
-- `cache` - **Use for temporary storage for project dependencies.** Not useful
- for keeping intermediate build results, like `jar` or `apk` files.
- Cache was designed to be used to speed up invocations of subsequent runs of a
- given job, by keeping things like dependencies (e.g., npm packages, Go vendor
- packages, etc.) so they don't have to be re-fetched from the public internet.
- While the cache can be abused to pass intermediate build results between stages,
- there may be cases where artifacts are a better fit.
-- `artifacts` - **Use for stage results that will be passed between stages.**
- Artifacts were designed to upload some compiled/generated bits of the build,
- and they can be fetched by any number of concurrent Runners. They are
- guaranteed to be available and are there to pass data between jobs. They are
- also exposed to be downloaded from the UI.
-
-It's sometimes confusing because the name artifact sounds like something that
-is only useful outside of the job, like for downloading a final image. But
-artifacts are also available in between stages within a pipeline. So if you
-build your application by downloading all the required modules, you might want
-to declare them as artifacts so that each subsequent stage can depend on them
-being there. There are some optimizations like declaring an
-[expiry time](../yaml/README.md#artifacts-expire_in) so you don't keep artifacts
-around too long, and using [dependencies](../yaml/README.md#dependencies) to
-control exactly where artifacts are passed around.
-
-So, to sum up:
-- Caches are disabled if not defined globally or per job (using `cache:`)
-- Caches are available for all jobs in your `.gitlab-ci.yml` if enabled globally
-- Caches can be used by subsequent pipelines of that very same job (a script in
- a stage) in which the cache was created (if not defined globally).
-- Caches are stored where the Runner is installed **and** uploaded to S3 if
- [distributed cache is enabled](https://docs.gitlab.com/runner/configuration/autoscale.html#distributed-runners-caching)
-- Caches defined per job are only used either a) for the next pipeline of that job,
- or b) if that same cache is also defined in a subsequent job of the same pipeline
-- Artifacts are disabled if not defined per job (using `artifacts:`)
-- Artifacts can only be enabled per job, not globally
-- Artifacts are created during a pipeline and can be used by the subsequent
- jobs of that currently active pipeline
-- Artifacts are always uploaded to GitLab (known as coordinator)
-- Artifacts can have an expiration value for controlling disk usage (30 days by default)
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index 5ae8ecaafa6..cf22450914c 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -100,3 +100,50 @@ golang:
reports:
junit: report.xml
```
+
+### Java examples
+
+There are a few tools that can produce JUnit reports in Java.
+
+#### Gradle
+
+In the following example, `gradle` is used to generate the test reports.
+If there are multiple test tasks defined, `gradle` will generate multiple
+directories under `build/test-results/`. In that case, you can leverage regex
+matching by defining the following path: `build/test-results/test/TEST-*.xml`:
+
+```yaml
+java:
+ stage: test
+ script:
+ - gradle test
+ artifacts:
+ reports:
+ junit: build/test-results/test/TEST-*.xml
+```
+
+#### Maven
+
+For parsing [Surefire](https://maven.apache.org/surefire/maven-surefire-plugin/)
+and [Failsafe](https://maven.apache.org/surefire/maven-failsafe-plugin/) test
+reports, use the following job in `.gitlab-ci.yml`:
+
+```yaml
+java:
+ stage: test
+ script:
+ - mvn verify
+ artifacts:
+ reports:
+ junit:
+ - target/surefire-reports/TEST-*.xml
+ - target/failsafe-reports/TEST-*.xml
+```
+
+## Limitations
+
+Currently, the following tools might not work because their XML formats are unsupported in GitLab.
+
+|Case|Tool|Issue|
+|---|---|---|
+|`<testcase>` does not have `classname` attribute|ESlint, sass-lint|https://gitlab.com/gitlab-org/gitlab-ce/issues/50964|
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index abba748db8b..e93060fec85 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -390,6 +390,28 @@ job:
The specification above, will make sure that `job` is built by a Runner that
has both `ruby` AND `postgres` tags defined.
+Tags are also a great way to run different jobs on different platforms, for
+example, given an OS X Runner with tag `osx` and Windows Runner with tag
+`windows`, the following jobs run on respective platforms:
+
+```yaml
+windows job:
+ stage:
+ - build
+ tags:
+ - windows
+ script:
+ - echo Hello, %USERNAME%!
+
+osx job:
+ stage:
+ - build
+ tags:
+ - osx
+ script:
+ - echo "Hello, $USER!"
+```
+
## `allow_failure`
`allow_failure` is used when you want to allow a job to fail without impacting
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index ddaf636a742..0e71cd47481 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -100,7 +100,7 @@ If a gem uses a license which is not listed above, open an issue and ask. If a l
Keep in mind that each license has its own restrictions (typically defined in their body text). Please make sure to comply with those restrictions at all times whenever an external library is used.
-Gems which are included only in the "development" or "test" groups by Bundler are exempt from license requirements, as they're not distributed for use in production.
+Dependencies which are only used in development or test environment are exempt from license requirements, as they're not distributed for use in production.
**NOTE:** This document is **not** legal advice, nor is it comprehensive. It should not be taken as such.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2d657163721..85431a80a81 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -12,7 +12,7 @@ Since installations from source don't have Runit, Sidekiq can't be terminated an
## Select Version to Install
-Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-2-stable`).
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-3-stable`).
You can select the branch in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
@@ -300,9 +300,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-2-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-3-stable gitlab
-**Note:** You can change `11-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `11-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -483,7 +483,7 @@ For more information about configuring Gitaly see
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables.
-
+
# or you can skip the question by adding force=yes
sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 5a6f26319c7..8852570b254 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -52,7 +52,7 @@ In order to deploy GitLab on Kubernetes, the following are required:
To deploy GitLab, the following three parameters are required:
- `global.hosts.domain`: the [base domain](preparation/networking.md) of the
- wildcard host entry. For example, `exampe.com` if the wild card entry is
+ wildcard host entry. For example, `example.com` if the wild card entry is
`*.example.com`.
- `global.hosts.externalIP`: the [external IP](preparation/networking.md) which
the wildcard DNS resolves to.
@@ -123,6 +123,8 @@ To deploy the Community Edition, include these options in your `helm install` co
--set gitlab.migrations.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-rails-ce
--set gitlab.sidekiq.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-sidekiq-ce
--set gitlab.unicorn.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-unicorn-ce
+--set gitlab.unicorn.workhorse.image=registry.gitlab.com/gitlab-org/build/cng/gitlab-workhorse-ce
+--set gitlab.task-runner.image.repository=registry.gitlab.com/gitlab-org/build/cng/gitlab-task-runner-ce
```
## Updating GitLab using the Helm Chart
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index d80cb6ad374..498b702cab1 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -101,7 +101,7 @@ Other common configuration options:
- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/), with `acs` also supported for the [Azure Container Service](https://azure.microsoft.com/en-us/services/container-service/).
For additional configuration options, consult the
-[`values.yaml`](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
+[`values.yaml`](https://gitlab.com/charts/gitlab-omnibus/blob/master/values.yaml).
### Choosing a different GitLab release version
@@ -228,7 +228,7 @@ helm upgrade gitlab --set gitlab=ee,gitlabEEImage=gitlab/gitlab-ee:9.5.5-ee.0 gi
To uninstall the GitLab Chart, run the following:
```bash
-helm delete gitlab
+helm delete --purge gitlab
```
## Troubleshooting
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index f1881e0f767..c2cf0d54aeb 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -124,7 +124,7 @@ To use the `copy` strategy instead of the default streaming strategy, specify
### Excluding specific directories from the backup
-You can choose what should be backed up by adding the environment variable `SKIP`.
+You can choose what should be exempt from the backup up by adding the environment variable `SKIP`.
The available options are:
- `db` (database)
@@ -138,6 +138,9 @@ The available options are:
Use a comma to specify several options at the same time:
+All wikis will be backed up as part of the `repositories` group. Non-existent wikis
+will be skipped during a backup.
+
```
# use this command if you've installed GitLab with the Omnibus package
sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
diff --git a/doc/security/README.md b/doc/security/README.md
index d397ff104ab..e22dc00759d 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -10,6 +10,7 @@ comments: false
- [Webhooks and insecure internal web services](webhooks.md)
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
+- [Unlock a locked user](unlock_user.md)
- [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md)
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index 3c13f262677..6a882ed6fe5 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -37,4 +37,4 @@ Don't forget to save the changes.
user.save!
```
-Exit the console and try to login with your new password. \ No newline at end of file
+Exit the console and try to login with your new password.
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
new file mode 100644
index 00000000000..d5ecef7f605
--- /dev/null
+++ b/doc/security/unlock_user.md
@@ -0,0 +1,31 @@
+# How to unlock a locked user
+
+Log into your server with root privileges. Then start a Ruby on Rails console.
+
+Start the console with this command:
+
+```bash
+gitlab-rails console production
+```
+
+Wait until the console has loaded.
+
+There are multiple ways to find your user. You can search for email or username.
+
+```bash
+user = User.where(id: 1).first
+```
+
+or
+
+```bash
+user = User.find_by(email: 'admin@local.host')
+```
+
+Unlock the user:
+
+```bash
+user.unlock_access!
+```
+
+Exit the console, the user should now be able to log in again.
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index 4293944ae8b..48c79cd4769 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -4,4 +4,4 @@ Gitlab admin can enable email confirmation on sign-up, if you want to confirm al
user emails before they are able to sign-in.
In the Admin area under **Settings** (`/admin/application_settings`), go to section
-**Sign-in Restrictions** and look for **Send confirmation email on sign-up** option.
+**Sign-up Restrictions** and look for **Send confirmation email on sign-up** option.
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 63f0a654fcf..5db042326f3 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -48,9 +48,11 @@ Note that Public SSH key may also be named as follows:
**Git Bash on Windows / GNU/Linux / macOS:**
```bash
- ssh-keygen -t rsa -C "your.email@example.com" -b 4096
+ ssh-keygen -o -t rsa -C "your.email@example.com" -b 4096
```
+ (Note: the `-o` option was introduced in 2014; if this command does not work for you, simply remove the `-o` option and try again)
+
**Windows:**
Alternatively on Windows you can download
@@ -75,7 +77,9 @@ Note that Public SSH key may also be named as follows:
NOTE: **Note:**
If you want to change the password of your SSH key pair, you can use
- `ssh-keygen -p <keyname>`.
+ `ssh-keygen -p -o -f <keyname>`.
+ The `-o` option was added in 2014, so if this command does not work for you,
+ simply remove the `-o` option and try again.
## Adding a SSH key to your GitLab account
@@ -191,15 +195,15 @@ project.
### Global shared deploy keys
-Global Shared Deploy keys allow read-only or read-write (if enabled) access to
+Global Shared Deploy keys allow read-only or read-write (if enabled) access to
be configured on any repository in the entire GitLab installation.
This is really useful for integrating repositories to secured, shared Continuous
-Integration (CI) services or other shared services.
-GitLab administrators can set up the Global Shared Deploy key in GitLab and
+Integration (CI) services or other shared services.
+GitLab administrators can set up the Global Shared Deploy key in GitLab and
add the private key to any shared systems. Individual repositories opt into
exposing their repository using these keys when a project maintainers (or higher)
-authorizes a Global Shared Deploy key to be used with their project.
+authorizes a Global Shared Deploy key to be used with their project.
Global Shared Keys can provide greater security compared to Per-Project Deploy
Keys since an administrator of the target integrated system is the only one
@@ -211,13 +215,13 @@ the primary way for project maintainers and owners to identify the correct Globa
Deploy key to add. For instance, if the key gives access to a SaaS CI instance,
use the name of that service in the key name if that is all it is used for.
When creating Global Shared Deploy keys, give some thought to the granularity
-of keys - they could be of very narrow usage such as just a specific service or
-of broader usage for something like "Anywhere you need to give read access to
+of keys - they could be of very narrow usage such as just a specific service or
+of broader usage for something like "Anywhere you need to give read access to
your repository".
-Once a GitLab administrator adds the Global Deployment key, project maintainers
-and owners can add it in project's **Settings > Repository** section by expanding the
-**Deploy Key** section and clicking **Enable** next to the appropriate key listed
+Once a GitLab administrator adds the Global Deployment key, project maintainers
+and owners can add it in project's **Settings > Repository** section by expanding the
+**Deploy Key** section and clicking **Enable** next to the appropriate key listed
under **Public deploy keys available to any project**.
NOTE: **Note:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 0474182e324..c0268ce136c 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -450,7 +450,7 @@ executed somewhere else, it cannot be accessed again.
> [Introduced][ce-19507] in GitLab 11.0.
-For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md###gitlab-deploy-token)
+For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md###gitlab-deploy-token)
will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
can be used for permanent access to the registry.
@@ -574,13 +574,13 @@ postgres://user:password@postgres-host:postgres-port/postgres-database
### Environment variables
The following variables can be used for setting up the Auto DevOps domain,
-providing a custom Helm chart, or scaling your application. PostgreSQL can be
+providing a custom Helm chart, or scaling your application. PostgreSQL can
also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
| **Variable** | **Description** |
| ------------ | --------------- |
| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
-| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/charts.gitlab.io/tree/master/charts/auto-deploy-app). |
+| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 44b0cf758dc..7ca441a2f74 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -143,7 +143,7 @@ In the next section we'll break down the pipeline and explain what each job does
By now you should see the pipeline running, but what is it running exactly?
-To navigate inside the pipeline, click its status badge. (It's status should be "running").
+To navigate inside the pipeline, click its status badge. (Its status should be "running").
The pipeline is split into 4 stages, each running a couple of jobs.
![Pipeline stages](img/guide_pipeline_stages.png)
@@ -194,7 +194,7 @@ applications. In the rightmost column for the production environment, you can ma
- The first icon will open the URL of the application that is deployed in
production. It's a very simple page, but the important part is that it works!
-- The next icon with the small graph will take you to the metrics page where
+- The next icon, with the small graph, will take you to the metrics page where
Prometheus collects data about the Kubernetes cluster and how the application
affects it (in terms of memory/CPU usage, latency, etc.).
@@ -217,7 +217,7 @@ under **Settings > CI/CD > Variables**.
### Working with branches
-Following the [GitLab flow](../../workflow/gitlab_flow.md#working-with-feature-branches)
+Following the [GitLab flow](../../workflow/gitlab_flow.md#working-with-feature-branches),
let's create a feature branch that will add some content to the application.
Under your repository, navigate to the following file: `app/views/welcome/index.html.erb`.
@@ -235,7 +235,7 @@ by clicking **Commit**.
![Web IDE commit](img/guide_ide_commit.png)
Once you submit the merge request, you'll see the pipeline running. This will
-run all the jobs as [described previously](#deploying-the-application), as well
+run all the jobs as [described previously](#deploying-the-application), as well as
a few more that run only on branches other than `master`.
![Merge request](img/guide_merge_request.png)
@@ -278,7 +278,7 @@ and the application will be eventually deployed straight to production.
After implementing this project, you should now have a solid understanding of the basics of Auto DevOps.
We started from building and testing to deploying and monitoring an application
-all within GitLab. Despite its automatic nature, Audo DevOps can also be configured
+all within GitLab. Despite its automatic nature, Auto DevOps can also be configured
and customized to fit your workflow. Here are some helpful resources for further reading:
1. [Auto DevOps](index.md)
diff --git a/doc/update/11.2-to-11-3.md b/doc/update/11.2-to-11-3.md
new file mode 100644
index 00000000000..d77f879ee57
--- /dev/null
+++ b/doc/update/11.2-to-11-3.md
@@ -0,0 +1,378 @@
+---
+comments: false
+---
+
+# From 11.2 to 11.3
+
+Make sure you view this update guide from the branch (version) of GitLab you would
+like to install (e.g., `11-3-stable`. You can select the branch in the version
+dropdown at the top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: GitLab 11.0 and higher only support Ruby 2.4.x and dropped support for Ruby 2.3.x. Be
+sure to upgrade your interpreter if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download Ruby and compile it:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.4.tar.gz
+echo 'ec82b0d53bd0adad9b19e6b45e44d54e9ec3f10c ruby-2.4.4.tar.gz' | shasum -c - && tar xzf ruby-2.4.4.tar.gz
+cd ruby-2.4.4
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-ri --no-rdoc
+```
+
+### 4. Update Node
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v6.0.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v6.0.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 11.0 and higher only supports Go 1.9.x and newer, and dropped support for Go
+1.5.x through 1.8.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
+echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.3.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-3-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-3-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### New Gitaly configuration options required
+
+In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`.
+
+```shell
+echo '
+[gitaly-ruby]
+dir = "/home/git/gitaly/ruby"
+
+[gitlab-shell]
+dir = "/home/git/gitlab-shell"
+' | sudo -u git tee -a /home/git/gitaly/config.toml
+```
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update gitlab-pages
+
+#### Only needed if you use GitLab Pages.
+
+Install and compile gitlab-pages. GitLab-Pages uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-pages
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
+
+### 11. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 12. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-1-stable:config/gitlab.yml.example origin/11-3-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/11-1-stable:lib/support/nginx/gitlab-ssl origin/11-3-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/11-1-stable:lib/support/nginx/gitlab origin/11-3-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-3-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-3-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-1-stable:lib/support/init.d/gitlab.default.example origin/11-3-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 13. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 14. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 15. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (11.2)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 11.1 to 11.2](11.1-to-11.2.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-3-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-3-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/update/README.md b/doc/update/README.md
index c98e20686e0..2c1fbc15719 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -142,4 +142,4 @@ possible.
[ee-ce]: ../downgrade_ee_to_ce/README.md
[ce]: https://about.gitlab.com/features/#community
[ee]: https://about.gitlab.com/features/#enterprise
-[omni-ce-ee]: http://docs.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition
+[omni-ce-ee]: https://docs.gitlab.com/omnibus/update/README.html#updating-community-edition-to-enterprise-edition
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 9b0ff02f227..aff7898ebf2 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -271,6 +271,8 @@ edit existing comments. Non-team members are restricted from adding or editing c
| :-----------: | :----------: |
| ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) |
+Additionally locked issues can not be reopened.
+
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 20886faf418..de5d7d0a3a0 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -74,22 +74,17 @@ or over the size limit, you can [reduce your repository size with Git](../projec
## Shared Runners
Shared Runners on GitLab.com run in [autoscale mode] and powered by
-Google Cloud Platform and DigitalOcean. Autoscaling means reduced
+Google Cloud Platform. Autoscaling means reduced
waiting times to spin up CI/CD jobs, and isolated VMs for each project,
thus maximizing security.
They're free to use for public open source projects and limited to 2000 CI
minutes per month per group for private projects. Read about all
[GitLab.com plans](https://about.gitlab.com/pricing/).
-In case of DigitalOcean based Runners, all your CI/CD jobs run on ephemeral
-instances with 2GB of RAM, CoreOS and the latest Docker Engine installed.
-Instances provide 2 vCPUs and 60GB of SSD disk space. The default region of the
-VMs is NYC1.
-
-In case of Google Cloud Platform based Runners, all your CI/CD jobs run on
-ephemeral instances with 3.75GB of RAM, CoreOS and the latest Docker Engine
+All your CI/CD jobs run on [n1-standard-1 instances](https://cloud.google.com/compute/docs/machine-types) with 3.75GB of RAM, CoreOS and the latest Docker Engine
installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
region of the VMs is US East1.
+Each instance is used only for one job, this ensures any sensitive data left on the system can't be accessed by other people their CI jobs.
Jobs handled by the shared Runners on GitLab.com (`shared-runners-manager-X.gitlab.com`),
**will be timed out after 3 hours**, regardless of the timeout configured in a
diff --git a/doc/user/group/img/groups.png b/doc/user/group/img/groups.png
index efdfd5f82cd..2e27d46b370 100644
--- a/doc/user/group/img/groups.png
+++ b/doc/user/group/img/groups.png
Binary files differ
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index b6438397db8..10ac6301aa1 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -197,7 +197,7 @@ They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
-
+
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
@@ -206,6 +206,21 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
+### Default internal users
+
+The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
+
+New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
+
+The regex pattern format is Ruby, but it needs to be convertible to JavaScript, and the ignore case flag will be set, e.g. "/regex pattern/i".
+
+Here are some examples:
+
+- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
+- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
+
+Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
+
## Auditor users **[PREMIUM ONLY]**
>[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
index c4e59444ef7..19eb95099ce 100644
--- a/doc/user/project/badges.md
+++ b/doc/user/project/badges.md
@@ -17,7 +17,7 @@ If you find that you have to add the same badges to several projects, you may wa
To add a new badge to a project:
-1. Navigate to your project's **Settings > Badges**.
+1. Navigate to your project's **Settings > General > Badges**.
1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed.
1. Submit the badge by clicking the **Add badge** button.
@@ -39,7 +39,7 @@ project, consider adding them on the [project level](#project-badges) or use
To add a new badge to a group:
-1. Navigate to your group's **Settings > Project Badges**.
+1. Navigate to your group's **Settings > General > Badges**.
1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed.
1. Submit the badge by clicking the **Add badge** button.
diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md
index 7a3628a39d7..16bc5121027 100644
--- a/doc/user/project/import/svn.md
+++ b/doc/user/project/import/svn.md
@@ -8,7 +8,7 @@ between the two, for more information consult your favorite search engine.
There are two approaches to SVN to Git migration:
-1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which:
+1. [Git/SVN Mirror](#smooth-migration-with-a-git-svn-mirror-using-subgit) which:
- Makes the GitLab repository to mirror the SVN project.
- Git and SVN repositories are kept in sync; you can use either one.
- Smoothens the migration process and allows to manage migration risks.
diff --git a/doc/user/project/integrations/microsoft_teams.md b/doc/user/project/integrations/microsoft_teams.md
index 5cf80a298ad..140c6738a49 100644
--- a/doc/user/project/integrations/microsoft_teams.md
+++ b/doc/user/project/integrations/microsoft_teams.md
@@ -2,7 +2,7 @@
## On Microsoft Teams
-To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors#setting-up-a-custom-incoming-webhook).
+To enable Microsoft Teams integration you must create an incoming webhook integration on Microsoft Teams by following the steps described in this [document](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook).
## On GitLab
diff --git a/doc/user/project/issues/automatic_issue_closing.md b/doc/user/project/issues/automatic_issue_closing.md
index b9607243c8a..b6570c777ae 100644
--- a/doc/user/project/issues/automatic_issue_closing.md
+++ b/doc/user/project/issues/automatic_issue_closing.md
@@ -26,8 +26,10 @@ used:
```
Note that `%{issue_ref}` is a complex regular expression defined inside GitLab's
-source code that can match a reference to 1) a local issue (`#123`),
-2) a cross-project issue (`group/project#123`) or 3) a link to an issue
+source code that can match references to:
+1. a local issue (`#123`),
+2. a cross-project issue (`group/project#123`)
+3. a link to an issue
(`https://gitlab.example.com/group/project/issues/123`).
---
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 556bf1db116..b0560c2f44c 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -94,7 +94,7 @@ where you'll find its default URL.
>
> - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but,
if you don't find yours among the templates, you'll need
-to configure your own `.gitlab-ci.yml`. Do do that, please
+to configure your own `.gitlab-ci.yml`. To do that, please
read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
the [example projects](https://gitlab.com/pages). If you set
up a new one, please
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index d41be0989d2..a17f911874b 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -55,6 +55,8 @@ started:
```sh
gpg --full-gen-key
```
+
+_NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_
This will spawn a series of questions.
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 6ac3bb8c0b4..3f9ffedd61a 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -24,7 +24,7 @@ There are various configuration options to help GitLab server administrators:
In `/etc/gitlab/gitlab.rb`:
```ruby
-# Change to true to enable lfs
+# Change to true to enable lfs - enabled by default if not defined
gitlab_rails['lfs_enabled'] = false
# Optionally, change the storage path location. Defaults to
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index 7e08c0e51ac..338b3a32265 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -9,6 +9,7 @@ Uncomment and customize if you want to change the default time zone of GitLab ap
To see all available time zones, run `bundle exec rake time:zones:all`.
+With Omnibus installations, run `gitlab-rake time:zones:all`.
## Changing time zone in omnibus installations
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index c17089759de..8ee7987cfff 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -84,7 +84,7 @@ module API
end
end
- module ClassMethods
+ class_methods do
private
def install_error_responders(base)
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 95b25d7351a..624eda3f5dd 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -10,6 +10,28 @@ module API
expose :content
end
+ class WikiAttachment < Grape::Entity
+ include Gitlab::FileMarkdownLinkBuilder
+
+ expose :file_name
+ expose :file_path
+ expose :branch
+ expose :link do
+ expose :file_path, as: :url
+ expose :markdown do |_entity|
+ self.markdown_link
+ end
+ end
+
+ def filename
+ object.file_name
+ end
+
+ def secure_url
+ object.file_path
+ end
+ end
+
class UserSafe < Grape::Entity
expose :id, :name, :username
end
@@ -370,6 +392,10 @@ module API
expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end
+
+ expose :default do |repo_branch, options|
+ options[:project].default_branch == repo_branch.name
+ end
end
class TreeObject < Grape::Entity
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 6482fd94ab8..9fd79c491c2 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -2,7 +2,7 @@ module API
module ProjectsRelationBuilder
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def prepare_relation(projects_relation, options = {})
projects_relation = preload_relation(projects_relation, options)
execute_batch_counting(projects_relation)
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index b3fc4e876ad..e86ebc573f2 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -1,6 +1,14 @@
module API
class Wikis < Grape::API
helpers do
+ def commit_params(attrs)
+ {
+ file_name: attrs[:file][:filename],
+ file_content: File.read(attrs[:file][:tempfile]),
+ branch_name: attrs[:branch]
+ }
+ end
+
params :wiki_page_params do
requires :content, type: String, desc: 'Content of a wiki page'
requires :title, type: String, desc: 'Title of a wiki page'
@@ -84,6 +92,29 @@ module API
status 204
WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
end
+
+ desc 'Upload an attachment to the wiki repository' do
+ detail 'This feature was introduced in GitLab 11.3.'
+ success Entities::WikiAttachment
+ end
+ params do
+ requires :file, type: File, desc: 'The attachment file to be uploaded'
+ optional :branch, type: String, desc: 'The name of the branch'
+ end
+ post ":id/wikis/attachments", requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
+ authorize! :create_wiki, user_project
+
+ result = ::Wikis::CreateAttachmentService.new(user_project,
+ current_user,
+ commit_params(declared_params(include_missing: false))).execute
+
+ if result[:status] == :success
+ status(201)
+ present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
+ else
+ render_api_error!(result[:message], 400)
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 870721f895d..1728a442533 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'uri'
-
module Banzai
module Filter
# HTML filter that "fixes" links to pages/files in a wiki.
@@ -13,8 +11,12 @@ module Banzai
def call
return doc unless project_wiki?
- doc.search('a:not(.gfm)').each do |el|
- process_link_attr el.attribute('href')
+ doc.search('a:not(.gfm)').each { |el| process_link_attr(el.attribute('href')) }
+ doc.search('video').each { |el| process_link_attr(el.attribute('src')) }
+ doc.search('img').each do |el|
+ attr = el.attribute('data-src') || el.attribute('src')
+
+ process_link_attr(attr)
end
doc
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index 072d24e5a11..4bf80aff418 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -10,11 +10,16 @@ module Banzai
def apply_rules
# Special case: relative URLs beginning with `/uploads/` refer to
- # user-uploaded files and will be handled elsewhere.
- return @uri.to_s if @uri.relative? && @uri.path.starts_with?('/uploads/')
+ # user-uploaded files will be handled elsewhere.
+ return @uri.to_s if public_upload?
+
+ # Special case: relative URLs beginning with Wikis::CreateAttachmentService::ATTACHMENT_PATH
+ # refer to user-uploaded files to the wiki repository.
+ unless repository_upload?
+ apply_file_link_rules!
+ apply_hierarchical_link_rules!
+ end
- apply_file_link_rules!
- apply_hierarchical_link_rules!
apply_relative_link_rules!
@uri.to_s
end
@@ -39,6 +44,14 @@ module Banzai
@uri = Addressable::URI.parse(link)
end
end
+
+ def public_upload?
+ @uri.relative? && @uri.path.starts_with?('/uploads/')
+ end
+
+ def repository_upload?
+ @uri.relative? && @uri.path.starts_with?(Wikis::CreateAttachmentService::ATTACHMENT_PATH)
+ end
end
end
end
diff --git a/lib/gitlab/background_migration/migrate_legacy_artifacts.rb b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb
new file mode 100644
index 00000000000..5cd638083b0
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+# rubocop:disable Metrics/ClassLength
+
+module Gitlab
+ module BackgroundMigration
+ ##
+ # The class to migrate job artifacts from `ci_builds` to `ci_job_artifacts`
+ class MigrateLegacyArtifacts
+ FILE_LOCAL_STORE = 1 # equal to ObjectStorage::Store::LOCAL
+ ARCHIVE_FILE_TYPE = 1 # equal to Ci::JobArtifact.file_types['archive']
+ METADATA_FILE_TYPE = 2 # equal to Ci::JobArtifact.file_types['metadata']
+ LEGACY_PATH_FILE_LOCATION = 1 # equal to Ci::JobArtifact.file_location['legacy_path']
+
+ def perform(start_id, stop_id)
+ ActiveRecord::Base.transaction do
+ insert_archives(start_id, stop_id)
+ insert_metadatas(start_id, stop_id)
+ delete_legacy_artifacts(start_id, stop_id)
+ end
+ end
+
+ private
+
+ def insert_archives(start_id, stop_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO
+ ci_job_artifacts (
+ project_id,
+ job_id,
+ expire_at,
+ file_location,
+ created_at,
+ updated_at,
+ file,
+ size,
+ file_store,
+ file_type
+ )
+ SELECT
+ project_id,
+ id,
+ artifacts_expire_at,
+ #{LEGACY_PATH_FILE_LOCATION},
+ created_at,
+ created_at,
+ artifacts_file,
+ artifacts_size,
+ COALESCE(artifacts_file_store, #{FILE_LOCAL_STORE}),
+ #{ARCHIVE_FILE_TYPE}
+ FROM
+ ci_builds
+ WHERE
+ id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
+ AND artifacts_file <> ''
+ AND NOT EXISTS (
+ SELECT
+ 1
+ FROM
+ ci_job_artifacts
+ WHERE
+ ci_builds.id = ci_job_artifacts.job_id
+ AND ci_job_artifacts.file_type = #{ARCHIVE_FILE_TYPE})
+ SQL
+ end
+
+ def insert_metadatas(start_id, stop_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ INSERT INTO
+ ci_job_artifacts (
+ project_id,
+ job_id,
+ expire_at,
+ file_location,
+ created_at,
+ updated_at,
+ file,
+ size,
+ file_store,
+ file_type
+ )
+ SELECT
+ project_id,
+ id,
+ artifacts_expire_at,
+ #{LEGACY_PATH_FILE_LOCATION},
+ created_at,
+ created_at,
+ artifacts_metadata,
+ NULL,
+ COALESCE(artifacts_metadata_store, #{FILE_LOCAL_STORE}),
+ #{METADATA_FILE_TYPE}
+ FROM
+ ci_builds
+ WHERE
+ id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
+ AND artifacts_file <> ''
+ AND artifacts_metadata <> ''
+ AND NOT EXISTS (
+ SELECT
+ 1
+ FROM
+ ci_job_artifacts
+ WHERE
+ ci_builds.id = ci_job_artifacts.job_id
+ AND ci_job_artifacts.file_type = #{METADATA_FILE_TYPE})
+ SQL
+ end
+
+ def delete_legacy_artifacts(start_id, stop_id)
+ ActiveRecord::Base.connection.execute <<~SQL
+ UPDATE
+ ci_builds
+ SET
+ artifacts_file = NULL,
+ artifacts_file_store = NULL,
+ artifacts_size = NULL,
+ artifacts_metadata = NULL,
+ artifacts_metadata_store = NULL
+ WHERE
+ id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
+ AND artifacts_file <> ''
+ SQL
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index b591d94668f..d044e0a484f 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -7,6 +7,7 @@ module Gitlab
attr_reader :recover_missing_commits
attr_reader :project, :project_key, :repository_slug, :client, :errors, :users
+ attr_accessor :logger
REMOTE_NAME = 'bitbucket_server'.freeze
BATCH_SIZE = 100
@@ -36,6 +37,7 @@ module Gitlab
@errors = []
@users = {}
@temp_branches = []
+ @logger = Gitlab::Import::Logger.build
end
def execute
@@ -44,6 +46,8 @@ module Gitlab
delete_temp_branches
handle_errors
+ log_info(stage: "complete")
+
true
end
@@ -118,15 +122,21 @@ module Gitlab
client.create_branch(project_key, repository_slug, branch_name, sha)
branches_created << temp_branch
rescue BitbucketServer::Connection::ConnectionError => e
- Rails.logger.warn("BitbucketServerImporter: Unable to recreate branch for SHA #{sha}: #{e}")
+ log_warn(message: "Unable to recreate branch", sha: sha, error: e.message)
end
end
end
def import_repository
+ log_info(stage: 'import_repository', message: 'starting import')
+
project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: self.class.refmap, remote_name: REMOTE_NAME)
+
+ log_info(stage: 'import_repository', message: 'finished import')
rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
+ log_error(stage: 'import_repository', message: 'failed import', error: e.message)
+
# Expire cache to prevent scenarios such as:
# 1. First import failed, but the repo was imported successfully, so +exists?+ returns true
# 2. Retried import, repo is broken or not imported but +exists?+ still returns true
@@ -157,7 +167,10 @@ module Gitlab
begin
import_bitbucket_pull_request(pull_request)
rescue StandardError => e
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
+ backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
+ log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
+
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
end
end
end
@@ -169,12 +182,15 @@ module Gitlab
client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
project.repository.delete_branch(branch.name)
rescue BitbucketServer::Connection::ConnectionError => e
+ log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
@errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
end
end
end
def import_bitbucket_pull_request(pull_request)
+ log_info(stage: 'import_bitbucket_pull_requests', message: 'starting', iid: pull_request.iid)
+
description = ''
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author_email)
description += pull_request.description if pull_request.description
@@ -201,9 +217,13 @@ module Gitlab
merge_request = creator.execute(attributes)
import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
+
+ log_info(stage: 'import_bitbucket_pull_requests', message: 'finished', iid: pull_request.iid)
end
def import_pull_request_comments(pull_request, merge_request)
+ log_info(stage: 'import_pull_request_comments', message: 'starting', iid: merge_request.iid)
+
comments, other_activities = client.activities(project_key, repository_slug, pull_request.iid).partition(&:comment?)
merge_event = other_activities.find(&:merge_event?)
@@ -213,9 +233,16 @@ module Gitlab
import_inline_comments(inline_comments.map(&:comment), merge_request)
import_standalone_pr_comments(pr_comments.map(&:comment), merge_request)
+
+ log_info(stage: 'import_pull_request_comments', message: 'finished', iid: merge_request.iid,
+ merge_event_found: merge_event.present?,
+ inline_comments_count: inline_comments.count,
+ standalone_pr_comments: pr_comments.count)
end
def import_merge_event(merge_request, merge_event)
+ log_info(stage: 'import_merge_event', message: 'starting', iid: merge_request.iid)
+
committer = merge_event.committer_email
user_id = gitlab_user_id(committer)
@@ -223,9 +250,13 @@ module Gitlab
merge_request.update({ merge_commit_sha: merge_event.merge_commit })
metric = MergeRequest::Metrics.find_or_initialize_by(merge_request: merge_request)
metric.update(merged_by_id: user_id, merged_at: timestamp)
+
+ log_info(stage: 'import_merge_event', message: 'finished', iid: merge_request.iid)
end
def import_inline_comments(inline_comments, merge_request)
+ log_info(stage: 'import_inline_comments', message: 'starting', iid: merge_request.iid)
+
inline_comments.each do |comment|
position = build_position(merge_request, comment)
parent = create_diff_note(merge_request, comment, position)
@@ -238,6 +269,8 @@ module Gitlab
create_diff_note(merge_request, reply, position, discussion_id)
end
end
+
+ log_info(stage: 'import_inline_comments', message: 'finished', iid: merge_request.iid)
end
def create_diff_note(merge_request, comment, position, discussion_id = nil)
@@ -252,11 +285,14 @@ module Gitlab
return note
end
+ log_info(stage: 'create_diff_note', message: 'creating fallback DiffNote', iid: merge_request.iid)
+
# Bitbucket Server supports the ability to comment on any line, not just the
# line in the diff. If we can't add the note as a DiffNote, fallback to creating
# a regular note.
create_fallback_diff_note(merge_request, comment, position)
rescue StandardError => e
+ log_error(stage: 'create_diff_note', comment_id: comment.id, error: e.message)
errors << { type: :pull_request, id: comment.id, errors: e.message }
nil
end
@@ -294,7 +330,8 @@ module Gitlab
merge_request.notes.create!(pull_request_comment_attributes(replies))
end
rescue StandardError => e
- errors << { type: :pull_request, iid: comment.id, errors: e.message }
+ log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
+ errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
end
end
end
@@ -324,6 +361,26 @@ module Gitlab
updated_at: comment.updated_at
}
end
+
+ def log_info(details)
+ logger.info(log_base_data.merge(details))
+ end
+
+ def log_error(details)
+ logger.error(log_base_data.merge(details))
+ end
+
+ def log_warn(details)
+ logger.warn(log_base_data.merge(details))
+ end
+
+ def log_base_data
+ {
+ class: self.class.name,
+ project_id: project.id,
+ project_path: project.full_path
+ }
+ end
end
end
end
diff --git a/lib/gitlab/ci/parsers/junit.rb b/lib/gitlab/ci/parsers/junit.rb
index 3c4668ec13b..d1c136f2009 100644
--- a/lib/gitlab/ci/parsers/junit.rb
+++ b/lib/gitlab/ci/parsers/junit.rb
@@ -2,18 +2,14 @@ module Gitlab
module Ci
module Parsers
class Junit
- attr_reader :data
-
JunitParserError = Class.new(StandardError)
def parse!(xml_data, test_suite)
- @data = Hash.from_xml(xml_data)
+ root = Hash.from_xml(xml_data)
- each_suite do |testcases|
- testcases.each do |testcase|
- test_case = create_test_case(testcase)
- test_suite.add_test_case(test_case)
- end
+ all_cases(root) do |test_case|
+ test_case = create_test_case(test_case)
+ test_suite.add_test_case(test_case)
end
rescue REXML::ParseException => e
raise JunitParserError, "XML parsing failed: #{e.message}"
@@ -23,26 +19,27 @@ module Gitlab
private
- def each_suite
- testsuites.each do |testsuite|
- yield testcases(testsuite)
- end
- end
+ def all_cases(root, parent = nil, &blk)
+ return unless root.present?
- def testsuites
- if data['testsuites']
- data['testsuites']['testsuite']
- else
- [data['testsuite']]
+ [root].flatten.compact.map do |node|
+ next unless node.is_a?(Hash)
+
+ # we allow only one top-level 'testsuites'
+ all_cases(node['testsuites'], root, &blk) unless parent
+
+ # we require at least one level of testsuites or testsuite
+ each_case(node['testcase'], &blk) if parent
+
+ # we allow multiple nested 'testsuite' (eg. PHPUnit)
+ all_cases(node['testsuite'], root, &blk)
end
end
- def testcases(testsuite)
- if testsuite['testcase'].is_a?(Array)
- testsuite['testcase']
- else
- [testsuite['testcase']]
- end
+ def each_case(testcase, &blk)
+ return unless testcase.present?
+
+ [testcase].flatten.compact.map(&blk)
end
def create_test_case(data)
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index c169c8fe135..b498f113859 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -97,11 +97,15 @@ module Gitlab
}
end
- # This method provide a sample data generated with
+ # This method provides a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
+ # Use sample data if repo has no commit
+ # (expect the case of test service configuration settings)
+ return sample_data if project.empty_repo?
+
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
- commits = project.repository.commits(project.default_branch.to_s, limit: 3) rescue []
+ commits = project.repository.commits(project.default_branch.to_s, limit: 3)
build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 1ab6df0b6ae..5b67cd46c48 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -79,16 +79,10 @@ module Gitlab
}
end
+ # We have to keep this here since it is still used for conflict resolution
+ # Conflict::File#as_json renders json diff lines in sections
def as_json(opts = nil)
- {
- line_code: line_code,
- type: type,
- old_line: old_line,
- new_line: new_line,
- text: text,
- rich_text: rich_text || CGI.escapeHTML(text),
- meta_data: meta_positions
- }
+ DiffLineSerializer.new.represent(self)
end
private
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
index e08b5be8984..cebedb19dcc 100644
--- a/lib/gitlab/email/handler.rb
+++ b/lib/gitlab/email/handler.rb
@@ -1,20 +1,23 @@
-require 'gitlab/email/handler/create_merge_request_handler'
-require 'gitlab/email/handler/create_note_handler'
-require 'gitlab/email/handler/create_issue_handler'
-require 'gitlab/email/handler/unsubscribe_handler'
+# frozen_string_literal: true
module Gitlab
module Email
module Handler
- HANDLERS = [
- UnsubscribeHandler,
- CreateNoteHandler,
- CreateMergeRequestHandler,
- CreateIssueHandler
- ].freeze
+ def self.handlers
+ @handlers ||= load_handlers
+ end
+
+ def self.load_handlers
+ [
+ UnsubscribeHandler,
+ CreateNoteHandler,
+ CreateMergeRequestHandler,
+ CreateIssueHandler
+ ]
+ end
def self.for(mail, mail_key)
- HANDLERS.find do |klass|
+ handlers.find do |klass|
handler = klass.new(mail, mail_key)
break handler if handler.can_handle?
end
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
index 0bba433d04b..35bb49ad19a 100644
--- a/lib/gitlab/email/handler/base_handler.rb
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Email
module Handler
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index fc8615afcae..64ed9e036ad 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'gitlab/email/handler/base_handler'
module Gitlab
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 2316e58c3fc..a5bd70248af 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
index 379b114e957..c7c573595fa 100644
--- a/lib/gitlab/email/handler/create_note_handler.rb
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'gitlab/email/handler/base_handler'
require 'gitlab/email/handler/reply_processing'
diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb
index 38b1425364f..ff6b2c729b2 100644
--- a/lib/gitlab/email/handler/reply_processing.rb
+++ b/lib/gitlab/email/handler/reply_processing.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Email
module Handler
diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb
index 56751e4e41e..d2f617b868a 100644
--- a/lib/gitlab/email/handler/unsubscribe_handler.rb
+++ b/lib/gitlab/email/handler/unsubscribe_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'gitlab/email/handler/base_handler'
module Gitlab
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index d1fd5dfe0cb..0f336fbaa10 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -75,7 +75,7 @@ module Gitlab
end
def binary_stringio(str)
- StringIO.new(str || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
+ StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
end
private
diff --git a/lib/gitlab/file_markdown_link_builder.rb b/lib/gitlab/file_markdown_link_builder.rb
new file mode 100644
index 00000000000..5386656efe7
--- /dev/null
+++ b/lib/gitlab/file_markdown_link_builder.rb
@@ -0,0 +1,21 @@
+# Builds the markdown link of a file
+# It needs the methods filename and secure_url (final destination url) to be defined.
+module Gitlab
+ module FileMarkdownLinkBuilder
+ include FileTypeDetection
+
+ def markdown_link
+ return unless name = markdown_name
+
+ markdown = "[#{name.gsub(']', '\\]')}](#{secure_url})"
+ markdown.prepend("!") if image_or_video? || dangerous?
+ markdown
+ end
+
+ def markdown_name
+ return unless filename.present?
+
+ image_or_video? ? File.basename(filename, File.extname(filename)) : filename
+ end
+ end
+end
diff --git a/lib/gitlab/file_type_detection.rb b/lib/gitlab/file_type_detection.rb
new file mode 100644
index 00000000000..25ee07cf940
--- /dev/null
+++ b/lib/gitlab/file_type_detection.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+# File helpers methods.
+# It needs the method filename to be defined.
+module Gitlab
+ module FileTypeDetection
+ IMAGE_EXT = %w[png jpg jpeg gif bmp tiff ico].freeze
+ # We recommend using the .mp4 format over .mov. Videos in .mov format can
+ # still be used but you really need to make sure they are served with the
+ # proper MIME type video/mp4 and not video/quicktime or your videos won't play
+ # on IE >= 9.
+ # http://archive.sublimevideo.info/20150912/docs.sublimevideo.net/troubleshooting.html
+ VIDEO_EXT = %w[mp4 m4v mov webm ogv].freeze
+ # These extension types can contain dangerous code and should only be embedded inline with
+ # proper filtering. They should always be tagged as "Content-Disposition: attachment", not "inline".
+ DANGEROUS_EXT = %w[svg].freeze
+
+ def image?
+ extension_match?(IMAGE_EXT)
+ end
+
+ def video?
+ extension_match?(VIDEO_EXT)
+ end
+
+ def image_or_video?
+ image? || video?
+ end
+
+ def dangerous?
+ extension_match?(DANGEROUS_EXT)
+ end
+
+ private
+
+ def extension_match?(extensions)
+ return false unless filename
+
+ extension = File.extname(filename).delete('.')
+ extensions.include?(extension.downcase)
+ end
+ end
+end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 35808149b90..258e19a340b 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -233,6 +233,8 @@ module Gitlab
end
elsif user
# User access is verified in check_change_access!
+ elsif authed_via_jwt?
+ # Authenticated via JWT
else
raise UnauthorizedError, ERROR_MESSAGES[:upload]
end
@@ -321,6 +323,10 @@ module Gitlab
!Gitlab.config.gitlab_shell.receive_pack
end
+ def authed_via_jwt?
+ false
+ end
+
protected
def changes_list
diff --git a/lib/gitlab/github_import/representation/expose_attribute.rb b/lib/gitlab/github_import/representation/expose_attribute.rb
index c3405759631..d2438ee8094 100644
--- a/lib/gitlab/github_import/representation/expose_attribute.rb
+++ b/lib/gitlab/github_import/representation/expose_attribute.rb
@@ -6,7 +6,7 @@ module Gitlab
module ExposeAttribute
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Defines getter methods for the given attribute names.
#
# Example:
diff --git a/lib/gitlab/graphql/mount_mutation.rb b/lib/gitlab/graphql/mount_mutation.rb
index 8cab84d7a5f..9048967d4e1 100644
--- a/lib/gitlab/graphql/mount_mutation.rb
+++ b/lib/gitlab/graphql/mount_mutation.rb
@@ -5,7 +5,7 @@ module Gitlab
module MountMutation
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
def mount_mutation(mutation_class)
# Using an underscored field name symbol will make `graphql-ruby`
# standardize the field name
diff --git a/lib/gitlab/import/logger.rb b/lib/gitlab/import/logger.rb
new file mode 100644
index 00000000000..8414954d141
--- /dev/null
+++ b/lib/gitlab/import/logger.rb
@@ -0,0 +1,9 @@
+module Gitlab
+ module Import
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'importer'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 76b99b1de16..f4106e03a57 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -94,7 +94,10 @@ module Gitlab
end
def restore_project
- @project.update_columns(project_params)
+ Gitlab::Timeless.timeless(@project) do
+ @project.update(project_params)
+ end
+
@project
end
diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb
index 61a69e7ffe4..b372b4af090 100644
--- a/lib/object_storage/direct_upload.rb
+++ b/lib/object_storage/direct_upload.rb
@@ -41,7 +41,9 @@ module ObjectStorage
GetURL: get_url,
StoreURL: store_url,
DeleteURL: delete_url,
- MultipartUpload: multipart_upload_hash
+ MultipartUpload: multipart_upload_hash,
+ CustomPutHeaders: true,
+ PutHeaders: upload_options
}.compact
end
diff --git a/lib/static_model.rb b/lib/static_model.rb
index 60e2dd82e4e..44673c2b5f6 100644
--- a/lib/static_model.rb
+++ b/lib/static_model.rb
@@ -2,7 +2,7 @@
module StaticModel
extend ActiveSupport::Concern
- module ClassMethods
+ class_methods do
# Used by ActiveRecord's polymorphic association to set object_id
def primary_key
'id'
diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake
deleted file mode 100644
index 4bec013a141..00000000000
--- a/lib/tasks/flay.rake
+++ /dev/null
@@ -1,9 +0,0 @@
-desc 'Code duplication analyze via flay'
-task :flay do
- output = `bundle exec flay --mass 35 app/ lib/gitlab/ ee/ 2> #{File::NULL}`
-
- if output.include?("Similar code found") || output.include?("IDENTICAL code found")
- puts output
- exit 1
- end
-end
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 006fcdd31a4..5d673a1a285 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -34,7 +34,6 @@ unless Rails.env.production?
config_lint
lint:haml
scss_lint
- flay
gettext:lint
gettext:updated_check
lint:static_verification
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ce5d82d479b..5f9b02bd559 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -505,6 +505,18 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
+msgid "An error occurred while fetching stages."
+msgstr ""
+
+msgid "An error occurred while fetching the job log."
+msgstr ""
+
+msgid "An error occurred while fetching the job."
+msgstr ""
+
+msgid "An error occurred while fetching the jobs."
+msgstr ""
+
msgid "An error occurred while fetching the pipeline."
msgstr ""
@@ -727,6 +739,9 @@ msgstr ""
msgid "AutoDevOps|enable Auto DevOps"
msgstr ""
+msgid "Automatically marked as default internal user"
+msgstr ""
+
msgid "Available"
msgstr ""
@@ -790,6 +805,9 @@ msgstr ""
msgid "Badges|No image to preview"
msgstr ""
+msgid "Badges|Please fill in a valid URL"
+msgstr ""
+
msgid "Badges|Project Badge"
msgstr ""
@@ -823,6 +841,9 @@ msgstr ""
msgid "Badges|Your badges"
msgstr ""
+msgid "Badges|e.g. %{exampleUrl}"
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -2801,6 +2822,9 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
+msgid "GitLab’s issue tracker"
+msgstr ""
+
msgid "Gitaly"
msgstr ""
@@ -2879,6 +2903,15 @@ msgstr ""
msgid "Group: %{group_name}"
msgstr ""
+msgid "GroupSettings|Badges"
+msgstr ""
+
+msgid "GroupSettings|Customize your group badges."
+msgstr ""
+
+msgid "GroupSettings|Learn more about badges."
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -3187,6 +3220,9 @@ msgstr ""
msgid "Internal - The project can be accessed by any logged in user."
msgstr ""
+msgid "Internal users"
+msgstr ""
+
msgid "Interval Pattern"
msgstr ""
@@ -3387,6 +3423,9 @@ msgstr ""
msgid "Learn more"
msgstr ""
+msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
+msgstr ""
+
msgid "Learn more about Kubernetes"
msgstr ""
@@ -4512,6 +4551,15 @@ msgstr ""
msgid "ProjectPage|Project ID: %{project_id}"
msgstr ""
+msgid "ProjectSettings|Badges"
+msgstr ""
+
+msgid "ProjectSettings|Customize your project badges."
+msgstr ""
+
+msgid "ProjectSettings|Learn more about badges."
+msgstr ""
+
msgid "Projects"
msgstr ""
@@ -4670,6 +4718,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] ""
msgstr[1] ""
+msgid "Regex pattern"
+msgstr ""
+
msgid "Register / Sign In"
msgstr ""
@@ -5277,6 +5328,9 @@ msgstr ""
msgid "Specific Runners"
msgstr ""
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -5381,6 +5435,9 @@ msgstr[1] ""
msgid "Tags"
msgstr ""
+msgid "Tags feed"
+msgstr ""
+
msgid "Tags:"
msgstr ""
@@ -5555,6 +5612,9 @@ msgstr ""
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 ""
+msgid "The tabs below will be removed in a future version"
+msgstr ""
+
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 ""
@@ -5934,6 +5994,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
msgstr ""
@@ -6590,6 +6653,9 @@ msgstr ""
msgid "importing"
msgstr ""
+msgid "issue boards"
+msgstr ""
+
msgid "latest version"
msgstr ""
@@ -6769,6 +6835,9 @@ msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled"
msgstr ""
+msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
+msgstr ""
+
msgid "mrWidget|You can merge this merge request manually using the"
msgstr ""
diff --git a/qa/qa.rb b/qa/qa.rb
index cf15a8b229c..830a6aa17de 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
$: << File.expand_path(File.dirname(__FILE__))
Encoding.default_external = 'UTF-8'
@@ -94,9 +96,11 @@ module QA
autoload :LDAP, 'qa/scenario/test/integration/ldap'
autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes'
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
+ autoload :ObjectStorage, 'qa/scenario/test/integration/object_storage'
end
module Sanity
+ autoload :Failing, 'qa/scenario/test/sanity/failing'
autoload :Selectors, 'qa/scenario/test/sanity/selectors'
end
end
@@ -211,10 +215,6 @@ module QA
end
end
- module Shared
- autoload :ClonePanel, 'qa/page/shared/clone_panel'
- end
-
module Profile
autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
end
@@ -248,6 +248,7 @@ module QA
# Classes describing components that are used by several pages.
#
module Component
+ autoload :ClonePanel, 'qa/page/component/clone_panel'
autoload :Dropzone, 'qa/page/component/dropzone'
autoload :Select2, 'qa/page/component/select2'
end
diff --git a/qa/qa/page/shared/clone_panel.rb b/qa/qa/page/component/clone_panel.rb
index 73e3dff956d..8e8ff4e3bb0 100644
--- a/qa/qa/page/shared/clone_panel.rb
+++ b/qa/qa/page/component/clone_panel.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
module QA
module Page
- module Shared
+ module Component
module ClonePanel
def self.included(base)
base.view 'app/views/shared/_clone_panel.html.haml' do
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 5bc0598a524..587a02163b9 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index c751b472535..07b4d0b745d 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -2,7 +2,7 @@ module QA
module Page
module Project
class Show < Page::Base
- include Page::Shared::ClonePanel
+ include Page::Component::ClonePanel
view 'app/views/projects/_last_push.html.haml' do
element :create_merge_request
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index 044e514bab3..c47a715687f 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
module Wiki
class Show < Page::Base
- include Page::Shared::ClonePanel
+ include Page::Component::ClonePanel
view 'app/views/projects/wikis/pages.html.haml' do
element :clone_repository_link, 'Clone repository'
diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb
index b2a2da4dbf3..c59fad2e223 100644
--- a/qa/qa/page/view.rb
+++ b/qa/qa/page/view.rb
@@ -1,3 +1,5 @@
+require 'pathname'
+
module QA
module Page
class View
@@ -9,7 +11,7 @@ module QA
end
def pathname
- @pathname ||= Pathname.new(::File.join(__dir__, '../../../', @path))
+ @pathname ||= ::Pathname.new(::File.join(__dir__, '../../../', @path))
.cleanpath.expand_path
end
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index 831c6a9fcff..ece6fba75c9 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -7,7 +7,7 @@ module QA
# including staging and on-premises installation.
#
class Mattermost < Test::Instance::All
- tags :core, :mattermost
+ tags :mattermost
def perform(address, mattermost, *rspec_options)
Runtime::Scenario.define(:mattermost_address, mattermost)
diff --git a/qa/qa/scenario/test/integration/object_storage.rb b/qa/qa/scenario/test/integration/object_storage.rb
new file mode 100644
index 00000000000..2e028bbb5c6
--- /dev/null
+++ b/qa/qa/scenario/test/integration/object_storage.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class ObjectStorage < Test::Instance::All
+ tags :object_storage
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/test/sanity/failing.rb b/qa/qa/scenario/test/sanity/failing.rb
new file mode 100644
index 00000000000..03452f6693d
--- /dev/null
+++ b/qa/qa/scenario/test/sanity/failing.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Sanity
+ ##
+ # This scenario exits with a 1 exit code.
+ #
+ class Failing < Template
+ include Bootable
+
+ tags :failing
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index dd1be935220..542f532a629 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -5,18 +5,46 @@ module QA
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
- it 'user creates an issue' do
+ def create_issue
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Issue.fabricate! do |issue|
issue.title = issue_title
end
+ end
+
+ it 'user creates an issue' do
+ create_issue
Page::Menu::Side.act { click_issues }
expect(page).to have_content(issue_title)
end
+
+ context 'when using attachments in comments', :object_storage do
+ let(:file_to_attach) do
+ File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif'))
+ end
+
+ it 'user comments on an issue with an attachment' do
+ create_issue
+
+ Page::Project::Issue::Show.perform do |show|
+ show.comment('See attached banana for scale', attachment: file_to_attach)
+
+ show.refresh
+
+ image_url = find('a[href$="banana_sample.gif"]')[:href]
+
+ found = show.wait(reload: false) do
+ show.asset_exists?(image_url)
+ end
+
+ expect(found).to be_truthy
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index f18655442c1..82d635065a0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context :create, :core do
+ context :create do
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/sanity/failing_spec.rb b/qa/qa/specs/features/sanity/failing_spec.rb
new file mode 100644
index 00000000000..7e0480e9067
--- /dev/null
+++ b/qa/qa/specs/features/sanity/failing_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Sanity checks', :orchestrated, :failing do
+ describe 'Failing orchestrated example' do
+ it 'always fails' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ expect(page).to have_text("These Aren't the Texts You're Looking For", wait: 1)
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/prefer_class_methods_over_module.rb b/rubocop/cop/prefer_class_methods_over_module.rb
new file mode 100644
index 00000000000..0dfa80ccfab
--- /dev/null
+++ b/rubocop/cop/prefer_class_methods_over_module.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ # Enforces the use of 'class_methods' instead of 'module ClassMethods' for activesupport concerns.
+ # For more information see: https://gitlab.com/gitlab-org/gitlab-ce/issues/50414
+ #
+ # @example
+ # # bad
+ # module Foo
+ # extend ActiveSupport::Concern
+ #
+ # module ClassMethods
+ # def a_class_method
+ # end
+ # end
+ # end
+ #
+ # # good
+ # module Foo
+ # extend ActiveSupport::Concern
+ #
+ # class_methods do
+ # def a_class_method
+ # end
+ # end
+ # end
+ #
+ class PreferClassMethodsOverModule < RuboCop::Cop::Cop
+ include RangeHelp
+
+ MSG = 'Do not use module ClassMethods, use class_methods block instead.'
+
+ def_node_matcher :extend_activesupport_concern?, <<~PATTERN
+ (:send nil? :extend (:const (:const nil? :ActiveSupport) :Concern))
+ PATTERN
+
+ def on_module(node)
+ add_offense(node) if node.defined_module_name == 'ClassMethods' && module_extends_activesupport_concern?(node)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(module_range(node), 'class_methods do')
+ end
+ end
+
+ private
+
+ def module_extends_activesupport_concern?(node)
+ container_module = container_module_of(node)
+ return false unless container_module
+
+ container_module.descendants.any? do |descendant|
+ extend_activesupport_concern?(descendant)
+ end
+ end
+
+ def container_module_of(node)
+ while node = node.parent
+ break if node.type == :module
+ end
+
+ node
+ end
+
+ def module_range(node)
+ module_node, _ = *node
+ range_between(node.loc.keyword.begin_pos, module_node.source_range.end_pos)
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index eaf421a7235..d823fa4edb1 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -7,6 +7,7 @@ require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/avoid_return_from_blocks'
require_relative 'cop/avoid_break_from_strong_memoize'
require_relative 'cop/line_break_around_conditional_block'
+require_relative 'cop/prefer_class_methods_over_module'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 178b209aacf..848364b4a9b 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -5,7 +5,7 @@ cd "$(dirname "$0")/.."
# Use long options (e.g. --header instead of -H) for curl examples in documentation.
echo '=> Checking for cURL short options...'
grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/ >/dev/null 2>&1
-if [ $? == 0 ]
+if [ $? -eq 0 ]
then
echo '✖ ERROR: Short options for curl should not be used in documentation!
Use long options (e.g., --header instead of -H):' >&2
diff --git a/scripts/schema_changed.sh b/scripts/schema_changed.sh
index 5de2b35571d..b5e510c2367 100644
--- a/scripts/schema_changed.sh
+++ b/scripts/schema_changed.sh
@@ -1,9 +1,14 @@
-function schema_changed() {
- if [[ ! -z `git diff --name-only -- db/schema.rb` ]]; then
- echo "db/schema.rb after rake db:migrate:reset is different from one in the repository"
+#!/bin/sh
+
+schema_changed() {
+ if [ ! -z "$(git diff --name-only -- db/schema.rb)" ]; then
+ printf "db/schema.rb after rake db:migrate:reset is different from one in the repository"
+ printf "The diff is as follows:\n"
+ diff=$(git diff -p --binary -- db/schema.rb)
+ printf "%s" "$diff"
exit 1
else
- echo "db/schema.rb after rake db:migrate:reset matches one in the repository"
+ printf "db/schema.rb after rake db:migrate:reset matches one in the repository"
fi
}
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index c1f42bbb9d7..d16a3464495 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -21,6 +21,34 @@ describe IssuableCollections do
controller
end
+ describe '#set_set_order_from_cookie' do
+ describe 'when sort param given' do
+ let(:cookies) { {} }
+ let(:params) { { sort: 'downvotes_asc' } }
+
+ it 'sets the cookie with the right values and flags' do
+ allow(controller).to receive(:cookies).and_return(cookies)
+
+ controller.send(:set_sort_order_from_cookie)
+
+ expect(cookies['issue_sort']).to eq({ value: 'popularity', secure: false, httponly: false })
+ end
+ end
+
+ describe 'when cookie exists' do
+ let(:cookies) { { 'issue_sort' => 'id_asc' } }
+ let(:params) { {} }
+
+ it 'sets the cookie with the right values and flags' do
+ allow(controller).to receive(:cookies).and_return(cookies)
+
+ controller.send(:set_sort_order_from_cookie)
+
+ expect(cookies['issue_sort']).to eq({ value: 'created_asc', secure: false, httponly: false })
+ end
+ end
+ end
+
describe '#page_count_for_relation' do
let(:params) { { state: 'opened' } }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 7a037828035..ae49490f31c 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -57,6 +57,16 @@ describe GroupsController do
end
end
+ describe 'GET edit' do
+ it 'sets the badge API endpoint' do
+ sign_in(owner)
+
+ get :edit, id: group.to_param
+
+ expect(assigns(:badge_api_endpoint)).not_to be_nil
+ end
+ end
+
describe 'GET #new' do
context 'when creating subgroups', :nested_groups do
[true, false].each do |can_create_group_status|
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 17c9a61f339..14059cff74c 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -1,24 +1,55 @@
require 'spec_helper'
describe Projects::AvatarsController do
- let(:project) { create(:project, :repository, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
- let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
before do
- sign_in(user)
- project.add_maintainer(user)
controller.instance_variable_set(:@project, project)
end
- it 'GET #show' do
- get :show, namespace_id: project.namespace.id, project_id: project.id
+ describe 'GET #show' do
+ subject { get :show, namespace_id: project.namespace, project_id: project }
- expect(response).to have_gitlab_http_status(404)
+ context 'when repository has no avatar' do
+ it 'shows 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when repository has an avatar' do
+ before do
+ allow(project).to receive(:avatar_in_git).and_return(filepath)
+ end
+
+ context 'when the avatar is stored in the repository' do
+ let(:filepath) { 'files/images/logo-white.png' }
+
+ it 'sends the avatar' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Type']).to eq('image/png')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ end
+ end
+
+ context 'when the avatar is stored in lfs' do
+ it_behaves_like 'repository lfs file load' do
+ let(:filename) { 'lfs_object.iso' }
+ let(:filepath) { "files/lfs/#{filename}" }
+ end
+ end
+ end
end
- it 'removes avatar from DB by calling destroy' do
- delete :destroy, namespace_id: project.namespace.id, project_id: project.id
- expect(project.avatar.present?).to be_falsey
- expect(project).to be_valid
+ describe 'DELETE #destroy' do
+ it 'removes avatar from DB by calling destroy' do
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id
+
+ expect(project.avatar.present?).to be_falsey
+ expect(project).to be_valid
+ end
end
end
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index 0f3033b0933..7d3a8c3d0d3 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -30,6 +30,7 @@ describe Projects::HooksController do
tag_push_events: true,
merge_requests_events: true,
issues_events: true,
+ confidential_note_events: true,
confidential_issues_events: true,
note_events: true,
job_events: true,
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 1aca44c6e74..d9499d7e207 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -135,7 +135,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- context 'when requesting JSON' do
+ context 'when requesting JSON with failed job' do
let(:merge_request) { create(:merge_request, source_project: project) }
before do
@@ -149,10 +149,60 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
it 'exposes needed information' do
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
+ expect(json_response['merge_request']['path']).to match(%r{merge_requests/\d+\z})
+ expect(json_response['new_issue_path']).to include('/issues/new')
+ end
+ end
+
+ context 'when request JSON for successful job' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'exposes needed information' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']['download_path']).to match(%r{artifacts/download})
+ expect(json_response['artifact']['browse_path']).to match(%r{artifacts/browse})
+ expect(json_response['artifact']).not_to have_key(:expired)
+ expect(json_response['artifact']).not_to have_key(:expired_at)
expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z})
- expect(json_response['new_issue_path'])
- .to include('/issues/new')
+ end
+
+ context 'when request JSON for successful job with expired artifacts' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:job) { create(:ci_build, :success, :artifacts, :expired, pipeline: pipeline) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+
+ allow_any_instance_of(Ci::Build).to receive(:merge_request).and_return(merge_request)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'exposes needed information' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['artifact']).not_to have_key(:download_path)
+ expect(json_response['artifact']).not_to have_key(:browse_path)
+ expect(json_response['artifact']['expired']).to eq(true)
+ expect(json_response['artifact']['expire_at']).not_to be_empty
+ expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z})
+ expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z})
+ end
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index c3468536ae1..6b658bf5295 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -1,14 +1,21 @@
require 'spec_helper'
describe Projects::RawController do
- let(:public_project) { create(:project, :public, :repository) }
+ let(:project) { create(:project, :public, :repository) }
+
+ describe 'GET #show' do
+ subject do
+ get(:show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: filepath)
+ end
- describe '#show' do
context 'regular filename' do
- let(:id) { 'master/README.md' }
+ let(:filepath) { 'master/README.md' }
it 'delivers ASCII file' do
- get_show(public_project, id)
+ subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
@@ -19,10 +26,10 @@ describe Projects::RawController do
end
context 'image header' do
- let(:id) { 'master/files/images/6049019_460s.jpg' }
+ let(:filepath) { 'master/files/images/6049019_460s.jpg' }
it 'sets image content type header' do
- get_show(public_project, id)
+ subject
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('image/jpeg')
@@ -30,85 +37,9 @@ describe Projects::RawController do
end
end
- context 'lfs object' do
- let(:id) { 'be93687/files/lfs/lfs_object.iso' }
- let!(:lfs_object) { create(:lfs_object, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
-
- context 'when lfs is enabled' do
- before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
- end
-
- context 'when project has access' do
- before do
- public_project.lfs_objects << lfs_object
- allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
- allow(controller).to receive(:send_file) { controller.head :ok }
- end
-
- it 'serves the file' do
- expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: 'lfs_object.iso', disposition: 'attachment')
- get_show(public_project, id)
-
- expect(response).to have_gitlab_http_status(200)
- end
-
- context 'and lfs uses object storage' do
- let(:lfs_object) { create(:lfs_object, :with_file, oid: '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', size: '1575078') }
-
- before do
- stub_lfs_object_storage
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- end
-
- it 'responds with redirect to file' do
- get_show(public_project, id)
-
- expect(response).to have_gitlab_http_status(302)
- expect(response.location).to include(lfs_object.reload.file.path)
- end
-
- it 'sets content disposition' do
- get_show(public_project, id)
-
- file_uri = URI.parse(response.location)
- params = CGI.parse(file_uri.query)
-
- expect(params["response-content-disposition"].first).to eq 'attachment;filename="lfs_object.iso"'
- end
- end
- end
-
- context 'when project does not have access' do
- it 'does not serve the file' do
- get_show(public_project, id)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
- context 'when lfs is not enabled' do
- before do
- allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
- end
-
- it 'delivers ASCII file' do
- get_show(public_project, id)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(response.header['Content-Disposition'])
- .to eq('inline')
- expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
- end
- end
+ it_behaves_like 'repository lfs file load' do
+ let(:filename) { 'lfs_object.iso' }
+ let(:filepath) { "be93687/files/lfs/#{filename}" }
end
end
-
- def get_show(project, id)
- get(:show, namespace_id: project.namespace.to_param,
- project_id: project,
- id: id)
- end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 94644b1f9fd..c3a66477b6a 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -284,6 +284,19 @@ describe ProjectsController do
end
end
+ describe 'GET edit' do
+ it 'sets the badge API endpoint' do
+ sign_in(user)
+ project.add_maintainer(user)
+
+ get :edit,
+ namespace_id: project.namespace.path,
+ id: project.path
+
+ expect(assigns(:badge_api_endpoint)).not_to be_nil
+ end
+ end
+
describe "#update" do
render_views
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 46aaaf6aa5d..f028803ca74 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -24,6 +24,12 @@ FactoryBot.define do
end
end
+ trait :legacy_archive do
+ archive
+
+ file_location :legacy_path
+ end
+
trait :metadata do
file_type :metadata
file_format :gzip
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index af1c153dec8..a3229fe1741 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -78,6 +78,18 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
+ it 'Change New users set to external', :js do
+ user_internal_regex = find('#application_setting_user_default_internal_regex', visible: :all)
+
+ expect(user_internal_regex).to be_readonly
+ expect(user_internal_regex['placeholder']).to eq 'To define internal users, first enable new users set to external'
+
+ check 'application_setting_user_default_external'
+
+ expect(user_internal_regex).not_to be_readonly
+ expect(user_internal_regex['placeholder']).to eq 'Regex pattern'
+ end
+
it 'Change Sign-in restrictions' do
page.within('.as-signin') do
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index b2eaeb1c487..d32f33ca1e2 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -125,6 +125,52 @@ describe "Admin::Users" do
expect(page).to have_content('Username can contain only letters, digits')
end
end
+
+ context 'with new users set to external enabled' do
+ context 'with regex to match internal user email address set', :js do
+ before do
+ stub_application_setting(user_default_external: true)
+ stub_application_setting(user_default_internal_regex: '.internal@')
+
+ visit new_admin_user_path
+ end
+
+ def expects_external_to_be_checked
+ expect(find('#user_external')).to be_checked
+ end
+
+ def expects_external_to_be_unchecked
+ expect(find('#user_external')).not_to be_checked
+ end
+
+ def expects_warning_to_be_hidden
+ expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden'
+ end
+
+ def expects_warning_to_be_shown
+ expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden'
+ end
+
+ it 'automatically unchecks external for matching email' do
+ expects_external_to_be_checked
+ expects_warning_to_be_hidden
+
+ fill_in 'user_email', with: 'test.internal@domain.ch'
+
+ expects_external_to_be_unchecked
+ expects_warning_to_be_shown
+
+ fill_in 'user_email', with: 'test@domain.ch'
+
+ expects_external_to_be_checked
+ expects_warning_to_be_hidden
+
+ uncheck 'user_external'
+
+ expects_warning_to_be_hidden
+ end
+ end
+ end
end
describe "GET /admin/users/:id" do
diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb
index 070a4a31ffa..a5c8dbf18d0 100644
--- a/spec/features/groups/settings/group_badges_spec.rb
+++ b/spec/features/groups/settings/group_badges_spec.rb
@@ -14,7 +14,7 @@ describe 'Group Badges' do
group.add_owner(user)
sign_in(user)
- visit(group_settings_badges_path(group))
+ visit(edit_group_path(group))
end
it 'shows a list of badges', :js do
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb
index 0e1383cd607..0e1383cd607 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/issues/rss_spec.rb
diff --git a/spec/features/projects/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index ba5b80ed04b..ba5b80ed04b 100644
--- a/spec/features/projects/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
diff --git a/spec/features/projects/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 5e8662100c5..5e8662100c5 100644
--- a/spec/features/projects/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
diff --git a/spec/features/projects/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 1d9c3abc20f..1d9c3abc20f 100644
--- a/spec/features/projects/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
diff --git a/spec/features/projects/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index 7d261ec7dae..7d261ec7dae 100644
--- a/spec/features/projects/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
diff --git a/spec/features/projects/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index c2b2a193682..c2b2a193682 100644
--- a/spec/features/projects/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
diff --git a/spec/features/projects/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb
index 117e5986f29..117e5986f29 100644
--- a/spec/features/projects/issues/user_views_issue_spec.rb
+++ b/spec/features/issues/user_views_issue_spec.rb
diff --git a/spec/features/projects/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb
index 58afb4efb86..58afb4efb86 100644
--- a/spec/features/projects/issues/user_views_issues_spec.rb
+++ b/spec/features/issues/user_views_issues_spec.rb
diff --git a/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 01aeed93947..01aeed93947 100644
--- a/spec/features/projects/merge_requests/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
index 2d12d690151..2d12d690151 100644
--- a/spec/features/projects/merge_requests/user_closes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_closes_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_comments_on_commit_spec.rb b/spec/features/merge_request/user_comments_on_commit_spec.rb
index 8ea358bcc70..8ea358bcc70 100644
--- a/spec/features/projects/merge_requests/user_comments_on_commit_spec.rb
+++ b/spec/features/merge_request/user_comments_on_commit_spec.rb
diff --git a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index 441b080bee5..441b080bee5 100644
--- a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
diff --git a/spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
index 69bdab85d81..69bdab85d81 100644
--- a/spec/features/projects/merge_requests/user_comments_on_merge_request_spec.rb
+++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb
index 38b4e4a6d1b..38b4e4a6d1b 100644
--- a/spec/features/projects/merge_requests/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 7de0f9daac6..7de0f9daac6 100644
--- a/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index 68a835e7f77..68a835e7f77 100644
--- a/spec/features/projects/merge_requests/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
diff --git a/spec/features/projects/merge_requests/user_merges_merge_request_spec.rb b/spec/features/merge_request/user_merges_merge_request_spec.rb
index 6539e6e9208..6539e6e9208 100644
--- a/spec/features/projects/merge_requests/user_merges_merge_request_spec.rb
+++ b/spec/features/merge_request/user_merges_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_rebases_merge_request_spec.rb b/spec/features/merge_request/user_rebases_merge_request_spec.rb
index 92e1c9942b1..92e1c9942b1 100644
--- a/spec/features/projects/merge_requests/user_rebases_merge_request_spec.rb
+++ b/spec/features/merge_request/user_rebases_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
index 745b4537e72..745b4537e72 100644
--- a/spec/features/projects/merge_requests/user_reopens_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb
index 67b6aefb2d8..67b6aefb2d8 100644
--- a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 0c15febe8df..0df9e4bbc1a 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -122,7 +122,7 @@ describe 'Merge request > User sees diff', :js do
}
CONTENT
- file_name = 'xss_file.txt'
+ file_name = 'xss_file.rs'
create_file('master', file_name, file_content)
merge_request = create(:merge_request, source_project: project)
@@ -133,6 +133,7 @@ describe 'Merge request > User sees diff', :js do
visit diffs_project_merge_request_path(project, merge_request)
expect(page).to have_text("function foo<input> {")
+ expect(page).to have_css(".line[lang='rust'] .k")
end
end
end
diff --git a/spec/features/projects/merge_requests/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index b1bfe9e5de3..b1bfe9e5de3 100644
--- a/spec/features/projects/merge_requests/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 6ac495aa03d..6ac495aa03d 100644
--- a/spec/features/projects/merge_requests/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_user_status_on_merge_request_spec.rb b/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb
index 78d9c6c6db1..78d9c6c6db1 100644
--- a/spec/features/projects/merge_requests/user_views_user_status_on_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_user_status_on_merge_request_spec.rb
diff --git a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index 82cfe600d52..82cfe600d52 100644
--- a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
index 6c695bd7aa9..6c695bd7aa9 100644
--- a/spec/features/projects/merge_requests/user_views_all_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
index 853809fe87a..853809fe87a 100644
--- a/spec/features/projects/merge_requests/user_views_closed_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
index eb012694f1e..eb012694f1e 100644
--- a/spec/features/projects/merge_requests/user_views_merged_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
diff --git a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
index 115e548b691..115e548b691 100644
--- a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb
index 2ec94274f80..42b5547d43b 100644
--- a/spec/features/projects/settings/project_badges_spec.rb
+++ b/spec/features/projects/settings/project_badges_spec.rb
@@ -15,7 +15,7 @@ describe 'Project Badges' do
group.add_maintainer(user)
sign_in(user)
- visit(project_settings_badges_path(project))
+ visit(edit_project_path(project))
end
it 'shows a list of badges', :js do
diff --git a/spec/features/projects/tags/user_views_tags_spec.rb b/spec/features/projects/tags/user_views_tags_spec.rb
new file mode 100644
index 00000000000..f344b682715
--- /dev/null
+++ b/spec/features/projects/tags/user_views_tags_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'User views tags', :feature do
+ context 'rss' do
+ shared_examples 'has access to the tags RSS feed' do
+ it do
+ visit project_tags_path(project, format: :atom)
+
+ expect(page).to have_gitlab_http_status(200)
+ end
+ end
+
+ shared_examples 'does not have access to the tags RSS feed' do
+ it do
+ visit project_tags_path(project, format: :atom)
+
+ expect(page).to have_gitlab_http_status(401)
+ end
+ end
+
+ context 'when project public' do
+ let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+
+ context 'when user signed in' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ visit project_tags_path(project)
+ end
+
+ it_behaves_like "it has an RSS button with current_user's feed token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
+ it_behaves_like 'has access to the tags RSS feed'
+ end
+
+ context 'when user signed out' do
+ before do
+ visit project_tags_path(project)
+ end
+
+ it_behaves_like 'it has an RSS button without a feed token'
+ it_behaves_like 'an autodiscoverable RSS feed without a feed token'
+ it_behaves_like 'has access to the tags RSS feed'
+ end
+ end
+
+ context 'when project is not public' do
+ let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
+
+ context 'when user signed in' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it_behaves_like 'has access to the tags RSS feed'
+ end
+
+ context 'when user signed out' do
+ it_behaves_like 'does not have access to the tags RSS feed'
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 149eeb4f9ba..b30286e4446 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -146,6 +146,8 @@ describe "User creates wiki page" do
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
end
end
+
+ it_behaves_like 'wiki file attachments'
end
context "in a group namespace", :js do
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 2840d28cf30..2ce5ee0e87d 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe 'User updates wiki page' do
shared_examples 'wiki page user update' do
let(:user) { create(:user) }
+
before do
project.add_maintainer(user)
sign_in(user)
@@ -55,6 +56,8 @@ describe 'User updates wiki page' do
expect(page).to have_content('Updated Wiki Content')
end
+
+ it_behaves_like 'wiki file attachments'
end
end
@@ -64,14 +67,14 @@ describe 'User updates wiki page' do
before do
visit(project_wikis_path(project))
+
+ click_link('Edit')
end
context 'in a user namespace' do
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
it 'updates a page' do
- click_link('Edit')
-
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
@@ -84,8 +87,6 @@ describe 'User updates wiki page' do
end
it 'shows a validation error message' do
- click_link('Edit')
-
fill_in(:wiki_content, with: '')
click_button('Save changes')
@@ -97,8 +98,6 @@ describe 'User updates wiki page' do
end
it 'shows the emoji autocompletion dropdown', :js do
- click_link('Edit')
-
find('#wiki_content').native.send_keys('')
fill_in(:wiki_content, with: ':')
@@ -106,8 +105,6 @@ describe 'User updates wiki page' do
end
it 'shows the error message' do
- click_link('Edit')
-
wiki_page.update(content: 'Update')
click_button('Save changes')
@@ -116,30 +113,27 @@ describe 'User updates wiki page' do
end
it 'updates a page' do
- click_on('Edit')
fill_in('Content', with: 'Updated Wiki Content')
click_on('Save changes')
expect(page).to have_content('Updated Wiki Content')
end
- it 'cancels edititng of a page' do
- click_on('Edit')
-
+ it 'cancels editing of a page' do
page.within(:css, '.wiki-form .form-actions') do
click_on('Cancel')
end
expect(current_path).to eq(project_wiki_path(project, wiki_page))
end
+
+ it_behaves_like 'wiki file attachments'
end
context 'in a group namespace' do
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
it 'updates a page' do
- click_link('Edit')
-
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
@@ -151,6 +145,8 @@ describe 'User updates wiki page' do
expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
+
+ it_behaves_like 'wiki file attachments'
end
end
@@ -222,6 +218,8 @@ describe 'User updates wiki page' do
expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
end
+
+ it_behaves_like 'wiki file attachments'
end
end
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 760324adacc..747406efc8b 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -93,7 +93,7 @@ describe 'User views a wiki page' do
allow(wiki_file).to receive(:mime_type).and_return('image/jpeg')
allow_any_instance_of(ProjectWiki).to receive(:find_file).with('image.jpg', nil).and_return(wiki_file)
- expect(page).to have_xpath('//img[@data-src="image.jpg"]')
+ expect(page).to have_xpath("//img[@data-src='#{project.wiki.wiki_base_path}/image.jpg']")
expect(page).to have_link('image', href: "#{project.wiki.wiki_base_path}/image.jpg")
click_on('image')
diff --git a/spec/fixtures/api/schemas/ci_detailed_status.json b/spec/fixtures/api/schemas/ci_detailed_status.json
index 01e34249bf1..d74248eabef 100644
--- a/spec/fixtures/api/schemas/ci_detailed_status.json
+++ b/spec/fixtures/api/schemas/ci_detailed_status.json
@@ -18,7 +18,29 @@
"tooltip": { "type": "string" },
"has_details": { "type": "boolean" },
"details_path": { "type": "string" },
- "favicon": { "type": "string" }
+ "favicon": { "type": "string" },
+ "action": {
+ "type": "object",
+ "required": [
+ "icon",
+ "title",
+ "path",
+ "method"
+ ],
+ "properties": {
+ "icon": {
+ "type": "string",
+ "enum": [
+ "retry",
+ "play",
+ "cancel"
+ ]
+ },
+ "title": { "type": "string" },
+ "path": { "type": "string" },
+ "method": { "$ref": "http_method.json" }
+ }
+ }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/entities/diff_line.json b/spec/fixtures/api/schemas/entities/diff_line.json
new file mode 100644
index 00000000000..66e8b443e1b
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/diff_line.json
@@ -0,0 +1,14 @@
+{
+ "type": "object",
+ "required": ["type"],
+ "properties": {
+ "line_code": { "type": ["string", "null"] },
+ "type": { "type": ["string", "null"] },
+ "old_line": { "type": ["integer", "null"] },
+ "new_line": { "type": ["integer", "null"] },
+ "text": { "type": ["string"] },
+ "rich_text": { "type": ["string"] },
+ "meta_data": { "type": ["object", "null"] }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/diff_line_parallel.json b/spec/fixtures/api/schemas/entities/diff_line_parallel.json
new file mode 100644
index 00000000000..f924eb0c601
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/diff_line_parallel.json
@@ -0,0 +1,11 @@
+{
+ "required" : [
+ "left",
+ "right"
+ ],
+ "properties" : {
+ "left": { "$ref": "diff_line.json" },
+ "right": { "$ref": "diff_line.json" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/http_method.json b/spec/fixtures/api/schemas/http_method.json
new file mode 100644
index 00000000000..c0f8acc5781
--- /dev/null
+++ b/spec/fixtures/api/schemas/http_method.json
@@ -0,0 +1,5 @@
+{
+ "type": "string",
+ "description": "HTTP methods that the API can specify to send.",
+ "enum": [ "post", "get", "put", "patch" ]
+}
diff --git a/spec/fixtures/api/schemas/job/artifact.json b/spec/fixtures/api/schemas/job/artifact.json
new file mode 100644
index 00000000000..1812e69fbd6
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/artifact.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "properties": {
+ "download_path": { "type": "string"},
+ "browse_path": { "type": "string"},
+ "keep_path": { "type": "string"},
+ "expired": { "type": "boolean" },
+ "expire_at": { "type": "string", "format": "date-time" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/job.json b/spec/fixtures/api/schemas/job/job.json
index 7b92ab25bc1..c793d93c0f6 100644
--- a/spec/fixtures/api/schemas/job.json
+++ b/spec/fixtures/api/schemas/job/job.json
@@ -1,4 +1,5 @@
{
+ "description": "Basic job information",
"type": "object",
"required": [
"id",
@@ -13,12 +14,18 @@
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
- "started": { "type": "boolean" } ,
+ "started": {
+ "oneOf": [
+ { "type": "string", "format": "date-time" },
+ { "type": "boolean" }
+ ]
+ },
"build_path": { "type": "string" },
+ "retry_path": { "type": "string" },
"playable": { "type": "boolean" },
"created_at": { "type": "string" },
"updated_at": { "type": "string" },
- "status": { "$ref": "ci_detailed_status.json" }
+ "status": { "$ref": "../ci_detailed_status.json" }
},
- "additionalProperties": false
+ "additionalProperties": true
}
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
new file mode 100644
index 00000000000..73eca83d788
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -0,0 +1,7 @@
+{
+ "allOf": [{ "$ref": "job.json" }],
+ "description": "An extension of job.json with more detailed information",
+ "properties": {
+ "artifact": { "$ref": "artifact.json" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/pipeline_stage.json b/spec/fixtures/api/schemas/pipeline_stage.json
index 55454200bb3..eb2667295f0 100644
--- a/spec/fixtures/api/schemas/pipeline_stage.json
+++ b/spec/fixtures/api/schemas/pipeline_stage.json
@@ -13,7 +13,7 @@
"groups": { "optional": true },
"latest_statuses": {
"type": "array",
- "items": { "$ref": "job.json" },
+ "items": { "$ref": "job/job.json" },
"optional": true
},
"status": { "$ref": "ci_detailed_status.json" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/branch.json b/spec/fixtures/api/schemas/public_api/v4/branch.json
index a8891680d06..3b0f010bc4f 100644
--- a/spec/fixtures/api/schemas/public_api/v4/branch.json
+++ b/spec/fixtures/api/schemas/public_api/v4/branch.json
@@ -5,6 +5,7 @@
"commit",
"merged",
"protected",
+ "default",
"developers_can_push",
"developers_can_merge"
],
@@ -13,6 +14,7 @@
"commit": { "$ref": "commit/basic.json" },
"merged": { "type": "boolean" },
"protected": { "type": "boolean" },
+ "default": { "type": "boolean" },
"developers_can_push": { "type": "boolean" },
"developers_can_merge": { "type": "boolean" },
"can_push": { "type": "boolean" }
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 0c0a0003231..eebae1d7290 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -40,12 +40,24 @@ describe ButtonHelper do
end
context 'when user has no personal access tokens' do
- it 'has a personal access token text on the dropdown description ' do
+ it 'has a personal access token text on the dropdown description' do
description = element.search('.dropdown-menu-inner-content').first
expect(description.inner_text).to eq 'Create a personal access token on your account to pull or push via HTTP.'
end
end
+
+ context 'when user has personal access tokens' do
+ before do
+ create(:personal_access_token, user: user)
+ end
+
+ it 'does not have a personal access token text on the dropdown description' do
+ description = element.search('.dropdown-menu-inner-content').first
+
+ expect(description).to be_nil
+ end
+ end
end
context 'when user is ldap user' do
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 234690e742b..7ccbdcd1332 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -50,9 +50,12 @@ describe NamespacesHelper do
end
it 'selects the new group by default' do
+ # Ensure we don't select a group with the same name
+ create(:group, name: 'new-group', path: 'another-path')
+
allow(helper).to receive(:current_user).and_return(user)
- options = helper.namespaces_options(:extra_group, display_path: true, extra_group: build(:group, name: 'new-group'))
+ options = helper.namespaces_options(:extra_group, display_path: true, extra_group: build(:group, name: 'new-group', path: 'new-group'))
expect(options).to include(user_group.name)
expect(options).not_to include(admin_group.name)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index a64f8a11ef2..8662cadc7a0 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -162,42 +162,77 @@ describe SubmoduleHelper do
end
context 'submodules with relative links' do
- let(:group) { create(:group, name: "Master Project", path: "master-project") }
+ let(:group) { create(:group, name: "top group", path: "top-group") }
let(:project) { create(:project, group: group) }
- let(:commit_id) { sample_commit[:id] }
+ let(:repo) { double(:repo, project: project) }
+
+ def expect_relative_link_to_resolve_to(relative_path, expected_path)
+ allow(repo).to receive(:submodule_url_for).and_return(relative_path)
+
+ result = submodule_links(submodule_item)
+
+ expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"])
+ end
- it 'one level down' do
- result = relative_self_links('../test.git', commit_id, project)
- expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
+ it 'handles project under same group' do
+ expect_relative_link_to_resolve_to('../test.git', "/#{group.path}/test")
end
- it 'with trailing whitespace' do
- result = relative_self_links('../test.git ', commit_id, project)
- expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
+ it 'handles trailing whitespace' do
+ expect_relative_link_to_resolve_to('../test.git ', "/#{group.path}/test")
end
- it 'two levels down' do
- result = relative_self_links('../../test.git', commit_id, project)
- expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"])
+ it 'handles project under another top group' do
+ expect_relative_link_to_resolve_to('../../baz/test.git ', "/baz/test")
+ end
+
+ context 'repo path resolves to be located at root (namespace absent)' do
+ it 'returns nil' do
+ allow(repo).to receive(:submodule_url_for).and_return('../../test.git')
+
+ result = submodule_links(submodule_item)
+
+ expect(result).to eq([nil, nil])
+ end
end
- it 'one level down with namespace and repo' do
- result = relative_self_links('../foobar/test.git', commit_id, project)
- expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"])
+ context 'repo path resolves to be located underneath current project path' do
+ it 'returns nil because it is not possible to have repo nested under another repo' do
+ allow(repo).to receive(:submodule_url_for).and_return('./test.git')
+
+ result = submodule_links(submodule_item)
+
+ expect(result).to eq([nil, nil])
+ end
end
- it 'two levels down with namespace and repo' do
- result = relative_self_links('../foobar/baz/test.git', commit_id, project)
- expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"])
+ context 'subgroup' do
+ let(:sub_group) { create(:group, parent: group, name: "sub group", path: "sub-group") }
+ let(:sub_project) { create(:project, group: sub_group) }
+
+ context 'project in sub group' do
+ let(:project) { sub_project }
+
+ it "handles referencing ancestor group's project" do
+ expect_relative_link_to_resolve_to('../../../top-group/test.git', "/#{group.path}/test")
+ end
+ end
+
+ it "handles referencing descendent group's project" do
+ expect_relative_link_to_resolve_to('../sub-group/test.git', "/top-group/sub-group/test")
+ end
+
+ it "handles referencing another top group's project" do
+ expect_relative_link_to_resolve_to('../../frontend/css/test.git', "/frontend/css/test")
+ end
end
context 'personal project' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
- it 'one level down with personal project' do
- result = relative_self_links('../test.git', commit_id, project)
- expect(result).to eq(["/#{user.username}/test", "/#{user.username}/test/tree/#{commit_id}"])
+ it 'handles referencing another personal project' do
+ expect_relative_link_to_resolve_to('../test.git', "/#{user.username}/test")
end
end
end
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index b079802cb81..34d9115a1f6 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -42,6 +42,30 @@ describe UsersHelper do
end
end
+ describe '#user_internal_regex_data' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ false | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ false | 'mockRegexPattern' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | 'mockRegexPattern' | { user_internal_regex_pattern: 'mockRegexPattern', user_internal_regex_options: 'gi' }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+ end
+
+ subject { helper.user_internal_regex_data }
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
describe '#current_user_menu_items' do
subject(:items) { helper.current_user_menu_items }
diff --git a/spec/javascripts/badges/components/badge_form_spec.js b/spec/javascripts/badges/components/badge_form_spec.js
index dd21ec279cb..31195bd762b 100644
--- a/spec/javascripts/badges/components/badge_form_spec.js
+++ b/spec/javascripts/badges/components/badge_form_spec.js
@@ -1,21 +1,31 @@
import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import store from '~/badges/store';
+import createEmptyBadge from '~/badges/empty_badge';
import BadgeForm from '~/badges/components/badge_form.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { createDummyBadge } from '../dummy_badge';
+import { DUMMY_IMAGE_URL, TEST_HOST } from '../../test_constants';
+
+// avoid preview background process
+BadgeForm.methods.debouncedPreview = () => {};
describe('BadgeForm component', () => {
const Component = Vue.extend(BadgeForm);
+ let axiosMock;
let vm;
beforeEach(() => {
setFixtures(`
<div id="dummy-element"></div>
`);
+
+ axiosMock = new MockAdapter(axios);
});
afterEach(() => {
vm.$destroy();
+ axiosMock.restore();
});
describe('methods', () => {
@@ -38,93 +48,86 @@ describe('BadgeForm component', () => {
expect(vm.stopEditing).toHaveBeenCalled();
});
});
+ });
- describe('onSubmit', () => {
- describe('if isEditing is true', () => {
- beforeEach(() => {
- spyOn(vm, 'saveBadge').and.returnValue(Promise.resolve());
- store.replaceState({
- ...store.state,
- isSaving: false,
- badgeInEditForm: createDummyBadge(),
- });
- vm.isEditing = true;
- });
-
- it('returns immediately if imageUrl is empty', () => {
- store.state.badgeInEditForm.imageUrl = '';
-
- vm.onSubmit();
-
- expect(vm.saveBadge).not.toHaveBeenCalled();
- });
-
- it('returns immediately if linkUrl is empty', () => {
- store.state.badgeInEditForm.linkUrl = '';
-
- vm.onSubmit();
-
- expect(vm.saveBadge).not.toHaveBeenCalled();
- });
-
- it('returns immediately if isSaving is true', () => {
- store.state.isSaving = true;
+ const sharedSubmitTests = submitAction => {
+ const imageUrlSelector = '#badge-image-url';
+ const findImageUrlElement = () => vm.$el.querySelector(imageUrlSelector);
+ const linkUrlSelector = '#badge-link-url';
+ const findLinkUrlElement = () => vm.$el.querySelector(linkUrlSelector);
+ const setValue = (inputElementSelector, url) => {
+ const inputElement = vm.$el.querySelector(inputElementSelector);
+ inputElement.value = url;
+ inputElement.dispatchEvent(new Event('input'));
+ };
+ const submitForm = () => {
+ const submitButton = vm.$el.querySelector('button[type="submit"]');
+ submitButton.click();
+ };
+ const expectInvalidInput = inputElementSelector => {
+ const inputElement = vm.$el.querySelector(inputElementSelector);
+ expect(inputElement).toBeMatchedBy(':invalid');
+ const feedbackElement = vm.$el.querySelector(`${inputElementSelector} + .invalid-feedback`);
+ expect(feedbackElement).toBeVisible();
+ };
- vm.onSubmit();
+ beforeEach(() => {
+ spyOn(vm, submitAction).and.returnValue(Promise.resolve());
+ store.replaceState({
+ ...store.state,
+ badgeInAddForm: createEmptyBadge(),
+ badgeInEditForm: createEmptyBadge(),
+ isSaving: false,
+ });
- expect(vm.saveBadge).not.toHaveBeenCalled();
- });
+ setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
+ setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
+ });
- it('calls saveBadge', () => {
- vm.onSubmit();
+ it('returns immediately if imageUrl is empty', () => {
+ setValue(imageUrlSelector, '');
- expect(vm.saveBadge).toHaveBeenCalled();
- });
- });
+ submitForm();
- describe('if isEditing is false', () => {
- beforeEach(() => {
- spyOn(vm, 'addBadge').and.returnValue(Promise.resolve());
- store.replaceState({
- ...store.state,
- isSaving: false,
- badgeInAddForm: createDummyBadge(),
- });
- vm.isEditing = false;
- });
+ expectInvalidInput(imageUrlSelector);
+ expect(vm[submitAction]).not.toHaveBeenCalled();
+ });
- it('returns immediately if imageUrl is empty', () => {
- store.state.badgeInAddForm.imageUrl = '';
+ it('returns immediately if imageUrl is malformed', () => {
+ setValue(imageUrlSelector, 'not-a-url');
- vm.onSubmit();
+ submitForm();
- expect(vm.addBadge).not.toHaveBeenCalled();
- });
+ expectInvalidInput(imageUrlSelector);
+ expect(vm[submitAction]).not.toHaveBeenCalled();
+ });
- it('returns immediately if linkUrl is empty', () => {
- store.state.badgeInAddForm.linkUrl = '';
+ it('returns immediately if linkUrl is empty', () => {
+ setValue(linkUrlSelector, '');
- vm.onSubmit();
+ submitForm();
- expect(vm.addBadge).not.toHaveBeenCalled();
- });
+ expectInvalidInput(linkUrlSelector);
+ expect(vm[submitAction]).not.toHaveBeenCalled();
+ });
- it('returns immediately if isSaving is true', () => {
- store.state.isSaving = true;
+ it('returns immediately if linkUrl is malformed', () => {
+ setValue(linkUrlSelector, 'not-a-url');
- vm.onSubmit();
+ submitForm();
- expect(vm.addBadge).not.toHaveBeenCalled();
- });
+ expectInvalidInput(linkUrlSelector);
+ expect(vm[submitAction]).not.toHaveBeenCalled();
+ });
- it('calls addBadge', () => {
- vm.onSubmit();
+ it(`calls ${submitAction}`, () => {
+ submitForm();
- expect(vm.addBadge).toHaveBeenCalled();
- });
- });
+ expect(findImageUrlElement()).toBeMatchedBy(':valid');
+ expect(findLinkUrlElement()).toBeMatchedBy(':valid');
+ expect(vm[submitAction]).toHaveBeenCalled();
});
- });
+ };
describe('if isEditing is false', () => {
beforeEach(() => {
@@ -138,12 +141,15 @@ describe('BadgeForm component', () => {
});
it('renders one button', () => {
- const buttons = vm.$el.querySelectorAll('.row-content-block button');
+ expect(vm.$el.querySelector('.row-content-block')).toBeNull();
+ const buttons = vm.$el.querySelectorAll('.form-group:last-of-type button');
expect(buttons.length).toBe(1);
const buttonAddElement = buttons[0];
expect(buttonAddElement).toBeVisible();
expect(buttonAddElement).toHaveText('Add badge');
});
+
+ sharedSubmitTests('addBadge');
});
describe('if isEditing is true', () => {
@@ -167,5 +173,7 @@ describe('BadgeForm component', () => {
expect(buttonCancelElement).toBeVisible();
expect(buttonCancelElement).toHaveText('Cancel');
});
+
+ sharedSubmitTests('saveBadge');
});
});
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
new file mode 100644
index 00000000000..9989ac4fff2
--- /dev/null
+++ b/spec/javascripts/fixtures/admin_users.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('admin/users')
+ end
+
+ it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
+ stub_application_setting(user_default_external: true)
+ stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
+
+ get :new
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
new file mode 100644
index 00000000000..a9d3043f73d
--- /dev/null
+++ b/spec/javascripts/fixtures/application_settings.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('application_settings/')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'application_settings/accounts_and_limit.html.raw' do |example|
+ stub_application_setting(user_default_external: false)
+
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
index 9c76500cfe5..70b885ede26 100644
--- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js
@@ -21,6 +21,23 @@ describe('new dropdown upload', () => {
vm.$destroy();
});
+ describe('openFile', () => {
+ it('calls for each file', () => {
+ const files = ['test', 'test2', 'test3'];
+
+ spyOn(vm, 'readFile');
+ spyOnProperty(vm.$refs.fileUpload, 'files').and.returnValue(files);
+
+ vm.openFile();
+
+ expect(vm.readFile.calls.count()).toBe(3);
+
+ files.forEach((file, i) => {
+ expect(vm.readFile.calls.argsFor(i)).toEqual([file]);
+ });
+ });
+ });
+
describe('readFile', () => {
beforeEach(() => {
spyOn(FileReader.prototype, 'readAsText');
diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js
index f99d1f9890a..fc639a672e2 100644
--- a/spec/javascripts/ide/components/repo_file_spec.js
+++ b/spec/javascripts/ide/components/repo_file_spec.js
@@ -121,4 +121,25 @@ describe('RepoFile', () => {
).toContain('Locked by testuser');
});
});
+
+ it('calls scrollIntoView if made active', done => {
+ createComponent({
+ file: {
+ ...file(),
+ type: 'blob',
+ active: false,
+ },
+ level: 0,
+ });
+
+ spyOn(vm, 'scrollIntoView');
+
+ vm.file.active = true;
+
+ vm.$nextTick(() => {
+ expect(vm.scrollIntoView).toHaveBeenCalled();
+
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
new file mode 100644
index 00000000000..5042718dfa0
--- /dev/null
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -0,0 +1,625 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import {
+ setJobEndpoint,
+ setTraceEndpoint,
+ setStagesEndpoint,
+ setJobsEndpoint,
+ clearEtagPoll,
+ stopPolling,
+ requestJob,
+ fetchJob,
+ receiveJobSuccess,
+ receiveJobError,
+ scrollTop,
+ scrollBottom,
+ requestTrace,
+ fetchTrace,
+ stopPollingTrace,
+ receiveTraceSuccess,
+ receiveTraceError,
+ fetchFavicon,
+ requestStatusFavicon,
+ receiveStatusFaviconSuccess,
+ requestStatusFaviconError,
+ requestStages,
+ fetchStages,
+ receiveStagesSuccess,
+ receiveStagesError,
+ requestJobsForStage,
+ setSelectedStage,
+ fetchJobsForStage,
+ receiveJobsForStageSuccess,
+ receiveJobsForStageError,
+} from '~/jobs/store/actions';
+import state from '~/jobs/store/state';
+import * as types from '~/jobs/store/mutation_types';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { TEST_HOST } from 'spec/test_constants';
+
+describe('Job State actions', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('setJobEndpoint', () => {
+ it('should commit SET_JOB_ENDPOINT mutation', done => {
+ testAction(
+ setJobEndpoint,
+ 'job/872324.json',
+ mockedState,
+ [{ type: types.SET_JOB_ENDPOINT, payload: 'job/872324.json' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setTraceEndpoint', () => {
+ it('should commit SET_TRACE_ENDPOINT mutation', done => {
+ testAction(
+ setTraceEndpoint,
+ 'job/872324/trace.json',
+ mockedState,
+ [{ type: types.SET_TRACE_ENDPOINT, payload: 'job/872324/trace.json' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setStagesEndpoint', () => {
+ it('should commit SET_STAGES_ENDPOINT mutation', done => {
+ testAction(
+ setStagesEndpoint,
+ 'job/872324/stages.json',
+ mockedState,
+ [{ type: types.SET_STAGES_ENDPOINT, payload: 'job/872324/stages.json' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setJobsEndpoint', () => {
+ it('should commit SET_JOBS_ENDPOINT mutation', done => {
+ testAction(
+ setJobsEndpoint,
+ 'job/872324/stages/build.json',
+ mockedState,
+ [{ type: types.SET_JOBS_ENDPOINT, payload: 'job/872324/stages/build.json' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestJob', () => {
+ it('should commit REQUEST_JOB mutation', done => {
+ testAction(requestJob, null, mockedState, [{ type: types.REQUEST_JOB }], [], done);
+ });
+ });
+
+ describe('fetchJob', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.jobEndpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestJob and receiveJobSuccess ', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 121212, name: 'karma' });
+
+ testAction(
+ fetchJob,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJob',
+ },
+ {
+ payload: { id: 121212, name: 'karma' },
+ type: 'receiveJobSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ });
+
+ it('dispatches requestJob and receiveJobError ', done => {
+ testAction(
+ fetchJob,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJob',
+ },
+ {
+ type: 'receiveJobError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveJobSuccess', () => {
+ it('should commit RECEIVE_JOB_SUCCESS mutation', done => {
+ testAction(
+ receiveJobSuccess,
+ { id: 121232132 },
+ mockedState,
+ [{ type: types.RECEIVE_JOB_SUCCESS, payload: { id: 121232132 } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveJobError', () => {
+ it('should commit RECEIVE_JOB_ERROR mutation', done => {
+ testAction(receiveJobError, null, mockedState, [{ type: types.RECEIVE_JOB_ERROR }], [], done);
+ });
+ });
+
+ describe('scrollTop', () => {
+ it('should commit SCROLL_TO_TOP mutation', done => {
+ testAction(scrollTop, null, mockedState, [{ type: types.SCROLL_TO_TOP }], [], done);
+ });
+ });
+
+ describe('scrollBottom', () => {
+ it('should commit SCROLL_TO_BOTTOM mutation', done => {
+ testAction(scrollBottom, null, mockedState, [{ type: types.SCROLL_TO_BOTTOM }], [], done);
+ });
+ });
+
+ describe('requestTrace', () => {
+ it('should commit REQUEST_TRACE mutation', done => {
+ testAction(requestTrace, null, mockedState, [{ type: types.REQUEST_TRACE }], [], done);
+ });
+ });
+
+ describe('fetchTrace', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.traceEndpoint = `${TEST_HOST}/endpoint`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestTrace, fetchFavicon, receiveTraceSuccess and stopPollingTrace when job is complete', done => {
+ mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(200, {
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
+ complete: true,
+ });
+
+ testAction(
+ fetchTrace,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestTrace',
+ },
+ {
+ type: 'fetchFavicon',
+ },
+ {
+ payload: {
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', complete: true,
+ },
+ type: 'receiveTraceSuccess',
+ },
+ {
+ type: 'stopPollingTrace',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(500);
+ });
+
+ it('dispatches requestTrace and receiveTraceError ', done => {
+ testAction(
+ fetchTrace,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestTrace',
+ },
+ {
+ type: 'receiveTraceError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('stopPollingTrace', () => {
+ it('should commit STOP_POLLING_TRACE mutation ', done => {
+ testAction(
+ stopPollingTrace,
+ null,
+ mockedState,
+ [{ type: types.STOP_POLLING_TRACE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveTraceSuccess', () => {
+ it('should commit RECEIVE_TRACE_SUCCESS mutation ', done => {
+ testAction(
+ receiveTraceSuccess,
+ 'hello world',
+ mockedState,
+ [{ type: types.RECEIVE_TRACE_SUCCESS, payload: 'hello world' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveTraceError', () => {
+ it('should commit RECEIVE_TRACE_ERROR mutation ', done => {
+ testAction(
+ receiveTraceError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_TRACE_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchFavicon', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.pagePath = `${TEST_HOST}/endpoint`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ it('dispatches requestStatusFavicon and receiveStatusFaviconSuccess ', done => {
+ mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(200);
+
+ testAction(
+ fetchFavicon,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestStatusFavicon',
+ },
+ {
+ type: 'receiveStatusFaviconSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(500);
+ });
+
+ it('dispatches requestStatusFavicon and requestStatusFaviconError ', done => {
+ testAction(
+ fetchFavicon,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestStatusFavicon',
+ },
+ {
+ type: 'requestStatusFaviconError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('requestStatusFavicon', () => {
+ it('should commit REQUEST_STATUS_FAVICON mutation ', done => {
+ testAction(
+ requestStatusFavicon,
+ null,
+ mockedState,
+ [{ type: types.REQUEST_STATUS_FAVICON }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveStatusFaviconSuccess', () => {
+ it('should commit RECEIVE_STATUS_FAVICON_SUCCESS mutation ', done => {
+ testAction(
+ receiveStatusFaviconSuccess,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_STATUS_FAVICON_SUCCESS }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestStatusFaviconError', () => {
+ it('should commit RECEIVE_STATUS_FAVICON_ERROR mutation ', done => {
+ testAction(
+ requestStatusFaviconError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_STATUS_FAVICON_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestStages', () => {
+ it('should commit REQUEST_STAGES mutation ', done => {
+ testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done);
+ });
+ });
+
+ describe('fetchStages', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.stagesEndpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ it('dispatches requestStages and receiveStagesSuccess ', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
+
+ testAction(
+ fetchStages,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestStages',
+ },
+ {
+ payload: [{ id: 121212, name: 'build' }],
+ type: 'receiveStagesSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ });
+
+ it('dispatches requestStages and receiveStagesError ', done => {
+ testAction(
+ fetchStages,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestStages',
+ },
+ {
+ type: 'receiveStagesError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveStagesSuccess', () => {
+ it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => {
+ testAction(
+ receiveStagesSuccess,
+ {},
+ mockedState,
+ [{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveStagesError', () => {
+ it('should commit RECEIVE_STAGES_ERROR mutation ', done => {
+ testAction(
+ receiveStagesError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_STAGES_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('requestJobsForStage', () => {
+ it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
+ testAction(
+ requestJobsForStage,
+ null,
+ mockedState,
+ [{ type: types.REQUEST_JOBS_FOR_STAGE }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('setSelectedStage', () => {
+ it('should commit SET_SELECTED_STAGE mutation ', done => {
+ testAction(
+ setSelectedStage,
+ { name: 'build' },
+ mockedState,
+ [{ type: types.SET_SELECTED_STAGE, payload: { name: 'build' } }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('fetchJobsForStage', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.stageJobsEndpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess ', done => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
+
+ testAction(
+ fetchJobsForStage,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'setSelectedStage',
+ payload: null,
+ },
+ {
+ type: 'requestJobsForStage',
+ },
+ {
+ payload: [{ id: 121212, name: 'build' }],
+ type: 'receiveJobsForStageSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
+ });
+
+ it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageError', done => {
+ testAction(
+ fetchJobsForStage,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ payload: null,
+ type: 'setSelectedStage',
+ },
+ {
+ type: 'requestJobsForStage',
+ },
+ {
+ type: 'receiveJobsForStageError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveJobsForStageSuccess', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation ', done => {
+ testAction(
+ receiveJobsForStageSuccess,
+ [{ id: 121212, name: 'karma' }],
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, payload: [{ id: 121212, name: 'karma' }] }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveJobsForStageError', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation ', done => {
+ testAction(
+ receiveJobsForStageError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_FOR_STAGE_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js
new file mode 100644
index 00000000000..6900b2e5602
--- /dev/null
+++ b/spec/javascripts/jobs/store/mutations_spec.js
@@ -0,0 +1,228 @@
+import state from '~/jobs/store/state';
+import mutations from '~/jobs/store/mutations';
+import * as types from '~/jobs/store/mutation_types';
+
+describe('Jobs Store Mutations', () => {
+ let stateCopy;
+
+ const html =
+ 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('REQUEST_STATUS_FAVICON', () => {
+ it('should set fetchingStatusFavicon to true', () => {
+ mutations[types.REQUEST_STATUS_FAVICON](stateCopy);
+ expect(stateCopy.fetchingStatusFavicon).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_STATUS_FAVICON_SUCCESS', () => {
+ it('should set fetchingStatusFavicon to false', () => {
+ mutations[types.RECEIVE_STATUS_FAVICON_SUCCESS](stateCopy);
+ expect(stateCopy.fetchingStatusFavicon).toEqual(false);
+ });
+ });
+
+ describe('RECEIVE_STATUS_FAVICON_ERROR', () => {
+ it('should set fetchingStatusFavicon to false', () => {
+ mutations[types.RECEIVE_STATUS_FAVICON_ERROR](stateCopy);
+ expect(stateCopy.fetchingStatusFavicon).toEqual(false);
+ });
+ });
+
+ describe('RECEIVE_TRACE_SUCCESS', () => {
+ describe('when trace has state', () => {
+ it('sets traceState', () => {
+ const stateLog =
+ 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0=';
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
+ state: stateLog,
+ });
+ expect(stateCopy.traceState).toEqual(stateLog);
+ });
+ });
+
+ describe('when traceSize is smaller than the total size', () => {
+ it('sets isTraceSizeVisible to true', () => {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 });
+
+ expect(stateCopy.isTraceSizeVisible).toEqual(true);
+ });
+ });
+
+ describe('when traceSize is bigger than the total size', () => {
+ it('sets isTraceSizeVisible to false', () => {
+ const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 });
+
+ mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 });
+
+ expect(copy.isTraceSizeVisible).toEqual(false);
+ });
+ });
+
+ it('sets trace, trace size and isTraceComplete', () => {
+ mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
+ append: true,
+ html,
+ size: 511846,
+ complete: true,
+ });
+ expect(stateCopy.trace).toEqual(html);
+ expect(stateCopy.traceSize).toEqual(511846);
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ });
+ });
+
+ describe('STOP_POLLING_TRACE', () => {
+ it('sets isTraceComplete to true', () => {
+ mutations[types.STOP_POLLING_TRACE](stateCopy);
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_TRACE_ERROR', () => {
+ it('resets trace state and sets error to true', () => {
+ mutations[types.RECEIVE_TRACE_ERROR](stateCopy);
+ expect(stateCopy.isLoadingTrace).toEqual(false);
+ expect(stateCopy.isTraceComplete).toEqual(true);
+ expect(stateCopy.hasTraceError).toEqual(true);
+ });
+ });
+
+ describe('REQUEST_JOB', () => {
+ it('sets isLoading to true', () => {
+ mutations[types.REQUEST_JOB](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_JOB_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+ });
+
+ it('sets is loading to false', () => {
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ expect(stateCopy.hasError).toEqual(false);
+ });
+
+ it('sets job data', () => {
+ expect(stateCopy.job).toEqual({ id: 1312321 });
+ });
+ });
+
+ describe('RECEIVE_JOB_ERROR', () => {
+ it('resets job data', () => {
+ mutations[types.RECEIVE_JOB_ERROR](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(false);
+ expect(stateCopy.hasError).toEqual(true);
+ expect(stateCopy.job).toEqual({});
+ });
+ });
+
+ describe('SCROLL_TO_TOP', () => {
+ beforeEach(() => {
+ mutations[types.SCROLL_TO_TOP](stateCopy);
+ });
+
+ it('sets isTraceScrolledToBottom to false', () => {
+ expect(stateCopy.isTraceScrolledToBottom).toEqual(false);
+ });
+
+ it('sets hasBeenScrolled to true', () => {
+ expect(stateCopy.hasBeenScrolled).toEqual(true);
+ });
+ });
+
+ describe('SCROLL_TO_BOTTOM', () => {
+ beforeEach(() => {
+ mutations[types.SCROLL_TO_BOTTOM](stateCopy);
+ });
+
+ it('sets isTraceScrolledToBottom to true', () => {
+ expect(stateCopy.isTraceScrolledToBottom).toEqual(true);
+ });
+
+ it('sets hasBeenScrolled to true', () => {
+ expect(stateCopy.hasBeenScrolled).toEqual(true);
+ });
+ });
+
+ describe('REQUEST_STAGES', () => {
+ it('sets isLoadingStages to true', () => {
+ mutations[types.REQUEST_STAGES](stateCopy);
+ expect(stateCopy.isLoadingStages).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_STAGES_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]);
+ });
+
+ it('sets isLoadingStages to false', () => {
+ expect(stateCopy.isLoadingStages).toEqual(false);
+ });
+
+ it('sets stages', () => {
+ expect(stateCopy.stages).toEqual([{ name: 'build' }]);
+ });
+ });
+
+ describe('RECEIVE_STAGES_ERROR', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_STAGES_ERROR](stateCopy);
+ });
+
+ it('sets isLoadingStages to false', () => {
+ expect(stateCopy.isLoadingStages).toEqual(false);
+ });
+
+ it('resets stages', () => {
+ expect(stateCopy.stages).toEqual([]);
+ });
+ });
+
+ describe('REQUEST_JOBS_FOR_STAGE', () => {
+ it('sets isLoadingStages to true', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy);
+ expect(stateCopy.isLoadingJobs).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('sets jobs', () => {
+ expect(stateCopy.jobs).toEqual([{ name: 'karma' }]);
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('resets jobs', () => {
+ expect(stateCopy.jobs).toEqual([]);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 71b26a315af..babad296f09 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -403,6 +403,7 @@ describe('common_utils', () => {
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
+
it('should set page favicon to provided favicon', () => {
const faviconPath = '//custom_favicon';
commonUtils.setFavicon(faviconPath);
@@ -479,17 +480,14 @@ describe('common_utils', () => {
});
it('should reset favicon in case of error', (done) => {
- mock.onGet(BUILD_URL).networkError();
+ mock.onGet(BUILD_URL).replyOnce(500);
commonUtils.setCiStatusFavicon(BUILD_URL)
- .then(() => {
+ .catch(() => {
const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual(faviconDataUrl);
done();
- })
- // Error is already caught in catch() block of setCiStatusFavicon,
- // It won't throw another error for us to catch
- .catch(done.fail);
+ });
});
it('should set page favicon to CI status favicon based on provided status', (done) => {
diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
new file mode 100644
index 00000000000..4dbfd8f0eaa
--- /dev/null
+++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
@@ -0,0 +1,33 @@
+import $ from 'jquery';
+import initUserInternalRegexPlaceholder, { PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
+ PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE } from '~/pages/admin/application_settings/account_and_limits';
+
+describe('AccountAndLimits', () => {
+ const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
+ let $userDefaultExternal;
+ let $userInternalRegex;
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ initUserInternalRegexPlaceholder();
+ $userDefaultExternal = $('#application_setting_user_default_external');
+ $userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
+ });
+
+ describe('Changing of userInternalRegex when userDefaultExternal', () => {
+ it('is unchecked', () => {
+ expect($userDefaultExternal.prop('checked')).toBeFalsy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
+ expect($userInternalRegex.readOnly).toBeTruthy();
+ });
+
+ it('is checked', (done) => {
+ if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
+ expect($userDefaultExternal.prop('checked')).toBeTruthy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
+ expect($userInternalRegex.readOnly).toBeFalsy();
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js
new file mode 100644
index 00000000000..2bac3951c3a
--- /dev/null
+++ b/spec/javascripts/pages/admin/users/new/index_spec.js
@@ -0,0 +1,43 @@
+import $ from 'jquery';
+import UserInternalRegexHandler from '~/pages/admin/users/new/index';
+
+describe('UserInternalRegexHandler', () => {
+ const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
+ let $userExternal;
+ let $userEmail;
+ let $warningMessage;
+
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ // eslint-disable-next-line no-new
+ new UserInternalRegexHandler();
+ $userExternal = $('#user_external');
+ $userEmail = $('#user_email');
+ $warningMessage = $('#warning_external_automatically_set');
+ if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
+ });
+
+ describe('Behaviour of userExternal checkbox when', () => {
+ it('matches email as internal', (done) => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeFalsy();
+ expect($warningMessage.hasClass('hidden')).toBeFalsy();
+ done();
+ });
+
+ it('matches email as external', (done) => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test.ext@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeTruthy();
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index ff1bfd7f650..a207f2afce6 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -3,53 +3,45 @@ import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
import PageComponent from '~/pdf/page/index.vue';
-import testPDF from '../fixtures/blob/pdf/test.pdf';
-
-const Component = Vue.extend(PageComponent);
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import testPDF from 'spec/fixtures/blob/pdf/test.pdf';
describe('Page component', () => {
+ const Component = Vue.extend(PageComponent);
let vm;
let testPage;
- pdfjsLib.PDFJS.workerSrc = workerSrc;
-
- const checkRendered = (done) => {
- if (vm.rendering) {
- setTimeout(() => {
- checkRendered(done);
- }, 100);
- } else {
- done();
- }
- };
- beforeEach((done) => {
- pdfjsLib.getDocument(testPDF)
+ beforeEach(done => {
+ pdfjsLib.PDFJS.workerSrc = workerSrc;
+ pdfjsLib
+ .getDocument(testPDF)
.then(pdf => pdf.getPage(1))
- .then((page) => {
+ .then(page => {
testPage = page;
- done();
})
- .catch((error) => {
- done.fail(error);
- });
+ .then(done)
+ .catch(done.fail);
});
- describe('render', () => {
- beforeEach((done) => {
- vm = new Component({
- propsData: {
- page: testPage,
- number: 1,
- },
- });
-
- vm.$mount();
+ afterEach(() => {
+ vm.$destroy();
+ });
- checkRendered(done);
+ it('renders the page when mounting', done => {
+ const promise = Promise.resolve();
+ spyOn(testPage, 'render').and.callFake(() => promise);
+ vm = mountComponent(Component, {
+ page: testPage,
+ number: 1,
});
+ expect(vm.rendering).toBe(true);
- it('renders first page', () => {
- expect(vm.$el.tagName).toBeDefined();
- });
+ promise
+ .then(() => {
+ expect(testPage.render).toHaveBeenCalledWith(vm.renderContext);
+ expect(vm.rendering).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 0e42537faed..237e2fa79f2 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
@@ -114,28 +114,31 @@ describe('MRWidgetHeader', () => {
});
describe('with an open merge request', () => {
+ const mrDefaultOptions = {
+ iid: 1,
+ divergedCommitsCount: 12,
+ sourceBranch: 'mr-widget-refactor',
+ sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
+ sourceBranchRemoved: false,
+ targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
+ targetBranch: 'master',
+ isOpen: true,
+ canPushToSourceBranch: true,
+ emailPatchesPath: '/mr/email-patches',
+ plainDiffPath: '/mr/plainDiffPath',
+ statusPath: 'abc',
+ sourceProjectFullPath: 'root/gitlab-ce',
+ targetProjectFullPath: 'gitlab-org/gitlab-ce',
+ };
+
afterEach(() => {
vm.$destroy();
});
beforeEach(() => {
vm = mountComponent(Component, {
- mr: {
- iid: 1,
- divergedCommitsCount: 12,
- sourceBranch: 'mr-widget-refactor',
- sourceBranchLink: '<a href="/foo/bar/mr-widget-refactor">mr-widget-refactor</a>',
- sourceBranchRemoved: false,
- targetBranchPath: 'foo/bar/commits-path',
- targetBranchTreePath: 'foo/bar/tree/path',
- targetBranch: 'master',
- isOpen: true,
- emailPatchesPath: '/mr/email-patches',
- plainDiffPath: '/mr/plainDiffPath',
- statusPath: 'abc',
- sourceProjectFullPath: 'root/gitlab-ce',
- targetProjectFullPath: 'gitlab-org/gitlab-ce',
- },
+ mr: Object.assign({}, mrDefaultOptions),
});
});
@@ -151,11 +154,21 @@ describe('MRWidgetHeader', () => {
const button = vm.$el.querySelector('.js-web-ide');
expect(button.textContent.trim()).toEqual('Open in Web IDE');
+ expect(button.classList.contains('disabled')).toBe(false);
expect(button.getAttribute('href')).toEqual(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce',
);
});
+ it('renders web ide button in disabled state with no href', () => {
+ const mr = Object.assign({}, mrDefaultOptions, { canPushToSourceBranch: false });
+ vm = mountComponent(Component, { mr });
+
+ const link = vm.$el.querySelector('.js-web-ide');
+ expect(link.classList.contains('disabled')).toBe(true);
+ expect(link.getAttribute('href')).toBeNull();
+ });
+
it('renders web ide button with blank query string if target & source project branch', done => {
vm.mr.targetProjectFullPath = 'root/gitlab-ce';
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 50d053011b3..b9059b85fdc 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -7,6 +7,7 @@ describe Banzai::Filter::WikiLinkFilter do
let(:project) { build_stubbed(:project, :public, name: "wiki_link_project", namespace: namespace) }
let(:user) { double }
let(:wiki) { ProjectWiki.new(project, user) }
+ let(:repository_upload_folder) { Wikis::CreateAttachmentService::ATTACHMENT_PATH }
it "doesn't rewrite absolute links" do
filtered_link = filter("<a href='http://example.com:8000/'>Link</a>", project_wiki: wiki).children[0]
@@ -20,6 +21,45 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq('/uploads/a.test')
end
+ describe "when links point to the #{Wikis::CreateAttachmentService::ATTACHMENT_PATH} folder" do
+ context 'with an "a" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<a href='#{repository_upload_folder}/a.test'>Link</a>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('href').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.test")
+ end
+ end
+
+ context 'with "img" html tag' do
+ let(:path) { "#{wiki.wiki_base_path}/#{repository_upload_folder}/a.jpg" }
+
+ context 'inside an "a" html tag' do
+ it 'rewrites links' do
+ filtered_elements = filter("<a href='#{repository_upload_folder}/a.jpg'><img src='#{repository_upload_folder}/a.jpg'>example</img></a>", project_wiki: wiki)
+
+ expect(filtered_elements.search('img').first.attribute('src').value).to eq(path)
+ expect(filtered_elements.search('a').first.attribute('href').value).to eq(path)
+ end
+ end
+
+ context 'outside an "a" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<img src='#{repository_upload_folder}/a.jpg'>example</img>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('src').value).to eq(path)
+ end
+ end
+ end
+
+ context 'with "video" html tag' do
+ it 'rewrites links' do
+ filtered_link = filter("<video src='#{repository_upload_folder}/a.mp4'></video>", project_wiki: wiki).children[0]
+
+ expect(filtered_link.attribute('src').value).to eq("#{wiki.wiki_base_path}/#{repository_upload_folder}/a.mp4")
+ end
+ end
+ end
+
describe "invalid links" do
invalid_links = ["http://:8080", "http://", "http://:8080/path"]
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
new file mode 100644
index 00000000000..2d1505dacfe
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, :migration, schema: 20180816161409 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:jobs) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+
+ subject { described_class.new.perform(*range) }
+
+ context 'when a pipeline exists' do
+ let!(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let!(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let!(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+
+ context 'when a legacy artifacts exists' do
+ let(:artifacts_expire_at) { 1.day.since.to_s }
+ let(:file_store) { ::ObjectStorage::Store::REMOTE }
+
+ let!(:job) do
+ jobs.create!(
+ commit_id: pipeline.id,
+ project_id: project.id,
+ status: :success,
+ **artifacts_archive_attributes,
+ **artifacts_metadata_attributes)
+ end
+
+ let(:artifacts_archive_attributes) do
+ {
+ artifacts_file: 'archive.zip',
+ artifacts_file_store: file_store,
+ artifacts_size: 123,
+ artifacts_expire_at: artifacts_expire_at
+ }
+ end
+
+ let(:artifacts_metadata_attributes) do
+ {
+ artifacts_metadata: 'metadata.gz',
+ artifacts_metadata_store: file_store
+ }
+ end
+
+ it 'has legacy artifacts' do
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([artifacts_archive_attributes.values])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([artifacts_metadata_attributes.values])
+ end
+
+ it 'does not have new artifacts yet' do
+ expect(job_artifacts.count).to be_zero
+ end
+
+ context 'when the record exists inside of the range of a background migration' do
+ let(:range) { [job.id, job.id] }
+
+ it 'migrates a legacy artifact to ci_job_artifacts table' do
+ expect { subject }.to change { job_artifacts.count }.by(2)
+
+ expect(job_artifacts.order(:id).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location'))
+ .to eq([[project.id,
+ job.id,
+ described_class::ARCHIVE_FILE_TYPE,
+ file_store,
+ artifacts_archive_attributes[:artifacts_size],
+ artifacts_expire_at,
+ 'archive.zip',
+ nil,
+ described_class::LEGACY_PATH_FILE_LOCATION],
+ [project.id,
+ job.id,
+ described_class::METADATA_FILE_TYPE,
+ file_store,
+ nil,
+ artifacts_expire_at,
+ 'metadata.gz',
+ nil,
+ described_class::LEGACY_PATH_FILE_LOCATION]])
+
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([[nil, nil, nil, artifacts_expire_at]])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ context 'when file_store is nil' do
+ let(:file_store) { nil }
+
+ it 'has nullified file_store in all legacy artifacts' do
+ expect(jobs.pluck('artifacts_file_store, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ it 'fills file_store by the value of local file store' do
+ subject
+
+ expect(job_artifacts.pluck('file_store')).to all(eq(::ObjectStorage::Store::LOCAL))
+ end
+ end
+
+ context 'when new artifacts has already existed' do
+ context 'when only archive.zip existed' do
+ before do
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE, size: 999, file: 'archive.zip')
+ end
+
+ it 'had archive.zip already' do
+ expect(job_artifacts.exists?(job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE)).to be_truthy
+ end
+
+ it 'migrates metadata' do
+ expect { subject }.to change { job_artifacts.count }.by(1)
+
+ expect(job_artifacts.exists?(job_id: job.id, file_type: described_class::METADATA_FILE_TYPE)).to be_truthy
+ end
+ end
+
+ context 'when both archive and metadata existed' do
+ before do
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::ARCHIVE_FILE_TYPE, size: 999, file: 'archive.zip')
+ job_artifacts.create!(project_id: project.id, job_id: job.id, file_type: described_class::METADATA_FILE_TYPE, size: 999, file: 'metadata.zip')
+ end
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+ end
+
+ context 'when the record exists outside of the range of a background migration' do
+ let(:range) { [job.id + 1, job.id + 1] }
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+
+ context 'when the job does not have legacy artifacts' do
+ let!(:job) { jobs.create!(commit_id: pipeline.id, project_id: project.id, status: :success) }
+
+ it 'does not have the legacy artifacts in database' do
+ expect(jobs.count).to eq(1)
+ expect(jobs.pluck('artifacts_file, artifacts_file_store, artifacts_size, artifacts_expire_at')).to eq([[nil, nil, nil, nil]])
+ expect(jobs.pluck('artifacts_metadata, artifacts_metadata_store')).to eq([[nil, nil]])
+ end
+
+ context 'when the record exists inside of the range of a background migration' do
+ let(:range) { [job.id, job.id] }
+
+ it 'does not migrate' do
+ expect { subject }.not_to change { job_artifacts.count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/parsers/junit_spec.rb b/spec/lib/gitlab/ci/parsers/junit_spec.rb
index f7ec86f5385..47497f06229 100644
--- a/spec/lib/gitlab/ci/parsers/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/junit_spec.rb
@@ -1,4 +1,4 @@
-require 'spec_helper'
+require 'fast_spec_helper'
describe Gitlab::Ci::Parsers::Junit do
describe '#parse!' do
@@ -8,21 +8,35 @@ describe Gitlab::Ci::Parsers::Junit do
let(:test_cases) { flattened_test_cases(test_suite) }
context 'when data is JUnit style XML' do
- context 'when there are no test cases' do
+ context 'when there are no <testcases> in <testsuite>' do
let(:junit) do
<<-EOF.strip_heredoc
<testsuite></testsuite>
EOF
end
- it 'raises an error and does not add any test cases' do
- expect { subject }.to raise_error(described_class::JunitParserError)
+ it 'ignores the case' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases.count).to eq(0)
+ end
+ end
+
+ context 'when there are no <testcases> in <testsuites>' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites><testsuite /></testsuites>
+ EOF
+ end
+
+ it 'ignores the case' do
+ expect { subject }.not_to raise_error
expect(test_cases.count).to eq(0)
end
end
- context 'when there is a test case' do
+ context 'when there is only one <testcase> in <testsuite>' do
let(:junit) do
<<-EOF.strip_heredoc
<testsuite>
@@ -40,6 +54,46 @@ describe Gitlab::Ci::Parsers::Junit do
end
end
+ context 'when there is only one <testsuite> in <testsuites>' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites>
+ <testsuite>
+ <testcase classname='Calculator' name='sumTest1' time='0.01'></testcase>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'parses XML and adds a test case to a suite' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases[0].classname).to eq('Calculator')
+ expect(test_cases[0].name).to eq('sumTest1')
+ expect(test_cases[0].execution_time).to eq(0.01)
+ end
+ end
+
+ context 'PHPUnit' do
+ let(:junit) do
+ <<-EOF.strip_heredoc
+ <testsuites>
+ <testsuite name="Project Test Suite" tests="1" assertions="1" failures="0" errors="0" time="1.376748">
+ <testsuite name="XXX\\FrontEnd\\WebBundle\\Tests\\Controller\\LogControllerTest" file="/Users/mcfedr/projects/xxx/server/tests/XXX/FrontEnd/WebBundle/Tests/Controller/LogControllerTest.php" tests="1" assertions="1" failures="0" errors="0" time="1.376748">
+ <testcase name="testIndexAction" class="XXX\\FrontEnd\\WebBundle\\Tests\\Controller\\LogControllerTest" file="/Users/mcfedr/projects/xxx/server/tests/XXX/FrontEnd/WebBundle/Tests/Controller/LogControllerTest.php" line="9" assertions="1" time="1.376748"/>
+ </testsuite>
+ </testsuite>
+ </testsuites>
+ EOF
+ end
+
+ it 'parses XML and adds a test case to a suite' do
+ expect { subject }.not_to raise_error
+
+ expect(test_cases.count).to eq(1)
+ end
+ end
+
context 'when there are two test cases' do
let(:junit) do
<<-EOF.strip_heredoc
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 154ab4b3856..1d75e8cb5da 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateIssueHandler do
diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
index 43c6280f251..ace3104f36f 100644
--- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateMergeRequestHandler do
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 950a7dd7d6c..b1f48c15c21 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::CreateNoteHandler do
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index ce160e11de2..b8660b133ec 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler::UnsubscribeHandler do
diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb
index cedbfcc0d18..c651765dc0f 100644
--- a/spec/lib/gitlab/email/handler_spec.rb
+++ b/spec/lib/gitlab/email/handler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Handler do
@@ -40,7 +42,7 @@ describe Gitlab::Email::Handler do
end
def ce_handlers
- @ce_handlers ||= Gitlab::Email::Handler::HANDLERS.reject do |handler|
+ @ce_handlers ||= Gitlab::Email::Handler.handlers.reject do |handler|
handler.name.start_with?('Gitlab::Email::Handler::EE::')
end
end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index e68c9850f6b..a5bf2f2b3df 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
require "spec_helper"
describe Gitlab::EncodingHelper do
@@ -187,4 +188,15 @@ describe Gitlab::EncodingHelper do
end
end
end
+
+ describe '#binary_stringio' do
+ it 'does not mutate the original string encoding' do
+ test = 'my-test'
+
+ io_stream = ext_class.binary_stringio(test)
+
+ expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
+ expect(test.encoding.name).to eq('UTF-8')
+ end
+ end
end
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
new file mode 100644
index 00000000000..feb2776c5d0
--- /dev/null
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe Gitlab::FileMarkdownLinkBuilder do
+ let(:custom_class) do
+ Class.new do
+ include Gitlab::FileMarkdownLinkBuilder
+ end.new
+ end
+
+ before do
+ allow(custom_class).to receive(:filename).and_return(filename)
+ end
+
+ describe 'markdown_link' do
+ let(:url) { "/uploads/#{filename}"}
+
+ before do
+ allow(custom_class).to receive(:secure_url).and_return(url)
+ end
+
+ context 'when file name has the character ]' do
+ let(:filename) { 'd]k.png' }
+
+ it 'escapes the character' do
+ expect(custom_class.markdown_link).to eq '![d\\]k](/uploads/d]k.png)'
+ end
+ end
+
+ context 'when file is an image or video' do
+ let(:filename) { 'dk.png' }
+
+ it 'returns preview markdown link' do
+ expect(custom_class.markdown_link).to eq '![dk](/uploads/dk.png)'
+ end
+ end
+
+ context 'when file is not an image or video' do
+ let(:filename) { 'dk.zip' }
+
+ it 'returns markdown link' do
+ expect(custom_class.markdown_link).to eq '[dk.zip](/uploads/dk.zip)'
+ end
+ end
+
+ context 'when file name is blank' do
+ let(:filename) { nil }
+
+ it 'returns nil' do
+ expect(custom_class.markdown_link).to eq nil
+ end
+ end
+ end
+
+ describe 'mardown_name' do
+ context 'when file is an image or video' do
+ let(:filename) { 'dk.png' }
+
+ it 'retrieves the name without the extension' do
+ expect(custom_class.markdown_name).to eq 'dk'
+ end
+ end
+
+ context 'when file is not an image or video' do
+ let(:filename) { 'dk.zip' }
+
+ it 'retrieves the name with the extesion' do
+ expect(custom_class.markdown_name).to eq 'dk.zip'
+ end
+ end
+
+ context 'when file name is blank' do
+ let(:filename) { nil }
+
+ it 'returns nil' do
+ expect(custom_class.markdown_name).to eq nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
new file mode 100644
index 00000000000..5e9b8988cc8
--- /dev/null
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+require 'rails_helper'
+
+describe Gitlab::FileTypeDetection do
+ def upload_fixture(filename)
+ fixture_file_upload(File.join('spec', 'fixtures', filename))
+ end
+
+ describe '#image_or_video?' do
+ context 'when class is an uploader' do
+ let(:uploader) do
+ example_uploader = Class.new(CarrierWave::Uploader::Base) do
+ include Gitlab::FileTypeDetection
+
+ storage :file
+ end
+
+ example_uploader.new
+ end
+
+ it 'returns true for an image file' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns true for a video file' do
+ uploader.store!(upload_fixture('video_sample.mp4'))
+
+ expect(uploader).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ uploader.store!(upload_fixture('doc_sample.txt'))
+
+ expect(uploader).not_to be_image_or_video
+ end
+
+ it 'returns false if filename is blank' do
+ uploader.store!(upload_fixture('dk.png'))
+
+ allow(uploader).to receive(:filename).and_return(nil)
+
+ expect(uploader).not_to be_image_or_video
+ end
+ end
+
+ context 'when class is a regular class' do
+ let(:custom_class) do
+ custom_class = Class.new do
+ include Gitlab::FileTypeDetection
+ end
+
+ custom_class.new
+ end
+
+ it 'returns true for an image file' do
+ allow(custom_class).to receive(:filename).and_return('dk.png')
+
+ expect(custom_class).to be_image_or_video
+ end
+
+ it 'returns true for a video file' do
+ allow(custom_class).to receive(:filename).and_return('video_sample.mp4')
+
+ expect(custom_class).to be_image_or_video
+ end
+
+ it 'returns false for other extensions' do
+ allow(custom_class).to receive(:filename).and_return('doc_sample.txt')
+
+ expect(custom_class).not_to be_image_or_video
+ end
+
+ it 'returns false if filename is blank' do
+ allow(custom_class).to receive(:filename).and_return(nil)
+
+ expect(custom_class).not_to be_image_or_video
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/importer_spec.rb b/spec/lib/gitlab/import_export/importer_spec.rb
index f07946824c4..8053c48ad6c 100644
--- a/spec/lib/gitlab/import_export/importer_spec.rb
+++ b/spec/lib/gitlab/import_export/importer_spec.rb
@@ -63,6 +63,16 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
+
+ it 'sets the correct visibility_level when visibility level is a string' do
+ project.create_or_update_import_data(
+ data: { override_params: { visibility_level: Gitlab::VisibilityLevel::PRIVATE.to_s } }
+ )
+
+ importer.execute
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
end
context 'when project successfully restored' do
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index e0569218d78..632acd6eb46 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -61,6 +61,8 @@ describe ObjectStorage::DirectUpload do
expect(subject[:GetURL]).to start_with(storage_url)
expect(subject[:StoreURL]).to start_with(storage_url)
expect(subject[:DeleteURL]).to start_with(storage_url)
+ expect(subject[:CustomPutHeaders]).to be_truthy
+ expect(subject[:PutHeaders]).to eq({ 'Content-Type' => 'application/octet-stream' })
end
end
diff --git a/spec/migrations/migrate_legacy_artifacts_to_job_artifacts_spec.rb b/spec/migrations/migrate_legacy_artifacts_to_job_artifacts_spec.rb
new file mode 100644
index 00000000000..df82672f254
--- /dev/null
+++ b/spec/migrations/migrate_legacy_artifacts_to_job_artifacts_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180816161409_migrate_legacy_artifacts_to_job_artifacts.rb')
+
+describe MigrateLegacyArtifactsToJobArtifacts, :migration, :sidekiq do
+ let(:migration_class) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:jobs) { table(:ci_builds) }
+ let(:job_artifacts) { table(:ci_job_artifacts) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let(:pipeline) { pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a') }
+ let(:archive_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::ARCHIVE_FILE_TYPE }
+ let(:metadata_file_type) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::METADATA_FILE_TYPE }
+ let(:local_store) { ::ObjectStorage::Store::LOCAL }
+ let(:remote_store) { ::ObjectStorage::Store::REMOTE }
+ let(:legacy_location) { Gitlab::BackgroundMigration::MigrateLegacyArtifacts::LEGACY_PATH_FILE_LOCATION }
+
+ context 'when legacy artifacts exist' do
+ before do
+ jobs.create!(id: 1, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip')
+ jobs.create!(id: 2, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 3, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 4, commit_id: pipeline.id, project_id: project.id, status: :running)
+ jobs.create!(id: 5, commit_id: pipeline.id, project_id: project.id, status: :success, artifacts_file: 'archive.zip', artifacts_file_store: remote_store, artifacts_metadata: 'metadata.gz')
+ jobs.create!(id: 6, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_file: 'archive.zip', artifacts_metadata: 'metadata.gz')
+ end
+
+ it 'schedules a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(5.minutes, 1, 6)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 1
+ end
+ end
+ end
+
+ it 'migrates legacy artifacts to ci_job_artifacts table' do
+ migrate!
+
+ expect(job_artifacts.order(:job_id, :file_type).pluck('project_id, job_id, file_type, file_store, size, expire_at, file, file_sha256, file_location'))
+ .to eq([[project.id, 1, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 3, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 5, archive_file_type, remote_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 5, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location],
+ [project.id, 6, archive_file_type, local_store, nil, nil, 'archive.zip', nil, legacy_location],
+ [project.id, 6, metadata_file_type, local_store, nil, nil, 'metadata.gz', nil, legacy_location]])
+ end
+ end
+
+ context 'when legacy artifacts do not exist' do
+ before do
+ jobs.create!(id: 1, commit_id: pipeline.id, project_id: project.id, status: :success)
+ jobs.create!(id: 2, commit_id: pipeline.id, project_id: project.id, status: :failed, artifacts_metadata: 'metadata.gz')
+ end
+
+ it 'does not schedule background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 02f74e2ea54..483cc546423 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -538,4 +538,28 @@ describe ApplicationSetting do
expect(setting.allow_signup?).to be_falsey
end
end
+
+ describe '#user_default_internal_regex_enabled?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | false
+ false | '' | false
+ false | '^(?:(?!\.ext@).)*$\r?\n?' | false
+ true | '' | false
+ true | nil | false
+ true | '^(?:(?!\.ext@).)*$\r?\n?' | true
+ end
+
+ with_them do
+ before do
+ setting.update(user_default_external: user_default_external)
+ setting.update(user_default_internal_regex: user_default_internal_regex)
+ end
+
+ subject { setting.user_default_internal_regex_enabled? }
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 3aa1039d8bf..46713df77da 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -26,4 +26,54 @@ describe ChatNotificationService do
end
end
end
+
+ describe '#execute' do
+ let(:chat_service) { described_class.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:webhook_url) { 'https://example.gitlab.com/' }
+
+ before do
+ allow(chat_service).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+
+ subject.active = true
+ end
+
+ context 'with a repository' do
+ it 'returns true' do
+ subject.project = project
+ data = Gitlab::DataBuilder::Push.build_sample(project, user)
+
+ expect(Slack::Notifier).to receive(:new)
+ .with(webhook_url, {})
+ .and_return(
+ double(:slack_service).as_null_object
+ )
+
+ expect(chat_service.execute(data)).to be true
+ end
+ end
+
+ context 'with an empty repository' do
+ it 'returns true' do
+ subject.project = create(:project, :empty_repo)
+ data = Gitlab::DataBuilder::Push.build_sample(subject.project, user)
+
+ expect(Slack::Notifier).to receive(:new)
+ .with(webhook_url, {})
+ .and_return(
+ double(:slack_service).as_null_object
+ )
+
+ expect(chat_service.execute(data)).to be true
+ end
+ end
+ end
end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 793b724bfca..93e85b3a6fa 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -112,6 +112,7 @@ describe IssuePolicy do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, assignees: [assignee], author: author) }
let(:issue_no_assignee) { create(:issue, project: project) }
+ let(:issue_locked) { create(:issue, project: project, discussion_locked: true, author: author, assignees: [assignee]) }
before do
project.add_guest(guest)
@@ -124,36 +125,49 @@ describe IssuePolicy do
it 'allows guests to read issues' do
expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid)
- expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue)
+ expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
- expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
+ expect(permissions(guest, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
+
+ expect(permissions(guest, issue_locked)).to be_allowed(:read_issue, :read_issue_iid)
+ expect(permissions(guest, issue_locked)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
end
- it 'allows reporters to read, update, and admin issues' do
- expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
- expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
+ it 'allows reporters to read, update, reopen, and admin issues' do
+ expect(permissions(reporter, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue)
+ expect(permissions(reporter, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue)
+ expect(permissions(reporter, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
+ expect(permissions(reporter, issue_locked)).to be_disallowed(:reopen_issue)
end
- it 'allows reporters from group links to read, update, and admin issues' do
- expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
- expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
+ it 'allows reporters from group links to read, update, reopen and admin issues' do
+ expect(permissions(reporter_from_group_link, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue)
+ expect(permissions(reporter_from_group_link, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :reopen_issue)
+ expect(permissions(reporter_from_group_link, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
+ expect(permissions(reporter_from_group_link, issue_locked)).to be_disallowed(:reopen_issue)
end
- it 'allows issue authors to read and update their issues' do
- expect(permissions(author, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
+ it 'allows issue authors to read, reopen and update their issues' do
+ expect(permissions(author, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :reopen_issue)
expect(permissions(author, issue)).to be_disallowed(:admin_issue)
expect(permissions(author, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
- expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
+ expect(permissions(author, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
+
+ expect(permissions(author, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
+ expect(permissions(author, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue)
end
- it 'allows issue assignees to read and update their issues' do
- expect(permissions(assignee, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
+ it 'allows issue assignees to read, reopen and update their issues' do
+ expect(permissions(assignee, issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :reopen_issue)
expect(permissions(assignee, issue)).to be_disallowed(:admin_issue)
expect(permissions(assignee, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid)
- expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue)
+ expect(permissions(assignee, issue_no_assignee)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue)
+
+ expect(permissions(assignee, issue_locked)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
+ expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue)
end
context 'with confidential issues' do
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 489cb001b82..c40d01e1a14 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -139,6 +139,27 @@ describe API::Wikis do
end
end
+ shared_examples_for 'uploads wiki attachment' do
+ it 'pushes attachment to the wiki repository' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ post(api(url, user), payload)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
+
+ it 'responds with validation error on empty file' do
+ payload.delete(:file)
+
+ post(api(url, user), payload)
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response.size).to eq(1)
+ expect(json_response['error']).to eq('file is missing')
+ end
+ end
+
describe 'GET /projects/:id/wikis' do
let(:url) { "/projects/#{project.id}/wikis" }
@@ -698,4 +719,107 @@ describe API::Wikis do
include_examples '204 No Content'
end
end
+
+ describe 'POST /projects/:id/wikis/attachments' do
+ let(:payload) { { file: fixture_file_upload('spec/fixtures/dk.png') } }
+ let(:url) { "/projects/#{project.id}/wikis/attachments" }
+ let(:file_path) { "#{Wikis::CreateAttachmentService::ATTACHMENT_PATH}/fixed_hex/dk.png" }
+ let(:result_hash) do
+ {
+ file_name: 'dk.png',
+ file_path: file_path,
+ branch: 'master',
+ link: {
+ url: file_path,
+ markdown: "![dk](#{file_path})"
+ }
+ }
+ end
+
+ context 'when wiki is disabled' do
+ let(:project) { create(:project, :wiki_disabled, :wiki_repo) }
+
+ context 'when user is guest' do
+ before do
+ post(api(url), payload)
+ end
+
+ include_examples '404 Project Not Found'
+ end
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ post(api(url, user), payload)
+ end
+
+ include_examples '403 Forbidden'
+ end
+
+ context 'when user is maintainer' do
+ before do
+ project.add_maintainer(user)
+ post(api(url, user), payload)
+ end
+
+ include_examples '403 Forbidden'
+ end
+ end
+
+ context 'when wiki is available only for team members' do
+ let(:project) { create(:project, :wiki_private, :wiki_repo) }
+
+ context 'when user is guest' do
+ before do
+ post(api(url), payload)
+ end
+
+ include_examples '404 Project Not Found'
+ end
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ include_examples 'uploads wiki attachment'
+ end
+
+ context 'when user is maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ include_examples 'uploads wiki attachment'
+ end
+ end
+
+ context 'when wiki is available for everyone with access' do
+ let(:project) { create(:project, :wiki_repo) }
+
+ context 'when user is guest' do
+ before do
+ post(api(url), payload)
+ end
+
+ include_examples '404 Project Not Found'
+ end
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ include_examples 'uploads wiki attachment'
+ end
+
+ context 'when user is maintainer' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ include_examples 'uploads wiki attachment'
+ end
+ end
+ end
end
diff --git a/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
new file mode 100644
index 00000000000..4739f0e6c47
--- /dev/null
+++ b/spec/rubocop/cop/prefer_class_methods_over_module_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/prefer_class_methods_over_module'
+
+describe RuboCop::Cop::PreferClassMethodsOverModule do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags violation when using module ClassMethods' do
+ expect_offense(<<~RUBY)
+ module Foo
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ ^^^^^^^^^^^^^^^^^^^ Do not use module ClassMethods, use class_methods block instead.
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ end
+
+ it "doesn't flag violation when using class_methods" do
+ expect_no_offenses(<<~RUBY)
+ module Foo
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ end
+
+ it "doesn't flag violation when module is not extending ActiveSupport::Concern" do
+ expect_no_offenses(<<~RUBY)
+ module Foo
+ module ClassMethods
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ end
+
+ it "doesn't flag violation when ClassMethods is used inside a class" do
+ expect_no_offenses(<<~RUBY)
+ class Foo
+ module ClassMethods
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ end
+
+ it "doesn't flag violation when not using either class_methods or ClassMethods" do
+ expect_no_offenses(<<~RUBY)
+ module Foo
+ extend ActiveSupport::Concern
+
+ def a_method
+ end
+ end
+ RUBY
+ end
+
+ it 'autocorrects ClassMethods into class_methods' do
+ source = <<~RUBY
+ module Foo
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ autocorrected = autocorrect_source(source)
+
+ expected_source = <<~RUBY
+ module Foo
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def a_class_method
+ end
+ end
+ end
+ RUBY
+ expect(autocorrected).to eq(expected_source)
+ end
+end
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index 00b2146dc86..3d90ce44dfb 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -67,4 +67,21 @@ describe DiffFileEntity do
end
end
end
+
+ context '#parallel_diff_lines' do
+ it 'exposes parallel diff lines correctly' do
+ response = subject
+
+ lines = response[:parallel_diff_lines]
+
+ # make sure at least one line is present for each side
+ expect(lines.map { |line| line[:right] }.compact).to be_present
+ expect(lines.map { |line| line[:left] }.compact).to be_present
+ # make sure all lines are in correct format
+ lines.each do |parallel_line|
+ expect(parallel_line[:left].as_json).to match_schema('entities/diff_line') if parallel_line[:left]
+ expect(parallel_line[:right].as_json).to match_schema('entities/diff_line') if parallel_line[:right]
+ end
+ end
+ end
end
diff --git a/spec/serializers/diff_line_serializer_spec.rb b/spec/serializers/diff_line_serializer_spec.rb
new file mode 100644
index 00000000000..6dd8abd0579
--- /dev/null
+++ b/spec/serializers/diff_line_serializer_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe DiffLineSerializer do
+ let(:line) { Gitlab::Diff::Line.new('hello world', 'new', 1, nil, 1) }
+ let(:serializer) { described_class.new.represent(line) }
+
+ describe '#to_json' do
+ subject { serializer.to_json }
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/diff_line')
+ end
+
+ context 'when lines are parallel' do
+ let(:right_line) { Gitlab::Diff::Line.new('right line', 'new', 1, nil, 1) }
+ let(:left_line) { Gitlab::Diff::Line.new('left line', 'match', 1, nil, 1) }
+ let(:parallel_line) { [{ right: right_line, left: left_line }] }
+ let(:serializer) { described_class.new.represent(parallel_line, {}, DiffLineParallelEntity) }
+
+ it 'matches the schema' do
+ expect(subject).to match_schema('entities/diff_line_parallel')
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 1a85c52fc97..92c5ac7354a 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -169,6 +169,35 @@ describe Projects::TransferService do
it { expect(project.errors[:new_namespace]).to include('Cannot move project') }
end
+ context 'target namespace containing the same project name' do
+ before do
+ group.add_owner(user)
+ project.update(name: 'new_name')
+
+ create(:project, name: 'new_name', group: group, path: 'other')
+
+ @result = transfer_project(project, user, group)
+ end
+
+ it { expect(@result).to eq false }
+ it { expect(project.namespace).to eq(user.namespace) }
+ it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
+ end
+
+ context 'target namespace containing the same project path' do
+ before do
+ group.add_owner(user)
+
+ create(:project, name: 'other-name', path: project.path, group: group)
+
+ @result = transfer_project(project, user, group)
+ end
+
+ it { expect(@result).to eq false }
+ it { expect(project.namespace).to eq(user.namespace) }
+ it { expect(project.errors[:new_namespace]).to include('Project with same name or path in target namespace already exists') }
+ end
+
def transfer_project(project, user, new_namespace)
service = Projects::TransferService.new(project, user)
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index 677d4a622e1..b987fe45138 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -13,6 +13,59 @@ describe Users::BuildService do
it 'returns a valid user' do
expect(service.execute).to be_valid
end
+
+ context 'with "user_default_external" application setting' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
+ true | nil | 'fl@example.com' | nil | true
+ true | true | 'fl@example.com' | nil | true
+ true | false | 'fl@example.com' | nil | false
+
+ true | nil | 'fl@example.com' | '' | true
+ true | true | 'fl@example.com' | '' | true
+ true | false | 'fl@example.com' | '' | false
+
+ true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'fl@example.com' | nil | false
+ false | true | 'fl@example.com' | nil | true
+ false | false | 'fl@example.com' | nil | false
+
+ false | nil | 'fl@example.com' | '' | false
+ false | true | 'fl@example.com' | '' | true
+ false | false | 'fl@example.com' | '' | false
+
+ false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+
+ params.merge!({ external: external, email: email }.compact)
+ end
+
+ subject(:user) { service.execute }
+
+ it 'correctly sets user.external' do
+ expect(user.external).to eq(result)
+ end
+ end
+ end
end
context 'with non admin user' do
@@ -50,6 +103,59 @@ describe Users::BuildService do
expect(service.execute).to be_confirmed
end
end
+
+ context 'with "user_default_external" application setting' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
+ true | nil | 'fl@example.com' | nil | true
+ true | true | 'fl@example.com' | nil | true
+ true | false | 'fl@example.com' | nil | true
+
+ true | nil | 'fl@example.com' | '' | true
+ true | true | 'fl@example.com' | '' | true
+ true | false | 'fl@example.com' | '' | true
+
+ true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+
+ true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+
+ false | nil | 'fl@example.com' | nil | false
+ false | true | 'fl@example.com' | nil | false
+ false | false | 'fl@example.com' | nil | false
+
+ false | nil | 'fl@example.com' | '' | false
+ false | true | 'fl@example.com' | '' | false
+ false | false | 'fl@example.com' | '' | false
+
+ false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+
+ params.merge!({ external: external, email: email }.compact)
+ end
+
+ subject(:user) { service.execute }
+
+ it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
+ expect(user.external).to eq(result)
+ end
+ end
+ end
end
end
end
diff --git a/spec/services/wikis/create_attachment_service_spec.rb b/spec/services/wikis/create_attachment_service_spec.rb
new file mode 100644
index 00000000000..3f4da873ce4
--- /dev/null
+++ b/spec/services/wikis/create_attachment_service_spec.rb
@@ -0,0 +1,202 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Wikis::CreateAttachmentService do
+ let(:project) { create(:project, :wiki_repo) }
+ let(:user) { create(:user) }
+ let(:file_name) { 'filename.txt' }
+ let(:file_path_regex) { %r{#{described_class::ATTACHMENT_PATH}/\h{32}/#{file_name}} }
+
+ let(:file_opts) do
+ {
+ file_name: file_name,
+ file_content: 'Content of attachment'
+ }
+ end
+ let(:opts) { file_opts }
+
+ subject(:service) { described_class.new(project, user, opts) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'initialization' do
+ context 'author commit info' do
+ it 'does not raise error if user is nil' do
+ service = described_class.new(project, nil, opts)
+
+ expect(service.instance_variable_get(:@author_email)).to be_nil
+ expect(service.instance_variable_get(:@author_name)).to be_nil
+ end
+
+ it 'fills file_path from the repository uploads folder' do
+ expect(service.instance_variable_get(:@file_path)).to match(file_path_regex)
+ end
+
+ context 'when no author info provided' do
+ it 'fills author_email and author_name from current_user info' do
+ expect(service.instance_variable_get(:@author_email)).to eq user.email
+ expect(service.instance_variable_get(:@author_name)).to eq user.name
+ end
+ end
+
+ context 'when author info provided' do
+ let(:author_email) { 'author_email' }
+ let(:author_name) { 'author_name' }
+ let(:opts) { file_opts.merge(author_email: author_email, author_name: author_name) }
+
+ it 'fills author_email and author_name from params' do
+ expect(service.instance_variable_get(:@author_email)).to eq author_email
+ expect(service.instance_variable_get(:@author_name)).to eq author_name
+ end
+ end
+ end
+
+ context 'commit message' do
+ context 'when no commit message provided' do
+ it 'sets a default commit message' do
+ expect(service.instance_variable_get(:@commit_message)).to eq "Upload attachment #{opts[:file_name]}"
+ end
+ end
+
+ context 'when commit message provided' do
+ let(:commit_message) { 'whatever' }
+ let(:opts) { file_opts.merge(commit_message: commit_message) }
+
+ it 'use the commit message from params' do
+ expect(service.instance_variable_get(:@commit_message)).to eq commit_message
+ end
+ end
+ end
+
+ context 'branch name' do
+ context 'when no branch provided' do
+ it 'sets the branch from the wiki default_branch' do
+ expect(service.instance_variable_get(:@branch_name)).to eq project.wiki.default_branch
+ end
+ end
+
+ context 'when branch provided' do
+ let(:branch_name) { 'whatever' }
+ let(:opts) { file_opts.merge(branch_name: branch_name) }
+
+ it 'use the commit message from params' do
+ expect(service.instance_variable_get(:@branch_name)).to eq branch_name
+ end
+ end
+ end
+ end
+
+ describe 'validations' do
+ context 'when file_name' do
+ context 'is not present' do
+ let(:file_name) { nil }
+
+ it 'returns error' do
+ result = service.execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'The file name cannot be empty'
+ end
+ end
+
+ context 'length' do
+ context 'is bigger than 255' do
+ let(:file_name) { "#{'0' * 256}.jpg" }
+
+ it 'truncates file name' do
+ result = service.execute
+
+ expect(result[:status]).to eq :success
+ expect(result[:result][:file_name].length).to eq 255
+ expect(result[:result][:file_name]).to match(/0{251}\.jpg/)
+ end
+ end
+
+ context 'is less or equal to 255 does not return error' do
+ let(:file_name) { '0' * 255 }
+
+ it 'does not return error' do
+ result = service.execute
+
+ expect(result[:status]).to eq :success
+ end
+ end
+ end
+ end
+
+ context 'when user' do
+ shared_examples 'wiki attachment user validations' do
+ it 'returns error' do
+ result = described_class.new(project, user2, opts).execute
+
+ expect(result[:status]).to eq :error
+ expect(result[:message]).to eq 'You are not allowed to push to the wiki'
+ end
+ end
+
+ context 'does not have permission' do
+ let(:user2) { create(:user) }
+
+ it_behaves_like 'wiki attachment user validations'
+ end
+
+ context 'is nil' do
+ let(:user2) { nil }
+
+ it_behaves_like 'wiki attachment user validations'
+ end
+ end
+ end
+
+ describe '#execute' do
+ let(:wiki) { project.wiki }
+ subject(:service_execute) { service.execute[:result] }
+
+ context 'creates branch if it does not exists' do
+ let(:branch_name) { 'new_branch' }
+ let(:opts) { file_opts.merge(branch_name: branch_name) }
+
+ it do
+ expect(wiki.repository.branches).to be_empty
+ expect { service.execute }.to change { wiki.repository.branches.count }.by(1)
+ expect(wiki.repository.branches.first.name).to eq branch_name
+ end
+ end
+
+ it 'adds file to the repository' do
+ expect(wiki.repository.ls_files('HEAD')).to be_empty
+
+ service.execute
+
+ files = wiki.repository.ls_files('HEAD')
+ expect(files.count).to eq 1
+ expect(files.first).to match(file_path_regex)
+ end
+
+ context 'returns' do
+ before do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ service_execute
+ end
+
+ it 'returns the file name' do
+ expect(service_execute[:file_name]).to eq file_name
+ end
+
+ it 'returns the path where file was stored' do
+ expect(service_execute[:file_path]).to eq 'uploads/fixed_hex/filename.txt'
+ end
+
+ it 'returns the branch where the file was pushed' do
+ expect(service_execute[:branch]).to eq wiki.default_branch
+ end
+
+ it 'returns the commit id' do
+ expect(service_execute[:commit]).not_to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a15a46a9534..c4bb1c13f2e 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -42,6 +42,7 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
+ config.fixture_path = Rails.root
config.verbose_retry = true
config.display_try_failure_messages = true
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index 9b8bcebcb3a..b38c5dfe60b 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -11,6 +11,4 @@ RSpec.configure do |config|
config.include StubMetrics
config.include StubObjectStorage
config.include StubENV
-
- config.fixture_path = Rails.root if defined?(Rails)
end
diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
new file mode 100644
index 00000000000..a3d31e26498
--- /dev/null
+++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+# Shared examples for controllers that load and send files from the git repository
+# (like Projects::RawController or Projects::AvatarsController)
+
+# These examples requires the following variables:
+# - `project`
+# - `filename`: filename of the file
+# - `filepath`: path of the file (contains filename)
+# - `subject`: the request to be made to the controller. Example:
+# subject { get :show, namespace_id: project.namespace, project_id: project }
+shared_examples 'repository lfs file load' do
+ context 'when file is stored in lfs' do
+ let(:lfs_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' }
+ let(:lfs_size) { '1575078' }
+ let!(:lfs_object) { create(:lfs_object, oid: lfs_oid, size: lfs_size) }
+
+ context 'when lfs is enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ context 'when project has access' do
+ before do
+ project.lfs_objects << lfs_object
+ allow_any_instance_of(LfsObjectUploader).to receive(:exists?).and_return(true)
+ allow(controller).to receive(:send_file) { controller.head :ok }
+ end
+
+ it 'serves the file' do
+ expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: filename, disposition: 'attachment')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ context 'and lfs uses object storage' do
+ let(:lfs_object) { create(:lfs_object, :with_file, oid: lfs_oid, size: lfs_size) }
+
+ before do
+ stub_lfs_object_storage
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+ end
+
+ it 'responds with redirect to file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(response.location).to include(lfs_object.reload.file.path)
+ end
+
+ it 'sets content disposition' do
+ subject
+
+ file_uri = URI.parse(response.location)
+ params = CGI.parse(file_uri.query)
+
+ expect(params["response-content-disposition"].first).to eq "attachment;filename=\"#{filename}\""
+ end
+ end
+ end
+
+ context 'when project does not have access' do
+ it 'does not serve the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when lfs is not enabled' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(false)
+ end
+
+ it 'delivers ASCII file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.header['Content-Disposition'])
+ .to eq('inline')
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/wiki_file_attachments_examples.rb b/spec/support/shared_examples/wiki_file_attachments_examples.rb
new file mode 100644
index 00000000000..b6fb2a66b0e
--- /dev/null
+++ b/spec/support/shared_examples/wiki_file_attachments_examples.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# Requires a context containing:
+# project
+
+shared_examples 'wiki file attachments' do
+ include DropzoneHelper
+
+ context 'uploading attachments', :js do
+ let(:wiki) { project.wiki }
+
+ def attach_with_dropzone(wait = false)
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, wait)
+ end
+
+ context 'before uploading' do
+ it 'shows "Attach a file" button' do
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+ end
+
+ context 'uploading is in progress' do
+ it 'cancels uploading on clicking to "Cancel" button' do
+ slow_requests do
+ attach_with_dropzone
+
+ click_button 'Cancel'
+ end
+
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_button('Cancel')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+
+ it 'shows "Attaching a file" message on uploading 1 file' do
+ slow_requests do
+ attach_with_dropzone
+
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -')
+ end
+ end
+ end
+
+ context 'uploading is complete' do
+ it 'shows "Attach a file" button on uploading complete' do
+ attach_with_dropzone
+ wait_for_requests
+
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
+
+ it 'the markdown link is added to the page' do
+ fill_in(:wiki_content, with: '')
+ attach_with_dropzone(true)
+ wait_for_requests
+
+ expect(page.find('#wiki_content').value)
+ .to match(%r{\!\[dk\]\(uploads/\h{32}/dk\.png\)$})
+ end
+
+ it 'the links point to the wiki root url' do
+ attach_with_dropzone(true)
+ wait_for_requests
+
+ find('.js-md-preview-button').click
+ file_path = page.find('input[name="files[]"]', visible: :hidden).value
+ link = page.find('a.no-attachment-icon')['href']
+ img_link = page.find('a.no-attachment-icon img')['src']
+
+ expect(link).to eq img_link
+ expect(URI.parse(link).path).to eq File.join(wiki.wiki_base_path, file_path)
+ end
+
+ it 'the file has been added to the wiki repository' do
+ expect do
+ attach_with_dropzone(true)
+ wait_for_requests
+ end.to change { wiki.repository.ls_files('HEAD').count }.by(1)
+
+ file_path = page.find('input[name="files[]"]', visible: :hidden).value
+
+ expect(wiki.find_file(file_path, 'HEAD').path).not_to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index 3ad5fe7e3b3..061432f082a 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -40,6 +40,53 @@ describe JobArtifactUploader do
it { is_expected.to end_with("ci_build_artifacts.zip") }
end
+ describe '#dynamic_segment' do
+ let(:uploaded_content) { File.binread(Rails.root + 'spec/fixtures/ci_build_artifacts.zip') }
+ let(:model) { uploader.model }
+
+ shared_examples_for 'Read file from legacy path' do
+ it 'store_path returns the legacy path' do
+ expect(model.file.store_path).to eq(File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.job_id.to_s, 'ci_build_artifacts.zip'))
+ end
+
+ it 'has exactly the same content' do
+ expect(::File.binread(model.file.path)).to eq(uploaded_content)
+ end
+ end
+
+ shared_examples_for 'Read file from hashed path' do
+ it 'store_path returns hashed path' do
+ expect(model.file.store_path).to eq(File.join(disk_hash[0..1], disk_hash[2..3], disk_hash, creation_date, model.job_id.to_s, model.id.to_s, 'ci_build_artifacts.zip'))
+ end
+
+ it 'has exactly the same content' do
+ expect(::File.binread(model.file.path)).to eq(uploaded_content)
+ end
+ end
+
+ context 'when a job artifact is stored in legacy_path' do
+ let(:job_artifact) { create(:ci_job_artifact, :legacy_archive) }
+
+ it_behaves_like 'Read file from legacy path'
+ end
+
+ context 'when the artifact file is stored in hashed_path' do
+ let(:job_artifact) { create(:ci_job_artifact, :archive) }
+ let(:disk_hash) { Digest::SHA2.hexdigest(model.project_id.to_s) }
+ let(:creation_date) { model.created_at.utc.strftime('%Y_%m_%d') }
+
+ it_behaves_like 'Read file from hashed path'
+
+ context 'when file_location column is empty' do
+ before do
+ job_artifact.update_column(:file_location, nil)
+ end
+
+ it_behaves_like 'Read file from hashed path'
+ end
+ end
+ end
+
describe "#migrate!" do
before do
uploader.store!(fixture_file_upload('spec/fixtures/trace/sample_trace'))
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
index 33da93cc9d0..fd6712d4645 100644
--- a/spec/uploaders/uploader_helper_spec.rb
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -11,27 +11,10 @@ describe UploaderHelper do
example_uploader.new
end
- def upload_fixture(filename)
- fixture_file_upload(File.join('spec', 'fixtures', filename))
- end
-
- describe '#image_or_video?' do
- it 'returns true for an image file' do
- uploader.store!(upload_fixture('dk.png'))
-
- expect(uploader).to be_image_or_video
- end
-
- it 'it returns true for a video file' do
- uploader.store!(upload_fixture('video_sample.mp4'))
-
- expect(uploader).to be_image_or_video
- end
-
- it 'returns false for other extensions' do
- uploader.store!(upload_fixture('doc_sample.txt'))
-
- expect(uploader).not_to be_image_or_video
+ describe '#extension_match?' do
+ it 'returns false if file does not exists' do
+ expect(uploader.file).to be_nil
+ expect(uploader.send(:extension_match?, 'jpg')).to eq false
end
end
end
diff --git a/spec/validators/js_regex_validator_spec.rb b/spec/validators/js_regex_validator_spec.rb
new file mode 100644
index 00000000000..aeb55cdc0e5
--- /dev/null
+++ b/spec/validators/js_regex_validator_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe JsRegexValidator do
+ describe '#validates_each' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:validator) { described_class.new(attributes: [:user_default_internal_regex]) }
+ let(:application_setting) { build(:application_setting, user_default_external: true) }
+
+ where(:user_default_internal_regex, :result) do
+ nil | []
+ '' | []
+ '(?#comment)' | ['Regex Pattern (?#comment) can not be expressed in Javascript']
+ '(?(a)b|c)' | ['invalid conditional pattern: /(?(a)b|c)/i']
+ '[a-z&&[^uo]]' | ["Dropped unsupported set intersection '[a-z&&[^uo]]' at index 0",
+ "Dropped unsupported nested negative set data '[^uo]' at index 6"]
+ end
+
+ with_them do
+ it 'generates correct errors' do
+ validator.validate_each(application_setting, :user_default_internal_regex, user_default_internal_regex)
+
+ expect(application_setting.errors[:user_default_internal_regex]).to eq result
+ end
+ end
+ end
+end
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index ffcf5648075..893ab9efa2a 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -825,7 +825,7 @@ rollout 100%:
fi
if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm delete "$name"
+ helm delete --purge "$name"
fi
}