summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.flayignore1
-rw-r--r--.gitignore4
-rw-r--r--.gitlab-ci.yml223
-rw-r--r--.nvmrc1
-rw-r--r--.rspec2
-rw-r--r--.rubocop.yml12
-rw-r--r--.rubocop_todo.yml10
-rw-r--r--.scss-lint.yml17
-rw-r--r--CHANGELOG.md269
-rw-r--r--CONTRIBUTING.md32
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile39
-rw-r--r--Gemfile.lock125
-rw-r--r--README.md2
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/new_nav.pngbin23771 -> 14322 bytes
-rw-r--r--app/assets/javascripts/blob/notebook/index.js3
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js6
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js5
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js6
-rw-r--r--app/assets/javascripts/boards/models/list.js13
-rw-r--r--app/assets/javascripts/boards/services/board_service.js5
-rw-r--r--app/assets/javascripts/build.js171
-rw-r--r--app/assets/javascripts/close_reopen_report_toggle.js97
-rw-r--r--app/assets/javascripts/comment_type_toggle.js5
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js29
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue19
-rw-r--r--app/assets/javascripts/commons/bootstrap.js1
-rw-r--r--app/assets/javascripts/copy_as_gfm.js10
-rw-r--r--app/assets/javascripts/diff.js7
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js22
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js26
-rw-r--r--app/assets/javascripts/dispatcher.js70
-rw-r--r--app/assets/javascripts/dropzone_input.js101
-rw-r--r--app/assets/javascripts/due_date_select.js11
-rw-r--r--app/assets/javascripts/environments/components/environment.vue28
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue4
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue8
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js18
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js22
-rw-r--r--app/assets/javascripts/experimental_flags.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js5
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js26
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js1
-rw-r--r--app/assets/javascripts/gpg_badges.js15
-rw-r--r--app/assets/javascripts/group_name.js15
-rw-r--r--app/assets/javascripts/groups/index.js13
-rw-r--r--app/assets/javascripts/helpers/issuables_helper.js27
-rw-r--r--app/assets/javascripts/how_to_merge.js12
-rw-r--r--app/assets/javascripts/issuable_bulk_update_sidebar.js13
-rw-r--r--app/assets/javascripts/issuable_form.js2
-rw-r--r--app/assets/javascripts/issue.js55
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue5
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue3
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js8
-rw-r--r--app/assets/javascripts/jobs/job_details_mediator.js3
-rw-r--r--app/assets/javascripts/layout_nav.js7
-rw-r--r--app/assets/javascripts/lazy_loader.js76
-rw-r--r--app/assets/javascripts/lib/utils/datefix.js8
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js1
-rw-r--r--app/assets/javascripts/lib/utils/poll.js3
-rw-r--r--app/assets/javascripts/main.js35
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js2
-rw-r--r--app/assets/javascripts/merge_request.js14
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_column.vue46
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_flag.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/monitoring_legends.vue10
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js11
-rw-r--r--app/assets/javascripts/new_sidebar.js23
-rw-r--r--app/assets/javascripts/notes.js6
-rw-r--r--app/assets/javascripts/oauth_remember_me.js32
-rw-r--r--app/assets/javascripts/peek.js16
-rw-r--r--app/assets/javascripts/performance_bar.js62
-rw-r--r--app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js3
-rw-r--r--app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js71
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue13
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue5
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediatior.js8
-rw-r--r--app/assets/javascripts/project_new.js4
-rw-r--r--app/assets/javascripts/project_select.js8
-rw-r--r--app/assets/javascripts/protected_branches/index.js9
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js53
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js106
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js39
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js114
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit_list.js28
-rw-r--r--app/assets/javascripts/protected_branches/protected_branches_bundle.js5
-rw-r--r--app/assets/javascripts/protected_tags/index.js11
-rw-r--r--app/assets/javascripts/shortcuts.js4
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js4
-rw-r--r--app/assets/javascripts/signin_tabs_memoizer.js82
-rw-r--r--app/assets/javascripts/single_file_diff.js151
-rw-r--r--app/assets/javascripts/smart_interval.js253
-rw-r--r--app/assets/javascripts/snippets_list.js18
-rw-r--r--app/assets/javascripts/star.js50
-rw-r--r--app/assets/javascripts/subscription.js74
-rw-r--r--app/assets/javascripts/subscription_select.js61
-rw-r--r--app/assets/javascripts/syntax_highlight.js27
-rw-r--r--app/assets/javascripts/task_list.js5
-rw-r--r--app/assets/javascripts/todos.js5
-rw-r--r--app/assets/javascripts/tree.js120
-rw-r--r--app/assets/javascripts/usage_ping.js5
-rw-r--r--app/assets/javascripts/user.js35
-rw-r--r--app/assets/javascripts/user_tabs.js175
-rw-r--r--app/assets/javascripts/username_validator.js214
-rw-r--r--app/assets/javascripts/users/activity_calendar.js227
-rw-r--r--app/assets/javascripts/users/calendar.js231
-rw-r--r--app/assets/javascripts/users/index.js7
-rw-r--r--app/assets/javascripts/users/user.js34
-rw-r--r--app/assets/javascripts/users/user_tabs.js173
-rw-r--r--app/assets/javascripts/users/users_bundle.js1
-rw-r--r--app/assets/javascripts/version_check_image.js3
-rw-r--r--app/assets/javascripts/visibility_select.js40
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js9
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue18
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js19
-rw-r--r--app/assets/javascripts/wikis.js95
-rw-r--r--app/assets/javascripts/zen_mode.js115
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework.scss88
-rw-r--r--app/assets/stylesheets/framework/avatar.scss6
-rw-r--r--app/assets/stylesheets/framework/awards.scss4
-rw-r--r--app/assets/stylesheets/framework/blank.scss67
-rw-r--r--app/assets/stylesheets/framework/buttons.scss18
-rw-r--r--app/assets/stylesheets/framework/calendar.scss2
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss93
-rw-r--r--app/assets/stylesheets/framework/files.scss10
-rw-r--r--app/assets/stylesheets/framework/filters.scss28
-rw-r--r--app/assets/stylesheets/framework/header.scss31
-rw-r--r--app/assets/stylesheets/framework/highlight.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss14
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss26
-rw-r--r--app/assets/stylesheets/framework/modal.scss6
-rw-r--r--app/assets/stylesheets/framework/nav.scss16
-rw-r--r--app/assets/stylesheets/framework/responsive-tables.scss2
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss10
-rw-r--r--app/assets/stylesheets/framework/typography.scss20
-rw-r--r--app/assets/stylesheets/framework/variables.scss59
-rw-r--r--app/assets/stylesheets/framework/wells.scss14
-rw-r--r--app/assets/stylesheets/highlight/white.scss8
-rw-r--r--app/assets/stylesheets/new_nav.scss123
-rw-r--r--app/assets/stylesheets/new_sidebar.scss204
-rw-r--r--app/assets/stylesheets/pages/boards.scss11
-rw-r--r--app/assets/stylesheets/pages/builds.scss129
-rw-r--r--app/assets/stylesheets/pages/commits.scss68
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss8
-rw-r--r--app/assets/stylesheets/pages/environments.scss34
-rw-r--r--app/assets/stylesheets/pages/issuable.scss38
-rw-r--r--app/assets/stylesheets/pages/members.scss112
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss14
-rw-r--r--app/assets/stylesheets/pages/note_form.scss51
-rw-r--r--app/assets/stylesheets/pages/notes.scss5
-rw-r--r--app/assets/stylesheets/pages/pipeline_schedules.scss83
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss3
-rw-r--r--app/assets/stylesheets/pages/profile.scss41
-rw-r--r--app/assets/stylesheets/pages/projects.scss9
-rw-r--r--app/assets/stylesheets/pages/status.scss21
-rw-r--r--app/assets/stylesheets/pages/todos.scss2
-rw-r--r--app/assets/stylesheets/pages/ui_dev_kit.scss8
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
-rw-r--r--app/assets/stylesheets/performance_bar.scss (renamed from vendor/assets/stylesheets/peek.scss)45
-rw-r--r--app/assets/stylesheets/print.scss2
-rw-r--r--app/controllers/admin/application_settings_controller.rb10
-rw-r--r--app/controllers/admin/dashboard_controller.rb2
-rw-r--r--app/controllers/admin/hook_logs_controller.rb4
-rw-r--r--app/controllers/admin/hooks_controller.rb15
-rw-r--r--app/controllers/admin/projects_controller.rb19
-rw-r--r--app/controllers/application_controller.rb21
-rw-r--r--app/controllers/autocomplete_controller.rb4
-rw-r--r--app/controllers/concerns/creates_commit.rb5
-rw-r--r--app/controllers/concerns/hooks_execution.rb13
-rw-r--r--app/controllers/concerns/issuable_collections.rb44
-rw-r--r--app/controllers/concerns/membership_actions.rb2
-rw-r--r--app/controllers/concerns/milestone_actions.rb2
-rw-r--r--app/controllers/concerns/repository_settings_redirect.rb2
-rw-r--r--app/controllers/concerns/requires_health_token.rb25
-rw-r--r--app/controllers/concerns/requires_whitelisted_monitoring_client.rb33
-rw-r--r--app/controllers/concerns/spammable_actions.rb10
-rw-r--r--app/controllers/concerns/with_performance_bar.rb17
-rw-r--r--app/controllers/dashboard/labels_controller.rb9
-rw-r--r--app/controllers/dashboard/todos_controller.rb10
-rw-r--r--app/controllers/groups/milestones_controller.rb77
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb24
-rw-r--r--app/controllers/groups/variables_controller.rb64
-rw-r--r--app/controllers/health_check_controller.rb2
-rw-r--r--app/controllers/health_controller.rb7
-rw-r--r--app/controllers/invites_controller.rb2
-rw-r--r--app/controllers/metrics_controller.rb4
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb8
-rw-r--r--app/controllers/passwords_controller.rb12
-rw-r--r--app/controllers/profiles/gpg_keys_controller.rb47
-rw-r--r--app/controllers/profiles/passwords_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb5
-rw-r--r--app/controllers/projects/artifacts_controller.rb2
-rw-r--r--app/controllers/projects/badges_controller.rb6
-rw-r--r--app/controllers/projects/blob_controller.rb16
-rw-r--r--app/controllers/projects/branches_controller.rb11
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb10
-rw-r--r--app/controllers/projects/builds_controller.rb6
-rw-r--r--app/controllers/projects/commit_controller.rb17
-rw-r--r--app/controllers/projects/commits_controller.rb40
-rw-r--r--app/controllers/projects/compare_controller.rb4
-rw-r--r--app/controllers/projects/environments_controller.rb8
-rw-r--r--app/controllers/projects/forks_controller.rb4
-rw-r--r--app/controllers/projects/graphs_controller.rb2
-rw-r--r--app/controllers/projects/group_links_controller.rb4
-rw-r--r--app/controllers/projects/hook_logs_controller.rb6
-rw-r--r--app/controllers/projects/hooks_controller.rb18
-rw-r--r--app/controllers/projects/imports_controller.rb12
-rw-r--r--app/controllers/projects/issues_controller.rb15
-rw-r--r--app/controllers/projects/jobs_controller.rb6
-rw-r--r--app/controllers/projects/labels_controller.rb17
-rw-r--r--app/controllers/projects/mattermosts_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb26
-rw-r--r--app/controllers/projects/milestones_controller.rb35
-rw-r--r--app/controllers/projects/network_controller.rb4
-rw-r--r--app/controllers/projects/pages_controller.rb2
-rw-r--r--app/controllers/projects/pages_domains_controller.rb4
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb21
-rw-r--r--app/controllers/projects/pipelines_controller.rb6
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb6
-rw-r--r--app/controllers/projects/project_members_controller.rb23
-rw-r--r--app/controllers/projects/refs_controller.rb18
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb4
-rw-r--r--app/controllers/projects/registry/tags_controller.rb4
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/controllers/projects/runners_controller.rb4
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb7
-rw-r--r--app/controllers/projects/settings/members_controller.rb27
-rw-r--r--app/controllers/projects/snippets_controller.rb8
-rw-r--r--app/controllers/projects/tags_controller.rb6
-rw-r--r--app/controllers/projects/tree_controller.rb6
-rw-r--r--app/controllers/projects/triggers_controller.rb13
-rw-r--r--app/controllers/projects/variables_controller.rb50
-rw-r--r--app/controllers/projects/wikis_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb9
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb12
-rw-r--r--app/controllers/snippets_controller.rb4
-rw-r--r--app/finders/admin/projects_finder.rb33
-rw-r--r--app/finders/concerns/created_at_filter.rb8
-rw-r--r--app/finders/issuable_finder.rb57
-rw-r--r--app/finders/issues_finder.rb12
-rw-r--r--app/finders/milestones_finder.rb60
-rw-r--r--app/finders/projects_finder.rb9
-rw-r--r--app/finders/users_finder.rb3
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/application_settings_helper.rb12
-rw-r--r--app/helpers/avatars_helper.rb9
-rw-r--r--app/helpers/award_emoji_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb16
-rw-r--r--app/helpers/boards_helper.rb6
-rw-r--r--app/helpers/branches_helper.rb2
-rw-r--r--app/helpers/breadcrumbs_helper.rb25
-rw-r--r--app/helpers/builds_helper.rb6
-rw-r--r--app/helpers/button_helper.rb4
-rw-r--r--app/helpers/ci_status_helper.rb14
-rw-r--r--app/helpers/commits_helper.rb19
-rw-r--r--app/helpers/compare_helper.rb3
-rw-r--r--app/helpers/diff_helper.rb14
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/environment_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/events_helper.rb23
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb153
-rw-r--r--app/helpers/hooks_helper.rb17
-rw-r--r--app/helpers/issuables_helper.rb67
-rw-r--r--app/helpers/issues_helper.rb28
-rw-r--r--app/helpers/labels_helper.rb10
-rw-r--r--app/helpers/lazy_image_tag_helper.rb24
-rw-r--r--app/helpers/merge_requests_helper.rb11
-rw-r--r--app/helpers/milestones_helper.rb26
-rw-r--r--app/helpers/nav_helper.rb1
-rw-r--r--app/helpers/notes_helper.rb13
-rw-r--r--app/helpers/page_layout_helper.rb4
-rw-r--r--app/helpers/performance_bar_helper.rb7
-rw-r--r--app/helpers/projects_helper.rb46
-rw-r--r--app/helpers/search_helper.rb24
-rw-r--r--app/helpers/snippets_helper.rb7
-rw-r--r--app/helpers/system_note_helper.rb3
-rw-r--r--app/helpers/tab_helper.rb3
-rw-r--r--app/helpers/tags_helper.rb2
-rw-r--r--app/helpers/todos_helper.rb2
-rw-r--r--app/helpers/triggers_helper.rb2
-rw-r--r--app/helpers/version_check_helper.rb2
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/merge_requests.rb4
-rw-r--r--app/mailers/emails/notes.rb10
-rw-r--r--app/mailers/emails/profile.rb12
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/models/appearance.rb2
-rw-r--r--app/models/application_setting.rb62
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/blob_viewer/readme.rb6
-rw-r--r--app/models/board.rb2
-rw-r--r--app/models/ci/build.rb52
-rw-r--r--app/models/ci/group_variable.rb13
-rw-r--r--app/models/ci/pipeline.rb23
-rw-r--r--app/models/ci/pipeline_schedule.rb12
-rw-r--r--app/models/ci/pipeline_schedule_variable.rb8
-rw-r--r--app/models/ci/runner.rb14
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb1
-rw-r--r--app/models/commit.rb37
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb4
-rw-r--r--app/models/concerns/created_at_filterable.rb12
-rw-r--r--app/models/concerns/each_batch.rb81
-rw-r--r--app/models/concerns/editable.rb4
-rw-r--r--app/models/concerns/internal_id.rb3
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/milestoneish.rb16
-rw-r--r--app/models/concerns/protected_ref.rb17
-rw-r--r--app/models/concerns/routable.rb4
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/concerns/subscribable.rb2
-rw-r--r--app/models/concerns/time_trackable.rb2
-rw-r--r--app/models/dashboard_milestone.rb4
-rw-r--r--app/models/deploy_key.rb2
-rw-r--r--app/models/diff_note.rb6
-rw-r--r--app/models/environment.rb5
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/global_label.rb2
-rw-r--r--app/models/global_milestone.rb47
-rw-r--r--app/models/gpg_key.rb107
-rw-r--r--app/models/gpg_signature.rb21
-rw-r--r--app/models/group.rb31
-rw-r--r--app/models/group_milestone.rb4
-rw-r--r--app/models/hooks/project_hook.rb25
-rw-r--r--app/models/hooks/service_hook.rb1
-rw-r--r--app/models/hooks/system_hook.rb10
-rw-r--r--app/models/hooks/web_hook.rb16
-rw-r--r--app/models/hooks/web_hook_log.rb6
-rw-r--r--app/models/issue.rb18
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/legacy_diff_note.rb2
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/merge_request.rb63
-rw-r--r--app/models/merge_request_diff.rb120
-rw-r--r--app/models/merge_request_diff_commit.rb38
-rw-r--r--app/models/merge_request_diff_file.rb10
-rw-r--r--app/models/milestone.rb74
-rw-r--r--app/models/namespace.rb9
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/project.rb216
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb16
-rw-r--r--app/models/project_services/issue_tracker_service.rb10
-rw-r--r--app/models/project_services/jira_service.rb26
-rw-r--r--app/models/project_services/kubernetes_service.rb33
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_wiki.rb6
-rw-r--r--app/models/repository.rb21
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/service.rb10
-rw-r--r--app/models/snippet.rb6
-rw-r--r--app/models/system_note_metadata.rb3
-rw-r--r--app/models/user.rb89
-rw-r--r--app/policies/ci/build_policy.rb14
-rw-r--r--app/policies/ci/pipeline_policy.rb12
-rw-r--r--app/policies/ci/pipeline_schedule_policy.rb10
-rw-r--r--app/policies/global_policy.rb2
-rw-r--r--app/policies/group_policy.rb2
-rw-r--r--app/policies/project_policy.rb8
-rw-r--r--app/presenters/ci/group_variable_presenter.rb25
-rw-r--r--app/presenters/ci/variable_presenter.rb25
-rw-r--r--app/presenters/merge_request_presenter.rb33
-rw-r--r--app/serializers/build_action_entity.rb5
-rw-r--r--app/serializers/build_artifact_entity.rb15
-rw-r--r--app/serializers/build_details_entity.rb11
-rw-r--r--app/serializers/commit_entity.rb10
-rw-r--r--app/serializers/deploy_key_entity.rb2
-rw-r--r--app/serializers/deployment_entity.rb5
-rw-r--r--app/serializers/environment_entity.rb20
-rw-r--r--app/serializers/issue_entity.rb2
-rw-r--r--app/serializers/label_entity.rb3
-rw-r--r--app/serializers/merge_request_entity.rb31
-rw-r--r--app/serializers/pipeline_entity.rb13
-rw-r--r--app/serializers/project_entity.rb2
-rw-r--r--app/serializers/runner_entity.rb2
-rw-r--r--app/serializers/stage_entity.rb6
-rw-r--r--app/services/access_token_validation_service.rb24
-rw-r--r--app/services/boards/create_service.rb19
-rw-r--r--app/services/boards/issues/list_service.rb5
-rw-r--r--app/services/chat_names/authorize_user_service.rb2
-rw-r--r--app/services/ci/create_pipeline_service.rb77
-rw-r--r--app/services/ci/create_trigger_request_service.rb8
-rw-r--r--app/services/git_operation_service.rb1
-rw-r--r--app/services/git_push_service.rb8
-rw-r--r--app/services/issuable_base_service.rb39
-rw-r--r--app/services/issues/base_service.rb8
-rw-r--r--app/services/issues/close_service.rb6
-rw-r--r--app/services/issues/duplicate_service.rb24
-rw-r--r--app/services/issues/move_service.rb14
-rw-r--r--app/services/issues/reopen_service.rb2
-rw-r--r--app/services/issues/update_service.rb18
-rw-r--r--app/services/merge_requests/close_service.rb2
-rw-r--r--app/services/merge_requests/create_service.rb3
-rw-r--r--app/services/merge_requests/get_urls_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/post_merge_service.rb2
-rw-r--r--app/services/merge_requests/refresh_service.rb11
-rw-r--r--app/services/merge_requests/reopen_service.rb2
-rw-r--r--app/services/metrics_service.rb7
-rw-r--r--app/services/milestones/base_service.rb6
-rw-r--r--app/services/milestones/close_service.rb2
-rw-r--r--app/services/milestones/create_service.rb4
-rw-r--r--app/services/milestones/destroy_service.rb6
-rw-r--r--app/services/milestones/reopen_service.rb2
-rw-r--r--app/services/milestones/update_service.rb4
-rw-r--r--app/services/notification_service.rb10
-rw-r--r--app/services/projects/destroy_service.rb72
-rw-r--r--app/services/projects/update_pages_service.rb6
-rw-r--r--app/services/projects/update_service.rb54
-rw-r--r--app/services/quick_actions/interpret_service.rb44
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/system_note_service.rb47
-rw-r--r--app/services/test_hook_service.rb6
-rw-r--r--app/services/test_hooks/base_service.rb41
-rw-r--r--app/services/test_hooks/project_service.rb63
-rw-r--r--app/services/test_hooks/system_service.rb48
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb2
-rw-r--r--app/services/web_hook_service.rb11
-rw-r--r--app/services/wiki_pages/base_service.rb16
-rw-r--r--app/uploaders/gitlab_uploader.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb4
-rw-r--r--app/validators/variable_duplicates_validator.rb13
-rw-r--r--app/views/admin/application_settings/_form.html.haml41
-rw-r--r--app/views/admin/applications/edit.html.haml1
-rw-r--r--app/views/admin/applications/new.html.haml2
-rw-r--r--app/views/admin/broadcast_messages/edit.html.haml1
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml1
-rw-r--r--app/views/admin/dashboard/index.html.haml322
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml8
-rw-r--r--app/views/admin/projects/_projects.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml10
-rw-r--r--app/views/admin/runners/index.html.haml33
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/admin/users/projects.html.haml4
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml16
-rw-r--r--app/views/ci/variables/_content.html.haml (renamed from app/views/projects/variables/_content.html.haml)0
-rw-r--r--app/views/ci/variables/_form.html.haml (renamed from app/views/projects/variables/_form.html.haml)6
-rw-r--r--app/views/ci/variables/_index.html.haml (renamed from app/views/projects/variables/_index.html.haml)10
-rw-r--r--app/views/ci/variables/_show.html.haml9
-rw-r--r--app/views/ci/variables/_table.html.haml (renamed from app/views/projects/variables/_table.html.haml)6
-rw-r--r--app/views/dashboard/_groups_head.html.haml9
-rw-r--r--app/views/dashboard/_projects_head.html.haml10
-rw-r--r--app/views/dashboard/_snippets_head.html.haml9
-rw-r--r--app/views/dashboard/issues.html.haml9
-rw-r--r--app/views/dashboard/merge_requests.html.haml7
-rw-r--r--app/views/dashboard/milestones/index.html.haml6
-rw-r--r--app/views/dashboard/projects/_blank_state_admin_welcome.html.haml33
-rw-r--r--app/views/dashboard/projects/_blank_state_welcome.html.haml48
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml59
-rw-r--r--app/views/dashboard/projects/index.html.haml1
-rw-r--r--app/views/dashboard/projects/starred.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml1
-rw-r--r--app/views/devise/sessions/new.html.haml6
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml5
-rw-r--r--app/views/devise/shared/_signin_box.html.haml4
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml4
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml2
-rw-r--r--app/views/discussions/_new_issue_for_all_discussions.html.haml2
-rw-r--r--app/views/discussions/_new_issue_for_discussion.html.haml2
-rw-r--r--app/views/events/_commit.html.haml2
-rw-r--r--app/views/events/_event_push.atom.haml2
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/explore/groups/index.html.haml1
-rw-r--r--app/views/explore/projects/index.html.haml1
-rw-r--r--app/views/explore/projects/starred.html.haml1
-rw-r--r--app/views/explore/projects/trending.html.haml1
-rw-r--r--app/views/explore/snippets/index.html.haml1
-rw-r--r--app/views/groups/_settings_head.html.haml5
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/issues.html.haml11
-rw-r--r--app/views/groups/labels/index.html.haml10
-rw-r--r--app/views/groups/labels/new.html.haml1
-rw-r--r--app/views/groups/merge_requests.html.haml6
-rw-r--r--app/views/groups/milestones/_form.html.haml27
-rw-r--r--app/views/groups/milestones/_milestone.html.haml3
-rw-r--r--app/views/groups/milestones/edit.html.haml7
-rw-r--r--app/views/groups/milestones/index.html.haml14
-rw-r--r--app/views/groups/milestones/new.html.haml39
-rw-r--r--app/views/groups/milestones/show.html.haml2
-rw-r--r--app/views/groups/new.html.haml3
-rw-r--r--app/views/groups/projects.html.haml4
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml4
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/groups/variables/show.html.haml1
-rw-r--r--app/views/help/_shortcuts.html.haml9
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/base/create.js.haml2
-rw-r--r--app/views/invites/show.html.haml2
-rw-r--r--app/views/issues/_issue.atom.builder4
-rw-r--r--app/views/layouts/_broadcast.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml12
-rw-r--r--app/views/layouts/_page.html.haml9
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml5
-rw-r--r--app/views/layouts/header/_default.html.haml10
-rw-r--r--app/views/layouts/header/_new.html.haml13
-rw-r--r--app/views/layouts/header/_new_dropdown.haml6
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml18
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml18
-rw-r--r--app/views/layouts/nav/_new_dashboard.html.haml2
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml39
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml16
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml92
-rw-r--r--app/views/layouts/nav/_profile.html.haml6
-rw-r--r--app/views/layouts/nav/_project.html.haml35
-rw-r--r--app/views/layouts/project.html.haml2
-rw-r--r--app/views/notify/closed_issue_email.text.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/issue_moved_email.html.haml2
-rw-r--r--app/views/notify/issue_moved_email.text.erb2
-rw-r--r--app/views/notify/issue_status_changed_email.text.erb2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/new_gpg_key_email.html.haml10
-rw-r--r--app/views/notify/new_gpg_key_email.text.erb7
-rw-r--r--app/views/notify/new_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_issue_email.text.erb2
-rw-r--r--app/views/notify/new_mention_in_merge_request_email.text.erb2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/notify/project_was_exported_email.html.haml2
-rw-r--r--app/views/notify/project_was_exported_email.text.erb2
-rw-r--r--app/views/notify/project_was_moved_email.html.haml2
-rw-r--r--app/views/notify/project_was_moved_email.text.erb2
-rw-r--r--app/views/notify/repository_push_email.html.haml4
-rw-r--r--app/views/notify/resolved_all_discussions_email.text.erb2
-rw-r--r--app/views/peek/views/_rblineprof.html.haml7
-rw-r--r--app/views/peek/views/_sql.html.haml8
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml2
-rw-r--r--app/views/profiles/gpg_keys/_email_with_badge.html.haml8
-rw-r--r--app/views/profiles/gpg_keys/_form.html.haml10
-rw-r--r--app/views/profiles/gpg_keys/_key.html.haml18
-rw-r--r--app/views/profiles/gpg_keys/_key_table.html.haml11
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml21
-rw-r--r--app/views/profiles/preferences/show.html.haml39
-rw-r--r--app/views/profiles/show.html.haml1
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml6
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/_deletion_failed.html.haml6
-rw-r--r--app/views/projects/_find_file_link.html.haml2
-rw-r--r--app/views/projects/_flash_messages.html.haml8
-rw-r--r--app/views/projects/_last_push.html.haml2
-rw-r--r--app/views/projects/_wiki.html.haml2
-rw-r--r--app/views/projects/activity.html.haml3
-rw-r--r--app/views/projects/artifacts/_tree_directory.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml16
-rw-r--r--app/views/projects/artifacts/file.html.haml6
-rw-r--r--app/views/projects/blame/show.html.haml6
-rw-r--r--app/views/projects/blob/_breadcrumb.html.haml14
-rw-r--r--app/views/projects/blob/_new_dir.html.haml2
-rw-r--r--app/views/projects/blob/_remove.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml9
-rw-r--r--app/views/projects/blob/new.html.haml5
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/blob/viewers/_changelog.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_image.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_readme.html.haml2
-rw-r--r--app/views/projects/boards/_show.html.haml5
-rw-r--r--app/views/projects/boards/components/sidebar/_assignee.html.haml2
-rw-r--r--app/views/projects/boards/components/sidebar/_due_date.html.haml2
-rw-r--r--app/views/projects/boards/components/sidebar/_labels.html.haml4
-rw-r--r--app/views/projects/boards/components/sidebar/_milestone.html.haml4
-rw-r--r--app/views/projects/boards/components/sidebar/_notifications.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml8
-rw-r--r--app/views/projects/branches/_commit.html.haml4
-rw-r--r--app/views/projects/branches/index.html.haml14
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/buttons/_download.html.haml10
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml18
-rw-r--r--app/views/projects/buttons/_fork.html.haml6
-rw-r--r--app/views/projects/buttons/_star.html.haml4
-rw-r--r--app/views/projects/ci/builds/_build.html.haml14
-rw-r--r--app/views/projects/commit/_ajax_signature.html.haml3
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml6
-rw-r--r--app/views/projects/commit/_commit_box.html.haml25
-rw-r--r--app/views/projects/commit/_invalid_signature_badge.html.haml9
-rw-r--r--app/views/projects/commit/_signature.html.haml5
-rw-r--r--app/views/projects/commit/_signature_badge.html.haml18
-rw-r--r--app/views/projects/commit/_valid_signature_badge.html.haml32
-rw-r--r--app/views/projects/commit/pipelines.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml1
-rw-r--r--app/views/projects/commits/_commit.atom.builder4
-rw-r--r--app/views/projects/commits/_commit.html.haml14
-rw-r--r--app/views/projects/commits/_commits.html.haml4
-rw-r--r--app/views/projects/commits/_head.html.haml16
-rw-r--r--app/views/projects/commits/_inline_commit.html.haml4
-rw-r--r--app/views/projects/commits/show.atom.builder6
-rw-r--r--app/views/projects/commits/show.html.haml12
-rw-r--r--app/views/projects/compare/_form.html.haml8
-rw-r--r--app/views/projects/compare/index.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml3
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml6
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/deploy_keys/edit.html.haml2
-rw-r--r--app/views/projects/deployments/_commit.html.haml4
-rw-r--r--app/views/projects/diffs/_warning.html.haml4
-rw-r--r--app/views/projects/diffs/viewers/_image.html.haml18
-rw-r--r--app/views/projects/edit.html.haml20
-rw-r--r--app/views/projects/empty.html.haml5
-rw-r--r--app/views/projects/environments/_form.html.haml2
-rw-r--r--app/views/projects/environments/_stop.html.haml2
-rw-r--r--app/views/projects/environments/_terminal_button.html.haml2
-rw-r--r--app/views/projects/environments/index.html.haml5
-rw-r--r--app/views/projects/environments/metrics.html.haml8
-rw-r--r--app/views/projects/environments/new.html.haml1
-rw-r--r--app/views/projects/environments/show.html.haml4
-rw-r--r--app/views/projects/environments/terminal.html.haml2
-rw-r--r--app/views/projects/find_file/show.html.haml8
-rw-r--r--app/views/projects/forks/error.html.haml2
-rw-r--r--app/views/projects/forks/index.html.haml2
-rw-r--r--app/views/projects/forks/new.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml6
-rw-r--r--app/views/projects/graphs/charts.html.haml2
-rw-r--r--app/views/projects/graphs/show.html.haml6
-rw-r--r--app/views/projects/hook_logs/_index.html.haml2
-rw-r--r--app/views/projects/hook_logs/show.html.haml2
-rw-r--r--app/views/projects/hooks/edit.html.haml6
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/_head.html.haml10
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/_issue_by_email.html.haml2
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml13
-rw-r--r--app/views/projects/issues/_new_branch.html.haml2
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder4
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/new.html.haml1
-rw-r--r--app/views/projects/issues/show.html.haml33
-rw-r--r--app/views/projects/jobs/_header.html.haml10
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml22
-rw-r--r--app/views/projects/jobs/index.html.haml5
-rw-r--r--app/views/projects/jobs/show.html.haml21
-rw-r--r--app/views/projects/labels/edit.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml22
-rw-r--r--app/views/projects/labels/new.html.haml3
-rw-r--r--app/views/projects/mattermosts/_no_teams.html.haml2
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml4
-rw-r--r--app/views/projects/merge_requests/_head.html.haml6
-rw-r--r--app/views/projects/merge_requests/_how_to_merge.html.haml14
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml33
-rw-r--r--app/views/projects/merge_requests/_pipelines.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml4
-rw-r--r--app/views/projects/merge_requests/conflicts/_submit_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml11
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml7
-rw-r--r--app/views/projects/merge_requests/creations/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/diffs/_versions.html.haml4
-rw-r--r--app/views/projects/merge_requests/index.html.haml4
-rw-r--r--app/views/projects/merge_requests/show.html.haml27
-rw-r--r--app/views/projects/milestones/_form.html.haml4
-rw-r--r--app/views/projects/milestones/_milestone.html.haml6
-rw-r--r--app/views/projects/milestones/index.html.haml9
-rw-r--r--app/views/projects/milestones/new.html.haml1
-rw-r--r--app/views/projects/milestones/show.html.haml8
-rw-r--r--app/views/projects/network/show.html.haml5
-rw-r--r--app/views/projects/new.html.haml10
-rw-r--r--app/views/projects/no_repo.html.haml4
-rw-r--r--app/views/projects/pages/_destroy.haml2
-rw-r--r--app/views/projects/pages/_list.html.haml4
-rw-r--r--app/views/projects/pages/show.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_form.html.haml8
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml6
-rw-r--r--app/views/projects/pipeline_schedules/_variable_row.html.haml17
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml16
-rw-r--r--app/views/projects/pipeline_schedules/new.html.haml5
-rw-r--r--app/views/projects/pipelines/_head.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml6
-rw-r--r--app/views/projects/pipelines/charts.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml4
-rw-r--r--app/views/projects/pipelines/new.html.haml5
-rw-r--r--app/views/projects/pipelines/show.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml10
-rw-r--r--app/views/projects/project_members/_group_members.html.haml18
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_new_shared_group.html.haml2
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml24
-rw-r--r--app/views/projects/project_members/_team.html.haml4
-rw-r--r--app/views/projects/project_members/import.html.haml4
-rw-r--r--app/views/projects/project_members/index.html.haml (renamed from app/views/projects/project_members/_index.html.haml)10
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml30
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml53
-rw-r--r--app/views/projects/protected_branches/_index.html.haml26
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml22
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml30
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml33
-rw-r--r--app/views/projects/protected_branches/shared/_dropdown.html.haml (renamed from app/views/projects/protected_branches/_dropdown.html.haml)0
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml24
-rw-r--r--app/views/projects/protected_branches/shared/_matching_branch.html.haml (renamed from app/views/projects/protected_branches/_matching_branch.html.haml)2
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml24
-rw-r--r--app/views/projects/protected_branches/show.html.haml2
-rw-r--r--app/views/projects/protected_tags/_create_protected_tag.html.haml38
-rw-r--r--app/views/projects/protected_tags/_index.html.haml26
-rw-r--r--app/views/projects/protected_tags/_protected_tag.html.haml22
-rw-r--r--app/views/projects/protected_tags/_tags_list.html.haml32
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml29
-rw-r--r--app/views/projects/protected_tags/shared/_dropdown.html.haml (renamed from app/views/projects/protected_tags/_dropdown.html.haml)0
-rw-r--r--app/views/projects/protected_tags/shared/_index.html.haml24
-rw-r--r--app/views/projects/protected_tags/shared/_matching_tag.html.haml (renamed from app/views/projects/protected_tags/_matching_tag.html.haml)2
-rw-r--r--app/views/projects/protected_tags/shared/_protected_tag.html.haml22
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml30
-rw-r--r--app/views/projects/protected_tags/show.html.haml2
-rw-r--r--app/views/projects/registry/repositories/_image.html.haml3
-rw-r--r--app/views/projects/registry/repositories/_tag.html.haml2
-rw-r--r--app/views/projects/releases/edit.html.haml4
-rw-r--r--app/views/projects/remove_fork.js.haml2
-rw-r--r--app/views/projects/repositories/_feed.html.haml4
-rw-r--r--app/views/projects/runners/_runner.html.haml4
-rw-r--r--app/views/projects/runners/_shared_runners.html.haml4
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml19
-rw-r--r--app/views/projects/services/_form.html.haml25
-rw-r--r--app/views/projects/services/_index.html.haml2
-rw-r--r--app/views/projects/services/edit.html.haml5
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml6
-rw-r--r--app/views/projects/settings/_head.html.haml10
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml6
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml14
-rw-r--r--app/views/projects/settings/integrations/show.html.haml2
-rw-r--r--app/views/projects/settings/repository/show.html.haml8
-rw-r--r--app/views/projects/show.atom.builder6
-rw-r--r--app/views/projects/show.html.haml17
-rw-r--r--app/views/projects/snippets/_actions.html.haml16
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml15
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml6
-rw-r--r--app/views/projects/tags/index.html.haml5
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/tags/show.html.haml8
-rw-r--r--app/views/projects/transfer.js.haml2
-rw-r--r--app/views/projects/tree/_blob_item.html.haml2
-rw-r--r--app/views/projects/tree/_readme.html.haml4
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml4
-rw-r--r--app/views/projects/tree/_tree_header.html.haml20
-rw-r--r--app/views/projects/tree/_tree_item.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml9
-rw-r--r--app/views/projects/triggers/_trigger.html.haml6
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/variables/show.html.haml10
-rw-r--r--app/views/projects/wikis/_form.html.haml6
-rw-r--r--app/views/projects/wikis/_main_links.html.haml4
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml4
-rw-r--r--app/views/projects/wikis/_sidebar_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml6
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml3
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/shared/_label.html.haml6
-rw-r--r--app/views/shared/_label_row.html.haml2
-rw-r--r--app/views/shared/_logo_type.svg1
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml2
-rw-r--r--app/views/shared/_mr_head.html.haml2
-rw-r--r--app/views/shared/_new_project_item_select.html.haml15
-rw-r--r--app/views/shared/_personal_access_tokens_table.html.haml19
-rw-r--r--app/views/shared/_ref_switcher.html.haml4
-rw-r--r--app/views/shared/_service_settings.html.haml9
-rw-r--r--app/views/shared/empty_states/_issues.html.haml3
-rw-r--r--app/views/shared/empty_states/_labels.html.haml4
-rw-r--r--app/views/shared/icons/_add_new_group.svg8
-rw-r--r--app/views/shared/icons/_add_new_project.svg1
-rw-r--r--app/views/shared/icons/_add_new_user.svg9
-rw-r--r--app/views/shared/icons/_configure_server.svg8
-rw-r--r--app/views/shared/icons/_globe.svg1
-rw-r--r--app/views/shared/icons/_icon_clone.svg3
-rw-r--r--app/views/shared/icons/_icon_status_notfound_borderless.svg1
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml6
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml14
-rw-r--r--app/views/shared/issuable/_close_reopen_report_toggle.html.haml49
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml2
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml4
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/members/_group.html.haml4
-rw-r--r--app/views/shared/members/_member.html.haml80
-rw-r--r--app/views/shared/members/_requests.html.haml8
-rw-r--r--app/views/shared/milestones/_issuable.html.haml11
-rw-r--r--app/views/shared/milestones/_milestone.html.haml41
-rw-r--r--app/views/shared/milestones/_participants_tab.html.haml4
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml6
-rw-r--r--app/views/shared/milestones/_tabs.html.haml11
-rw-r--r--app/views/shared/milestones/_top.html.haml64
-rw-r--r--app/views/shared/notes/_comment_button.html.haml10
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/shared/snippets/_snippet.html.haml2
-rw-r--r--app/views/shared/web_hooks/_test_button.html.haml12
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/views/snippets/show.html.haml1
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/background_migration_worker.rb20
-rw-r--r--app/workers/create_gpg_signature_worker.rb16
-rw-r--r--app/workers/expire_job_cache_worker.rb12
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb28
-rw-r--r--app/workers/invalid_gpg_signature_update_worker.rb12
-rw-r--r--app/workers/pipeline_schedule_worker.rb13
-rw-r--r--app/workers/project_destroy_worker.rb9
-rw-r--r--app/workers/project_service_worker.rb2
-rw-r--r--app/workers/web_hook_worker.rb2
-rw-r--r--changelogs/unreleased/10085-stop-encoding-user-name.yml4
-rw-r--r--changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml4
-rw-r--r--changelogs/unreleased/12200-add-french-translation.yml4
-rw-r--r--changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md4
-rw-r--r--changelogs/unreleased/13336-multiple-broadcast-messages.yml4
-rw-r--r--changelogs/unreleased/19629-remove-inactive-tokens-list.yml4
-rw-r--r--changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml4
-rw-r--r--changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-spinach.yml4
-rw-r--r--changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml4
-rw-r--r--changelogs/unreleased/23998-blame-age-map.yml4
-rw-r--r--changelogs/unreleased/25102-files-view-button.yml4
-rw-r--r--changelogs/unreleased/25164-disable-fork-on-project-limit.yml4
-rw-r--r--changelogs/unreleased/26125-match-username-on-search.yml5
-rw-r--r--changelogs/unreleased/26212-upload-user-avatar-trough-api.yml4
-rw-r--r--changelogs/unreleased/26372-duplicate-issue-slash-command.yml4
-rw-r--r--changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml5
-rw-r--r--changelogs/unreleased/27586-center-dropdown.yml4
-rw-r--r--changelogs/unreleased/27645-html-email-brackets-bug.yml4
-rw-r--r--changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml4
-rw-r--r--changelogs/unreleased/28139-use-color-input-broadcast-messages.yml4
-rw-r--r--changelogs/unreleased/28202_decrease_abc_threshold_step2.yml4
-rw-r--r--changelogs/unreleased/28717-support-additional-prometheus-metrics.yml4
-rw-r--r--changelogs/unreleased/29289-project-destroy-clean-up-after-failure.yml4
-rw-r--r--changelogs/unreleased/2971-multiproject-grah-ce-port.yml4
-rw-r--r--changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml4
-rw-r--r--changelogs/unreleased/30213-project-transfer-move-rollback.yml4
-rw-r--r--changelogs/unreleased/30634-protected-pipeline.yml5
-rw-r--r--changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml4
-rw-r--r--changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml4
-rw-r--r--changelogs/unreleased/31129-jira-project-key-elim.yml4
-rw-r--r--changelogs/unreleased/31397-job-detail-real-time.yml4
-rw-r--r--changelogs/unreleased/31415-responsive-pipelines-table-2.yml4
-rw-r--r--changelogs/unreleased/31533-usage-data-projects-stats.yml4
-rw-r--r--changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml5
-rw-r--r--changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml4
-rw-r--r--changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml4
-rw-r--r--changelogs/unreleased/32301-filter-archive-project-on-param-present.yml4
-rw-r--r--changelogs/unreleased/32470-pag-links.yml4
-rw-r--r--changelogs/unreleased/32517-disable-hover-state.yml5
-rw-r--r--changelogs/unreleased/32834-task-note-only.yml4
-rw-r--r--changelogs/unreleased/32885-unintentionally-removing-branch-when-merging-merge-request.yml4
-rw-r--r--changelogs/unreleased/33003-avatar-in-project-api.yml4
-rw-r--r--changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml4
-rw-r--r--changelogs/unreleased/33097-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/33130-remove-group-modal.yml4
-rw-r--r--changelogs/unreleased/33132-change-icon-color.yml4
-rw-r--r--changelogs/unreleased/33208-singup-active-state-underline.yml4
-rw-r--r--changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml4
-rw-r--r--changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml4
-rw-r--r--changelogs/unreleased/33461-display-user-id.yml4
-rw-r--r--changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml4
-rw-r--r--changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml4
-rw-r--r--changelogs/unreleased/33601-add-csrf-token-verification-to-api.yml4
-rw-r--r--changelogs/unreleased/33741-clarify-k8s-service-keys.yml5
-rw-r--r--changelogs/unreleased/33770-respect-blockquote-line-breaks.yml4
-rw-r--r--changelogs/unreleased/33837-remove-trash-on-registry-image.yml4
-rw-r--r--changelogs/unreleased/33846-no-runner-for-admin.yml4
-rw-r--r--changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml4
-rw-r--r--changelogs/unreleased/34075-pipelines-count-mt.yml5
-rw-r--r--changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml4
-rw-r--r--changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml4
-rw-r--r--changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml4
-rw-r--r--changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml4
-rw-r--r--changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml4
-rw-r--r--changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml4
-rw-r--r--changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml4
-rw-r--r--changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml4
-rw-r--r--changelogs/unreleased/34309-drop-gfm-mr-ms.yml4
-rw-r--r--changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml4
-rw-r--r--changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml4
-rw-r--r--changelogs/unreleased/34534-update-vue-resource.yml4
-rw-r--r--changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml4
-rw-r--r--changelogs/unreleased/34563-usage-ping-github.yml4
-rw-r--r--changelogs/unreleased/34729-blob.yml4
-rw-r--r--changelogs/unreleased/34810-vue-pagination.yml4
-rw-r--r--changelogs/unreleased/34831-remove-coffee-rails-gem.yml4
-rw-r--r--changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml4
-rw-r--r--changelogs/unreleased/34867-remove-net-ssh-gem.yml4
-rw-r--r--changelogs/unreleased/34927-protect-manual-actions-on-tags.yml4
-rw-r--r--changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml4
-rw-r--r--changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml4
-rw-r--r--changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml4
-rw-r--r--changelogs/unreleased/35164-cycle-analytics-firefox.yml4
-rw-r--r--changelogs/unreleased/35181-cannot-create-label-from-board-page.yml4
-rw-r--r--changelogs/unreleased/35191-prioritized-labels-for-non-member.yml4
-rw-r--r--changelogs/unreleased/35204-doc-api-ci-lint-typo.yml4
-rw-r--r--changelogs/unreleased/35225-transient-poll.yml4
-rw-r--r--changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml4
-rw-r--r--changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml4
-rw-r--r--changelogs/unreleased/35391-fix-star-i18n-in-js.yml4
-rw-r--r--changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml4
-rw-r--r--changelogs/unreleased/35478-allow-admin-to-read-user-list.yml4
-rw-r--r--changelogs/unreleased/35539-can-t-create-a-merge-request-containing-a-binary-file-with-non-utf-8-characters.yml5
-rw-r--r--changelogs/unreleased/5971-webhook-testing.yml4
-rw-r--r--changelogs/unreleased/adam-external-issue-references-spike.yml4
-rw-r--r--changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml6
-rw-r--r--changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml4
-rw-r--r--changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml4
-rw-r--r--changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml4
-rw-r--r--changelogs/unreleased/bump-omniauth-ldap-gem-version.yml4
-rw-r--r--changelogs/unreleased/bvl-free-unused-names.yml5
-rw-r--r--changelogs/unreleased/bvl-rename-all-reserved-paths.yml4
-rw-r--r--changelogs/unreleased/commit-comments-limited-width.yml4
-rw-r--r--changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml4
-rw-r--r--changelogs/unreleased/dm-blob-binaryness-change.yml5
-rw-r--r--changelogs/unreleased/dm-commit-row-browse-button.yml4
-rw-r--r--changelogs/unreleased/dm-diff-viewers.yml4
-rw-r--r--changelogs/unreleased/dm-empty-state-new-merge-request.yml5
-rw-r--r--changelogs/unreleased/dm-group-page-name.yml4
-rw-r--r--changelogs/unreleased/dm-mail-room-check-without-omnibus.yml4
-rw-r--r--changelogs/unreleased/dm-page-image-size.yml4
-rw-r--r--changelogs/unreleased/dm-target-branch-slash-command-desc.yml4
-rw-r--r--changelogs/unreleased/dm-unnecessary-top-padding.yml4
-rw-r--r--changelogs/unreleased/doc-gitaly-network.yml4
-rw-r--r--changelogs/unreleased/dt-printing-to-api.yml4
-rw-r--r--changelogs/unreleased/dz-fix-calendar-today.yml4
-rw-r--r--changelogs/unreleased/enable-scss-lint-bang-format.yml4
-rw-r--r--changelogs/unreleased/enable-scss-lint-declaration-order.yml (renamed from changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml)6
-rw-r--r--changelogs/unreleased/enable-scss-lint-import-path.yml4
-rw-r--r--changelogs/unreleased/enable-scss-lint-property-spelling.yml4
-rw-r--r--changelogs/unreleased/enable-scss-lint-space-after-comma.yml4
-rw-r--r--changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml4
-rw-r--r--changelogs/unreleased/enable-webpack-code-splitting.yml5
-rw-r--r--changelogs/unreleased/feature-add-support-for-services-configuration.yml4
-rw-r--r--changelogs/unreleased/feature-backup-custom-path.yml4
-rw-r--r--changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml4
-rw-r--r--changelogs/unreleased/feature-gpg-signed-commits.yml4
-rw-r--r--changelogs/unreleased/feature-unify-email-layouts.yml4
-rw-r--r--changelogs/unreleased/fix-2801.yml4
-rw-r--r--changelogs/unreleased/fix-33991.yml4
-rw-r--r--changelogs/unreleased/fix-assigned-issuable-lists.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml4
-rw-r--r--changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml4
-rw-r--r--changelogs/unreleased/fix-gb-handle-max-pages-artifacts-size-correctly.yml4
-rw-r--r--changelogs/unreleased/fix-overflow-slash-commands.yml4
-rw-r--r--changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml4
-rw-r--r--changelogs/unreleased/fix-u2f-for-opera.yml4
-rw-r--r--changelogs/unreleased/fix_docs_commits_multiple_files.yml5
-rw-r--r--changelogs/unreleased/fixes-for-internal-auth-disabled.yml4
-rw-r--r--changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml4
-rw-r--r--changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml4
-rw-r--r--changelogs/unreleased/help-landing-page-customizations.yml4
-rw-r--r--changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml4
-rw-r--r--changelogs/unreleased/issue_20900.yml4
-rw-r--r--changelogs/unreleased/issue_33205.yml4
-rw-r--r--changelogs/unreleased/issueable-list-cleanup.yml4
-rw-r--r--changelogs/unreleased/karma-headless-chrome.yml4
-rw-r--r--changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml4
-rw-r--r--changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml4
-rw-r--r--changelogs/unreleased/monitoring-dashboard-fix-y-label.yml4
-rw-r--r--changelogs/unreleased/moved-submodules.yml4
-rw-r--r--changelogs/unreleased/mr-branch-link-use-tree.yml4
-rw-r--r--changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml4
-rw-r--r--changelogs/unreleased/new-navigation-custom-logo.yml4
-rw-r--r--changelogs/unreleased/pass-before-script-as-is.yml4
-rw-r--r--changelogs/unreleased/pat-alert-when-signin-disabled.yml4
-rw-r--r--changelogs/unreleased/pat-msg-on-auth-failure.yml4
-rw-r--r--changelogs/unreleased/pawel-ensure_temp_files_are_deleted_in_fs_metrics-35457.yml4
-rw-r--r--changelogs/unreleased/pawel-prometheus_client_pid_reuse_error.yml4
-rw-r--r--changelogs/unreleased/polish-sidebar-toggle.yml4
-rw-r--r--changelogs/unreleased/post-upload-pack-opt-out.yml4
-rw-r--r--changelogs/unreleased/project-readme-limited-width.yml4
-rw-r--r--changelogs/unreleased/rc-fix-branches-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/remove-nprogress-gleaning.yml4
-rw-r--r--changelogs/unreleased/replace_spinach_spec_browse_files.yml4
-rw-r--r--changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml4
-rw-r--r--changelogs/unreleased/replase_spinach_spec_create-feature.yml4
-rw-r--r--changelogs/unreleased/request-store-wrap.yml4
-rw-r--r--changelogs/unreleased/sh-allow-force-repo-create.yml4
-rw-r--r--changelogs/unreleased/sh-bump-oauth2-gem.yml4
-rw-r--r--changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml4
-rw-r--r--changelogs/unreleased/sh-optimize-project-commit-api.yml4
-rw-r--r--changelogs/unreleased/sh-structured-logging.yml4
-rw-r--r--changelogs/unreleased/speed-up-graphs.yml4
-rw-r--r--changelogs/unreleased/speed-up-issue-counting-for-a-project.yml5
-rw-r--r--changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml5
-rw-r--r--changelogs/unreleased/tc-issue-api-assignee.yml4
-rw-r--r--changelogs/unreleased/tc-link-to-commit-on-help-page.yml4
-rw-r--r--changelogs/unreleased/tc-no-delete-protected-merged-branches.yml4
-rw-r--r--changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml4
-rw-r--r--changelogs/unreleased/toggle-new-project-import-description.yml4
-rw-r--r--changelogs/unreleased/update_bootsnap_1-1-1.yml4
-rw-r--r--changelogs/unreleased/zj-commit-status-sortable-name.yml4
-rw-r--r--changelogs/unreleased/zj-faster-charts-page.yml4
-rw-r--r--changelogs/unreleased/zj-pipeline-badge-improvements.yml4
-rw-r--r--changelogs/unreleased/zj-review-apps-usage-data.yml4
-rw-r--r--changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml4
-rw-r--r--config/README.md130
-rw-r--r--config/application.rb41
-rw-r--r--config/boot.rb11
-rw-r--r--config/database.yml.mysql1
-rw-r--r--config/database.yml.postgresql1
-rw-r--r--config/environments/test.rb5
-rw-r--r--config/gitlab.yml.example115
-rw-r--r--config/initializers/1_settings.rb32
-rw-r--r--config/initializers/7_prometheus_metrics.rb12
-rw-r--r--config/initializers/7_redis.rb11
-rw-r--r--config/initializers/8_gitaly.rb8
-rw-r--r--config/initializers/8_metrics.rb16
-rw-r--r--config/initializers/doorkeeper.rb4
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb2
-rw-r--r--config/initializers/flipper.rb8
-rw-r--r--config/initializers/gettext_rails_i18n_patch.rb1
-rw-r--r--config/initializers/grape_route_helpers_fix.rb35
-rw-r--r--config/initializers/lograge.rb21
-rw-r--r--config/initializers/mysql_set_length_for_binary_indexes.rb21
-rw-r--r--config/initializers/omniauth.rb2
-rw-r--r--config/initializers/peek.rb6
-rw-r--r--config/initializers/relative_naming_ci_namespace.rb4
-rw-r--r--config/initializers/session_store.rb6
-rw-r--r--config/initializers/sidekiq.rb12
-rw-r--r--config/mail_room.yml4
-rw-r--r--config/prometheus/additional_metrics.yml107
-rw-r--r--config/redis.cache.yml.example38
-rw-r--r--config/redis.queues.yml.example38
-rw-r--r--config/redis.shared_state.yml.example38
-rw-r--r--config/routes/api.rb2
-rw-r--r--config/routes/group.rb10
-rw-r--r--config/routes/legacy_builds.rb22
-rw-r--r--config/routes/profile.rb5
-rw-r--r--config/routes/project.rb11
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/routes/uploads.rb8
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/unicorn.rb.example4
-rw-r--r--config/webpack.config.js7
-rw-r--r--db/migrate/20160804142904_add_ci_config_file_to_project.rb11
-rw-r--r--db/migrate/20170222111732_create_gpg_keys.rb19
-rw-r--r--db/migrate/20170428064307_add_column_delete_error_to_projects.rb7
-rw-r--r--db/migrate/20170525130346_create_group_variables_table.rb23
-rw-r--r--db/migrate/20170525130758_add_foreign_key_to_group_variables.rb15
-rw-r--r--db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb187
-rw-r--r--db/migrate/20170613154149_create_gpg_signatures.rb23
-rw-r--r--db/migrate/20170616133147_create_merge_request_diff_commits.rb20
-rw-r--r--db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb25
-rw-r--r--db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb15
-rw-r--r--db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb40
-rw-r--r--db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb30
-rw-r--r--db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb15
-rw-r--r--db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb35
-rw-r--r--db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb9
-rw-r--r--db/migrate/20170707183807_add_group_id_to_milestones.rb20
-rw-r--r--db/migrate/20170707184243_add_group_milestone_id_indexes.rb21
-rw-r--r--db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb10
-rw-r--r--db/migrate/20170710083355_clean_stage_id_reference_migration.rb18
-rw-r--r--db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb45
-rw-r--r--db/migrate/20170717074009_move_system_upload_folder.rb60
-rw-r--r--db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb34
-rw-r--r--db/migrate/20170725145659_add_binary_to_merge_request_diff_files.rb9
-rw-r--r--db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb4
-rw-r--r--db/post_migrate/20170406111121_clean_upload_symlinks.rb2
-rw-r--r--db/post_migrate/20170612071012_move_personal_snippets_files.rb91
-rw-r--r--db/post_migrate/20170613111224_clean_appearance_symlinks.rb52
-rw-r--r--db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb14
-rw-r--r--db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb33
-rw-r--r--db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb15
-rw-r--r--db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb40
-rw-r--r--db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb20
-rw-r--r--db/schema.rb131
-rw-r--r--doc/README.md14
-rw-r--r--doc/administration/auth/authentiq.md9
-rw-r--r--doc/administration/auth/ldap.md70
-rw-r--r--doc/administration/container_registry.md31
-rw-r--r--doc/administration/gitaly/index.md41
-rw-r--r--doc/administration/high_availability/database.md7
-rw-r--r--doc/administration/high_availability/gitlab.md6
-rw-r--r--doc/administration/high_availability/redis_source.md5
-rw-r--r--doc/administration/monitoring/ip_whitelist.md39
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin0 -> 186116 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_configuration_settings.pngbin0 -> 20385 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_line_profiling.pngbin0 -> 161313 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_sql_queries.pngbin0 -> 165124 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md35
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md66
-rw-r--r--doc/administration/monitoring/prometheus/index.md3
-rw-r--r--doc/administration/operations/cleaning_up_redis_sessions.md6
-rw-r--r--doc/api/README.md17
-rw-r--r--doc/api/ci/lint.md2
-rw-r--r--doc/api/features.md5
-rw-r--r--doc/api/group_milestones.md120
-rw-r--r--doc/api/issues.md83
-rw-r--r--doc/api/merge_requests.md1
-rw-r--r--doc/api/oauth2.md116
-rw-r--r--doc/api/project_snippets.md32
-rw-r--r--doc/api/projects.md260
-rw-r--r--doc/api/settings.md6
-rw-r--r--doc/api/snippets.md32
-rw-r--r--doc/api/users.md8
-rw-r--r--doc/api/v3_to_v4.md8
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md5
-rw-r--r--doc/articles/index.md21
-rw-r--r--doc/ci/environments.md3
-rw-r--r--doc/ci/img/environments_monitoring.pngbin94408 -> 243491 bytes
-rw-r--r--doc/ci/variables/README.md43
-rw-r--r--doc/ci/yaml/README.md73
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/background_migrations.md53
-rw-r--r--doc/development/code_review.md49
-rw-r--r--doc/development/doc_styleguide.md8
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md2
-rw-r--r--doc/development/fe_guide/performance.md13
-rw-r--r--doc/development/fe_guide/style_guide_js.md1
-rw-r--r--doc/development/fe_guide/vue.md119
-rw-r--r--doc/development/feature_flags.md18
-rw-r--r--doc/development/gotchas.md29
-rw-r--r--doc/development/iterating_tables_in_batches.md37
-rw-r--r--doc/development/rake_tasks.md50
-rw-r--r--doc/development/sidekiq_style_guide.md7
-rw-r--r--doc/development/testing.md6
-rw-r--r--doc/downgrade_ee_to_ce/README.md13
-rw-r--r--doc/gitlab-basics/README.md2
-rw-r--r--doc/gitlab-basics/create-group.md50
-rw-r--r--doc/gitlab-basics/img/create_new_group_sidebar.pngbin2682 -> 0 bytes
-rw-r--r--doc/install/database_mysql.md15
-rw-r--r--doc/install/google_cloud_platform/index.md8
-rw-r--r--doc/install/installation.md12
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md53
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/integration/README.md20
-rw-r--r--doc/integration/external-issue-tracker.md6
-rw-r--r--doc/integration/img/enable_trello_powerup.pngbin0 -> 17905 bytes
-rw-r--r--doc/integration/img/trello_card_with_gitlab_powerup.pngbin0 -> 18667 bytes
-rw-r--r--doc/integration/trello_power_up.md42
-rw-r--r--doc/raketasks/backup_restore.md9
-rw-r--r--doc/university/process/README.md2
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--doc/update/9.2-to-9.3.md30
-rw-r--r--doc/update/9.3-to-9.4.md50
-rw-r--r--doc/user/admin_area/monitoring/health_check.md130
-rw-r--r--doc/user/group/img/access_requests_management.png (renamed from doc/workflow/groups/access_requests_management.png)bin11186 -> 11186 bytes
-rw-r--r--doc/user/group/img/add_new_members.pngbin0 -> 67235 bytes
-rw-r--r--doc/user/group/img/create_new_group_info.png (renamed from doc/gitlab-basics/img/create_new_group_info.png)bin105173 -> 105173 bytes
-rw-r--r--doc/user/group/img/create_new_project_from_group.png (renamed from doc/gitlab-basics/img/create_new_project_from_group.png)bin3194 -> 3194 bytes
-rw-r--r--doc/user/group/img/group_settings.pngbin0 -> 28821 bytes
-rw-r--r--doc/user/group/img/groups.pngbin0 -> 202498 bytes
-rw-r--r--doc/user/group/img/membership_lock.pngbin0 -> 17333 bytes
-rw-r--r--doc/user/group/img/new_group_form.png (renamed from doc/workflow/groups/new_group_form.png)bin114515 -> 114515 bytes
-rw-r--r--doc/user/group/img/new_group_from_groups.pngbin0 -> 97271 bytes
-rw-r--r--doc/user/group/img/new_group_from_other_pages.pngbin0 -> 70899 bytes
-rw-r--r--doc/user/group/img/request_access_button.png (renamed from doc/workflow/groups/request_access_button.png)bin35917 -> 35917 bytes
-rw-r--r--doc/user/group/img/select_group_dropdown.png (renamed from doc/gitlab-basics/img/select_group_dropdown.png)bin3489 -> 3489 bytes
-rw-r--r--doc/user/group/img/share_with_group_lock.pngbin0 -> 18257 bytes
-rw-r--r--doc/user/group/img/transfer_project_to_other_group.pngbin0 -> 66460 bytes
-rw-r--r--doc/user/group/img/withdraw_access_request_button.png (renamed from doc/workflow/groups/withdraw_access_request_button.png)bin36413 -> 36413 bytes
-rw-r--r--doc/user/group/index.md208
-rw-r--r--doc/user/index.md175
-rw-r--r--doc/user/project/integrations/bugzilla.md4
-rw-r--r--doc/user/project/integrations/img/webhook_testing.pngbin0 -> 191267 bytes
-rw-r--r--doc/user/project/integrations/jira.md4
-rw-r--r--doc/user/project/integrations/kubernetes.md5
-rw-r--r--doc/user/project/integrations/prometheus.md66
-rw-r--r--doc/user/project/integrations/prometheus_library/cloudwatch.md25
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md20
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md26
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md26
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md23
-rw-r--r--doc/user/project/integrations/redmine.md2
-rw-r--r--doc/user/project/integrations/samples/cloudwatch.yml26
-rw-r--r--doc/user/project/integrations/samples/prometheus.yml38
-rw-r--r--doc/user/project/integrations/webhooks.md9
-rw-r--r--doc/user/project/issues/index.md31
-rw-r--r--doc/user/project/issues/issues_functionalities.md6
-rw-r--r--doc/user/project/merge_requests/index.md64
-rw-r--r--doc/user/project/milestones/index.md13
-rw-r--r--doc/user/project/pages/getting_started_part_one.md19
-rw-r--r--doc/user/project/pages/introduction.md3
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedule_variables.pngbin0 -> 13478 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_new_form.pngbin49873 -> 72501 bytes
-rw-r--r--doc/user/project/pipelines/schedules.md17
-rw-r--r--doc/user/project/pipelines/settings.md21
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys.pngbin0 -> 32699 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.pngbin0 -> 24514 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.pngbin0 -> 10331 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/img/project_signed_and_unsigned_commits.pngbin0 -> 112812 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/img/project_signed_commit_unverified_signature.pngbin0 -> 9542 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/img/project_signed_commit_verified_signature.pngbin0 -> 14029 bytes
-rw-r--r--doc/workflow/gpg_signed_commits/index.md84
-rw-r--r--doc/workflow/groups.md97
-rw-r--r--doc/workflow/groups/add_member_to_group.pngbin35724 -> 0 bytes
-rw-r--r--doc/workflow/groups/group_dashboard.pngbin28155 -> 0 bytes
-rw-r--r--doc/workflow/groups/group_with_two_projects.pngbin34462 -> 0 bytes
-rw-r--r--doc/workflow/groups/new_group_button.pngbin49708 -> 0 bytes
-rw-r--r--doc/workflow/groups/override_access_level.pngbin40993 -> 0 bytes
-rw-r--r--doc/workflow/groups/project_members_via_group.pngbin39532 -> 0 bytes
-rw-r--r--doc/workflow/groups/transfer_project.pngbin43502 -> 0 bytes
-rw-r--r--doc/workflow/share_projects_with_other_groups.md2
-rw-r--r--features/dashboard/dashboard.feature70
-rw-r--r--features/dashboard/event_filters.feature58
-rw-r--r--features/group/milestones.feature4
-rw-r--r--features/project/active_tab.feature11
-rw-r--r--features/project/badges/build.feature27
-rw-r--r--features/project/source/browse_files.feature333
-rw-r--r--features/snippets/snippets.feature40
-rw-r--r--features/steps/dashboard/dashboard.rb83
-rw-r--r--features/steps/dashboard/event_filters.rb92
-rw-r--r--features/steps/explore/projects.rb8
-rw-r--r--features/steps/group/milestones.rb11
-rw-r--r--features/steps/groups.rb2
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/archived.rb2
-rw-r--r--features/steps/project/badges/build.rb32
-rw-r--r--features/steps/project/commits/commits.rb8
-rw-r--r--features/steps/project/commits/revert.rb2
-rw-r--r--features/steps/project/commits/user_lookup.rb4
-rw-r--r--features/steps/project/create.rb4
-rw-r--r--features/steps/project/deploy_keys.rb2
-rw-r--r--features/steps/project/fork.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb6
-rw-r--r--features/steps/project/graph.rb10
-rw-r--r--features/steps/project/issues/award_emoji.rb2
-rw-r--r--features/steps/project/issues/issues.rb6
-rw-r--r--features/steps/project/issues/labels.rb2
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/project/merge_requests/acceptance.rb1
-rw-r--r--features/steps/project/merge_requests/revert.rb1
-rw-r--r--features/steps/project/network_graph.rb4
-rw-r--r--features/steps/project/pages.rb4
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/project_group_links.rb2
-rw-r--r--features/steps/project/redirects.rb6
-rw-r--r--features/steps/project/services.rb3
-rw-r--r--features/steps/project/snippets.rb2
-rw-r--r--features/steps/project/source/browse_files.rb32
-rw-r--r--features/steps/project/source/markdown_render.rb72
-rw-r--r--features/steps/project/wiki.rb10
-rw-r--r--features/steps/shared/builds.rb6
-rw-r--r--features/steps/shared/issuable.rb8
-rw-r--r--features/steps/shared/paths.rb108
-rw-r--r--features/steps/shared/project.rb11
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--features/steps/shared/snippet.rb63
-rw-r--r--features/steps/snippets/snippets.rb86
-rw-r--r--features/support/env.rb1
-rw-r--r--lib/api/access_requests.rb1
-rw-r--r--lib/api/api.rb10
-rw-r--r--lib/api/api_guard.rb33
-rw-r--r--lib/api/award_emoji.rb1
-rw-r--r--lib/api/branches.rb18
-rw-r--r--lib/api/broadcast_messages.rb1
-rw-r--r--lib/api/deploy_keys.rb1
-rw-r--r--lib/api/entities.rb116
-rw-r--r--lib/api/environments.rb1
-rw-r--r--lib/api/features.rb29
-rw-r--r--lib/api/group_milestones.rb85
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb38
-rw-r--r--lib/api/helpers/internal_helpers.rb35
-rw-r--r--lib/api/helpers/related_resources_helpers.rb28
-rw-r--r--lib/api/internal.rb7
-rw-r--r--lib/api/issues.rb27
-rw-r--r--lib/api/labels.rb1
-rw-r--r--lib/api/members.rb1
-rw-r--r--lib/api/merge_requests.rb43
-rw-r--r--lib/api/milestone_responses.rb98
-rw-r--r--lib/api/milestones.rb154
-rw-r--r--lib/api/notes.rb1
-rw-r--r--lib/api/pipeline_schedules.rb9
-rw-r--r--lib/api/project_hooks.rb1
-rw-r--r--lib/api/project_milestones.rb91
-rw-r--r--lib/api/project_snippets.rb17
-rw-r--r--lib/api/projects.rb122
-rw-r--r--lib/api/runner.rb1
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/scope.rb23
-rw-r--r--lib/api/services.rb6
-rw-r--r--lib/api/settings.rb9
-rw-r--r--lib/api/snippets.rb17
-rw-r--r--lib/api/system_hooks.rb1
-rw-r--r--lib/api/triggers.rb12
-rw-r--r--lib/api/users.rb27
-rw-r--r--lib/api/v3/entities.rb34
-rw-r--r--lib/api/v3/settings.rb14
-rw-r--r--lib/api/v3/triggers.rb11
-rw-r--r--lib/api/v3/users.rb4
-rw-r--r--lib/api/variables.rb9
-rw-r--r--lib/backup/manager.rb42
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb2
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb16
-rw-r--r--lib/banzai/filter/image_link_filter.rb2
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb2
-rw-r--r--lib/banzai/filter/label_reference_filter.rb3
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb2
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
-rw-r--r--lib/banzai/filter/reference_filter.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb1
-rw-r--r--lib/banzai/filter/snippet_reference_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb2
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/reference_parser/external_issue_parser.rb10
-rw-r--r--lib/ci/api/entities.rb2
-rw-r--r--lib/ci/api/triggers.rb11
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb5
-rw-r--r--lib/declarative_policy.rb40
-rw-r--r--lib/declarative_policy/cache.rb13
-rw-r--r--lib/declarative_policy/condition.rb15
-rw-r--r--lib/extracts_path.rb3
-rw-r--r--lib/feature.rb6
-rw-r--r--lib/gitlab/auth.rb10
-rw-r--r--lib/gitlab/auth/unique_ips_limiter.rb2
-rw-r--r--lib/gitlab/background_migration.rb35
-rw-r--r--lib/gitlab/background_migration/migrate_build_stage_id_reference.rb19
-rw-r--r--lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb26
-rw-r--r--lib/gitlab/badge/coverage/metadata.rb6
-rw-r--r--lib/gitlab/badge/metadata.rb2
-rw-r--r--lib/gitlab/badge/pipeline/metadata.rb (renamed from lib/gitlab/badge/build/metadata.rb)11
-rw-r--r--lib/gitlab/badge/pipeline/status.rb (renamed from lib/gitlab/badge/build/status.rb)10
-rw-r--r--lib/gitlab/badge/pipeline/template.rb (renamed from lib/gitlab/badge/build/template.rb)9
-rw-r--r--lib/gitlab/cache/ci/project_pipeline_status.rb10
-rw-r--r--lib/gitlab/cache/request_cache.rb94
-rw-r--r--lib/gitlab/chat_name_token.rb14
-rw-r--r--lib/gitlab/ci/build/step.rb3
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb14
-rw-r--r--lib/gitlab/ci/config/entry/job.rb10
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb4
-rw-r--r--lib/gitlab/ci/status/build/common.rb4
-rw-r--r--lib/gitlab/ci/status/build/play.rb4
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb4
-rw-r--r--lib/gitlab/ci/status/build/stop.rb4
-rw-r--r--lib/gitlab/ci/status/pipeline/common.rb4
-rw-r--r--lib/gitlab/ci/status/stage/common.rb5
-rw-r--r--lib/gitlab/ci/trace/stream.rb7
-rw-r--r--lib/gitlab/conflict/file.rb15
-rw-r--r--lib/gitlab/current_settings.rb9
-rw-r--r--lib/gitlab/cycle_analytics/metrics_tables.rb4
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb37
-rw-r--r--lib/gitlab/data_builder/push.rb2
-rw-r--r--lib/gitlab/data_builder/wiki_page.rb22
-rw-r--r--lib/gitlab/database/migration_helpers.rb4
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb4
-rw-r--r--lib/gitlab/ee_compat_check.rb15
-rw-r--r--lib/gitlab/email/message/repository_push.rb15
-rw-r--r--lib/gitlab/etag_caching/store.rb12
-rw-r--r--lib/gitlab/exclusive_lease.rb20
-rw-r--r--lib/gitlab/git/attributes.rb5
-rw-r--r--lib/gitlab/git/blame.rb3
-rw-r--r--lib/gitlab/git/blob.rb55
-rw-r--r--lib/gitlab/git/blob_snippet.rb2
-rw-r--r--lib/gitlab/git/branch.rb37
-rw-r--r--lib/gitlab/git/commit.rb71
-rw-r--r--lib/gitlab/git/commit_stats.rb4
-rw-r--r--lib/gitlab/git/compare.rb2
-rw-r--r--lib/gitlab/git/diff.rb102
-rw-r--r--lib/gitlab/git/diff_collection.rb76
-rw-r--r--lib/gitlab/git/env.rb2
-rw-r--r--lib/gitlab/git/gitmodules_parser.rb2
-rw-r--r--lib/gitlab/git/hook.rb4
-rw-r--r--lib/gitlab/git/index.rb8
-rw-r--r--lib/gitlab/git/path_helper.rb2
-rw-r--r--lib/gitlab/git/popen.rb2
-rw-r--r--lib/gitlab/git/ref.rb10
-rw-r--r--lib/gitlab/git/repository.rb375
-rw-r--r--lib/gitlab/git/rev_list.rb4
-rw-r--r--lib/gitlab/git/tag.rb6
-rw-r--r--lib/gitlab/git/tree.rb69
-rw-r--r--lib/gitlab/git/util.rb2
-rw-r--r--lib/gitlab/git_ref_validator.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb12
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb30
-rw-r--r--lib/gitlab/gitaly_client/commit.rb69
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb144
-rw-r--r--lib/gitlab/gitaly_client/diff.rb2
-rw-r--r--lib/gitlab/gitaly_client/notification_service.rb (renamed from lib/gitlab/gitaly_client/notifications.rb)4
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb (renamed from lib/gitlab/gitaly_client/ref.rb)42
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb16
-rw-r--r--lib/gitlab/gpg.rb62
-rw-r--r--lib/gitlab/gpg/commit.rb85
-rw-r--r--lib/gitlab/gpg/invalid_gpg_signature_updater.rb19
-rw-r--r--lib/gitlab/health_checks/base_abstract_check.rb6
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb97
-rw-r--r--lib/gitlab/health_checks/redis/cache_check.rb31
-rw-r--r--lib/gitlab/health_checks/redis/queues_check.rb31
-rw-r--r--lib/gitlab/health_checks/redis/redis_check.rb27
-rw-r--r--lib/gitlab/health_checks/redis/shared_state_check.rb31
-rw-r--r--lib/gitlab/health_checks/redis_check.rb25
-rw-r--r--lib/gitlab/health_checks/simple_abstract_check.rb15
-rw-r--r--lib/gitlab/i18n.rb12
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/issuable_metadata.rb36
-rw-r--r--lib/gitlab/kubernetes.rb39
-rw-r--r--lib/gitlab/ldap/adapter.rb2
-rw-r--r--lib/gitlab/ldap/config.rb56
-rw-r--r--lib/gitlab/lfs_token.rb8
-rw-r--r--lib/gitlab/mail_room.rb10
-rw-r--r--lib/gitlab/metrics/base_sampler.rb94
-rw-r--r--lib/gitlab/metrics/influx_sampler.rb (renamed from lib/gitlab/metrics/sampler.rb)40
-rw-r--r--lib/gitlab/metrics/prometheus.rb12
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb40
-rw-r--r--lib/gitlab/metrics/unicorn_sampler.rb48
-rw-r--r--lib/gitlab/o_auth/user.rb10
-rw-r--r--lib/gitlab/otp_key_rotator.rb2
-rw-r--r--lib/gitlab/path_regex.rb22
-rw-r--r--lib/gitlab/performance_bar.rb31
-rw-r--r--lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb22
-rw-r--r--lib/gitlab/performance_bar/peek_query_tracker.rb11
-rw-r--r--lib/gitlab/redis.rb102
-rw-r--r--lib/gitlab/redis/cache.rb34
-rw-r--r--lib/gitlab/redis/queues.rb35
-rw-r--r--lib/gitlab/redis/shared_state.rb34
-rw-r--r--lib/gitlab/redis/wrapper.rb135
-rw-r--r--lib/gitlab/reference_extractor.rb7
-rw-r--r--lib/gitlab/regex.rb30
-rw-r--r--lib/gitlab/request_forgery_protection.rb (renamed from lib/omni_auth/request_forgery_protection.rb)14
-rw-r--r--lib/gitlab/route_map.rb8
-rw-r--r--lib/gitlab/routes/legacy_builds.rb36
-rw-r--r--lib/gitlab/routing.rb25
-rw-r--r--lib/gitlab/shell.rb13
-rw-r--r--lib/gitlab/slash_commands/issue_command.rb2
-rw-r--r--lib/gitlab/slash_commands/presenters/base.rb2
-rw-r--r--lib/gitlab/sql/glob.rb22
-rw-r--r--lib/gitlab/untrusted_regexp.rb49
-rw-r--r--lib/gitlab/url_builder.rb18
-rw-r--r--lib/gitlab/usage_data.rb16
-rw-r--r--lib/gitlab/user_access.rb50
-rw-r--r--lib/gitlab/user_activities.rb6
-rw-r--r--lib/gitlab/visibility_level.rb4
-rw-r--r--lib/gitlab/workhorse.rb71
-rw-r--r--lib/peek/rblineprof/custom_controller_helpers.rb32
-rw-r--r--lib/tasks/cache.rake4
-rw-r--r--lib/tasks/gettext.rake2
-rw-r--r--lib/tasks/gitlab/assets.rake1
-rw-r--r--lib/tasks/gitlab/info.rake3
-rw-r--r--lib/tasks/gitlab/task_helpers.rb9
-rw-r--r--lib/tasks/migrate/setup_postgresql.rake2
-rw-r--r--locale/bg/gitlab.po124
-rw-r--r--locale/en/gitlab.po22
-rw-r--r--locale/eo/gitlab.po124
-rw-r--r--locale/es/gitlab.po95
-rw-r--r--locale/fr/gitlab.po1123
-rw-r--r--locale/gitlab.pot33
-rw-r--r--locale/it/gitlab.po1242
-rw-r--r--locale/it/gitlab.po.time_stamp0
-rw-r--r--locale/ja/gitlab.po1203
-rw-r--r--locale/ja/gitlab.po.time_stamp0
-rw-r--r--locale/pt_BR/gitlab.po1090
-rw-r--r--locale/ru/gitlab.po1233
-rw-r--r--locale/ru/gitlab.po.time_stamp0
-rw-r--r--locale/uk/gitlab.po1256
-rw-r--r--locale/uk/gitlab.po.time_stamp0
-rw-r--r--locale/zh_CN/gitlab.po124
-rw-r--r--locale/zh_HK/gitlab.po128
-rw-r--r--locale/zh_TW/gitlab.po129
-rw-r--r--package.json2
-rw-r--r--public/ci/favicon.icobin5430 -> 0 bytes
-rw-r--r--qa/qa.rb10
-rw-r--r--qa/qa/page/dashboard/groups.rb (renamed from qa/qa/page/main/groups.rb)6
-rw-r--r--qa/qa/page/group/show.rb11
-rw-r--r--qa/qa/page/main/projects.rb16
-rw-r--r--qa/qa/scenario/gitlab/project/create.rb5
-rw-r--r--qa/qa/specs/config.rb18
-rw-r--r--rubocop/cop/active_record_dependent.rb26
-rw-r--r--rubocop/cop/active_record_serialize.rb (renamed from rubocop/cop/activerecord_serialize.rb)2
-rw-r--r--rubocop/cop/in_batches.rb16
-rw-r--r--rubocop/cop/migration/add_column_with_default_to_large_table.rb3
-rw-r--r--rubocop/cop/migration/hash_index.rb51
-rw-r--r--rubocop/cop/project_path_helper.rb51
-rw-r--r--rubocop/rubocop.rb6
-rw-r--r--scripts/prepare_build.sh17
-rw-r--r--spec/config/mail_room_spec.rb22
-rw-r--r--spec/controllers/admin/dashboard_controller_spec.rb21
-rw-r--r--spec/controllers/application_controller_spec.rb9
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb15
-rw-r--r--spec/controllers/dashboard/labels_controller_spec.rb25
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb30
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb118
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb20
-rw-r--r--spec/controllers/groups/variables_controller_spec.rb56
-rw-r--r--spec/controllers/health_check_controller_spec.rb75
-rw-r--r--spec/controllers/health_controller_spec.rb103
-rw-r--r--spec/controllers/metrics_controller_spec.rb70
-rw-r--r--spec/controllers/passwords_controller_spec.rb29
-rw-r--r--spec/controllers/profiles/accounts_controller_spec.rb2
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb11
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb28
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb11
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb8
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb11
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb10
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb6
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb21
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb10
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb103
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb4
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb5
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb34
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb330
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb7
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb19
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb48
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/members_controller_spec.rb14
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb6
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb9
-rw-r--r--spec/controllers/projects_controller_spec.rb43
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb12
-rw-r--r--spec/controllers/sessions_controller_spec.rb10
-rw-r--r--spec/controllers/snippets_controller_spec.rb10
-rw-r--r--spec/controllers/uploads_controller_spec.rb4
-rw-r--r--spec/db/production/settings_spec.rb2
-rw-r--r--spec/factories/ci/builds.rb9
-rw-r--r--spec/factories/ci/group_variables.rb12
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb8
-rw-r--r--spec/factories/ci/runner_projects.rb4
-rw-r--r--spec/factories/ci/triggers.rb7
-rw-r--r--spec/factories/commits.rb9
-rw-r--r--spec/factories/gpg_keys.rb8
-rw-r--r--spec/factories/gpg_signature.rb11
-rw-r--r--spec/factories/milestones.rb22
-rw-r--r--spec/factories/personal_snippets.rb4
-rw-r--r--spec/factories/project_hooks.rb1
-rw-r--r--spec/factories/project_snippets.rb5
-rw-r--r--spec/factories/projects.rb4
-rw-r--r--spec/factories/snippets.rb7
-rw-r--r--spec/factories/uploads.rb2
-rw-r--r--spec/features/abuse_report_spec.rb4
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb4
-rw-r--r--spec/features/admin/admin_active_tab_spec.rb2
-rw-r--r--spec/features/admin/admin_appearance_spec.rb14
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb4
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_builds_spec.rb2
-rw-r--r--spec/features/admin/admin_cohorts_spec.rb4
-rw-r--r--spec/features/admin/admin_conversational_development_index_spec.rb2
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb4
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb6
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb6
-rw-r--r--spec/features/admin/admin_groups_spec.rb5
-rw-r--r--spec/features/admin/admin_health_check_spec.rb4
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb4
-rw-r--r--spec/features/admin/admin_hooks_spec.rb10
-rw-r--r--spec/features/admin/admin_labels_spec.rb2
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb4
-rw-r--r--spec/features/admin/admin_projects_spec.rb16
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb4
-rw-r--r--spec/features/admin/admin_runners_spec.rb11
-rw-r--r--spec/features/admin/admin_settings_spec.rb17
-rw-r--r--spec/features/admin/admin_system_info_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb16
-rw-r--r--spec/features/admin/admin_users_spec.rb8
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb6
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb2
-rw-r--r--spec/features/atom/dashboard_spec.rb2
-rw-r--r--spec/features/atom/issues_spec.rb12
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/auto_deploy_spec.rb6
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb8
-rw-r--r--spec/features/boards/boards_spec.rb25
-rw-r--r--spec/features/boards/issue_ordering_spec.rb10
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb6
-rw-r--r--spec/features/boards/modal_filter_spec.rb6
-rw-r--r--spec/features/boards/new_issue_spec.rb8
-rw-r--r--spec/features/boards/sidebar_spec.rb8
-rw-r--r--spec/features/boards/sub_group_project_spec.rb6
-rw-r--r--spec/features/calendar_spec.rb4
-rw-r--r--spec/features/ci_lint_spec.rb2
-rw-r--r--spec/features/commits_spec.rb111
-rw-r--r--spec/features/container_registry_spec.rb5
-rw-r--r--spec/features/copy_as_gfm_spec.rb20
-rw-r--r--spec/features/cycle_analytics_spec.rb18
-rw-r--r--spec/features/dashboard/active_tab_spec.rb4
-rw-r--r--spec/features/dashboard/activity_spec.rb161
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb4
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb6
-rw-r--r--spec/features/dashboard/group_spec.rb4
-rw-r--r--spec/features/dashboard/groups_list_spec.rb12
-rw-r--r--spec/features/dashboard/help_spec.rb4
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb4
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb (renamed from spec/features/dashboard_issues_spec.rb)60
-rw-r--r--spec/features/dashboard/issues_spec.rb29
-rw-r--r--spec/features/dashboard/label_filter_spec.rb4
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb17
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb4
-rw-r--r--spec/features/dashboard/milestone_tabs_spec.rb4
-rw-r--r--spec/features/dashboard/milestones_spec.rb (renamed from spec/features/dashboard_milestones_spec.rb)4
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb4
-rw-r--r--spec/features/dashboard/projects_spec.rb55
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb4
-rw-r--r--spec/features/dashboard/snippets_spec.rb6
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb2
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb4
-rw-r--r--spec/features/discussion_comments/commit_spec.rb6
-rw-r--r--spec/features/discussion_comments/issue_spec.rb6
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb6
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb6
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb10
-rw-r--r--spec/features/explore/groups_list_spec.rb4
-rw-r--r--spec/features/explore/new_menu_spec.rb41
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb28
-rw-r--r--spec/features/global_search_spec.rb4
-rw-r--r--spec/features/group_variables_spec.rb78
-rw-r--r--spec/features/groups/activity_spec.rb6
-rw-r--r--spec/features/groups/empty_states_spec.rb2
-rw-r--r--spec/features/groups/group_name_toggle_spec.rb4
-rw-r--r--spec/features/groups/group_settings_spec.rb4
-rw-r--r--spec/features/groups/issues_spec.rb2
-rw-r--r--spec/features/groups/labels/edit_spec.rb4
-rw-r--r--spec/features/groups/labels/subscription_spec.rb2
-rw-r--r--spec/features/groups/members/leave_group_spec.rb2
-rw-r--r--spec/features/groups/members/list_members_spec.rb2
-rw-r--r--spec/features/groups/members/manage_access_requests_spec.rb4
-rw-r--r--spec/features/groups/members/manage_members.rb4
-rw-r--r--spec/features/groups/members/request_access_spec.rb4
-rw-r--r--spec/features/groups/members/sort_members_spec.rb8
-rw-r--r--spec/features/groups/merge_requests_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb32
-rw-r--r--spec/features/groups/show_spec.rb9
-rw-r--r--spec/features/groups_spec.rb12
-rw-r--r--spec/features/help_pages_spec.rb6
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb116
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb8
-rw-r--r--spec/features/issuables/markdown_references_spec.rb193
-rw-r--r--spec/features/issuables/user_sees_sidebar_spec.rb4
-rw-r--r--spec/features/issues/award_emoji_spec.rb10
-rw-r--r--spec/features/issues/award_spec.rb12
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb10
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb20
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb24
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb19
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb8
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb20
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb8
-rw-r--r--spec/features/issues/form_spec.rb17
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb18
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb10
-rw-r--r--spec/features/issues/issue_detail_spec.rb43
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb6
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb6
-rw-r--r--spec/features/issues/move_spec.rb8
-rw-r--r--spec/features/issues/note_polling_spec.rb16
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb6
-rw-r--r--spec/features/issues/spam_issues_spec.rb8
-rw-r--r--spec/features/issues/todo_spec.rb10
-rw-r--r--spec/features/issues/update_issues_spec.rb12
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb93
-rw-r--r--spec/features/issues_spec.rb80
-rw-r--r--spec/features/login_spec.rb6
-rw-r--r--spec/features/markdown_spec.rb4
-rw-r--r--spec/features/merge_requests/assign_issues_spec.rb6
-rw-r--r--spec/features/merge_requests/award_spec.rb12
-rw-r--r--spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb6
-rw-r--r--spec/features/merge_requests/cherry_pick_spec.rb6
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb6
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb17
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb28
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb5
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb10
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb14
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb10
-rw-r--r--spec/features/merge_requests/diffs_spec.rb18
-rw-r--r--spec/features/merge_requests/discussion_spec.rb12
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb8
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb6
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb4
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb30
-rw-r--r--spec/features/merge_requests/form_spec.rb18
-rw-r--r--spec/features/merge_requests/merge_commit_message_toggle_spec.rb6
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb6
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb8
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb8
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb6
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb8
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb2
-rw-r--r--spec/features/merge_requests/target_branch_spec.rb9
-rw-r--r--spec/features/merge_requests/toggle_whitespace_changes_spec.rb6
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb6
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb16
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb6
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb16
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb4
-rw-r--r--spec/features/merge_requests/user_sees_system_notes_spec.rb6
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb40
-rw-r--r--spec/features/merge_requests/versions_spec.rb9
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb6
-rw-r--r--spec/features/merge_requests/widget_spec.rb34
-rw-r--r--spec/features/merge_requests/wip_message_spec.rb16
-rw-r--r--spec/features/milestone_spec.rb33
-rw-r--r--spec/features/milestones/show_spec.rb6
-rw-r--r--spec/features/oauth_login_spec.rb114
-rw-r--r--spec/features/participants_autocomplete_spec.rb11
-rw-r--r--spec/features/password_reset_spec.rb2
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/profiles/account_spec.rb4
-rw-r--r--spec/features/profiles/chat_names_spec.rb4
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb58
-rw-r--r--spec/features/profiles/keys_spec.rb4
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb4
-rw-r--r--spec/features/profiles/password_spec.rb82
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb16
-rw-r--r--spec/features/profiles/preferences_spec.rb4
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb4
-rw-r--r--spec/features/projects/activity/rss_spec.rb6
-rw-r--r--spec/features/projects/artifacts/browse_spec.rb4
-rw-r--r--spec/features/projects/artifacts/download_spec.rb10
-rw-r--r--spec/features/projects/artifacts/file_spec.rb4
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb4
-rw-r--r--spec/features/projects/badges/coverage_spec.rb7
-rw-r--r--spec/features/projects/badges/list_spec.rb16
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb70
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb20
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb4
-rw-r--r--spec/features/projects/blobs/edit_spec.rb32
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb8
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb10
-rw-r--r--spec/features/projects/branches/new_branch_ref_dropdown_spec.rb6
-rw-r--r--spec/features/projects/branches_spec.rb37
-rw-r--r--spec/features/projects/commit/builds_spec.rb4
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb4
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb8
-rw-r--r--spec/features/projects/commit/rss_spec.rb6
-rw-r--r--spec/features/projects/compare_spec.rb4
-rw-r--r--spec/features/projects/deploy_keys_spec.rb6
-rw-r--r--spec/features/projects/developer_views_empty_project_instructions_spec.rb6
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb8
-rw-r--r--spec/features/projects/edit_spec.rb6
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb8
-rw-r--r--spec/features/projects/environments/environment_spec.rb18
-rw-r--r--spec/features/projects/environments/environments_spec.rb16
-rw-r--r--spec/features/projects/features_visibility_spec.rb65
-rw-r--r--spec/features/projects/files/browse_files_spec.rb6
-rw-r--r--spec/features/projects/files/creating_a_file_spec.rb11
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb6
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb11
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb6
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb6
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb6
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb6
-rw-r--r--spec/features/projects/files/find_files_spec.rb15
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb6
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb6
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb14
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb10
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb12
-rw-r--r--spec/features/projects/files/undo_template_spec.rb6
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb8
-rw-r--r--spec/features/projects/group_links_spec.rb10
-rw-r--r--spec/features/projects/guest_navigation_menu_spec.rb16
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb8
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb3
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb8
-rw-r--r--spec/features/projects/issuable_counts_caching_spec.rb132
-rw-r--r--spec/features/projects/issuable_templates_spec.rb19
-rw-r--r--spec/features/projects/issues/list_spec.rb4
-rw-r--r--spec/features/projects/issues/rss_spec.rb7
-rw-r--r--spec/features/projects/jobs_spec.rb79
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb10
-rw-r--r--spec/features/projects/labels/subscription_spec.rb8
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb28
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb10
-rw-r--r--spec/features/projects/main/rss_spec.rb6
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb6
-rw-r--r--spec/features/projects/members/group_links_spec.rb8
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb6
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb6
-rw-r--r--spec/features/projects/members/group_members_spec.rb12
-rw-r--r--spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb6
-rw-r--r--spec/features/projects/members/list_spec.rb6
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb8
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb10
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb6
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb6
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb6
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb6
-rw-r--r--spec/features/projects/members/sorting_spec.rb10
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb17
-rw-r--r--spec/features/projects/merge_request_button_spec.rb44
-rw-r--r--spec/features/projects/merge_requests/list_spec.rb12
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb10
-rw-r--r--spec/features/projects/milestones/milestones_sorting_spec.rb6
-rw-r--r--spec/features/projects/milestones/new_spec.rb4
-rw-r--r--spec/features/projects/no_password_spec.rb10
-rw-r--r--spec/features/projects/pages_spec.rb10
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb275
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb12
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb20
-rw-r--r--spec/features/projects/project_settings_spec.rb20
-rw-r--r--spec/features/projects/ref_switcher_spec.rb10
-rw-r--r--spec/features/projects/services/jira_service_spec.rb33
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb12
-rw-r--r--spec/features/projects/services/slack_service_spec.rb6
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb12
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb39
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb6
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb8
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb16
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb10
-rw-r--r--spec/features/projects/shortcuts_spec.rb6
-rw-r--r--spec/features/projects/show_project_spec.rb20
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb8
-rw-r--r--spec/features/projects/snippets/show_spec.rb10
-rw-r--r--spec/features/projects/snippets_spec.rb10
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb8
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb10
-rw-r--r--spec/features/projects/tree/rss_spec.rb6
-rw-r--r--spec/features/projects/user_browses_files_spec.rb188
-rw-r--r--spec/features/projects/user_create_dir_spec.rb57
-rw-r--r--spec/features/projects/user_creates_directory_spec.rb87
-rw-r--r--spec/features/projects/user_creates_files_spec.rb153
-rw-r--r--spec/features/projects/user_creates_project_spec.rb2
-rw-r--r--spec/features/projects/user_deletes_files_spec.rb68
-rw-r--r--spec/features/projects/user_edits_files_spec.rb122
-rw-r--r--spec/features/projects/user_replaces_files_spec.rb87
-rw-r--r--spec/features/projects/user_uploads_files_spec.rb82
-rw-r--r--spec/features/projects/view_on_env_spec.rb24
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb6
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb27
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_views_project_wiki_page_spec.rb13
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb12
-rw-r--r--spec/features/projects_spec.rb26
-rw-r--r--spec/features/protected_branches_spec.rb18
-rw-r--r--spec/features/protected_tags_spec.rb18
-rw-r--r--spec/features/raven_js_spec.rb2
-rw-r--r--spec/features/reportable_note/commit_spec.rb8
-rw-r--r--spec/features/reportable_note/issue_spec.rb6
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb6
-rw-r--r--spec/features/reportable_note/snippets_spec.rb6
-rw-r--r--spec/features/runners_spec.rb6
-rw-r--r--spec/features/search_spec.rb22
-rw-r--r--spec/features/security/admin_access_spec.rb2
-rw-r--r--spec/features/security/dashboard_access_spec.rb2
-rw-r--r--spec/features/security/group/internal_access_spec.rb2
-rw-r--r--spec/features/security/group/private_access_spec.rb2
-rw-r--r--spec/features/security/group/public_access_spec.rb2
-rw-r--r--spec/features/security/profile_access_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb64
-rw-r--r--spec/features/security/project/private_access_spec.rb64
-rw-r--r--spec/features/security/project/public_access_spec.rb64
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb14
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb10
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb18
-rw-r--r--spec/features/signup_spec.rb2
-rw-r--r--spec/features/snippets/explore_spec.rb6
-rw-r--r--spec/features/snippets/internal_snippet_spec.rb4
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb4
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/search_snippets_spec.rb6
-rw-r--r--spec/features/snippets/show_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb (renamed from spec/features/snippets/create_snippet_spec.rb)12
-rw-r--r--spec/features/snippets/user_deletes_snippet_spec.rb19
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb (renamed from spec/features/snippets/edit_snippet_spec.rb)28
-rw-r--r--spec/features/snippets/user_snippets_spec.rb4
-rw-r--r--spec/features/snippets_spec.rb2
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb12
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb10
-rw-r--r--spec/features/tags/master_updates_tag_spec.rb10
-rw-r--r--spec/features/tags/master_views_tags_spec.rb24
-rw-r--r--spec/features/task_lists_spec.rb20
-rw-r--r--spec/features/triggers_spec.rb22
-rw-r--r--spec/features/unsubscribe_links_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb6
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb6
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb6
-rw-r--r--spec/features/user_callout_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb23
-rw-r--r--spec/features/users/projects_spec.rb4
-rw-r--r--spec/features/users/rss_spec.rb3
-rw-r--r--spec/features/users/snippets_spec.rb4
-rw-r--r--spec/features/users_spec.rb2
-rw-r--r--spec/features/variables_spec.rb20
-rw-r--r--spec/finders/access_requests_finder_spec.rb2
-rw-r--r--spec/finders/admin/projects_finder_spec.rb136
-rw-r--r--spec/finders/issues_finder_spec.rb17
-rw-r--r--spec/finders/merge_requests_finder_spec.rb19
-rw-r--r--spec/finders/milestones_finder_spec.rb90
-rw-r--r--spec/finders/users_finder_spec.rb11
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request.json1
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/issues.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/merge_requests.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/branch.json20
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/branches.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit/basic.json37
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/issues.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/admin.json34
-rw-r--r--spec/fixtures/config/kubeconfig-without-ca.yml18
-rw-r--r--spec/fixtures/config/kubeconfig.yml19
-rw-r--r--spec/fixtures/config/redis_cache_config_with_env.yml2
-rw-r--r--spec/fixtures/config/redis_cache_new_format_host.yml29
-rw-r--r--spec/fixtures/config/redis_cache_new_format_socket.yml6
-rw-r--r--spec/fixtures/config/redis_cache_old_format_host.yml5
-rw-r--r--spec/fixtures/config/redis_cache_old_format_socket.yml3
-rw-r--r--spec/fixtures/config/redis_new_format_host.yml12
-rw-r--r--spec/fixtures/config/redis_queues_config_with_env.yml2
-rw-r--r--spec/fixtures/config/redis_queues_new_format_host.yml29
-rw-r--r--spec/fixtures/config/redis_queues_new_format_socket.yml6
-rw-r--r--spec/fixtures/config/redis_queues_old_format_host.yml5
-rw-r--r--spec/fixtures/config/redis_queues_old_format_socket.yml3
-rw-r--r--spec/fixtures/config/redis_shared_state_config_with_env.yml2
-rw-r--r--spec/fixtures/config/redis_shared_state_new_format_host.yml29
-rw-r--r--spec/fixtures/config/redis_shared_state_new_format_socket.yml6
-rw-r--r--spec/fixtures/config/redis_shared_state_old_format_host.yml5
-rw-r--r--spec/fixtures/config/redis_shared_state_old_format_socket.yml3
-rw-r--r--spec/fixtures/markdown.md.erb22
-rw-r--r--spec/helpers/application_helper_spec.rb27
-rw-r--r--spec/helpers/auth_helper_spec.rb2
-rw-r--r--spec/helpers/avatars_helper_spec.rb48
-rw-r--r--spec/helpers/award_emoji_helper_spec.rb4
-rw-r--r--spec/helpers/button_helper_spec.rb2
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb16
-rw-r--r--spec/helpers/groups_helper_spec.rb2
-rw-r--r--spec/helpers/hooks_helper_spec.rb20
-rw-r--r--spec/helpers/issuables_helper_spec.rb22
-rw-r--r--spec/helpers/issues_helper_spec.rb10
-rw-r--r--spec/helpers/markup_helper_spec.rb12
-rw-r--r--spec/helpers/milestones_helper_spec.rb4
-rw-r--r--spec/helpers/namespaces_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb14
-rw-r--r--spec/helpers/page_layout_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb6
-rw-r--r--spec/initializers/6_validations_spec.rb2
-rw-r--r--spec/initializers/8_metrics_spec.rb12
-rw-r--r--spec/initializers/secret_token_spec.rb2
-rw-r--r--spec/initializers/settings_spec.rb42
-rw-r--r--spec/initializers/trusted_proxies_spec.rb2
-rw-r--r--spec/javascripts/close_reopen_report_toggle_spec.js270
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js35
-rw-r--r--spec/javascripts/environments/environment_spec.js9
-rw-r--r--spec/javascripts/environments/environments_store_spec.js21
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js4
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js9
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml5
-rw-r--r--spec/javascripts/fixtures/u2f.rb4
-rw-r--r--spec/javascripts/helpers/vue_resource_helper.js11
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js55
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js54
-rw-r--r--spec/javascripts/issue_spec.js65
-rw-r--r--spec/javascripts/lazy_loader_spec.js57
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js1
-rw-r--r--spec/javascripts/lib/utils/poll_spec.js52
-rw-r--r--spec/javascripts/merge_request_spec.js33
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js1
-rw-r--r--spec/javascripts/monitoring/mock_data.js1
-rw-r--r--spec/javascripts/monitoring/monitoring_column_spec.js3
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js26
-rw-r--r--spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js145
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js17
-rw-r--r--spec/javascripts/todos_spec.js4
-rw-r--r--spec/javascripts/visibility_select_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js1
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js7
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js66
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js302
-rw-r--r--spec/javascripts/zen_mode_spec.js3
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb19
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb64
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb17
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb24
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/redactor_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/upload_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb91
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/ci/ansi2html_spec.rb2
-rw-r--r--spec/lib/ci/charts_spec.rb2
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb78
-rw-r--r--spec/lib/ci/mask_secret_spec.rb2
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb6
-rw-r--r--spec/lib/event_filter_spec.rb18
-rw-r--r--spec/lib/extracts_path_spec.rb6
-rw-r--r--spec/lib/feature_spec.rb2
-rw-r--r--spec/lib/file_size_validator_spec.rb4
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb19
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb120
-rw-r--r--spec/lib/gitlab/backup/manager_spec.rb54
-rw-r--r--spec/lib/gitlab/backup/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb (renamed from spec/lib/gitlab/badge/build/metadata_spec.rb)6
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb (renamed from spec/lib/gitlab/badge/build/status_spec.rb)37
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb (renamed from spec/lib/gitlab/badge/build/template_spec.rb)18
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb52
-rw-r--r--spec/lib/gitlab/cache/request_cache_spec.rb133
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/step_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb8
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb25
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb50
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb4
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb10
-rw-r--r--spec/lib/gitlab/color_schemes_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb6
-rw-r--r--spec/lib/gitlab/conflict/parser_spec.rb4
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb17
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb21
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb16
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb4
-rw-r--r--spec/lib/gitlab/database_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_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/receiver_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_spec.rb2
-rw-r--r--spec/lib/gitlab/fake_application_settings_spec.rb10
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb45
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb160
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb14
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb14
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb186
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb6
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb23
-rw-r--r--spec/lib/gitlab/git_access_spec.rb8
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb4
-rw-r--r--spec/lib/gitlab/git_ref_validator_spec.rb44
-rw-r--r--spec/lib/gitlab/git_spec.rb8
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb123
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_spec.rb85
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/notification_service_spec.rb (renamed from spec/lib/gitlab/gitaly_client/notifications_spec.rb)4
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb (renamed from spec/lib/gitlab/gitaly_client/ref_spec.rb)18
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb19
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/issuable_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/label_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/milestone_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/release_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/user_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb4
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/gitlab_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/google_code_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb127
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb173
-rw-r--r--spec/lib/gitlab/gpg_spec.rb83
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb2
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb65
-rw-r--r--spec/lib/gitlab/health_checks/redis/cache_check_spec.rb6
-rw-r--r--spec/lib/gitlab/health_checks/redis/queues_check_spec.rb6
-rw-r--r--spec/lib/gitlab/health_checks/redis/redis_check_spec.rb6
-rw-r--r--spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb6
-rw-r--r--spec/lib/gitlab/health_checks/redis_check_spec.rb6
-rw-r--r--spec/lib/gitlab/health_checks/simple_check_shared.rb10
-rw-r--r--spec/lib/gitlab/highlight_spec.rb6
-rw-r--r--spec/lib/gitlab/i18n_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml7
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/attribute_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/hash_util_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/model_configuration_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.json44
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml15
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb2
-rw-r--r--spec/lib/gitlab/issuable_metadata_spec.rb59
-rw-r--r--spec/lib/gitlab/issuable_sorter_spec.rb2
-rw-r--r--spec/lib/gitlab/key_fingerprint_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb24
-rw-r--r--spec/lib/gitlab/lazy_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/adapter_spec.rb8
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb256
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb6
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb2
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb14
-rw-r--r--spec/lib/gitlab/metrics/influx_sampler_spec.rb (renamed from spec/lib/gitlab/metrics/sampler_spec.rb)10
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb71
-rw-r--r--spec/lib/gitlab/metrics/unicorn_sampler_spec.rb108
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/auth_hash_spec.rb4
-rw-r--r--spec/lib/gitlab/o_auth/provider_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb4
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb63
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb92
-rw-r--r--spec/lib/gitlab/polling_interval_spec.rb2
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb20
-rw-r--r--spec/lib/gitlab/redis/queues_spec.rb20
-rw-r--r--spec/lib/gitlab/redis/shared_state_spec.rb20
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb20
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb35
-rw-r--r--spec/lib/gitlab/regex_spec.rb19
-rw-r--r--spec/lib/gitlab/request_context_spec.rb6
-rw-r--r--spec/lib/gitlab/request_forgery_protection_spec.rb89
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/route_map_spec.rb15
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb10
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/file_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/location_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb12
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb53
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb98
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb4
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb2
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb30
-rw-r--r--spec/lib/gitlab/user_access_spec.rb6
-rw-r--r--spec/lib/gitlab/user_activities_spec.rb34
-rw-r--r--spec/lib/gitlab/utils_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb51
-rw-r--r--spec/lib/gitlab_spec.rb2
-rw-r--r--spec/lib/repository_cache_spec.rb4
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/lib/system_check_spec.rb4
-rw-r--r--spec/mailers/emails/profile_spec.rb30
-rw-r--r--spec/mailers/notify_spec.rb43
-rw-r--r--spec/migrations/add_foreign_key_to_merge_requests_spec.rb39
-rw-r--r--spec/migrations/clean_appearance_symlinks_spec.rb46
-rw-r--r--spec/migrations/clean_stage_id_reference_migration_spec.rb34
-rw-r--r--spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb35
-rw-r--r--spec/migrations/migrate_process_commit_worker_jobs_spec.rb4
-rw-r--r--spec/migrations/migrate_stage_id_reference_in_background_spec.rb68
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb4
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb180
-rw-r--r--spec/migrations/move_system_upload_folder_spec.rb62
-rw-r--r--spec/models/ability_spec.rb12
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb170
-rw-r--r--spec/models/award_emoji_spec.rb2
-rw-r--r--spec/models/blob_viewer/base_spec.rb2
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb2
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/license_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/readme_spec.rb49
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb10
-rw-r--r--spec/models/chat_name_spec.rb2
-rw-r--r--spec/models/chat_team_spec.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb194
-rw-r--r--spec/models/ci/group_spec.rb2
-rw-r--r--spec/models/ci/group_variable_spec.rb31
-rw-r--r--spec/models/ci/legacy_stage_spec.rb2
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb23
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb7
-rw-r--r--spec/models/ci/pipeline_spec.rb39
-rw-r--r--spec/models/ci/runner_spec.rb30
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/ci/variable_spec.rb9
-rw-r--r--spec/models/commit_range_spec.rb2
-rw-r--r--spec/models/commit_spec.rb10
-rw-r--r--spec/models/commit_status_spec.rb2
-rw-r--r--spec/models/compare_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb2
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/each_batch_spec.rb53
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/mentionable_spec.rb18
-rw-r--r--spec/models/concerns/noteable_spec.rb2
-rw-r--r--spec/models/concerns/participable_spec.rb2
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_discussion_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb31
-rw-r--r--spec/models/concerns/uniquify_spec.rb2
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/deploy_key_spec.rb2
-rw-r--r--spec/models/deploy_keys_project_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/diff_note_spec.rb2
-rw-r--r--spec/models/diff_viewer/base_spec.rb2
-rw-r--r--spec/models/diff_viewer/server_side_spec.rb2
-rw-r--r--spec/models/discussion_spec.rb2
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/event_spec.rb2
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/forked_project_link_spec.rb6
-rw-r--r--spec/models/generic_commit_status_spec.rb2
-rw-r--r--spec/models/global_milestone_spec.rb16
-rw-r--r--spec/models/gpg_key_spec.rb157
-rw-r--r--spec/models/gpg_signature_spec.rb28
-rw-r--r--spec/models/group_label_spec.rb2
-rw-r--r--spec/models/group_milestone_spec.rb6
-rw-r--r--spec/models/group_spec.rb76
-rw-r--r--spec/models/guest_spec.rb12
-rw-r--r--spec/models/hooks/project_hook_spec.rb12
-rw-r--r--spec/models/hooks/service_hook_spec.rb6
-rw-r--r--spec/models/hooks/system_hook_spec.rb7
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/identity_spec.rb2
-rw-r--r--spec/models/issue/metrics_spec.rb2
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_link_spec.rb2
-rw-r--r--spec/models/label_priority_spec.rb2
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/legacy_diff_discussion_spec.rb2
-rw-r--r--spec/models/lfs_objects_project_spec.rb2
-rw-r--r--spec/models/list_spec.rb6
-rw-r--r--spec/models/member_spec.rb4
-rw-r--r--spec/models/members/group_member_spec.rb2
-rw-r--r--spec/models/members/project_member_spec.rb4
-rw-r--r--spec/models/merge_request/metrics_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb15
-rw-r--r--spec/models/merge_request_diff_file_spec.rb29
-rw-r--r--spec/models/merge_request_diff_spec.rb17
-rw-r--r--spec/models/merge_request_spec.rb74
-rw-r--r--spec/models/milestone_spec.rb48
-rw-r--r--spec/models/namespace_spec.rb30
-rw-r--r--spec/models/network/graph_spec.rb2
-rw-r--r--spec/models/note_spec.rb4
-rw-r--r--spec/models/notification_setting_spec.rb15
-rw-r--r--spec/models/pages_domain_spec.rb4
-rw-r--r--spec/models/personal_access_token_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb12
-rw-r--r--spec/models/project_label_spec.rb2
-rw-r--r--spec/models/project_services/asana_service_spec.rb4
-rw-r--r--spec/models/project_services/assembla_service_spec.rb4
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb2
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb2
-rw-r--r--spec/models/project_services/campfire_service_spec.rb4
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb6
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb4
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb4
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb29
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb4
-rw-r--r--spec/models/project_services/irker_service_spec.rb4
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb22
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb50
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb4
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb4
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb2
-rw-r--r--spec/models/project_services/pushover_service_spec.rb4
-rw-r--r--spec/models/project_services/redmine_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_snippet_spec.rb2
-rw-r--r--spec/models/project_spec.rb246
-rw-r--r--spec/models/project_statistics_spec.rb2
-rw-r--r--spec/models/project_team_spec.rb2
-rw-r--r--spec/models/project_wiki_spec.rb6
-rw-r--r--spec/models/protectable_dropdown_spec.rb2
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch_spec.rb38
-rw-r--r--spec/models/protected_tag_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb6
-rw-r--r--spec/models/release_spec.rb2
-rw-r--r--spec/models/repository_spec.rb47
-rw-r--r--spec/models/route_spec.rb8
-rw-r--r--spec/models/sent_notification_spec.rb6
-rw-r--r--spec/models/service_spec.rb2
-rw-r--r--spec/models/snippet_blob_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb2
-rw-r--r--spec/models/spam_log_spec.rb2
-rw-r--r--spec/models/subscription_spec.rb2
-rw-r--r--spec/models/system_note_metadata_spec.rb2
-rw-r--r--spec/models/timelog_spec.rb2
-rw-r--r--spec/models/todo_spec.rb2
-rw-r--r--spec/models/tree_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_spec.rb200
-rw-r--r--spec/models/wiki_directory_spec.rb6
-rw-r--r--spec/models/wiki_page_spec.rb16
-rw-r--r--spec/policies/base_policy_spec.rb2
-rw-r--r--spec/policies/ci/build_policy_spec.rb62
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb66
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb2
-rw-r--r--spec/policies/deploy_key_policy_spec.rb2
-rw-r--r--spec/policies/global_policy_spec.rb24
-rw-r--r--spec/policies/group_policy_spec.rb2
-rw-r--r--spec/policies/issue_policy_spec.rb2
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb44
-rw-r--r--spec/policies/project_snippet_policy_spec.rb2
-rw-r--r--spec/policies/user_policy_spec.rb4
-rw-r--r--spec/presenters/ci/build_presenter_spec.rb4
-rw-r--r--spec/presenters/ci/group_variable_presenter_spec.rb63
-rw-r--r--spec/presenters/ci/variable_presenter_spec.rb63
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb41
-rw-r--r--spec/requests/api/branches_spec.rb508
-rw-r--r--spec/requests/api/commit_statuses_spec.rb22
-rw-r--r--spec/requests/api/events_spec.rb2
-rw-r--r--spec/requests/api/features_spec.rb14
-rw-r--r--spec/requests/api/group_milestones_spec.rb21
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/helpers_spec.rb55
-rw-r--r--spec/requests/api/internal_spec.rb107
-rw-r--r--spec/requests/api/issues_spec.rb73
-rw-r--r--spec/requests/api/jobs_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb55
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb2
-rw-r--r--spec/requests/api/project_milestones_spec.rb25
-rw-r--r--spec/requests/api/project_snippets_spec.rb24
-rw-r--r--spec/requests/api/projects_spec.rb126
-rw-r--r--spec/requests/api/runner_spec.rb3
-rw-r--r--spec/requests/api/settings_spec.rb6
-rw-r--r--spec/requests/api/snippets_spec.rb21
-rw-r--r--spec/requests/api/todos_spec.rb2
-rw-r--r--spec/requests/api/triggers_spec.rb3
-rw-r--r--spec/requests/api/users_spec.rb101
-rw-r--r--spec/requests/api/v3/groups_spec.rb2
-rw-r--r--spec/requests/api/v3/projects_spec.rb2
-rw-r--r--spec/requests/api/v3/settings_spec.rb6
-rw-r--r--spec/requests/api/v3/triggers_spec.rb3
-rw-r--r--spec/requests/api/v3/users_spec.rb23
-rw-r--r--spec/requests/api/version_spec.rb4
-rw-r--r--spec/requests/ci/api/builds_spec.rb66
-rw-r--r--spec/requests/ci/api/triggers_spec.rb14
-rw-r--r--spec/requests/git_http_spec.rb6
-rw-r--r--spec/requests/jwt_controller_spec.rb2
-rw-r--r--spec/requests/openid_connect_spec.rb4
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb24
-rw-r--r--spec/routing/environments_spec.rb2
-rw-r--r--spec/routing/project_routing_spec.rb22
-rw-r--r--spec/rubocop/cop/active_record_dependent_spec.rb33
-rw-r--r--spec/rubocop/cop/active_record_serialize_spec.rb (renamed from spec/rubocop/cop/activerecord_serialize_spec.rb)4
-rw-r--r--spec/rubocop/cop/in_batches_spec.rb19
-rw-r--r--spec/rubocop/cop/migration/hash_index_spec.rb53
-rw-r--r--spec/rubocop/cop/project_path_helper_spec.rb41
-rw-r--r--spec/serializers/build_details_entity_spec.rb89
-rw-r--r--spec/serializers/deploy_key_entity_spec.rb6
-rw-r--r--spec/serializers/job_entity_spec.rb11
-rw-r--r--spec/serializers/merge_request_entity_spec.rb2
-rw-r--r--spec/serializers/pipeline_details_entity_spec.rb6
-rw-r--r--spec/serializers/pipeline_entity_spec.rb4
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb33
-rw-r--r--spec/services/access_token_validation_service_spec.rb45
-rw-r--r--spec/services/after_branch_delete_service_spec.rb2
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb2
-rw-r--r--spec/services/boards/create_service_spec.rb4
-rw-r--r--spec/services/boards/issues/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/boards/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/create_service_spec.rb2
-rw-r--r--spec/services/boards/lists/destroy_service_spec.rb2
-rw-r--r--spec/services/boards/lists/generate_service_spec.rb2
-rw-r--r--spec/services/boards/lists/list_service_spec.rb2
-rw-r--r--spec/services/boards/lists/move_service_spec.rb2
-rw-r--r--spec/services/chat_names/authorize_user_service_spec.rb2
-rw-r--r--spec/services/chat_names/find_user_service_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb233
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb26
-rw-r--r--spec/services/ci/play_build_service_spec.rb2
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb33
-rw-r--r--spec/services/ci/register_job_service_spec.rb2
-rw-r--r--spec/services/ci/retry_build_service_spec.rb6
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb22
-rw-r--r--spec/services/ci/stop_environments_service_spec.rb2
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb2
-rw-r--r--spec/services/ci/update_runner_service_spec.rb2
-rw-r--r--spec/services/compare_service_spec.rb2
-rw-r--r--spec/services/create_branch_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb4
-rw-r--r--spec/services/create_release_service_spec.rb4
-rw-r--r--spec/services/create_snippet_service_spec.rb2
-rw-r--r--spec/services/delete_branch_service_spec.rb2
-rw-r--r--spec/services/delete_merged_branches_service_spec.rb2
-rw-r--r--spec/services/discussions/update_diff_position_service_spec.rb2
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/event_create_service_spec.rb6
-rw-r--r--spec/services/git_hooks_service_spec.rb4
-rw-r--r--spec/services/git_push_service_spec.rb90
-rw-r--r--spec/services/git_tag_push_service_spec.rb6
-rw-r--r--spec/services/gravatar_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb2
-rw-r--r--spec/services/groups/destroy_service_spec.rb4
-rw-r--r--spec/services/groups/update_service_spec.rb2
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb2
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb2
-rw-r--r--spec/services/issues/build_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb10
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/services/issues/duplicate_service_spec.rb80
-rw-r--r--spec/services/issues/move_service_spec.rb67
-rw-r--r--spec/services/issues/reopen_service_spec.rb2
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb31
-rw-r--r--spec/services/labels/create_service_spec.rb38
-rw-r--r--spec/services/labels/find_or_create_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb2
-rw-r--r--spec/services/labels/transfer_service_spec.rb2
-rw-r--r--spec/services/labels/update_service_spec.rb14
-rw-r--r--spec/services/members/approve_access_request_service_spec.rb2
-rw-r--r--spec/services/members/authorized_destroy_service_spec.rb2
-rw-r--r--spec/services/members/create_service_spec.rb2
-rw-r--r--spec/services/members/destroy_service_spec.rb2
-rw-r--r--spec/services/members/request_access_service_spec.rb2
-rw-r--r--spec/services/merge_requests/assign_issues_service_spec.rb2
-rw-r--r--spec/services/merge_requests/build_service_spec.rb6
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb77
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb58
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolved_discussion_notification_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb22
-rw-r--r--spec/services/milestones/close_service_spec.rb4
-rw-r--r--spec/services/milestones/create_service_spec.rb4
-rw-r--r--spec/services/milestones/destroy_service_spec.rb51
-rw-r--r--spec/services/note_summary_spec.rb2
-rw-r--r--spec/services/notes/build_service_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb2
-rw-r--r--spec/services/notes/destroy_service_spec.rb2
-rw-r--r--spec/services/notes/post_process_service_spec.rb4
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb2
-rw-r--r--spec/services/notes/update_service_spec.rb2
-rw-r--r--spec/services/notification_recipient_service_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb18
-rw-r--r--spec/services/pages_service_spec.rb4
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_service_spec.rb80
-rw-r--r--spec/services/projects/download_service_spec.rb2
-rw-r--r--spec/services/projects/enable_deploy_key_service_spec.rb2
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/services/projects/participants_service_spec.rb6
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb4
-rw-r--r--spec/services/projects/update_pages_configuration_service_spec.rb2
-rw-r--r--spec/services/projects/update_pages_service_spec.rb72
-rw-r--r--spec/services/projects/update_service_spec.rb57
-rw-r--r--spec/services/protected_branches/create_service_spec.rb2
-rw-r--r--spec/services/protected_branches/update_service_spec.rb2
-rw-r--r--spec/services/protected_tags/create_service_spec.rb2
-rw-r--r--spec/services/protected_tags/update_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb67
-rw-r--r--spec/services/repair_ldap_blocked_user_service_spec.rb4
-rw-r--r--spec/services/repository_archive_clean_up_service_spec.rb2
-rw-r--r--spec/services/search/global_service_spec.rb10
-rw-r--r--spec/services/search/group_service_spec.rb4
-rw-r--r--spec/services/search/snippet_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb58
-rw-r--r--spec/services/spam_service_spec.rb2
-rw-r--r--spec/services/system_hooks_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb62
-rw-r--r--spec/services/tags/create_service_spec.rb2
-rw-r--r--spec/services/tags/destroy_service_spec.rb2
-rw-r--r--spec/services/test_hook_service_spec.rb14
-rw-r--r--spec/services/test_hooks/project_service_spec.rb188
-rw-r--r--spec/services/test_hooks/system_service_spec.rb82
-rw-r--r--spec/services/todo_service_spec.rb22
-rw-r--r--spec/services/update_release_service_spec.rb4
-rw-r--r--spec/services/update_snippet_service_spec.rb2
-rw-r--r--spec/services/upload_service_spec.rb2
-rw-r--r--spec/services/users/activity_service_spec.rb4
-rw-r--r--spec/services/users/build_service_spec.rb2
-rw-r--r--spec/services/users/create_service_spec.rb2
-rw-r--r--spec/services/users/destroy_service_spec.rb2
-rw-r--r--spec/services/users/migrate_to_ghost_user_service_spec.rb33
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb2
-rw-r--r--spec/services/users/update_service_spec.rb2
-rw-r--r--spec/services/web_hook_service_spec.rb14
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb2
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb2
-rw-r--r--spec/spec_helper.rb38
-rw-r--r--spec/support/api/milestones_shared_examples.rb (renamed from spec/requests/api/milestones_spec.rb)190
-rw-r--r--spec/support/api/schema_matcher.rb9
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb79
-rw-r--r--spec/support/api_helpers.rb18
-rw-r--r--spec/support/capybara_helpers.rb5
-rw-r--r--spec/support/cycle_analytics_helpers.rb4
-rw-r--r--spec/support/devise_helpers.rb14
-rw-r--r--spec/support/dropzone_helper.rb19
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb34
-rw-r--r--spec/support/features/rss_shared_examples.rb4
-rw-r--r--spec/support/forgery_protection.rb11
-rw-r--r--spec/support/gitaly.rb10
-rw-r--r--spec/support/gpg_helpers.rb202
-rw-r--r--spec/support/issuable_shared_examples.rb31
-rw-r--r--spec/support/issue_helpers.rb2
-rw-r--r--spec/support/jira_service_helper.rb2
-rw-r--r--spec/support/json_response_helpers.rb2
-rw-r--r--spec/support/login_helpers.rb30
-rw-r--r--spec/support/malicious_regexp_shared_examples.rb8
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb28
-rw-r--r--spec/support/matchers/have_gitlab_http_status.rb14
-rw-r--r--spec/support/matchers/markdown_matchers.rb4
-rw-r--r--spec/support/merge_request_helpers.rb2
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb4
-rw-r--r--spec/support/redis/redis_shared_examples.rb (renamed from spec/lib/gitlab/redis_spec.rb)82
-rw-r--r--spec/support/routing_helpers.rb3
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb12
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb (renamed from spec/support/api/status_shared_examples.rb)6
-rw-r--r--spec/support/sidekiq.rb10
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb2
-rw-r--r--spec/support/sorting_helper.rb18
-rw-r--r--spec/support/stub_configuration.rb34
-rw-r--r--spec/support/stub_feature_flags.rb8
-rw-r--r--spec/support/test_env.rb7
-rw-r--r--spec/support/unique_ip_check_shared_examples.rb4
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb15
-rw-r--r--spec/uploaders/attachment_uploader_spec.rb2
-rw-r--r--spec/uploaders/avatar_uploader_spec.rb2
-rw-r--r--spec/uploaders/file_mover_spec.rb14
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb4
-rw-r--r--spec/views/ci/status/_badge.html.haml_spec.rb5
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/projects/blob/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb2
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/diffs/_viewer.html.haml_spec.rb2
-rw-r--r--spec/views/projects/jobs/show.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/_commits.html.haml_spec.rb5
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb2
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb2
-rw-r--r--spec/views/projects/pipelines/_stage.html.haml_spec.rb2
-rw-r--r--spec/views/projects/registry/repositories/index.html.haml_spec.rb2
-rw-r--r--spec/views/projects/tags/index.html.haml_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb33
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb47
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb14
-rw-r--r--spec/workers/invalid_gpg_signature_update_worker_spec.rb29
-rw-r--r--spec/workers/post_receive_spec.rb1
-rw-r--r--spec/workers/project_destroy_worker_spec.rb20
-rw-r--r--spec/workers/schedule_update_user_activity_worker_spec.rb2
-rw-r--r--spec/workers/update_user_activity_worker_spec.rb4
-rw-r--r--vendor/gitlab-ci-yml/Docker.gitlab-ci.yml19
-rw-r--r--vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml8
-rw-r--r--vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml6
-rw-r--r--vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml12
-rw-r--r--vendor/licenses.csv560
-rw-r--r--yarn.lock102
2748 files changed, 41692 insertions, 14858 deletions
diff --git a/.flayignore b/.flayignore
index 47597025115..e2d0a2e50c5 100644
--- a/.flayignore
+++ b/.flayignore
@@ -3,3 +3,4 @@ lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
+lib/gitlab/redis/*.rb
diff --git a/.gitignore b/.gitignore
index e529e33530a..3baf640a9c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,9 @@ eslint-report.html
/config/initializers/smtp_settings.rb
/config/initializers/relative_url.rb
/config/resque.yml
+/config/redis.cache.yml
+/config/redis.queues.yml
+/config/redis.shared_state.yml
/config/unicorn.rb
/config/secrets.yml
/config/sidekiq.yml
@@ -60,3 +63,4 @@ eslint-report.html
/.gitlab_workhorse_secret
/webpack-report/
/locale/**/LC_MESSAGES
+/.rspec
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a3ce1de50c2..adde3400107 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,10 +1,20 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-phantomjs-2.1-node-7.1-postgresql-9.6"
-cache:
+.default-cache: &default-cache
key: "ruby-233-with-yarn"
paths:
- - vendor/ruby
- - .yarn-cache/
+ - vendor/ruby
+ - .yarn-cache/
+
+.push-cache: &push-cache
+ cache:
+ <<: *default-cache
+ policy: push
+
+.pull-cache: &pull-cache
+ cache:
+ <<: *default-cache
+ policy: pull
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
@@ -24,11 +34,11 @@ before_script:
- source scripts/prepare_build.sh
stages:
-- build
-- prepare
-- test
-- post-test
-- pages
+ - build
+ - prepare
+ - test
+ - post-test
+ - pages
# Predefined scopes
.dedicated-runner: &dedicated-runner
@@ -41,10 +51,6 @@ stages:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
KNAPSACK_S3_BUCKET: "gitlab-ce-cache"
- cache:
- key: "knapsack"
- paths:
- - knapsack/
artifacts:
expire_in: 31d
paths:
@@ -79,8 +85,9 @@ stages:
- /(^docs[\/-].*|.*-docs$)/
.rspec-knapsack: &rspec-knapsack
- stage: test
<<: *dedicated-runner
+ <<: *pull-cache
+ stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
@@ -110,8 +117,9 @@ stages:
<<: *except-docs
.spinach-knapsack: &spinach-knapsack
- stage: test
<<: *dedicated-runner
+ <<: *pull-cache
+ stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[-2]}
@@ -157,9 +165,13 @@ build-package:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
stage: build
+ cache: {}
when: manual
script:
- scripts/trigger-build
+ only:
+ - //@gitlab-org/gitlab-ce
+ - //@gitlab-org/gitlab-ee
# Prepare and merge knapsack tests
knapsack:
@@ -167,6 +179,11 @@ knapsack:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
+ cache:
+ key: knapsack
+ paths:
+ - knapsack/
+ policy: pull
script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${KNAPSACK_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
@@ -179,7 +196,13 @@ update-knapsack:
<<: *dedicated-runner
<<: *only-canonical-masters
stage: post-test
+ cache:
+ key: knapsack
+ paths:
+ - knapsack/
+ policy: push
script:
+ - retry gem install fog-aws mime-types
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- '[[ -z ${KNAPSACK_S3_BUCKET} ]] || scripts/sync-reports put $KNAPSACK_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
@@ -190,6 +213,8 @@ setup-test-env:
<<: *dedicated-runner
<<: *except-docs
stage: prepare
+ cache:
+ <<: *default-cache
script:
- node --version
- yarn install --pure-lockfile --cache-folder .yarn-cache
@@ -203,72 +228,73 @@ setup-test-env:
- public/assets
- tmp/tests
-rspec-pg 0 20: *rspec-knapsack-pg
-rspec-pg 1 20: *rspec-knapsack-pg
-rspec-pg 2 20: *rspec-knapsack-pg
-rspec-pg 3 20: *rspec-knapsack-pg
-rspec-pg 4 20: *rspec-knapsack-pg
-rspec-pg 5 20: *rspec-knapsack-pg
-rspec-pg 6 20: *rspec-knapsack-pg
-rspec-pg 7 20: *rspec-knapsack-pg
-rspec-pg 8 20: *rspec-knapsack-pg
-rspec-pg 9 20: *rspec-knapsack-pg
-rspec-pg 10 20: *rspec-knapsack-pg
-rspec-pg 11 20: *rspec-knapsack-pg
-rspec-pg 12 20: *rspec-knapsack-pg
-rspec-pg 13 20: *rspec-knapsack-pg
-rspec-pg 14 20: *rspec-knapsack-pg
-rspec-pg 15 20: *rspec-knapsack-pg
-rspec-pg 16 20: *rspec-knapsack-pg
-rspec-pg 17 20: *rspec-knapsack-pg
-rspec-pg 18 20: *rspec-knapsack-pg
-rspec-pg 19 20: *rspec-knapsack-pg
-
-rspec-mysql 0 20: *rspec-knapsack-mysql
-rspec-mysql 1 20: *rspec-knapsack-mysql
-rspec-mysql 2 20: *rspec-knapsack-mysql
-rspec-mysql 3 20: *rspec-knapsack-mysql
-rspec-mysql 4 20: *rspec-knapsack-mysql
-rspec-mysql 5 20: *rspec-knapsack-mysql
-rspec-mysql 6 20: *rspec-knapsack-mysql
-rspec-mysql 7 20: *rspec-knapsack-mysql
-rspec-mysql 8 20: *rspec-knapsack-mysql
-rspec-mysql 9 20: *rspec-knapsack-mysql
-rspec-mysql 10 20: *rspec-knapsack-mysql
-rspec-mysql 11 20: *rspec-knapsack-mysql
-rspec-mysql 12 20: *rspec-knapsack-mysql
-rspec-mysql 13 20: *rspec-knapsack-mysql
-rspec-mysql 14 20: *rspec-knapsack-mysql
-rspec-mysql 15 20: *rspec-knapsack-mysql
-rspec-mysql 16 20: *rspec-knapsack-mysql
-rspec-mysql 17 20: *rspec-knapsack-mysql
-rspec-mysql 18 20: *rspec-knapsack-mysql
-rspec-mysql 19 20: *rspec-knapsack-mysql
-
-spinach-pg 0 10: *spinach-knapsack-pg
-spinach-pg 1 10: *spinach-knapsack-pg
-spinach-pg 2 10: *spinach-knapsack-pg
-spinach-pg 3 10: *spinach-knapsack-pg
-spinach-pg 4 10: *spinach-knapsack-pg
-spinach-pg 5 10: *spinach-knapsack-pg
-spinach-pg 6 10: *spinach-knapsack-pg
-spinach-pg 7 10: *spinach-knapsack-pg
-spinach-pg 8 10: *spinach-knapsack-pg
-spinach-pg 9 10: *spinach-knapsack-pg
-
-spinach-mysql 0 10: *spinach-knapsack-mysql
-spinach-mysql 1 10: *spinach-knapsack-mysql
-spinach-mysql 2 10: *spinach-knapsack-mysql
-spinach-mysql 3 10: *spinach-knapsack-mysql
-spinach-mysql 4 10: *spinach-knapsack-mysql
-spinach-mysql 5 10: *spinach-knapsack-mysql
-spinach-mysql 6 10: *spinach-knapsack-mysql
-spinach-mysql 7 10: *spinach-knapsack-mysql
-spinach-mysql 8 10: *spinach-knapsack-mysql
-spinach-mysql 9 10: *spinach-knapsack-mysql
+rspec-pg 0 25: *rspec-knapsack-pg
+rspec-pg 1 25: *rspec-knapsack-pg
+rspec-pg 2 25: *rspec-knapsack-pg
+rspec-pg 3 25: *rspec-knapsack-pg
+rspec-pg 4 25: *rspec-knapsack-pg
+rspec-pg 5 25: *rspec-knapsack-pg
+rspec-pg 6 25: *rspec-knapsack-pg
+rspec-pg 7 25: *rspec-knapsack-pg
+rspec-pg 8 25: *rspec-knapsack-pg
+rspec-pg 9 25: *rspec-knapsack-pg
+rspec-pg 10 25: *rspec-knapsack-pg
+rspec-pg 11 25: *rspec-knapsack-pg
+rspec-pg 12 25: *rspec-knapsack-pg
+rspec-pg 13 25: *rspec-knapsack-pg
+rspec-pg 14 25: *rspec-knapsack-pg
+rspec-pg 15 25: *rspec-knapsack-pg
+rspec-pg 16 25: *rspec-knapsack-pg
+rspec-pg 17 25: *rspec-knapsack-pg
+rspec-pg 18 25: *rspec-knapsack-pg
+rspec-pg 19 25: *rspec-knapsack-pg
+rspec-pg 20 25: *rspec-knapsack-pg
+rspec-pg 21 25: *rspec-knapsack-pg
+rspec-pg 22 25: *rspec-knapsack-pg
+rspec-pg 23 25: *rspec-knapsack-pg
+rspec-pg 24 25: *rspec-knapsack-pg
+
+rspec-mysql 0 25: *rspec-knapsack-mysql
+rspec-mysql 1 25: *rspec-knapsack-mysql
+rspec-mysql 2 25: *rspec-knapsack-mysql
+rspec-mysql 3 25: *rspec-knapsack-mysql
+rspec-mysql 4 25: *rspec-knapsack-mysql
+rspec-mysql 5 25: *rspec-knapsack-mysql
+rspec-mysql 6 25: *rspec-knapsack-mysql
+rspec-mysql 7 25: *rspec-knapsack-mysql
+rspec-mysql 8 25: *rspec-knapsack-mysql
+rspec-mysql 9 25: *rspec-knapsack-mysql
+rspec-mysql 10 25: *rspec-knapsack-mysql
+rspec-mysql 11 25: *rspec-knapsack-mysql
+rspec-mysql 12 25: *rspec-knapsack-mysql
+rspec-mysql 13 25: *rspec-knapsack-mysql
+rspec-mysql 14 25: *rspec-knapsack-mysql
+rspec-mysql 15 25: *rspec-knapsack-mysql
+rspec-mysql 16 25: *rspec-knapsack-mysql
+rspec-mysql 17 25: *rspec-knapsack-mysql
+rspec-mysql 18 25: *rspec-knapsack-mysql
+rspec-mysql 19 25: *rspec-knapsack-mysql
+rspec-mysql 20 25: *rspec-knapsack-mysql
+rspec-mysql 21 25: *rspec-knapsack-mysql
+rspec-mysql 22 25: *rspec-knapsack-mysql
+rspec-mysql 23 25: *rspec-knapsack-mysql
+rspec-mysql 24 25: *rspec-knapsack-mysql
+
+spinach-pg 0 5: *spinach-knapsack-pg
+spinach-pg 1 5: *spinach-knapsack-pg
+spinach-pg 2 5: *spinach-knapsack-pg
+spinach-pg 3 5: *spinach-knapsack-pg
+spinach-pg 4 5: *spinach-knapsack-pg
+
+spinach-mysql 0 5: *spinach-knapsack-mysql
+spinach-mysql 1 5: *spinach-knapsack-mysql
+spinach-mysql 2 5: *spinach-knapsack-mysql
+spinach-mysql 3 5: *spinach-knapsack-mysql
+spinach-mysql 4 5: *spinach-knapsack-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
+ <<: *pull-cache
variables:
SIMPLECOV: "false"
SETUP_DB: "false"
@@ -277,6 +303,7 @@ spinach-mysql 9 10: *spinach-knapsack-mysql
<<: *ruby-static-analysis
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
stage: test
script:
- bundle exec rake $CI_JOB_NAME
@@ -293,9 +320,9 @@ static-analysis:
# - Check validity of relative links
# - Make sure cURL examples in API docs use the full switches
docs lint:
+ <<: *dedicated-runner
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
- <<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
@@ -338,9 +365,10 @@ ee_compat_check:
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
script:
- bundle exec rake db:migrate:reset
@@ -353,15 +381,17 @@ db:migrate:reset-mysql:
<<: *use-mysql
.migration-paths: &migration-paths
- stage: test
<<: *dedicated-runner
+ <<: *only-canonical-masters
+ <<: *pull-cache
+ stage: test
variables:
SETUP_DB: "false"
- <<: *only-canonical-masters
script:
- git fetch origin v8.14.10
- git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS
+ - cp config/gitlab.yml.example config/gitlab.yml
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS
@@ -377,9 +407,10 @@ migration:path-mysql:
<<: *use-mysql
.db-rollback: &db-rollback
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
@@ -393,9 +424,10 @@ db:rollback-mysql:
<<: *use-mysql
.db-seed_fu: &db-seed_fu
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
variables:
SIZE: "1"
SETUP_DB: "false"
@@ -420,9 +452,10 @@ db:seed_fu-mysql:
# Frontend-related jobs
gitlab:assets:compile:
- stage: test
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: test
dependencies: []
variables:
NODE_ENV: "production"
@@ -440,14 +473,15 @@ gitlab:assets:compile:
name: webpack-report
expire_in: 31d
paths:
- - webpack-report/
+ - webpack-report/
karma:
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
- stage: test
<<: *use-pg
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
+ stage: test
variables:
BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
@@ -465,6 +499,7 @@ karma:
codeclimate:
<<: *except-docs
+ <<: *pull-cache
before_script: []
image: docker:latest
stage: test
@@ -480,10 +515,11 @@ codeclimate:
paths: [codeclimate.json]
coverage:
- stage: post-test
- services: []
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
+ stage: post-test
+ services: []
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true"
@@ -500,7 +536,10 @@ coverage:
lint:javascript:report:
<<: *dedicated-runner
<<: *except-docs
+ <<: *pull-cache
stage: post-test
+ dependencies:
+ - setup-test-env
before_script: []
script:
- find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
@@ -512,9 +551,10 @@ lint:javascript:report:
- eslint-report.html
pages:
+ <<: *dedicated-runner
+ <<: *pull-cache
before_script: []
stage: pages
- <<: *dedicated-runner
dependencies:
- coverage
- karma
@@ -538,6 +578,7 @@ pages:
# rubygems.org in the future.
cache gems:
<<: *dedicated-runner
+ <<: *pull-cache
only:
- tags
variables:
@@ -552,6 +593,8 @@ cache gems:
- master@gitlab-org/gitlab-ee
gitlab_git_test:
+ <<: *pull-cache
+ <<: *except-docs
variables:
SETUP_DB: "false"
script:
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000000..72906051c5c
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+7.5 \ No newline at end of file
diff --git a/.rspec b/.rspec
deleted file mode 100644
index 35f4d7441e0..00000000000
--- a/.rspec
+++ /dev/null
@@ -1,2 +0,0 @@
---color
---format Fuubar
diff --git a/.rubocop.yml b/.rubocop.yml
index 32ec60f540b..f661a29d9d1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -563,7 +563,7 @@ Style/Proc:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
- Max: 57.08
+ Max: 56.96
# This cop checks if the length of a block exceeds some maximum value.
Metrics/BlockLength:
@@ -965,6 +965,10 @@ RSpec/AnyInstance:
RSpec/BeEql:
Enabled: true
+# We don't enforce this as we use this technique in a few places.
+RSpec/BeforeAfterAll:
+ Enabled: false
+
# Check that the first argument to the top level describe is the tested class or
# module.
RSpec/DescribeClass:
@@ -1024,6 +1028,12 @@ RSpec/FilePath:
RSpec/Focus:
Enabled: true
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: is_expected, should
+RSpec/ImplicitExpect:
+ Enabled: true
+ EnforcedStyle: is_expected
+
# Checks for the usage of instance variables.
RSpec/InstanceVariable:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 5ab4692dd60..2ec558e274f 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -6,10 +6,6 @@
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 54
-RSpec/BeforeAfterAll:
- Enabled: false
-
# Offense count: 233
RSpec/EmptyLineAfterFinalLet:
Enabled: false
@@ -24,12 +20,6 @@ RSpec/EmptyLineAfterSubject:
RSpec/HookArgument:
Enabled: false
-# Offense count: 12
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: is_expected, should
-RSpec/ImplicitExpect:
- Enabled: false
-
# Offense count: 11
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: it_behaves_like, it_should_behave_like
diff --git a/.scss-lint.yml b/.scss-lint.yml
index db234ad739c..73f8d27f78c 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -10,7 +10,7 @@ linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
- enabled: false
+ enabled: true
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
@@ -43,10 +43,11 @@ linters:
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
- # - properties, @include declarations with inner @content
+ # - properties
+ # - @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
- enabled: false
+ enabled: true
# `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file.
@@ -93,7 +94,7 @@ linters:
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
- enabled: false
+ enabled: true
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
@@ -133,7 +134,7 @@ linters:
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
- enabled: false
+ enabled: true
# Configure which units are allowed for property values.
PropertyUnits:
@@ -176,6 +177,10 @@ linters:
# Commas in lists should be followed by a space.
SpaceAfterComma:
+ enabled: true
+
+ # Comment literals should be followed by a space.
+ SpaceAfterComment:
enabled: false
# Properties should be formatted with a single space separating the colon
@@ -240,7 +245,7 @@ linters:
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
- enabled: false
+ enabled: true
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec768ffe9b1..580d2357512 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,239 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.4.1 (2017-07-25)
+
+- Fix pipeline_schedules pages throwing error 500 (when ref is empty). !12983
+- Fix editing project with container images present. !13028
+- Fix some invalid entries in PO files. !13032
+- Fix cross site request protection when logging in as a regular user when LDAP is enabled. !13049
+- Fix bug causing metrics files to be truncated. !35420
+- Fix anonymous access to public projects in groups with pending invites.
+- Fixed issue boards sidebar close icon size.
+- Fixed duplicate new milestone buttons when new navigation is turned on.
+- Fix margins in the mini graph for pipeline in commits box.
+
+## 9.4.0 (2017-07-22)
+
+- Add blame view age mapping. !7198 (Jeff Stubler)
+- Add support for image and services configuration in .gitlab-ci.yml. !8578
+- Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients. !9045 (jneen)
+- Use fa-chevron-down on dropdown arrows for consistency. !9659 (TM Lee)
+- Update the devise mail templates to match the design of the pipeline emails. !10483 (Alexis Reigel)
+- Handle renamed submodules in repository browser. !10798 (David Turner)
+- Display all current broadcast messages, not just the last one. !11113 (rickettm)
+- Fix CI/CD status in case there are only allowed to failed jobs in the pipeline. !11166
+- Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname. !11218 (Stefan Hanreich)
+- Moved "Members in a project" menu entry and path locations. !11560
+- Additional Prometheus metrics support. !11712
+- Rename all reserved paths that could have been created. !11713
+- Move uploads from `uploads/system` to `uploads/-/system` to free up `system` as a group name. !11713
+- Fix offline runner detection. !11751 (Alessio Caiazza)
+- Use authorize_update_pipeline_schedule in PipelineSchedulesController. !11846
+- Rollback project repo move if there is an error in Projects::TransferService. !11877
+- Help landing page customizations. !11878 (Robin Bobbitt)
+- Fixes "sign in / Register" active state underline misalignment. !11890 (Frank Sierra)
+- Honor the "Remember me" parameter for OAuth-based login. !11963
+- Instruct user to use personal access token for Git over HTTP. !11986 (Robin Bobbitt)
+- Accept image for avatar in project API. !11988 (Ivan Chernov)
+- Supplement Simplified Chinese translation of Project Page & Repository Page. !11994 (Huang Tao)
+- Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page. !11995 (Huang Tao)
+- Make the revision on the `/help` page clickable. !12016
+- Display issue state in issue links section of merge request widget. !12021
+- Enable support for webpack code-splitting by dynamically setting publicPath at runtime. !12032
+- Replace PhantomJS with headless Chrome for karma test suite. !12036
+- Prevent description change notes when toggling tasks. !12057 (Jared Deckard <jared.deckard@gmail.com>)
+- Update QA Dockerfile to lock Chrome browser version. !12071
+- Fix FIDO U2F for Opera browser. !12082 (Jakub Kramarz and Jonas Kalderstam)
+- Supplement Bulgarian translation of Project Page & Repository Page. !12083 (Lyubomir Vasilev)
+- Removes deleted_at and pending_delete occurrences in Project related queries. !12091
+- Provide hint to create a personal access token for Git over HTTP. !12105 (Robin Bobbitt)
+- Display own user id in account settings page. !12141 (Riccardo Padovani)
+- Accept image for avatar in user API. !12143 (Ivan Chernov)
+- Disable fork button on project limit. !12145 (Ivan Chernov)
+- Added "created_after" and "created_before" params to issuables. !12151 (Kyle Bishop @kybishop)
+- Supplement Portuguese Brazil translation of Project Page & Repository Page. !12156 (Huang Tao)
+- Add review apps to usage metrics. !12185
+- Adding French translations. !12200 (Erwan "Dremor" Georget)
+- Ensures default user limits when external user is unchecked. !12218
+- Provide KUBECONFIG from KubernetesService for runners. !12223
+- Filter archived project in API v3 only if param present. !12245 (Ivan Chernov)
+- Add explicit message when no runners on admin. !12266 (Takuya Noguchi)
+- Split pipelines as internal and external in the usage data. !12277
+- Fix API Scoping. !12300
+- Remove registry image delete button if user cant delete it. !12317 (Ivan Chernov)
+- Allow the feature flags to be enabled/disabled with more granularity. !12357
+- Allow to enable the performance bar per user or Feature group. !12362
+- Rename duplicated variables with the same key for projects. Add environment_scope column to variables and add unique constraint to make sure that no variables could be created with the same key within a project. !12363
+- Add variables to pipelines schedules. !12372
+- Add User#full_private_access? to check if user has access to all private groups & projects. !12373
+- Change milestone endpoint for groups. !12374 (Takuya Noguchi)
+- Improve performance of the pipeline charts page. !12378
+- Add option to run Gitaly on a remote server. !12381
+- #20628 Enable implicit grant in GitLab as OAuth Provider. !12384 (Mateusz Pytel)
+- Replace 'snippets/snippets.feature' spinach with rspec. !12385 (Alexander Randa @randaalex)
+- Add Simplified Chinese translations of Commits Page. !12405 (Huang Tao)
+- Add Traditional Chinese in HongKong translations of Commits Page. !12406 (Huang Tao)
+- Add Traditional Chinese in Taiwan translations of Commits Page. !12407 (Huang Tao)
+- Add Portuguese Brazil translations of Commits Page. !12408 (Huang Tao)
+- Add French translations of Commits Page. !12409 (Huang Tao)
+- Add Esperanto translations of Commits Page. !12410 (Huang Tao)
+- Add Bulgarian translations of Commits Page. !12411 (Huang Tao)
+- Remove bin/ci/upgrade.rb as not working all. !12414 (Takuya Noguchi)
+- Store merge request ref_fetched status in the database. !12424
+- Replace 'dashboard/merge_requests' spinach with rspec. !12440 (Alexander Randa (@randaalex))
+- Add Esperanto translations for Cycle Analytics, Project, and Repository pages. !12442 (Huang Tao)
+- Allow unauthenticated access to the /api/v4/users API. !12445
+- Drop GFM support for the title of Milestone/MergeRequest in template. !12451 (Takuya Noguchi)
+- Replace 'dashboard/todos' spinach with rspec. !12453 (Alexander Randa (@randaalex))
+- Cache open issue and merge request counts for project tabs to speed up project pages. !12457
+- Introduce cache policies for CI jobs. !12483
+- Improve support for external issue references. !12485
+- Fix errors caused by attempts to report already blocked or deleted users. !12502 (Horacio Bertorello)
+- Allow customize CI config path. !12509 (Keith Pope)
+- Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page. !12514 (Huang Tao)
+- Closes any open Autocomplete of the markdown editor when the form is closed. !12521
+- Inserts exact matches of name, username and email to the top of the search list. !12525
+- Use smaller min-width for dropdown-menu-nav only on mobile. !12528 (Takuya Noguchi)
+- Hide archived project labels from group issue tracker. !12547 (Horacio Bertorello)
+- Replace 'dashboard/new-project.feature' spinach with rspec. !12550 (Alexander Randa (@randaalex))
+- Remove group modal like remove project modal (requires typing + confirmation). !12569 (Diego Souza)
+- Add Italian translation of Cycle Analytics Page & Project Page & Repository Page. !12578 (Huang Tao)
+- Add Group secret variables. !12582
+- Update jobs page output to have a scrollable page. !12587
+- Add user projects API. !12596 (Ivan Chernov)
+- Allow creation of files and directories with spaces through Web UI. !12608
+- Improve members view on mobile. !12619
+- Fixed the chart legend not being set correctly. !12628
+- Add Italian translations of Commits Page. !12645 (Huang Tao)
+- Allow admins to disable all restricted visibility levels. !12649
+- Allow admins to retrieve user agent details for an issue or snippet. !12655
+- Update welcome page UX for new users. !12662
+- N+1 problems on milestone page. !12670 (Takuya Noguchi)
+- Upgrade GitLab Workhorse to v2.3.0. !12676
+- Remove option to disable Gitaly. !12677
+- Improve the performance of the project list API. !12679
+- Add creation time filters to user search API for admins. !12682
+- Add Japanese translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12693 (Huang Tao)
+- Undo adding the /reassign quick action. !12701
+- Fix dashboard labels dropdown. !12708
+- Username and password are no longer stripped from import url on mirror update. !12725
+- Add Russian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12743 (Huang Tao)
+- Add Ukrainian translations for Cycle Analytics & Project pages & Repository pages & Commits pages & Pipeline Charts. !12744 (Huang Tao)
+- Prevent bad data being added to application settings when Redis is unavailable. !12750
+- Do not show pipeline schedule button for non-member. !12757 (Takuya Noguchi)
+- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
+- Recover from renaming project that has container images. !12840
+- Exact matches of username and email are now on top of the user search. !12868
+- Use Ghost user for last_edited_by and merge_user when original user is deleted. !12933
+- Fix docker tag reference routing constraints. !12961
+- Optimize creation of commit API by using Repository#commit instead of Repository#commits.
+- Speed up used languages calculation on charts page.
+- Make loading new merge requests (those created after the 9.4 upgrade) faster.
+- Ensure participants for issues, merge requests, etc. are calculated correctly when sending notifications.
+- Handle nameless legacy jobs.
+- Bump Faraday and dependent OAuth2 gem version to support no_proxy variable.
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Render CI statuses with warnings in orange.
+- Document the Delete Merged Branches functionality.
+- Add wells to admin dashboard overview to fix spacing problems.
+- Removes hover style for nodes that are either links or buttons in the pipeline graph.
+- more visual contrast in pagination widget.
+- Deprecate Healthcheck Access Token in favor of IP whitelist.
+- Drop GFM support for issuable title on milestone for consistency and performance. (Takuya Noguchi)
+- fix left & right padding on sidebar.
+- Cleanup minor UX issues in the performance dashboard.
+- Remove two columned layout from project member settings.
+- Make font size of contextual sub menu items 14px.
+- Fix vertical space in job details sidebar.
+- Fix alignment of controls in mr issuable list.
+- Add wip message to new navigation preference section.
+- Add group members counting and plan related data on namespaces API.
+- Fix spacing on runner buttons.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+- Change order of monospace fonts to fix bug on some linux distros.
+- Limit commit & snippets comments width.
+- Fixed dashboard milestone tabs not loading.
+- Detect if file that appears to be text in the first 1024 bytes is actually binary afer loading all data.
+- Fix inconsistent display of the "Browse files" button in the commit list.
+- Implement diff viewers.
+- Fix 'New merge request' button for users who don't have push access to canonical project.
+- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
+- Show group name instead of path on group page.
+- Don't check if MailRoom is running on Omnibus.
+- Limit OpenGraph image size to 64x64.
+- Don't show auxiliary blob viewer for README when there is no wiki.
+- Strip trailing whitespace in relative submodule URL.
+- Update /target_branch slash command description to be more consistent.
+- Remove unnecessary top padding on group MR index.
+- Added printing_merge_requst_link_enabled to the API. (David Turner <dturner@twosigma.com>)
+- Re-enable realtime for environments table.
+- Create responsive mobile view for pipelines table.
+- Adds realtime feature to job show view header and sidebar info. Updates UX.
+- Use color inputs for broadcast messages.
+- Center dropdown for mini graph.
+- Users can subscribe to group labels on the group labels page.
+- Add issuable-list class to shared mr/issue lists to fix new responsive layout design.
+- Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor of "slash commands".
+- Don't mark empty MRs as merged on push to the target branch.
+- Improve issue rendering performance with lots of notes from other users.
+- Fixed overflow on mobile screens for the slash commands.
+- Fix an infinite loop when handling user-supplied regular expressions.
+- Fixed sidebar not collapsing on merge requests in mobile screens.
+- Speed up project removals by adding foreign keys with cascading deletes to various tables.
+- Fix mobile view of files view buttons.
+- Fixed dropdown filter input not focusing after transition.
+- Fixed GFM references not being included when updating issues inline.
+- Remove issues/merge requests drag n drop and sorting from milestone view.
+- Add native group milestones.
+- Fix API bug accepting wrong parameter to create merge request.
+- Clean up UI of issuable lists and make more responsive.
+- Improve the overall UX for the new monitoring dashboard.
+- Fixed the y_label not setting correctly for each graph on the monitoring dashboard.
+- Changed utilities imports from ~ to relative paths.
+- Remove unused space in sidebar todo toggle when not signed in.
+- Limit the width of the projects README text.
+- Add a simple mode to merge request API.
+- Make Project#ensure_repository force create a repo.
+- Use uploads/system directory for personal snippets.
+- Defer project destroys within a namespace in Groups::DestroyService#async_execute.
+- Log rescued exceptions to Sentry.
+- Remove remaining N+1 queries in merge requests API with emojis and labels.
+
+## 9.3.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.3.8 (2017-07-19)
+
+- Improve support for external issue references. !12485
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Use uploads/system directory for personal snippets.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
+## 9.3.7 (2017-07-18)
+
+- Prevent bad data being added to application settings when Redis is unavailable. !12750
+- Return `is_admin` attribute in the GET /user endpoint for admins. !12811
+
+## 9.3.6 (2017-07-12)
+
+- Fix API Scoping. !12300
+- Username and password are no longer stripped from import url on mirror update. !12725
+- Fix issues with non-UTF8 filenames by always fixing the encoding of tree and blob paths.
+- Fixed GFM references not being included when updating issues inline.
+
+## 9.3.5 (2017-07-05)
+
+- Remove "Remove from board" button from backlog and closed list. !12430
+- Do not delete protected branches when deleting all merged branches. !12624
+- Set default for Remove source branch to false.
+- Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion.
+- Expires full_path cache after a repository is renamed/transferred.
+
## 9.3.4 (2017-07-03)
-- No changes.
+- Update gitlab-shell to 5.1.1 !12615
## 9.3.3 (2017-06-30)
@@ -243,6 +473,17 @@ entry.
- Remove foreigh key on ci_trigger_schedules only if it exists.
- Allow translation of Pipeline Schedules.
+## 9.2.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.2.8 (2017-07-19)
+
+- Improve support for external issue references. !12485
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
## 9.2.7 (2017-06-21)
- Reinstate is_admin flag in users api when authenticated user is an admin. !12211 (rickettm)
@@ -487,6 +728,17 @@ entry.
- Fix preemptive scroll bar on user activity calendar.
- Pipeline chat notifications convert seconds to minutes and hours.
+## 9.1.9 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.1.8 (2017-07-19)
+
+- Improve support for external issue references. !12485
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
## 9.1.7 (2017-06-07)
- No changes.
@@ -799,6 +1051,16 @@ entry.
- Only send chat notifications for the default branch.
- Don't fill in the default kubernetes namespace.
+## 9.0.12 (2017-07-20)
+
+- Fix an infinite loop when handling user-supplied regular expressions.
+
+## 9.0.11 (2017-07-19)
+
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+- Remove uploads/appearance symlink. A leftover from a previous migration.
+
## 9.0.10 (2017-06-07)
- No changes.
@@ -1169,6 +1431,11 @@ entry.
- Change development tanuki favicon colors to match logo color order.
- API issues - support filtering by iids.
+## 8.17.7 (2017-07-19)
+
+- Renders 404 if given project is not readable by the user on Todos dashboard.
+- Fix incorrect project authorizations.
+
## 8.17.6 (2017-05-05)
- Enforce project features when searching blobs and wikis.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 89e505709a3..12fb34b24be 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -31,7 +31,7 @@ _This notice should stay as the first item in the CONTRIBUTING.MD file._
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- - [Technical debt](#technical-debt)
+ - [Technical and UX debt](#technical-and-ux-debt)
- [Stewardship](#stewardship)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
@@ -114,8 +114,8 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc.
-- Subject: ~wiki, ~"container registry", ~ldap, ~api, etc.
-- Team: ~CI, ~Discussion, ~Edge, ~Frontend, ~Platform, etc.
+- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
+- Team: ~CI, ~Discussion, ~Edge, ~Platform, etc.
- Priority: ~Deliverable, ~Stretch
All labels, their meaning and priority are defined on the
@@ -278,7 +278,7 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label `feature proposal` to the issue or add the following
+members to add the label ~"feature proposal" to the issue or add the following
code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
@@ -344,27 +344,29 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
-### Technical debt
+### Technical and UX debt
-In order to track things that can be improved in GitLab's codebase, we created
-the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+In order to track things that can be improved in GitLab's codebase,
+we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+For user experience improvements, we use the ~"UX debt" label.
-This label should be added to issues that describe things that can be improved,
-shortcuts that have been taken, code that needs refactoring, features that need
-additional attention, and all other things that have been left behind due to
-high velocity of development.
+These labels should be added to issues that describe things that can be improved,
+shortcuts that have been taken, features that need additional attention, and all
+other things that have been left behind due to high velocity of development.
+For example, code that needs refactoring should use the ~"technical debt" label,
+user experience refinements should use the ~"UX debt" label.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
-can be combined with the `technical debt` label, to make it easier to schedule
+can be combined with these labels, to make it easier to schedule
the improvements for a release.
-Issues tagged with the `technical debt` label have the same priority like issues
+Issues tagged with these labels have the same priority like issues
that describe a new feature to be introduced in GitLab, and should be scheduled
for a release by the appropriate person.
-Make sure to mention the merge request that the `technical debt` issue is
-associated with in the description of the issue.
+Make sure to mention the merge request that the ~"technical debt" issue or
+~"UX debt" issue is associated with in the description of the issue.
### Stewardship
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index a803cc227fe..ca222b7cf39 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.14.0
+0.23.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 17b2ccd9bf9..8f0916f768f 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-0.4.3
+0.5.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index ac14c3dfaa8..8a30e8f94a3 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.1.1
+5.4.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ccbccc3dc62..4a36342fcab 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-2.2.0
+3.0.0
diff --git a/Gemfile b/Gemfile
index 71d2eb1557c..aa38090e44a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,6 @@ source 'https://rubygems.org'
gem 'rails', '4.2.8'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
-gem 'bootsnap', '~> 1.1'
# Responders respond_to and respond_with
gem 'responders', '~> 2.0'
@@ -13,10 +12,11 @@ gem 'sprockets', '~> 3.7.0'
gem 'default_value_for', '~> 3.0.0'
# Supported DBs
-gem 'mysql2', '~> 0.3.16', group: :mysql
+gem 'mysql2', '~> 0.4.5', group: :mysql
gem 'pg', '~> 0.18.2', group: :postgres
-gem 'rugged', '~> 0.25.1.1'
+gem 'rugged', '~> 0.26.0'
+gem 'grape-route-helpers', '~> 2.0.0'
gem 'faraday', '~> 0.12'
@@ -38,7 +38,7 @@ gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
-gem 'omniauth-authentiq', '~> 0.3.0'
+gem 'omniauth-authentiq', '~> 0.3.1'
gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt', '~> 1.5.6'
@@ -58,10 +58,14 @@ gem 'validates_hostname', '~> 1.0.6'
# Browser detection
gem 'browser', '~> 2.2'
+# GPG
+gem 'gpgme'
+
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
+gem 'gitlab_omniauth-ldap', '~> 2.0.3', require: 'omniauth-ldap'
+gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
@@ -72,7 +76,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
-gem 'grape', '~> 0.19.0'
+gem 'grape', '~> 0.19.2'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
@@ -92,7 +96,7 @@ gem 'carrierwave', '~> 1.1'
gem 'dropzonejs-rails', '~> 0.7.1'
# for backups
-gem 'fog-aws', '~> 0.9'
+gem 'fog-aws', '~> 1.4'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
@@ -164,6 +168,9 @@ gem 'rainbow', '~> 2.2'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
+# Linear-time regex library for untrusted regular expressions
+gem 're2', '~> 1.1.1'
+
# Misc
gem 'version_sorter', '~> 2.1.0'
@@ -238,7 +245,6 @@ gem 'webpack-rails', '~> 0.9.10'
gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6'
-gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.3.8'
@@ -251,11 +257,10 @@ gem 'jquery-rails', '~> 4.1.0'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
-gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 2.4.0'
+gem 'sentry-raven', '~> 2.5.3'
gem 'premailer-rails', '~> 1.9.7'
@@ -271,7 +276,7 @@ gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql
-gem 'peek-performance_bar', '~> 1.2.1'
+gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
@@ -284,7 +289,8 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
- gem 'prometheus-client-mmap', '~>0.7.0.beta5'
+ gem 'prometheus-client-mmap', '~>0.7.0.beta11'
+ gem 'raindrops', '~> 0.18'
end
group :development do
@@ -335,7 +341,7 @@ group :development, :test do
gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.15.0', require: false
- gem 'scss_lint', '~> 0.47.0', require: false
+ gem 'scss_lint', '~> 0.54.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false
@@ -352,7 +358,7 @@ group :development, :test do
end
group :test do
- gem 'shoulda-matchers', '~> 2.8.0', require: false
+ gem 'shoulda-matchers', '~> 3.1.2', require: false
gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.6.2'
gem 'webmock', '~> 2.3.2'
@@ -385,10 +391,13 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client
-gem 'gitaly', '~> 0.9.0'
+gem 'gitaly', '~> 0.18.0'
gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
gem 'flipper', '~> 0.10.2'
gem 'flipper-active_record', '~> 0.10.2'
+
+# Structured logging
+gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index f4ddd30da1b..d124cd00ee0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -83,8 +83,6 @@ GEM
bindata (2.3.5)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- bootsnap (1.1.1)
- msgpack (~> 1.0)
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
@@ -124,13 +122,6 @@ GEM
coderay (1.1.1)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- coffee-rails (4.1.1)
- coffee-script (>= 2.2.0)
- railties (>= 4.0.0, < 5.1.x)
- coffee-script (2.4.1)
- coffee-script-source
- execjs
- coffee-script-source (1.10.0)
colorize (0.7.7)
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
@@ -188,7 +179,7 @@ GEM
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
- excon (0.55.0)
+ excon (0.57.1)
execjs (2.6.0)
expression_parser (0.9.0)
extlib (0.9.16)
@@ -224,26 +215,26 @@ GEM
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (0.13.0)
+ fog-aws (1.4.0)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
- fog-core (1.44.1)
+ fog-core (1.44.3)
builder
excon (~> 0.49)
formatador (~> 0.2)
- fog-google (0.5.0)
+ fog-google (0.5.3)
fog-core
fog-json
fog-xml
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
- fog-local (0.3.0)
+ fog-local (0.3.1)
fog-core (~> 1.27)
- fog-openstack (0.1.6)
- fog-core (>= 1.39)
+ fog-openstack (0.1.21)
+ fog-core (>= 1.40)
fog-json (>= 1.0)
ipaddress (>= 0.8)
fog-rackspace (0.1.1)
@@ -278,7 +269,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly (0.9.0)
+ gitaly (0.18.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -297,11 +288,11 @@ GEM
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
gitlab-markup (1.5.1)
- gitlab_omniauth-ldap (1.2.1)
- net-ldap (~> 0.9)
- omniauth (~> 1.0)
- pyu-ruby-sasl (~> 0.0.3.1)
- rubyntlm (~> 0.3)
+ gitlab_omniauth-ldap (2.0.3)
+ net-ldap (~> 0.16)
+ omniauth (~> 1.3)
+ pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
+ rubyntlm (~> 0.5)
globalid (0.3.7)
activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.1)
@@ -341,19 +332,25 @@ GEM
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
- grape (0.19.1)
+ gpgme (2.0.13)
+ mini_portile2 (~> 2.1)
+ grape (0.19.2)
activesupport
builder
hashie (>= 2.1.0)
multi_json (>= 1.3.2)
multi_xml (>= 0.5.2)
- mustermann-grape (~> 0.4.0)
+ mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
+ grape-route-helpers (2.0.0)
+ activesupport
+ grape (~> 0.16, >= 0.16.0)
+ rake
grpc (1.4.0)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
@@ -452,6 +449,10 @@ GEM
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
+ lograge (0.5.1)
+ actionpack (>= 4, < 5.2)
+ activesupport (>= 4, < 5.2)
+ railties (>= 4, < 5.2)
loofah (2.0.3)
nokogiri (>= 1.5.9)
mail (2.6.5)
@@ -465,17 +466,14 @@ GEM
minitest (5.7.0)
mmap2 (2.2.7)
mousetrap-rails (1.4.6)
- msgpack (1.1.0)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
- mustermann (0.4.0)
- tool (~> 0.2)
- mustermann-grape (0.4.0)
- mustermann (= 0.4.0)
- mysql2 (0.3.20)
- net-ldap (0.12.1)
- net-ssh (3.0.1)
+ mustermann (1.0.0)
+ mustermann-grape (1.0.0)
+ mustermann (~> 1.0.0)
+ mysql2 (0.4.5)
+ net-ldap (0.16.0)
netrc (0.11.0)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
@@ -495,7 +493,7 @@ GEM
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
- omniauth-authentiq (0.3.0)
+ omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0)
@@ -564,7 +562,7 @@ GEM
atomic (>= 1.0.0)
mysql2
peek
- peek-performance_bar (1.2.1)
+ peek-performance_bar (1.3.0)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
@@ -599,8 +597,8 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
- prometheus-client-mmap (0.7.0.beta5)
- mmap2 (~> 2.2.6)
+ prometheus-client-mmap (0.7.0.beta11)
+ mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -658,12 +656,13 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.2.2)
rake
- raindrops (0.17.0)
+ raindrops (0.18.0)
rake (10.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rdoc (4.2.2)
json (~> 1.4)
+ re2 (1.1.1)
recaptcha (3.0.0)
json
recursive-open-struct (1.0.0)
@@ -747,12 +746,12 @@ GEM
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
- rubyntlm (0.5.2)
+ rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.1)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
- rugged (0.25.1.1)
+ rugged (0.26.0)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -766,24 +765,24 @@ GEM
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
- scss_lint (0.47.1)
- rake (>= 0.9, < 11)
- sass (~> 3.4.15)
+ scss_lint (0.54.0)
+ rake (>= 0.9, < 13)
+ sass (~> 3.4.20)
securecompare (1.0.0)
seed-fu (2.3.6)
activerecord (>= 3.1)
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
- sentry-raven (2.4.0)
+ sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
sham_rack (1.3.6)
rack
- shoulda-matchers (2.8.0)
- activesupport (>= 3.0.0)
- sidekiq (5.0.0)
+ shoulda-matchers (3.1.2)
+ activesupport (>= 4.0.0)
+ sidekiq (5.0.4)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
@@ -856,7 +855,6 @@ GEM
timfel-krb5-auth (0.8.3)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
- tool (0.2.3)
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
@@ -930,7 +928,6 @@ DEPENDENCIES
benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0)
binding_of_caller (~> 0.7.2)
- bootsnap (~> 1.1)
bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0)
@@ -943,7 +940,6 @@ DEPENDENCIES
charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
- coffee-rails (~> 4.1.0)
concurrent-ruby (~> 1.0.5)
connection_pool (~> 2.0)
creole (~> 0.5.0)
@@ -966,7 +962,7 @@ DEPENDENCIES
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
fog-aliyun (~> 0.1.0)
- fog-aws (~> 0.9)
+ fog-aws (~> 1.4)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
@@ -980,17 +976,19 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly (~> 0.9.0)
+ gitaly (~> 0.18.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1)
- gitlab_omniauth-ldap (~> 1.2.1)
+ gitlab_omniauth-ldap (~> 2.0.3)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
- grape (~> 0.19.0)
+ gpgme
+ grape (~> 0.19.2)
grape-entity (~> 0.6.0)
+ grape-route-helpers (~> 2.0.0)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
@@ -1011,20 +1009,21 @@ DEPENDENCIES
letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0)
licensee (~> 8.7.0)
+ lograge (~> 0.5)
loofah (~> 2.0.3)
mail_room (~> 0.9.1)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
- mysql2 (~> 0.3.16)
- net-ssh (~> 3.0.1)
+ mysql2 (~> 0.4.5)
+ net-ldap
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
- omniauth-authentiq (~> 0.3.0)
+ omniauth-authentiq (~> 0.3.1)
omniauth-azure-oauth2 (~> 0.0.6)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 4.0.0)
@@ -1043,7 +1042,7 @@ DEPENDENCIES
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0)
- peek-performance_bar (~> 1.2.1)
+ peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
@@ -1051,7 +1050,7 @@ DEPENDENCIES
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.7)
- prometheus-client-mmap (~> 0.7.0.beta5)
+ prometheus-client-mmap (~> 0.7.0.beta11)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1)
@@ -1062,8 +1061,10 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9)
rainbow (~> 2.2)
+ raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rdoc (~> 4.2)
+ re2 (~> 1.1.1)
recaptcha (~> 3.0)
redcarpet (~> 3.4)
redis (~> 3.2)
@@ -1083,16 +1084,16 @@ DEPENDENCIES
ruby-prof (~> 0.16.2)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
- rugged (~> 0.25.1.1)
+ rugged (~> 0.26.0)
sanitize (~> 2.0)
sass-rails (~> 5.0.6)
- scss_lint (~> 0.47.0)
+ scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
- sentry-raven (~> 2.4.0)
+ sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
- shoulda-matchers (~> 2.8.0)
+ shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.0)
sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4)
diff --git a/README.md b/README.md
index 59de828e1ac..9309922ae39 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
## Test coverage
- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
-- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
+- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
## Canonical source
diff --git a/VERSION b/VERSION
index d821c124047..027fe8dd2cf 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-9.3.0-pre
+9.5.0-pre
diff --git a/app/assets/images/new_nav.png b/app/assets/images/new_nav.png
index 8879d26d341..f98ca15d787 100644
--- a/app/assets/images/new_nav.png
+++ b/app/assets/images/new_nav.png
Binary files differ
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
index 36fe8a7184f..27312d718b0 100644
--- a/app/assets/javascripts/blob/notebook/index.js
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -51,8 +51,9 @@ export default () => {
methods: {
loadFile() {
this.$http.get(el.dataset.endpoint)
+ .then(response => response.json())
.then((res) => {
- this.json = res.json();
+ this.json = res;
this.loading = false;
})
.catch((e) => {
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index b94009ee76b..88b054b76e6 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -81,8 +81,9 @@ $(() => {
mounted () {
Store.disabled = this.disabled;
gl.boardService.all()
+ .then(response => response.json())
.then((resp) => {
- resp.json().forEach((board) => {
+ resp.forEach((board) => {
const list = Store.addList(board, this.defaultAvatar);
if (list.type === 'closed') {
@@ -97,7 +98,8 @@ $(() => {
Store.addBlankState();
this.loading = false;
- }).catch(() => new Flash('An error occurred. Please try again.'));
+ })
+ .catch(() => new Flash('An error occurred. Please try again.'));
},
methods: {
updateTokens() {
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js b/app/assets/javascripts/boards/components/board_blank_state.js
index 870e115bd1a..e7f16899362 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js
+++ b/app/assets/javascripts/boards/components/board_blank_state.js
@@ -64,8 +64,9 @@ export default {
// Save the labels
gl.boardService.generateDefaultLists()
- .then((resp) => {
- resp.json().forEach((listObj) => {
+ .then(resp => resp.json())
+ .then((data) => {
+ data.forEach((listObj) => {
const list = Store.findList('title', listObj.title);
list.id = listObj.id;
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index 6356c266ee2..1d36519c75c 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -88,9 +88,9 @@ gl.issueBoards.IssuesModal = Vue.extend({
return gl.boardService.getBacklog(queryData(this.filter.path, {
page: this.page,
per: this.perPage,
- })).then((res) => {
- const data = res.json();
-
+ }))
+ .then(resp => resp.json())
+ .then((data) => {
if (clearIssues) {
this.issues = [];
}
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index b4b09b3876e..08f7c5ddcd2 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -40,9 +40,8 @@ class List {
save () {
return gl.boardService.createList(this.label.id)
- .then((resp) => {
- const data = resp.json();
-
+ .then(resp => resp.json())
+ .then((data) => {
this.id = data.id;
this.type = data.list_type;
this.position = data.position;
@@ -91,8 +90,8 @@ class List {
}
return gl.boardService.getIssuesForList(this.id, data)
- .then((resp) => {
- const data = resp.json();
+ .then(resp => resp.json())
+ .then((data) => {
this.loading = false;
this.issuesSize = data.size;
@@ -109,8 +108,8 @@ class List {
this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue)
- .then((resp) => {
- const data = resp.json();
+ .then(resp => resp.json())
+ .then((data) => {
issue.id = data.iid;
if (this.issuesSize > 1) {
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index db9bced2f89..3742507b236 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -23,11 +23,6 @@ class BoardService {
url: bulkUpdatePath,
},
});
-
- Vue.http.interceptors.push((request, next) => {
- request.headers['X-CSRF-Token'] = $.rails.csrfToken();
- next();
- });
}
all () {
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 60103155ce0..1dfa064acfd 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -13,25 +13,21 @@ window.Build = (function () {
this.options = options || $('.js-build-options').data();
this.pageUrl = this.options.pageUrl;
- this.buildUrl = this.options.buildUrl;
this.buildStatus = this.options.buildStatus;
this.state = this.options.logState;
this.buildStage = this.options.buildStage;
this.$document = $(document);
this.logBytes = 0;
- this.scrollOffsetPadding = 30;
this.hasBeenScrolled = false;
this.updateDropdown = this.updateDropdown.bind(this);
this.getBuildTrace = this.getBuildTrace.bind(this);
- this.scrollToBottom = this.scrollToBottom.bind(this);
- this.$body = $('body');
this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh');
this.$truncatedInfo = $('.js-truncated-info');
this.$buildTraceOutput = $('.js-build-output');
- this.$scrollContainer = $('.js-scroll-container');
+ this.$topBar = $('.js-top-bar');
// Scroll controllers
this.$scrollTopBtn = $('.js-scroll-up');
@@ -63,13 +59,22 @@ window.Build = (function () {
.off('click')
.on('click', this.scrollToBottom.bind(this));
- const scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
+ this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
- this.$scrollContainer
+ $(window)
.off('scroll')
.on('scroll', () => {
- this.hasBeenScrolled = true;
- scrollThrottled();
+ const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
+ if (contentHeight > this.windowSize) {
+ // means the user did not scroll, the content was updated.
+ this.windowSize = contentHeight;
+ } else {
+ // User scrolled
+ this.hasBeenScrolled = true;
+ this.toggleScrollAnimation(false);
+ }
+
+ this.scrollThrottled();
});
$(window)
@@ -77,59 +82,73 @@ window.Build = (function () {
.on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
this.updateArtifactRemoveDate();
+ this.initAffixTopArea();
- // eslint-disable-next-line
- this.getBuildTrace()
- .then(() => this.toggleScroll())
- .then(() => {
- if (!this.hasBeenScrolled) {
- this.scrollToBottom();
- }
- })
- .then(() => this.verifyTopPosition());
+ this.getBuildTrace();
}
+ Build.prototype.initAffixTopArea = function () {
+ /**
+ If the browser does not support position sticky, it returns the position as static.
+ If the browser does support sticky, then we allow the browser to handle it, if not
+ then we default back to Bootstraps affix
+ **/
+ if (this.$topBar.css('position') !== 'static') return;
+
+ const offsetTop = this.$buildTrace.offset().top;
+
+ this.$topBar.affix({
+ offset: {
+ top: offsetTop,
+ },
+ });
+ };
+
Build.prototype.canScroll = function () {
- return (this.$scrollContainer.prop('scrollHeight') - this.scrollOffsetPadding) > this.$scrollContainer.height();
+ return document.body.scrollHeight > window.innerHeight;
};
- /**
- * | | Up | Down |
- * |--------------------------|----------|----------|
- * | on scroll bottom | active | disabled |
- * | on scroll top | disabled | active |
- * | no scroll | disabled | disabled |
- * | on.('scroll') is on top | disabled | active |
- * | on('scroll) is on bottom | active | disabled |
- *
- */
Build.prototype.toggleScroll = function () {
- const currentPosition = this.$scrollContainer.scrollTop();
- const bottomScroll = currentPosition + this.$scrollContainer.innerHeight();
+ const currentPosition = document.body.scrollTop;
+ const windowHeight = window.innerHeight;
if (this.canScroll()) {
- if (currentPosition === 0) {
+ if (currentPosition > 0 &&
+ (document.body.scrollHeight - currentPosition !== windowHeight)) {
+ // User is in the middle of the log
+
+ this.toggleDisableButton(this.$scrollTopBtn, false);
+ this.toggleDisableButton(this.$scrollBottomBtn, false);
+ } else if (currentPosition === 0) {
+ // User is at Top of Build Log
+
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
- } else if (bottomScroll === this.$scrollContainer.prop('scrollHeight')) {
+ } else if (document.body.scrollHeight - currentPosition === windowHeight) {
+ // User is at the bottom of the build log.
+
this.toggleDisableButton(this.$scrollTopBtn, false);
this.toggleDisableButton(this.$scrollBottomBtn, true);
- } else {
- this.toggleDisableButton(this.$scrollTopBtn, false);
- this.toggleDisableButton(this.$scrollBottomBtn, false);
}
+ } else {
+ this.toggleDisableButton(this.$scrollTopBtn, true);
+ this.toggleDisableButton(this.$scrollBottomBtn, true);
}
};
- Build.prototype.scrollToTop = function () {
+ Build.prototype.scrollDown = function () {
+ document.body.scrollTop = document.body.scrollHeight;
+ };
+
+ Build.prototype.scrollToBottom = function () {
+ this.scrollDown();
this.hasBeenScrolled = true;
- this.$scrollContainer.scrollTop(0);
this.toggleScroll();
};
- Build.prototype.scrollToBottom = function () {
+ Build.prototype.scrollToTop = function () {
+ document.body.scrollTop = 0;
this.hasBeenScrolled = true;
- this.$scrollContainer.scrollTop(this.$scrollContainer.prop('scrollHeight'));
this.toggleScroll();
};
@@ -142,47 +161,6 @@ window.Build = (function () {
this.$scrollBottomBtn.toggleClass('animate', toggle);
};
- /**
- * Build trace top position depends on the space ocupied by the elments rendered before
- */
- Build.prototype.verifyTopPosition = function () {
- const $buildPage = $('.build-page');
-
- const $flashError = $('.alert-wrapper');
- const $header = $('.build-header', $buildPage);
- const $runnersStuck = $('.js-build-stuck', $buildPage);
- const $startsEnvironment = $('.js-environment-container', $buildPage);
- const $erased = $('.js-build-erased', $buildPage);
- const prependTopDefault = 20;
-
- // header + navigation + margin
- let topPostion = 168;
-
- if ($header.length) {
- topPostion += $header.outerHeight();
- }
-
- if ($runnersStuck.length) {
- topPostion += $runnersStuck.outerHeight();
- }
-
- if ($startsEnvironment.length) {
- topPostion += $startsEnvironment.outerHeight() + prependTopDefault;
- }
-
- if ($erased.length) {
- topPostion += $erased.outerHeight() + prependTopDefault;
- }
-
- if ($flashError.length) {
- topPostion += $flashError.outerHeight() + prependTopDefault;
- }
-
- this.$buildTrace.css({
- top: topPostion,
- });
- };
-
Build.prototype.initSidebar = function () {
this.$sidebar = $('.js-build-sidebar');
this.$sidebar.niceScroll();
@@ -200,6 +178,8 @@ window.Build = (function () {
this.state = log.state;
}
+ this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
+
if (log.append) {
this.$buildTraceOutput.append(log.html);
this.logBytes += log.size;
@@ -227,14 +207,7 @@ window.Build = (function () {
}
Build.timeout = setTimeout(() => {
- //eslint-disable-next-line
- this.getBuildTrace()
- .then(() => {
- if (!this.hasBeenScrolled) {
- this.scrollToBottom();
- }
- })
- .then(() => this.verifyTopPosition());
+ this.getBuildTrace();
}, 4000);
} else {
this.$buildRefreshAnimation.remove();
@@ -247,7 +220,13 @@ window.Build = (function () {
})
.fail(() => {
this.$buildRefreshAnimation.remove();
- });
+ })
+ .then(() => {
+ if (!this.hasBeenScrolled) {
+ this.scrollDown();
+ }
+ })
+ .then(() => this.toggleScroll());
};
Build.prototype.shouldHideSidebarForViewport = function () {
@@ -259,14 +238,11 @@ window.Build = (function () {
const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
const $toggleButton = $('.js-sidebar-build-toggle-header');
- this.$buildTrace
- .toggleClass('sidebar-expanded', shouldShow)
- .toggleClass('sidebar-collapsed', shouldHide);
this.$sidebar
.toggleClass('right-sidebar-expanded', shouldShow)
.toggleClass('right-sidebar-collapsed', shouldHide);
- $('.js-build-page')
+ this.$topBar
.toggleClass('sidebar-expanded', shouldShow)
.toggleClass('sidebar-collapsed', shouldHide);
@@ -279,17 +255,10 @@ window.Build = (function () {
Build.prototype.sidebarOnResize = function () {
this.toggleSidebar(this.shouldHideSidebarForViewport());
-
- this.verifyTopPosition();
-
- if (this.canScroll()) {
- this.toggleScroll();
- }
};
Build.prototype.sidebarOnClick = function () {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
- this.verifyTopPosition();
};
Build.prototype.updateArtifactRemoveDate = function () {
diff --git a/app/assets/javascripts/close_reopen_report_toggle.js b/app/assets/javascripts/close_reopen_report_toggle.js
new file mode 100644
index 00000000000..882d20671cc
--- /dev/null
+++ b/app/assets/javascripts/close_reopen_report_toggle.js
@@ -0,0 +1,97 @@
+import DropLab from './droplab/drop_lab';
+import ISetter from './droplab/plugins/input_setter';
+
+// Todo: Remove this when fixing issue in input_setter plugin
+const InputSetter = Object.assign({}, ISetter);
+
+class CloseReopenReportToggle {
+ constructor(opts = {}) {
+ this.dropdownTrigger = opts.dropdownTrigger;
+ this.dropdownList = opts.dropdownList;
+ this.button = opts.button;
+ }
+
+ initDroplab() {
+ this.reopenItem = this.dropdownList.querySelector('.reopen-item');
+ this.closeItem = this.dropdownList.querySelector('.close-item');
+
+ this.droplab = new DropLab();
+
+ const config = this.setConfig();
+
+ this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
+ }
+
+ updateButton(isClosed) {
+ this.toggleButtonType(isClosed);
+
+ this.button.blur();
+ }
+
+ toggleButtonType(isClosed) {
+ const [showItem, hideItem] = this.getButtonTypes(isClosed);
+
+ showItem.classList.remove('hidden');
+ showItem.classList.add('droplab-item-selected');
+
+ hideItem.classList.add('hidden');
+ hideItem.classList.remove('droplab-item-selected');
+
+ showItem.click();
+ }
+
+ getButtonTypes(isClosed) {
+ return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
+ }
+
+ setDisable(shouldDisable = true) {
+ if (shouldDisable) {
+ this.button.setAttribute('disabled', 'true');
+ this.dropdownTrigger.setAttribute('disabled', 'true');
+ } else {
+ this.button.removeAttribute('disabled');
+ this.dropdownTrigger.removeAttribute('disabled');
+ }
+ }
+
+ setConfig() {
+ const config = {
+ InputSetter: [
+ {
+ input: this.button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'data-value',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'title',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-button-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: this.dropdownTrigger,
+ valueAttribute: 'data-toggle-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-url',
+ inputAttribute: 'href',
+ },
+ {
+ input: this.button,
+ valueAttribute: 'data-method',
+ inputAttribute: 'data-method',
+ },
+ ],
+ };
+
+ return config;
+ }
+}
+
+export default CloseReopenReportToggle;
diff --git a/app/assets/javascripts/comment_type_toggle.js b/app/assets/javascripts/comment_type_toggle.js
index df0ba86198c..c74184949df 100644
--- a/app/assets/javascripts/comment_type_toggle.js
+++ b/app/assets/javascripts/comment_type_toggle.js
@@ -1,5 +1,8 @@
import DropLab from './droplab/drop_lab';
-import InputSetter from './droplab/plugins/input_setter';
+import ISetter from './droplab/plugins/input_setter';
+
+// Todo: Remove this when fixing issue in input_setter plugin
+const InputSetter = Object.assign({}, ISetter);
class CommentTypeToggle {
constructor(opts = {}) {
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index 2c38440a2af..687f09882a7 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -18,13 +18,26 @@ window.gl.CommitPipelinesTable = CommitPipelinesTable;
document.addEventListener('DOMContentLoaded', () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
- if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
- const table = new CommitPipelinesTable({
- propsData: {
- endpoint: pipelineTableViewEl.dataset.endpoint,
- helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
- },
- }).$mount();
- pipelineTableViewEl.appendChild(table.$el);
+ if (pipelineTableViewEl) {
+ // Update MR and Commits tabs
+ pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
+ if (event.detail.pipelines &&
+ event.detail.pipelines.count &&
+ event.detail.pipelines.count.all) {
+ const badge = document.querySelector('.js-pipelines-mr-count');
+
+ badge.textContent = event.detail.pipelines.count.all;
+ }
+ });
+
+ if (pipelineTableViewEl.dataset.disableInitialization === undefined) {
+ const table = new CommitPipelinesTable({
+ propsData: {
+ endpoint: pipelineTableViewEl.dataset.endpoint,
+ helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+ },
+ }).$mount();
+ pipelineTableViewEl.appendChild(table.$el);
+ }
}
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index 3c77f14d533..dd751ec97a8 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -51,11 +51,22 @@
},
methods: {
successCallback(resp) {
- const response = resp.json();
+ return resp.json().then((response) => {
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = response.pipelines || response;
+ this.setCommonData(pipelines);
- // depending of the endpoint the response can either bring a `pipelines` key or not.
- const pipelines = response.pipelines || response;
- this.setCommonData(pipelines);
+ const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
+ detail: {
+ pipelines: response,
+ },
+ });
+
+ // notifiy to update the count in tabs
+ if (this.$el.parentElement) {
+ this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
+ }
+ });
},
},
};
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index 36bfe457be9..510bedbf641 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -8,6 +8,7 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
+import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
// custom jQuery functions
$.fn.extend({
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index ba9d9a3e1f7..54257531284 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import './lib/utils/common_utils';
+import { placeholderImage } from './lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
@@ -56,6 +57,11 @@ const gfmRules = {
return text;
},
},
+ ImageLazyLoadFilter: {
+ 'img'(el, text) {
+ return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ },
+ },
VideoLinkFilter: {
'.video-container'(el) {
const videoEl = el.querySelector('video');
@@ -163,7 +169,9 @@ const gfmRules = {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
'img'(el) {
- return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ const imageSrc = el.src;
+ const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
+ return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 1be9df19c81..6a008112203 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -2,6 +2,7 @@
import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button';
+import SingleFileDiff from './single_file_diff';
const UNFOLD_COUNT = 20;
let isBound = false;
@@ -10,7 +11,11 @@ class Diff {
constructor() {
const $diffFile = $('.files .diff-file');
- $diffFile.singleFileDiff();
+ $diffFile.each((index, file) => {
+ if (!$.data(file, 'singleFileDiff')) {
+ $.data(file, 'singleFileDiff', new SingleFileDiff(file));
+ }
+ });
FilesCommentButton.init($diffFile);
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index 9d51fb53eb2..efb6ced9f46 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, no-new, max-len */
+/* eslint-disable comma-dangle, object-shorthand, func-names, quote-props, no-else-return, camelcase, max-len */
/* global CommentsStore */
/* global ResolveService */
/* global Flash */
@@ -64,8 +64,6 @@ const ResolveBtn = Vue.extend({
});
},
resolve: function () {
- const errorFlashMsg = 'An error occurred when trying to resolve a comment. Please try again.';
-
if (!this.canResolve) return;
let promise;
@@ -79,24 +77,20 @@ const ResolveBtn = Vue.extend({
.resolve(this.noteId);
}
- promise.then((response) => {
- this.loading = false;
+ promise
+ .then(resp => resp.json())
+ .then((data) => {
+ this.loading = false;
- if (response.status === 200) {
- const data = response.json();
const resolved_by = data ? data.resolved_by : null;
CommentsStore.update(this.discussionId, this.noteId, !this.isResolved, resolved_by);
this.discussion.updateHeadline(data);
gl.mrWidget.checkStatus();
- } else {
- new Flash(errorFlashMsg);
- }
- this.updateTooltip();
- }).catch(() => {
- new Flash(errorFlashMsg);
- });
+ this.updateTooltip();
+ })
+ .catch(() => new Flash('An error occurred when trying to resolve a comment. Please try again.'));
}
},
mounted: function () {
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js b/app/assets/javascripts/diff_notes/services/resolve.js
index 807ab11d292..2f063f6fe1f 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
@@ -1,4 +1,3 @@
-/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
/* global Flash */
/* global CommentsStore */
@@ -32,27 +31,22 @@ class ResolveServiceClass {
promise = this.resolveAll(mergeRequestId, discussionId);
}
- promise.then((response) => {
- discussion.loading = false;
-
- if (response.status === 200) {
- const data = response.json();
- const resolved_by = data ? data.resolved_by : null;
+ promise
+ .then(resp => resp.json())
+ .then((data) => {
+ discussion.loading = false;
+ const resolvedBy = data ? data.resolved_by : null;
if (isResolved) {
discussion.unResolveAllNotes();
} else {
- discussion.resolveAllNotes(resolved_by);
+ discussion.resolveAllNotes(resolvedBy);
}
gl.mrWidget.checkStatus();
discussion.updateHeadline(data);
- } else {
- throw new Error('An error occurred when trying to resolve discussion.');
- }
- }).catch(() => {
- new Flash('An error occurred when trying to resolve a discussion. Please try again.');
- });
+ })
+ .catch(() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'));
}
resolveAll(mergeRequestId, discussionId) {
@@ -62,7 +56,7 @@ class ResolveServiceClass {
return this.discussionResource.save({
mergeRequestId,
- discussionId
+ discussionId,
}, {});
}
@@ -73,7 +67,7 @@ class ResolveServiceClass {
return this.discussionResource.delete({
mergeRequestId,
- discussionId
+ discussionId,
}, {});
}
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 4247540de22..f2f814b9e18 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,17 +1,14 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-/* global UsernameValidator */
-/* global ActiveTabMemoizer */
+/* global ProjectSelect */
/* global ShortcutsNavigation */
/* global IssuableIndex */
/* global ShortcutsIssuable */
-/* global ZenMode */
/* global Milestone */
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
/* global Commit */
/* global NotificationsForm */
-/* global TreeView */
/* global NotificationsDropdown */
/* global GroupAvatar */
/* global LineHighlighter */
@@ -23,9 +20,10 @@
/* global NamespaceSelects */
/* global Project */
/* global ProjectAvatar */
+/* global MergeRequest */
+/* global Compare */
/* global CompareAutocomplete */
/* global ProjectNew */
-/* global Star */
/* global ProjectShow */
/* global Labels */
/* global Shortcuts */
@@ -45,7 +43,6 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout';
-import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
import Pipelines from './pipelines';
import BlobViewer from './blob/viewer/index';
@@ -54,8 +51,20 @@ import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
import ShortcutsBlob from './shortcuts_blob';
+import SigninTabsMemoizer from './signin_tabs_memoizer';
+import Star from './star';
+import Todos from './todos';
+import TreeView from './tree';
+import UsagePing from './usage_ping';
+import UsernameValidator from './username_validator';
+import VersionCheckImage from './version_check_image';
+import Wikis from './wikis';
+import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
+import OAuthRememberMe from './oauth_remember_me';
+import PerformanceBar from './performance_bar';
+import GpgBadges from './gpg_badges';
(function() {
var Dispatcher;
@@ -126,7 +135,8 @@ import initExperimentalFlags from './experimental_flags';
break;
case 'sessions:new':
new UsernameValidator();
- new ActiveTabMemoizer();
+ new SigninTabsMemoizer();
+ new OAuthRememberMe({ container: $(".omniauth-container") }).bindEvents();
break;
case 'projects:boards:show':
case 'projects:boards:index':
@@ -150,6 +160,9 @@ import initExperimentalFlags from './experimental_flags';
shortcut_handler = new ShortcutsIssuable();
new ZenMode();
break;
+ case 'dashboard:milestones:index':
+ new ProjectSelect();
+ break;
case 'projects:milestones:show':
case 'groups:milestones:show':
case 'dashboard:milestones:show':
@@ -159,9 +172,10 @@ import initExperimentalFlags from './experimental_flags';
case 'groups:issues':
case 'groups:merge_requests':
new UsersSelect();
+ new ProjectSelect();
break;
case 'dashboard:todos:index':
- new gl.Todos();
+ new Todos();
break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
@@ -210,6 +224,19 @@ import initExperimentalFlags from './experimental_flags';
new gl.IssuableTemplateSelectors();
break;
case 'projects:merge_requests:creations:new':
+ const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
+ if (mrNewCompareNode) {
+ new Compare({
+ targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
+ sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
+ targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
+ });
+ } else {
+ const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
+ new MergeRequest({
+ action: mrNewSubmitNode.dataset.mrSubmitAction,
+ });
+ }
case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
@@ -246,12 +273,17 @@ import initExperimentalFlags from './experimental_flags';
new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
+ const mrShowNode = document.querySelector('.merge-request');
+ window.mergeRequest = new MergeRequest({
+ action: mrShowNode.dataset.mrAction,
+ });
break;
case 'dashboard:activity':
new gl.Activities();
break;
case 'dashboard:issues':
case 'dashboard:merge_requests':
+ new ProjectSelect();
new UsersSelect();
break;
case 'projects:commit:show':
@@ -269,6 +301,9 @@ import initExperimentalFlags from './experimental_flags';
}).bindEvents();
break;
case 'projects:commits:show':
+ shortcut_handler = new ShortcutsNavigation();
+ GpgBadges.fetch();
+ break;
case 'projects:activity':
shortcut_handler = new ShortcutsNavigation();
break;
@@ -315,7 +350,7 @@ import initExperimentalFlags from './experimental_flags';
new gl.Members();
new UsersSelect();
break;
- case 'projects:settings:members:show':
+ case 'projects:project_members:index':
new gl.MemberExpirationDate('.js-access-expiration-date-groups');
new GroupsSelect();
new gl.MemberExpirationDate();
@@ -377,22 +412,17 @@ import initExperimentalFlags from './experimental_flags';
new BlobViewer();
break;
case 'help:index':
- gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
+ VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
break;
case 'search:show':
new Search();
break;
case 'projects:settings:repository:show':
- // Initialize Protected Branch Settings
- new gl.ProtectedBranchCreate();
- new gl.ProtectedBranchEditList();
- // Initialize Protected Tag Settings
- new ProtectedTagCreate();
- new ProtectedTagEditList();
// Initialize expandable settings panels
initSettingsPanels();
break;
case 'projects:settings:ci_cd:show':
+ case 'groups:settings:ci_cd:show':
new gl.ProjectVariables();
break;
case 'ci:lints:create':
@@ -429,7 +459,7 @@ import initExperimentalFlags from './experimental_flags';
new Admin();
switch (path[1]) {
case 'cohorts':
- new gl.UsagePing();
+ new UsagePing();
break;
case 'groups':
new UsersSelect();
@@ -481,7 +511,7 @@ import initExperimentalFlags from './experimental_flags';
new NotificationsDropdown();
break;
case 'wikis':
- new gl.Wikis();
+ new Wikis();
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
@@ -513,6 +543,10 @@ import initExperimentalFlags from './experimental_flags';
if (!shortcut_handler) {
new Shortcuts();
}
+
+ if (document.querySelector('#peek')) {
+ new PerformanceBar({ container: '#peek' });
+ }
};
Dispatcher.prototype.initSearch = function() {
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 73675d300be..9ebbb22e807 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -5,21 +5,28 @@ import './preview_markdown';
window.DropzoneInput = (function() {
function DropzoneInput(form) {
- var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile, addFileToForm;
Dropzone.autoDiscover = false;
- divHover = '<div class="div-dropzone-hover"></div>';
- iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
- $attachButton = form.find('.button-attach-file');
- $attachingFileMessage = form.find('.attaching-file-message');
- $cancelButton = form.find('.button-cancel-uploading-files');
- $retryLink = form.find('.retry-uploading-link');
- $uploadProgress = form.find('.uploading-progress');
- $uploadingErrorContainer = form.find('.uploading-error-container');
- $uploadingErrorMessage = form.find('.uploading-error-message');
- $uploadingProgressContainer = form.find('.uploading-progress-container');
- uploadsPath = window.uploads_path || null;
- maxFileSize = gon.max_file_size || 10;
- formTextarea = form.find('.js-gfm-input');
+ const divHover = '<div class="div-dropzone-hover"></div>';
+ const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
+ const $attachButton = form.find('.button-attach-file');
+ const $attachingFileMessage = form.find('.attaching-file-message');
+ const $cancelButton = form.find('.button-cancel-uploading-files');
+ const $retryLink = form.find('.retry-uploading-link');
+ const $uploadProgress = form.find('.uploading-progress');
+ const $uploadingErrorContainer = form.find('.uploading-error-container');
+ const $uploadingErrorMessage = form.find('.uploading-error-message');
+ const $uploadingProgressContainer = form.find('.uploading-progress-container');
+ const uploadsPath = window.uploads_path || null;
+ const maxFileSize = gon.max_file_size || 10;
+ const formTextarea = form.find('.js-gfm-input');
+ let handlePaste;
+ let pasteText;
+ let addFileToForm;
+ let updateAttachingMessage;
+ let isImage;
+ let getFilename;
+ let uploadFile;
+
formTextarea.wrap('<div class="div-dropzone"></div>');
formTextarea.on('paste', (function(_this) {
return function(event) {
@@ -28,16 +35,16 @@ window.DropzoneInput = (function() {
})(this));
// Add dropzone area to the form.
- $mdArea = formTextarea.closest('.md-area');
+ const $mdArea = formTextarea.closest('.md-area');
form.setupMarkdownPreview();
- $formDropzone = form.find('.div-dropzone');
+ const $formDropzone = form.find('.div-dropzone');
$formDropzone.parent().addClass('div-dropzone-wrapper');
$formDropzone.append(divHover);
$formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
if (!uploadsPath) return;
- dropzone = $formDropzone.dropzone({
+ const dropzone = $formDropzone.dropzone({
url: uploadsPath,
dictDefaultMessage: '',
clickable: true,
@@ -117,7 +124,7 @@ window.DropzoneInput = (function() {
}
});
- child = $(dropzone[0]).children('textarea');
+ const child = $(dropzone[0]).children('textarea');
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
@@ -214,6 +221,35 @@ window.DropzoneInput = (function() {
return value.first();
};
+ const showSpinner = function(e) {
+ return $uploadingProgressContainer.removeClass('hide');
+ };
+
+ const closeSpinner = function() {
+ return $uploadingProgressContainer.addClass('hide');
+ };
+
+ const showError = function(message) {
+ $uploadingErrorContainer.removeClass('hide');
+ $uploadingErrorMessage.html(message);
+ };
+
+ const closeAlertMessage = function() {
+ return form.find('.div-dropzone-alert').alert('close');
+ };
+
+ const insertToTextArea = function(filename, url) {
+ return $(child).val(function(index, val) {
+ return val.replace(`{{${filename}}}`, url);
+ });
+ };
+
+ const appendToTextArea = function(url) {
+ return $(child).val(function(index, val) {
+ return val + url + "\n";
+ });
+ };
+
uploadFile = function(item, filename) {
var formData;
formData = new FormData();
@@ -262,35 +298,6 @@ window.DropzoneInput = (function() {
messageContainer.text(attachingMessage);
};
- insertToTextArea = function(filename, url) {
- return $(child).val(function(index, val) {
- return val.replace(`{{${filename}}}`, url);
- });
- };
-
- appendToTextArea = function(url) {
- return $(child).val(function(index, val) {
- return val + url + "\n";
- });
- };
-
- showSpinner = function(e) {
- return $uploadingProgressContainer.removeClass('hide');
- };
-
- closeSpinner = function() {
- return $uploadingProgressContainer.addClass('hide');
- };
-
- showError = function(message) {
- $uploadingErrorContainer.removeClass('hide');
- $uploadingErrorMessage.html(message);
- };
-
- closeAlertMessage = function() {
- return form.find('.div-dropzone-alert').alert('close');
- };
-
form.find('.markdown-selector').click(function(e) {
e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click();
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index a8fc5b41fb4..2856c8e2862 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -2,6 +2,8 @@
/* global dateFormat */
/* global Pikaday */
+import DateFix from './lib/utils/datefix';
+
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
const $dropdownParent = $dropdown.closest('.dropdown');
@@ -43,14 +45,13 @@ class DueDateSelect {
initDatePicker() {
const $dueDateInput = $(`input[name='${this.fieldName}']`);
-
+ const dateFix = DateFix.dashedFix($dueDateInput.val());
const calendar = new Pikaday({
field: $dueDateInput.get(0),
theme: 'gitlab-theme',
format: 'yyyy-mm-dd',
onSelect: (dateText) => {
const formattedDate = dateFormat(new Date(dateText), 'yyyy-mm-dd');
-
$dueDateInput.val(formattedDate);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
@@ -62,7 +63,7 @@ class DueDateSelect {
}
});
- calendar.setDate(new Date($dueDateInput.val()));
+ calendar.setDate(dateFix);
this.$datePicker.append(calendar.el);
this.$datePicker.data('pikaday', calendar);
}
@@ -168,6 +169,7 @@ class DueDateSelectors {
initMilestoneDatePicker() {
$('.datepicker').each(function() {
const $datePicker = $(this);
+ const dateFix = DateFix.dashedFix($datePicker.val());
const calendar = new Pikaday({
field: $datePicker.get(0),
theme: 'gitlab-theme animate-picker',
@@ -177,7 +179,8 @@ class DueDateSelectors {
$datePicker.val(dateFormat(new Date(dateText), 'yyyy-mm-dd'));
}
});
- calendar.setDate(new Date($datePicker.val()));
+
+ calendar.setDate(dateFix);
$datePicker.data('pikaday', calendar);
});
diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue
index 8120ef182d4..91ed8c8467f 100644
--- a/app/assets/javascripts/environments/components/environment.vue
+++ b/app/assets/javascripts/environments/components/environment.vue
@@ -32,7 +32,6 @@ export default {
state: store.state,
visibility: 'available',
isLoading: false,
- isLoadingFolderContent: false,
cssContainerClass: environmentsData.cssClass,
endpoint: environmentsData.environmentsDataEndpoint,
canCreateDeployment: environmentsData.canCreateDeployment,
@@ -86,9 +85,6 @@ export default {
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
-
- // We need to verify if any folder is open to also fecth it
- this.openFolders = this.store.getOpenFolders();
},
});
@@ -119,7 +115,7 @@ export default {
this.store.toggleFolder(folder);
if (!folder.isOpen) {
- this.fetchChildEnvironments(folder, folderUrl);
+ this.fetchChildEnvironments(folder, folderUrl, true);
}
},
@@ -147,19 +143,17 @@ export default {
.catch(this.errorCallback);
},
- fetchChildEnvironments(folder, folderUrl) {
- this.isLoadingFolderContent = true;
+ fetchChildEnvironments(folder, folderUrl, showLoader = false) {
+ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folderUrl)
.then(resp => resp.json())
- .then((response) => {
- this.store.setfolderContent(folder, response.environments);
- this.isLoadingFolderContent = false;
- })
+ .then(response => this.store.setfolderContent(folder, response.environments))
+ .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => {
- this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
+ this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false);
});
},
@@ -176,13 +170,13 @@ export default {
successCallback(resp) {
this.saveData(resp);
- // If folders are open while polling we need to open them again
- if (this.openFolders.length) {
- this.openFolders.map((folder) => {
+ // We need to verify if any folder is open to also update it
+ const openFolders = this.store.getOpenFolders();
+ if (openFolders.length) {
+ openFolders.forEach((folder) => {
// TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
- this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl);
});
}
@@ -267,7 +261,7 @@ export default {
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
- :is-loading-folder-content="isLoadingFolderContent" />
+ />
</div>
<table-pagination
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index b25113e0fc6..d8b1b2f1b92 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -498,9 +498,9 @@ export default {
<div class="table-section section-15 hidden-xs hidden-sm" role="gridcell">
<a
v-if="shouldRenderBuildName"
- class="build-link"
+ class="build-link flex-truncate-parent"
:href="buildPath">
- {{buildName}}
+ <span class="flex-truncate-child">{{buildName}}</span>
</a>
</div>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index b1fd9db650b..175cc8f1f72 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -29,12 +29,6 @@ export default {
required: false,
default: false,
},
-
- isLoadingFolderContent: {
- type: Boolean,
- required: false,
- default: false,
- },
},
methods: {
@@ -74,7 +68,7 @@ export default {
/>
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
- <div v-if="isLoadingFolderContent">
+ <div v-if="model.isLoadingFolderContent">
<loading-icon size="2" />
</div>
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 25b24fbd6dc..8f4066e3a6e 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -1,17 +1,15 @@
export default {
methods: {
saveData(resp) {
- const response = {
- headers: resp.headers,
- body: resp.json(),
- };
+ const headers = resp.headers;
+ return resp.json().then((response) => {
+ this.isLoading = false;
- this.isLoading = false;
-
- this.store.storeAvailableCount(response.body.available_count);
- this.store.storeStoppedCount(response.body.stopped_count);
- this.store.storeEnvironments(response.body.environments);
- this.store.setPagination(response.headers);
+ this.store.storeAvailableCount(response.available_count);
+ this.store.storeStoppedCount(response.stopped_count);
+ this.store.storeEnvironments(response.environments);
+ this.store.setPagination(headers);
+ });
},
},
};
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index a5773dd7e4f..038c149be2d 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -35,14 +35,18 @@ export default class EnvironmentsStore {
*/
storeEnvironments(environments = []) {
const filteredEnvironments = environments.map((env) => {
+ const oldEnvironmentState = this.state.environments
+ .find(element => element.id === env.latest.id) || {};
+
let filtered = {};
if (env.size > 1) {
filtered = Object.assign({}, env, {
isFolder: true,
+ isLoadingFolderContent: oldEnvironmentState.isLoading || false,
folderName: env.name,
- isOpen: false,
- children: [],
+ isOpen: oldEnvironmentState.isOpen || false,
+ children: oldEnvironmentState.children || [],
});
}
@@ -98,7 +102,7 @@ export default class EnvironmentsStore {
* @return {Array}
*/
toggleFolder(folder) {
- return this.updateFolder(folder, 'isOpen', !folder.isOpen);
+ return this.updateEnvironmentProp(folder, 'isOpen', !folder.isOpen);
}
/**
@@ -125,23 +129,23 @@ export default class EnvironmentsStore {
return updated;
});
- return this.updateFolder(folder, 'children', updatedEnvironments);
+ return this.updateEnvironmentProp(folder, 'children', updatedEnvironments);
}
/**
- * Given a folder a prop and a new value updates the correct folder.
+ * Given a environment, a prop and a new value updates the correct environment.
*
- * @param {Object} folder
+ * @param {Object} environment
* @param {String} prop
* @param {String|Boolean|Object|Array} newValue
* @return {Array}
*/
- updateFolder(folder, prop, newValue) {
+ updateEnvironmentProp(environment, prop, newValue) {
const environments = this.state.environments;
const updatedEnvironments = environments.map((env) => {
const updateEnv = Object.assign({}, env);
- if (env.isFolder && env.id === folder.id) {
+ if (env.id === environment.id) {
updateEnv[prop] = newValue;
}
@@ -149,8 +153,6 @@ export default class EnvironmentsStore {
});
this.state.environments = updatedEnvironments;
-
- return updatedEnvironments;
}
getOpenFolders() {
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
index dbd3843cef7..6ee65ca72f9 100644
--- a/app/assets/javascripts/experimental_flags.js
+++ b/app/assets/javascripts/experimental_flags.js
@@ -7,5 +7,8 @@ export default () => {
Cookies.set(el.name, el.value, {
expires: 365 * 10,
});
+
+ document.body.scrollTop = 0;
+ window.location.reload();
});
};
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 5838b1bdbb7..a81389ab088 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -2,8 +2,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { input, tokenKeys } = options;
+ super(options);
this.config = {
Filter: {
template: 'hint',
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 34a9e34070c..2615d626c4c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -5,8 +5,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { input, endpoint, symbol } = options;
+ super(options);
this.symbol = symbol;
this.config = {
Ajax: {
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 19fed771197..7246ccbb281 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -5,8 +5,9 @@ import './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
class DropdownUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, tokenKeys, filter) {
- super(droplab, dropdown, input, filter);
+ constructor(options = {}) {
+ const { tokenKeys } = options;
+ super(options);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index 4209ca0d6e2..9e9a9ef74be 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -1,7 +1,7 @@
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
+ constructor({ droplab, dropdown, input, filter }) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 6bc6bc43f51..61cef435209 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -42,13 +42,19 @@ class FilteredSearchDropdownManager {
milestone: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
+ extraArguments: {
+ endpoint: `${this.baseEndpoint}/milestones.json`,
+ symbol: '%',
+ },
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
- extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
+ extraArguments: {
+ endpoint: `${this.baseEndpoint}/labels.json`,
+ symbol: '~',
+ },
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
@@ -97,13 +103,19 @@ class FilteredSearchDropdownManager {
let forceShowList = false;
if (!mappingKey.reference) {
- const dl = this.droplab;
- const defaultArguments =
- [null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
- const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
+ const defaultArguments = {
+ droplab: this.droplab,
+ dropdown: element,
+ input: this.filteredSearchInput,
+ tokenKeys: this.filteredSearchTokenKeys,
+ filter: key,
+ };
+ const extraArguments = mappingKey.extraArguments || {};
+ const glArguments = Object.assign({}, defaultArguments, extraArguments);
// Passing glArguments to `new gl[glClass](<arguments>)`
- mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
+ mappingKey.reference =
+ new (Function.prototype.bind.apply(gl[glClass], [null, glArguments]))();
}
if (firstLoad) {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 2c56b718212..6cb9cfe1382 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -30,6 +30,7 @@ class GfmAutoComplete {
this.input.each((i, input) => {
const $input = $(input);
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
+ $input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
// This triggers at.js again
// Needed for quick actions with suffixes (ex: /label ~)
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
new file mode 100644
index 00000000000..1c379e9bb67
--- /dev/null
+++ b/app/assets/javascripts/gpg_badges.js
@@ -0,0 +1,15 @@
+export default class GpgBadges {
+ static fetch() {
+ const form = $('.commits-search-form');
+
+ $.get({
+ url: form.data('signatures-path'),
+ data: form.serialize(),
+ }).done((response) => {
+ const badges = $('.js-loading-gpg-badge');
+ response.signatures.forEach((signature) => {
+ badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
+ });
+ });
+ }
+}
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
index 37c6765d942..3e483b69fd2 100644
--- a/app/assets/javascripts/group_name.js
+++ b/app/assets/javascripts/group_name.js
@@ -5,12 +5,15 @@ export default class GroupName {
constructor() {
this.titleContainer = document.querySelector('.js-title-container');
this.title = this.titleContainer.querySelector('.title');
- this.titleWidth = this.title.offsetWidth;
- this.groupTitle = this.titleContainer.querySelector('.group-title');
- this.groups = this.titleContainer.querySelectorAll('.group-path');
- this.toggle = null;
- this.isHidden = false;
- this.init();
+
+ if (this.title) {
+ this.titleWidth = this.title.offsetWidth;
+ this.groupTitle = this.titleContainer.querySelector('.group-title');
+ this.groups = this.titleContainer.querySelectorAll('.group-path');
+ this.toggle = null;
+ this.isHidden = false;
+ this.init();
+ }
}
init() {
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index ff601db2aa6..00e1bd94c9c 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -99,8 +99,10 @@ document.addEventListener('DOMContentLoaded', () => {
page: currentPath,
}, document.title, currentPath);
- this.updateGroups(response.json());
- this.updatePagination(response.headers);
+ return response.json().then((data) => {
+ this.updateGroups(data);
+ this.updatePagination(response.headers);
+ });
})
.catch(this.handleErrorResponse);
},
@@ -114,18 +116,19 @@ document.addEventListener('DOMContentLoaded', () => {
},
leaveGroup(group, collection) {
this.service.leaveGroup(group.leavePath)
+ .then(resp => resp.json())
.then((response) => {
$.scrollTo(0);
this.store.removeGroup(group, collection);
// eslint-disable-next-line no-new
- new Flash(response.json().notice, 'notice');
+ new Flash(response.notice, 'notice');
})
- .catch((response) => {
+ .catch((error) => {
let message = 'An error occurred. Please try again.';
- if (response.status === 403) {
+ if (error.status === 403) {
message = 'Failed to leave the group. Please make sure you are not the only owner';
}
diff --git a/app/assets/javascripts/helpers/issuables_helper.js b/app/assets/javascripts/helpers/issuables_helper.js
new file mode 100644
index 00000000000..52d0f7e43fc
--- /dev/null
+++ b/app/assets/javascripts/helpers/issuables_helper.js
@@ -0,0 +1,27 @@
+import CloseReopenReportToggle from '../close_reopen_report_toggle';
+
+function initCloseReopenReport() {
+ const container = document.querySelector('.js-issuable-close-dropdown');
+
+ if (!container) return undefined;
+
+ const dropdownTrigger = container.querySelector('.js-issuable-close-toggle');
+ const dropdownList = container.querySelector('.js-issuable-close-menu');
+ const button = container.querySelector('.js-issuable-close-button');
+
+ const closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ closeReopenReportToggle.initDroplab();
+
+ return closeReopenReportToggle;
+}
+
+const IssuablesHelper = {
+ initCloseReopenReport,
+};
+
+export default IssuablesHelper;
diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js
new file mode 100644
index 00000000000..19f4a946f73
--- /dev/null
+++ b/app/assets/javascripts/how_to_merge.js
@@ -0,0 +1,12 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const modal = $('#modal_merge_info').modal({
+ modal: true,
+ show: false,
+ });
+ $('.how_to_merge_link').on('click', () => {
+ modal.show();
+ });
+ $('.modal-header .close').on('click', () => {
+ modal.hide();
+ });
+});
diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js
index 4f376599ba9..d314f3c4d43 100644
--- a/app/assets/javascripts/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js
@@ -86,10 +86,23 @@ export default class IssuableBulkUpdateSidebar {
this.toggleCheckboxDisplay(enable);
if (enable) {
+ this.initAffix();
SidebarHeightManager.init();
}
}
+ initAffix() {
+ if (!this.$sidebar.hasClass('affix-top')) {
+ const offsetTop = $('.scrolling-tabs-container').outerHeight() + $('.sub-nav-scroll').outerHeight();
+
+ this.$sidebar.affix({
+ offset: {
+ top: offsetTop,
+ },
+ });
+ }
+ }
+
updateSelectedIssuableIds() {
this.$issuableIdsInput.val(IssuableBulkUpdateSidebar.getCheckedIssueIds());
}
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 92f6f0d4117..9ac1325fc95 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,12 +1,12 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */
-/* global ZenMode */
/* global Autosave */
/* global dateFormat */
/* global Pikaday */
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
+import ZenMode from './zen_mode';
(function() {
this.IssuableForm = (function() {
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 0860e237ce1..2bee4fb045a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -4,13 +4,14 @@
import 'vendor/jquery.waitforimages';
import '~/lib/utils/text_utility';
import './flash';
-import './task_list';
+import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
+import IssuablesHelper from './helpers/issuables_helper';
class Issue {
constructor() {
if ($('a.btn-close').length) {
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
@@ -28,6 +29,11 @@ class Issue {
Issue.initMergeRequests();
Issue.initRelatedBranches();
+ this.closeButtons = $('a.btn-close');
+ this.reopenButtons = $('a.btn-reopen');
+
+ this.initCloseReopenReport();
+
if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
}
@@ -35,13 +41,8 @@ class Issue {
initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.';
- const closeButtons = $('a.btn-close');
- const isClosedBadge = $('div.status-box-closed');
- const isOpenBadge = $('div.status-box-open');
- const projectIssuesCounter = $('.issue_counter');
- const reopenButtons = $('a.btn-reopen');
- return closeButtons.add(reopenButtons).on('click', (e) => {
+ return $(document).on('click', 'a.btn-close, a.btn-reopen', (e) => {
var $button, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
@@ -50,7 +51,9 @@ class Issue {
if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form'));
}
- $button.prop('disabled', true);
+
+ this.disableCloseReopenButton($button);
+
url = $button.attr('href');
return $.ajax({
type: 'PUT',
@@ -58,15 +61,19 @@ class Issue {
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
+ const isClosedBadge = $('div.status-box-closed');
+ const isOpenBadge = $('div.status-box-open');
+ const projectIssuesCounter = $('.issue_counter');
+
if ('id' in data) {
$(document).trigger('issuable:change');
const isClosed = $button.hasClass('btn-close');
- closeButtons.toggleClass('hidden', isClosed);
- reopenButtons.toggleClass('hidden', !isClosed);
isClosedBadge.toggleClass('hidden', !isClosed);
isOpenBadge.toggleClass('hidden', isClosed);
+ this.toggleCloseReopenButton(isClosed);
+
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
@@ -83,12 +90,34 @@ class Issue {
} else {
new Flash(issueFailMessage);
}
-
- $button.prop('disabled', false);
+ })
+ .then(() => {
+ this.disableCloseReopenButton($button, false);
});
});
}
+ initCloseReopenReport() {
+ this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
+
+ if (this.closeButtons) this.closeButtons = this.closeButtons.not('.issuable-close-button');
+ if (this.reopenButtons) this.reopenButtons = this.reopenButtons.not('.issuable-close-button');
+ }
+
+ disableCloseReopenButton($button, shouldDisable) {
+ if (this.closeReopenReportToggle) {
+ this.closeReopenReportToggle.setDisable(shouldDisable);
+ } else {
+ $button.prop('disabled', shouldDisable);
+ }
+ }
+
+ toggleCloseReopenButton(isClosed) {
+ if (this.closeReopenReportToggle) this.closeReopenReportToggle.updateButton(isClosed);
+ this.closeButtons.toggleClass('hidden', isClosed);
+ this.reopenButtons.toggleClass('hidden', !isClosed);
+ }
+
static submitNoteForm(form) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 3d5fb7f441c..efae112923d 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -202,10 +202,7 @@ export default {
this.poll = new Poll({
resource: this.service,
method: 'getData',
- successCallback: (res) => {
- const data = res.json();
- this.store.updateState(data);
- },
+ successCallback: res => res.json().then(data => this.store.updateState(data)),
errorCallback(err) {
throw new Error(err);
},
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 43db66c8e08..48bad8f1e68 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,5 +1,6 @@
<script>
import animateMixin from '../mixins/animate';
+ import TaskList from '../../task_list';
export default {
mixins: [animateMixin],
@@ -46,7 +47,7 @@
if (this.canUpdate) {
// eslint-disable-next-line no-new
- new gl.TaskList({
+ new TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 939d17129de..f92e669414a 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -26,14 +26,6 @@ document.addEventListener('DOMContentLoaded', () => {
mounted() {
this.mediator.initBuildClass();
},
- updated() {
- // Wait for flash message to be appended
- Vue.nextTick(() => {
- if (this.mediator.build) {
- this.mediator.build.verifyTopPosition();
- }
- });
- },
render(createElement) {
return createElement('job-header', {
props: {
diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js
index 063c52fac74..cc014b815c4 100644
--- a/app/assets/javascripts/jobs/job_details_mediator.js
+++ b/app/assets/javascripts/jobs/job_details_mediator.js
@@ -54,9 +54,8 @@ export default class JobMediator {
}
successCallback(response) {
- const data = response.json();
this.state.isLoading = false;
- this.store.storeJob(data);
+ return response.json().then(data => this.store.storeJob(data));
}
errorCallback() {
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 71064ccc539..6186ffe20b3 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
import _ from 'underscore';
+import Cookies from 'js-cookie';
+import NewNavSidebar from './new_sidebar';
(function() {
var hideEndFade;
@@ -53,6 +55,11 @@ import _ from 'underscore';
}
$(() => {
+ if (Cookies.get('new_nav') === 'true') {
+ const newNavSidebar = new NewNavSidebar();
+ newNavSidebar.bindEvents();
+ }
+
$(window).on('scroll', _.throttle(applyScrollNavClass, 100));
});
}).call(window);
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
new file mode 100644
index 00000000000..3d64b121fa7
--- /dev/null
+++ b/app/assets/javascripts/lazy_loader.js
@@ -0,0 +1,76 @@
+/* eslint-disable one-export, one-var, one-var-declaration-per-line */
+
+import _ from 'underscore';
+
+export const placeholderImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+const SCROLL_THRESHOLD = 300;
+
+export default class LazyLoader {
+ constructor(options = {}) {
+ this.lazyImages = [];
+ this.observerNode = options.observerNode || '#content-body';
+
+ const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
+ const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
+
+ window.addEventListener('scroll', throttledScrollCheck);
+ window.addEventListener('resize', debouncedElementsInView);
+
+ const scrollContainer = options.scrollContainer || window;
+ scrollContainer.addEventListener('load', () => this.loadCheck());
+ }
+ searchLazyImages() {
+ this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
+ this.checkElementsInView();
+ }
+ startContentObserver() {
+ const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
+
+ if (contentNode) {
+ const observer = new MutationObserver(() => this.searchLazyImages());
+
+ observer.observe(contentNode, {
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+ loadCheck() {
+ this.searchLazyImages();
+ this.startContentObserver();
+ }
+ scrollCheck() {
+ requestAnimationFrame(() => this.checkElementsInView());
+ }
+ checkElementsInView() {
+ const scrollTop = pageYOffset;
+ const visHeight = scrollTop + innerHeight + SCROLL_THRESHOLD;
+ let imgBoundRect, imgTop, imgBound;
+
+ // Loading Images which are in the current viewport or close to them
+ this.lazyImages = this.lazyImages.filter((selectedImage) => {
+ if (selectedImage.getAttribute('data-src')) {
+ imgBoundRect = selectedImage.getBoundingClientRect();
+
+ imgTop = scrollTop + imgBoundRect.top;
+ imgBound = imgTop + imgBoundRect.height;
+
+ if (scrollTop < imgBound && visHeight > imgTop) {
+ LazyLoader.loadImage(selectedImage);
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ });
+ }
+ static loadImage(img) {
+ if (img.getAttribute('data-src')) {
+ img.setAttribute('src', img.getAttribute('data-src'));
+ img.removeAttribute('data-src');
+ img.classList.remove('lazy');
+ img.classList.add('js-lazy-loaded');
+ }
+ }
+}
diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js
new file mode 100644
index 00000000000..990dc3f6d1a
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/datefix.js
@@ -0,0 +1,8 @@
+const DateFix = {
+ dashedFix(val) {
+ const [y, m, d] = val.split('-');
+ return new Date(y, m - 1, d);
+ },
+};
+
+export default DateFix;
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 415e50f32ae..625e53ee9de 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -3,6 +3,7 @@
*/
export default {
+ ABORTED: 0,
NO_CONTENT: 204,
OK: 200,
};
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js
index e31cc5fbabe..97666e13ebe 100644
--- a/app/assets/javascripts/lib/utils/poll.js
+++ b/app/assets/javascripts/lib/utils/poll.js
@@ -81,6 +81,9 @@ export default class Poll {
})
.catch((error) => {
notificationCallback(false);
+ if (error.status === httpStatusCodes.ABORTED) {
+ return;
+ }
errorCallback(error);
});
}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index fe752d95b90..d039ca9e47c 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -109,6 +109,7 @@ import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
+import LazyLoader from './lazy_loader';
import './line_highlighter';
import './logo';
import './member_expiration_date';
@@ -143,26 +144,12 @@ import './render_math';
import './right_sidebar';
import './search';
import './search_autocomplete';
-import './signin_tabs_memoizer';
-import './single_file_diff';
import './smart_interval';
import './snippets_list';
import './star';
import './subscription';
import './subscription_select';
import './syntax_highlight';
-import './task_list';
-import './todos';
-import './tree';
-import './usage_ping';
-import './user';
-import './user_tabs';
-import './username_validator';
-import './users_select';
-import './version_check_image';
-import './visibility_select';
-import './wikis';
-import './zen_mode';
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
@@ -172,6 +159,8 @@ document.addEventListener('beforeunload', function () {
$(document).off('scroll');
// Close any open tooltips
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
+ // Close any open popover
+ $('[data-toggle="popover"]').popover('destroy');
});
window.addEventListener('hashchange', gl.utils.handleLocationHash);
@@ -180,6 +169,11 @@ window.addEventListener('load', function onLoad() {
gl.utils.handleLocationHash();
}, false);
+gl.lazyLoader = new LazyLoader({
+ scrollContainer: window,
+ observerNode: '#content-body'
+});
+
$(function () {
var $body = $('body');
var $document = $(document);
@@ -255,6 +249,11 @@ $(function () {
return $(el).data('placement') || 'bottom';
}
});
+ // Initialize popovers
+ $body.popover({
+ selector: '[data-toggle="popover"]',
+ trigger: 'focus'
+ });
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
@@ -298,13 +297,7 @@ $(function () {
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', function () {
- $('.header-content .title, .header-content .navbar-sub-nav').toggle();
- $('.header-content .header-logo').toggle();
- $('.header-content .navbar-collapse').toggle();
- $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
- return $('.navbar-toggle').toggleClass('active');
- });
+ $('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded'));
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index c4e379a4a0b..8be7314ded8 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -175,7 +175,7 @@ import Cookies from 'js-cookie';
getConflictsCountText() {
const count = this.getConflictsCount();
- const text = count ? 'conflicts' : 'conflict';
+ const text = count > 1 ? 'conflicts' : 'conflict';
return `${count} ${text}`;
},
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index f93feeec1c2..0db2abe507d 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,8 +2,9 @@
/* global MergeRequestTabs */
import 'vendor/jquery.waitforimages';
-import './task_list';
+import TaskList from './task_list';
import './merge_request_tabs';
+import IssuablesHelper from './helpers/issuables_helper';
(function() {
this.MergeRequest = (function() {
@@ -21,11 +22,14 @@ import './merge_request_tabs';
return _this.showAllCommits();
};
})(this));
+
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
+ this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
+
if ($("a.btn-close").length) {
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
@@ -64,11 +68,15 @@ import './merge_request_tabs';
if (shouldSubmit && $this.data('submitted')) {
return;
}
+
+ if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
+
if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault();
e.stopImmediatePropagation();
- return _this.submitNoteForm($this.closest('form'), $this);
+
+ _this.submitNoteForm($this.closest('form'), $this);
}
}
});
diff --git a/app/assets/javascripts/monitoring/components/monitoring_column.vue b/app/assets/javascripts/monitoring/components/monitoring_column.vue
index e933634643b..c376baea79c 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_column.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_column.vue
@@ -35,7 +35,7 @@
data() {
return {
- graphHeight: 500,
+ graphHeight: 450,
graphWidth: 600,
graphHeightOffset: 120,
xScale: {},
@@ -88,7 +88,9 @@
},
paddingBottomRootSvg() {
- return (Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0;
+ return {
+ paddingBottom: `${(Math.ceil(this.graphHeight * 100) / this.graphWidth) || 0}%`,
+ };
},
},
@@ -103,9 +105,9 @@
this.measurements = measurements.small;
}
this.data = query.result[0].values;
- this.unitOfDisplay = query.unit || 'N/A';
+ this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.columnData.y_label || 'Values';
- this.legendTitle = query.legend || 'Average';
+ this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
@@ -157,12 +159,12 @@
const xAxis = d3.svg.axis()
.scale(axisXScale)
- .ticks(measurements.ticks)
+ .ticks(measurements.xTicks)
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(this.yScale)
- .ticks(measurements.ticks)
+ .ticks(measurements.yTicks)
.orient('left');
d3.select(this.$refs.baseSvg).select('.x-axis').call(xAxis);
@@ -170,8 +172,12 @@
const width = this.graphWidth;
d3.select(this.$refs.baseSvg).select('.y-axis').call(yAxis)
.selectAll('.tick')
- .each(function createTickLines() {
- d3.select(this).select('line').attr('x2', width);
+ .each(function createTickLines(d, i) {
+ if (i > 0) {
+ d3.select(this).select('line')
+ .attr('x2', width)
+ .attr('class', 'axis-tick');
+ } // Avoid adding the class to the first tick, to prevent coloring
}); // This will select all of the ticks once they're rendered
this.xScale = d3.time.scale()
@@ -198,7 +204,7 @@
watch: {
updateAspectRatio() {
if (this.updateAspectRatio) {
- this.graphHeight = 500;
+ this.graphHeight = 450;
this.graphWidth = 600;
this.measurements = measurements.large;
this.draw();
@@ -213,17 +219,17 @@
};
</script>
<template>
- <div
+ <div
:class="classType">
- <h5
- class="text-center">
+ <h5
+ class="text-center graph-title">
{{columnData.title}}
</h5>
- <div
- class="prometheus-svg-container">
- <svg
+ <div
+ class="prometheus-svg-container"
+ :style="paddingBottomRootSvg">
+ <svg
:viewBox="outterViewBox"
- :style="{ 'padding-bottom': paddingBottomRootSvg }"
ref="baseSvg">
<g
class="x-axis"
@@ -233,7 +239,7 @@
class="y-axis"
transform="translate(70, 20)">
</g>
- <monitoring-legends
+ <monitoring-legends
:graph-width="graphWidth"
:graph-height="graphHeight"
:margin="margin"
@@ -243,7 +249,7 @@
:y-axis-label="yAxisLabel"
:metric-usage="metricUsage"
/>
- <svg
+ <svg
class="graph-data"
:viewBox="innerViewBox"
ref="graphData">
@@ -261,7 +267,7 @@
stroke-width="2"
transform="translate(-5, 20)">
</path>
- <rect
+ <rect
class="prometheus-graph-overlay"
:width="(graphWidth - 70)"
:height="(graphHeight - 100)"
@@ -275,7 +281,7 @@
:graph-height="graphHeight"
:graph-height-offset="graphHeightOffset"
/>
- <monitoring-flag
+ <monitoring-flag
v-if="showFlag"
:current-x-coordinate="currentXCoordinate"
:current-y-coordinate="currentYCoordinate"
diff --git a/app/assets/javascripts/monitoring/components/monitoring_flag.vue b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
index 180a771415b..5a0e50fcab3 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_flag.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_flag.vue
@@ -87,14 +87,14 @@
</rect>
<text
class="text-metric text-metric-bold"
- x="8"
+ x="16"
y="35"
transform="translate(-5, 20)">
{{formatTime}}
</text>
<text
- class="text-metric-date"
- x="8"
+ class="text-metric"
+ x="16"
y="15"
transform="translate(-5, 20)">
{{formatDate}}
diff --git a/app/assets/javascripts/monitoring/components/monitoring_legends.vue b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
index b30ed3cc889..922a5e1bf0e 100644
--- a/app/assets/javascripts/monitoring/components/monitoring_legends.vue
+++ b/app/assets/javascripts/monitoring/components/monitoring_legends.vue
@@ -109,13 +109,13 @@
</text>
<rect
class="rect-axis-text"
- :x="xPosition + 50"
+ :x="xPosition + 60"
:y="graphHeight - 80"
- width="50"
+ width="35"
height="50">
</rect>
<text
- class="label-axis-text"
+ class="label-axis-text x-label-text"
:x="xPosition + 60"
:y="yPosition"
dy=".35em">
@@ -131,13 +131,13 @@
<text
class="text-metric-title"
x="50"
- :y="graphHeight - 40">
+ :y="graphHeight - 25">
{{legendTitle}}
</text>
<text
class="text-metric-usage"
x="50"
- :y="graphHeight - 25">
+ :y="graphHeight - 10">
{{metricUsage}}
</text>
</g>
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
index a60d2522f49..62cd19c86e1 100644
--- a/app/assets/javascripts/monitoring/utils/measurements.js
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -8,14 +8,14 @@ export default {
},
legends: {
width: 15,
- height: 30,
+ height: 25,
},
backgroundLegend: {
width: 30,
height: 50,
},
axisLabelLineOffset: -20,
- legendOffset: 52,
+ legendOffset: 35,
},
large: { // This covers both md and lg screen sizes
margin: {
@@ -26,14 +26,15 @@ export default {
},
legends: {
width: 20,
- height: 35,
+ height: 30,
},
backgroundLegend: {
width: 30,
height: 150,
},
axisLabelLineOffset: 20,
- legendOffset: 55,
+ legendOffset: 38,
},
- ticks: 3,
+ xTicks: 8,
+ yTicks: 3,
};
diff --git a/app/assets/javascripts/new_sidebar.js b/app/assets/javascripts/new_sidebar.js
new file mode 100644
index 00000000000..5f98aff8ced
--- /dev/null
+++ b/app/assets/javascripts/new_sidebar.js
@@ -0,0 +1,23 @@
+export default class NewNavSidebar {
+ constructor() {
+ this.initDomElements();
+ }
+
+ initDomElements() {
+ this.$sidebar = $('.nav-sidebar');
+ this.$overlay = $('.mobile-overlay');
+ this.$openSidebar = $('.toggle-mobile-nav');
+ this.$closeSidebar = $('.close-nav-button');
+ }
+
+ bindEvents() {
+ this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
+ this.$closeSidebar.on('click', () => this.toggleSidebarNav(false));
+ this.$overlay.on('click', () => this.toggleSidebarNav(false));
+ }
+
+ toggleSidebarNav(show) {
+ this.$sidebar.toggleClass('nav-sidebar-expanded', show);
+ this.$overlay.toggleClass('mobile-nav-open', show);
+ }
+}
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 555b8c8a65c..b2c503d1656 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -21,7 +21,7 @@ import CommentTypeToggle from './comment_type_toggle';
import loadAwardsHandler from './awards_handler';
import './autosave';
import './dropzone_input';
-import './task_list';
+import TaskList from './task_list';
window.autosize = autosize;
window.Dropzone = Dropzone;
@@ -71,7 +71,7 @@ export default class Notes {
this.addBinding();
this.setPollingInterval();
this.setupMainTargetNoteForm();
- this.taskList = new gl.TaskList({
+ this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes'
@@ -1270,7 +1270,7 @@ export default class Notes {
<div class="timeline-entry-inner">
<div class="timeline-icon">
<a href="/${currentUsername}">
- <img class="avatar s40" src="${currentUserAvatar}">
+ <img class="avatar s40" src="${currentUserAvatar}" />
</a>
</div>
<div class="timeline-content ${discussionClass}">
diff --git a/app/assets/javascripts/oauth_remember_me.js b/app/assets/javascripts/oauth_remember_me.js
new file mode 100644
index 00000000000..ffc2dd6bbca
--- /dev/null
+++ b/app/assets/javascripts/oauth_remember_me.js
@@ -0,0 +1,32 @@
+/**
+ * OAuth-based login buttons have a separate "remember me" checkbox.
+ *
+ * Toggling this checkbox adds/removes a `remember_me` parameter to the
+ * login buttons' href, which is passed on to the omniauth callback.
+ **/
+
+export default class OAuthRememberMe {
+ constructor(opts = {}) {
+ this.container = opts.container || '';
+ this.loginLinkSelector = '.oauth-login';
+ }
+
+ bindEvents() {
+ $('#remember_me', this.container).on('click', this.toggleRememberMe);
+ }
+
+ // eslint-disable-next-line class-methods-use-this
+ toggleRememberMe(event) {
+ const rememberMe = $(event.target).is(':checked');
+
+ $('.oauth-login', this.container).each((i, element) => {
+ const href = $(element).attr('href');
+
+ if (rememberMe) {
+ $(element).attr('href', `${href}?remember_me=1`);
+ } else {
+ $(element).attr('href', href.replace('?remember_me=1', ''));
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/peek.js b/app/assets/javascripts/peek.js
deleted file mode 100644
index de1a99fa3bd..00000000000
--- a/app/assets/javascripts/peek.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'vendor/peek';
-import 'vendor/peek.performance_bar';
-
-$(document).on('click', '#peek-show-queries', (e) => {
- e.preventDefault();
- $('.peek-rblineprof-modal').hide();
- const $modal = $('#modal-peek-pg-queries');
- if ($modal.length) {
- $modal.modal('toggle');
- }
-});
-
-$(document).on('click', '.js-lineprof-file', (e) => {
- e.preventDefault();
- $(e.target).parents('.peek-rblineprof-file').find('.data').toggle();
-});
diff --git a/app/assets/javascripts/performance_bar.js b/app/assets/javascripts/performance_bar.js
new file mode 100644
index 00000000000..9bbdf7f513c
--- /dev/null
+++ b/app/assets/javascripts/performance_bar.js
@@ -0,0 +1,62 @@
+import 'vendor/peek';
+import 'vendor/peek.performance_bar';
+
+export default class PerformanceBar {
+ constructor(opts) {
+ if (!PerformanceBar.singleton) {
+ this.init(opts);
+ PerformanceBar.singleton = this;
+ }
+ return PerformanceBar.singleton;
+ }
+
+ init(opts) {
+ const $container = $(opts.container);
+ this.$sqlProfileLink = $container.find('.js-toggle-modal-peek-sql');
+ this.$sqlProfileModal = $container.find('#modal-peek-pg-queries');
+ this.$lineProfileLink = $container.find('.js-toggle-modal-peek-line-profile');
+ this.$lineProfileModal = $('#modal-peek-line-profile');
+ this.initEventListeners();
+ this.showModalOnLoad();
+ }
+
+ initEventListeners() {
+ this.$sqlProfileLink.on('click', () => this.handleSQLProfileLink());
+ this.$lineProfileLink.on('click', e => this.handleLineProfileLink(e));
+ $(document).on('click', '.js-lineprof-file', PerformanceBar.toggleLineProfileFile);
+ }
+
+ showModalOnLoad() {
+ // When a lineprofiler query-string param is present, we show the line
+ // profiler modal upon page load
+ if (/lineprofiler/.test(window.location.search)) {
+ PerformanceBar.toggleModal(this.$lineProfileModal);
+ }
+ }
+
+ handleSQLProfileLink() {
+ PerformanceBar.toggleModal(this.$sqlProfileModal);
+ }
+
+ handleLineProfileLink(e) {
+ const lineProfilerParameter = gl.utils.getParameterValues('lineprofiler');
+ const lineProfilerParameterRegex = new RegExp(`lineprofiler=${lineProfilerParameter[0]}`);
+ const shouldToggleModal = lineProfilerParameter.length > 0 &&
+ lineProfilerParameterRegex.test(e.currentTarget.href);
+
+ if (shouldToggleModal) {
+ e.preventDefault();
+ PerformanceBar.toggleModal(this.$lineProfileModal);
+ }
+ }
+
+ static toggleModal($modal) {
+ if ($modal.length) {
+ $modal.modal('toggle');
+ }
+ }
+
+ static toggleLineProfileFile(e) {
+ $(e.currentTarget).parents('.peek-rblineprof-file').find('.data').toggle();
+ }
+}
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
index b424e7f205d..50c725aa3d5 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedule_form_bundle.js
@@ -3,6 +3,7 @@ import Translate from '../vue_shared/translate';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
+import { setupPipelineVariableList } from './setup_pipeline_variable_list';
Vue.use(Translate);
@@ -39,4 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
gl.timezoneDropdown = new TimezoneDropdown();
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
+
+ setupPipelineVariableList($('.js-pipeline-variable-list'));
});
diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
new file mode 100644
index 00000000000..644efd10509
--- /dev/null
+++ b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js
@@ -0,0 +1,71 @@
+function insertRow($row) {
+ const $rowClone = $row.clone();
+ $rowClone.removeAttr('data-is-persisted');
+ $rowClone.find('input, textarea').val('');
+ $row.after($rowClone);
+}
+
+function removeRow($row) {
+ const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
+
+ if (isPersisted) {
+ $row.hide();
+ $row
+ .find('.js-destroy-input')
+ .val(1);
+ } else {
+ $row.remove();
+ }
+}
+
+function checkIfRowTouched($row) {
+ return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
+}
+
+function setupPipelineVariableList(parent = document) {
+ const $parent = $(parent);
+
+ $parent.on('click', '.js-row-remove-button', (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+ removeRow($row);
+
+ e.preventDefault();
+ });
+
+ // Remove any empty rows except the last r
+ $parent.on('blur', '.js-user-input', (e) => {
+ const $row = $(e.currentTarget).closest('.js-row');
+
+ const isTouched = checkIfRowTouched($row);
+ if ($row.is(':not(:last-child)') && !isTouched) {
+ removeRow($row);
+ }
+ });
+
+ // Always make sure there is an empty last row
+ $parent.on('input', '.js-user-input', () => {
+ const $lastRow = $parent.find('.js-row').last();
+
+ const isTouched = checkIfRowTouched($lastRow);
+ if (isTouched) {
+ insertRow($lastRow);
+ }
+ });
+
+ // Clear out the empty last row so it
+ // doesn't get submitted and throw validation errors
+ $parent.closest('form').on('submit', () => {
+ const $lastRow = $parent.find('.js-row').last();
+
+ const isTouched = checkIfRowTouched($lastRow);
+ if (!isTouched) {
+ $lastRow.find('input, textarea').attr('name', '');
+ }
+ });
+}
+
+export {
+ setupPipelineVariableList,
+ insertRow,
+ removeRow,
+};
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index 01ae07aad65..5df317a76bf 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -129,14 +129,11 @@
},
successCallback(resp) {
- const response = {
- headers: resp.headers,
- body: resp.json(),
- };
-
- this.store.storeCount(response.body.count);
- this.store.storePagination(response.headers);
- this.setCommonData(response.body.pipelines);
+ return resp.json().then((response) => {
+ this.store.storeCount(response.count);
+ this.store.storePagination(resp.headers);
+ this.setCommonData(response.pipelines);
+ });
},
},
};
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 87b2725a045..a4a27247406 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -73,8 +73,9 @@ export default {
fetchJobs() {
this.$http.get(this.stage.dropdown_path)
- .then((response) => {
- this.dropdownContent = response.json().html;
+ .then(response => response.json())
+ .then((data) => {
+ this.dropdownContent = data.html;
this.isLoading = false;
})
.catch(() => {
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js
index 82537ea06f5..385e7430a7d 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediatior.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediatior.js
@@ -40,10 +40,10 @@ export default class pipelinesMediator {
}
successCallback(response) {
- const data = response.json();
-
- this.state.isLoading = false;
- this.store.storePipeline(data);
+ return response.json().then((data) => {
+ this.state.isLoading = false;
+ this.store.storePipeline(data);
+ });
}
errorCallback() {
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index c0f757269cb..fd89a1a85c3 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -1,5 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */
+import VisibilitySelect from './visibility_select';
+
function highlightChanges($elm) {
$elm.addClass('highlight-changes');
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
@@ -30,7 +32,7 @@ function highlightChanges($elm) {
ProjectNew.prototype.initVisibilitySelect = function() {
const visibilityContainer = document.querySelector('.js-visibility-select');
if (!visibilityContainer) return;
- const visibilitySelect = new gl.VisibilitySelect(visibilityContainer);
+ const visibilitySelect = new VisibilitySelect(visibilityContainer);
visibilitySelect.init();
const $visibilitySelect = $(visibilityContainer).find('select');
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 9896b88d487..ebcefc819f5 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -104,6 +104,14 @@ import Api from './api';
dropdownCssClass: "ajax-project-dropdown"
});
});
+
+ $('.new-project-item-select-button').on('click', function() {
+ $('.project-item-select', this.parentNode).select2('open');
+ });
+
+ $('.project-item-select').on('click', function() {
+ window.location = `${$(this).val()}/${this.dataset.relativePath}`;
+ });
}
return ProjectSelect;
diff --git a/app/assets/javascripts/protected_branches/index.js b/app/assets/javascripts/protected_branches/index.js
new file mode 100644
index 00000000000..c9e7af127d2
--- /dev/null
+++ b/app/assets/javascripts/protected_branches/index.js
@@ -0,0 +1,9 @@
+/* eslint-disable no-unused-vars */
+
+import ProtectedBranchCreate from './protected_branch_create';
+import ProtectedBranchEditList from './protected_branch_edit_list';
+
+$(() => {
+ const protectedBranchCreate = new ProtectedBranchCreate();
+ const protectedBranchEditList = new ProtectedBranchEditList();
+});
diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
index 42993a252c3..38b1406a99f 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
@@ -1,31 +1,26 @@
-/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */
+export default class ProtectedBranchAccessDropdown {
+ constructor(options) {
+ this.options = options;
+ this.initDropdown();
+ }
-(global => {
- global.gl = global.gl || {};
-
- gl.ProtectedBranchAccessDropdown = class {
- constructor(options) {
- const { $dropdown, data, onSelect } = options;
-
- $dropdown.glDropdown({
- data: data,
- selectable: true,
- inputId: $dropdown.data('input-id'),
- fieldName: $dropdown.data('field-name'),
- toggleLabel(item, el) {
- if (el.is('.is-active')) {
- return item.text;
- } else {
- return 'Select';
- }
- },
- clicked(opts) {
- const { e } = opts;
-
- e.preventDefault();
- onSelect();
+ initDropdown() {
+ const { $dropdown, data, onSelect } = this.options;
+ $dropdown.glDropdown({
+ data,
+ selectable: true,
+ inputId: $dropdown.data('input-id'),
+ fieldName: $dropdown.data('field-name'),
+ toggleLabel(item, $el) {
+ if ($el.is('.is-active')) {
+ return item.text;
}
- });
- }
- };
-})(window);
+ return 'Select';
+ },
+ clicked(options) {
+ options.e.preventDefault();
+ onSelect();
+ },
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 57ea2f52814..10da3783123 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
@@ -1,55 +1,51 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
-/* global ProtectedBranchDropdown */
-
-(global => {
- global.gl = global.gl || {};
-
- gl.ProtectedBranchCreate = class {
- constructor() {
- this.$wrap = this.$form = $('#new_protected_branch');
- this.buildDropdowns();
- }
-
- buildDropdowns() {
- const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
- const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
-
- // Cache callback
- this.onSelectCallback = this.onSelect.bind(this);
-
- // Allowed to Merge dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: $allowedToMergeDropdown,
- data: gon.merge_access_levels,
- onSelect: this.onSelectCallback
- });
-
- // Allowed to Push dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: $allowedToPushDropdown,
- data: gon.push_access_levels,
- onSelect: this.onSelectCallback
- });
-
- // Select default
- $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
- $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
-
- // Protected branch dropdown
- new ProtectedBranchDropdown({
- $dropdown: this.$wrap.find('.js-protected-branch-select'),
- onSelect: this.onSelectCallback
- });
- }
-
- // This will run after clicked callback
- onSelect() {
- // Enable submit button
- const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
- const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
- const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
-
- this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
- }
- };
-})(window);
+import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
+import ProtectedBranchDropdown from './protected_branch_dropdown';
+
+export default class ProtectedBranchCreate {
+ constructor() {
+ this.$form = $('.js-new-protected-branch');
+ this.buildDropdowns();
+ }
+
+ buildDropdowns() {
+ const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
+ const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
+
+ // Cache callback
+ this.onSelectCallback = this.onSelect.bind(this);
+
+ // Allowed to Merge dropdown
+ this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+
+ // Allowed to Push dropdown
+ this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+
+ // Select default
+ $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
+ $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
+
+ // Protected branch dropdown
+ this.protectedBranchDropdown = new ProtectedBranchDropdown({
+ $dropdown: this.$form.find('.js-protected-branch-select'),
+ onSelect: this.onSelectCallback,
+ });
+ }
+
+ // This will run after clicked callback
+ onSelect() {
+ // Enable submit button
+ const $branchInput = this.$form.find('input[name="protected_branch[name]"]');
+ const $allowedToMergeInput = this.$form.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
+ const $allowedToPushInput = this.$form.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
+
+ this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
index bc6110fcd4e..cc0b2ebe071 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
@@ -1,6 +1,10 @@
-/* eslint-disable comma-dangle, no-unused-vars */
-
-class ProtectedBranchDropdown {
+export default class ProtectedBranchDropdown {
+ /**
+ * @param {Object} options containing
+ * `$dropdown` target element
+ * `onSelect` event callback
+ * $dropdown must be an element created using `dropdown_branch()` rails helper
+ */
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
@@ -12,7 +16,7 @@ class ProtectedBranchDropdown {
this.bindEvents();
// Hide footer
- this.$dropdownFooter.addClass('hidden');
+ this.toggleFooter(true);
}
buildDropdown() {
@@ -21,7 +25,7 @@ class ProtectedBranchDropdown {
filterable: true,
remote: false,
search: {
- fields: ['title']
+ fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
@@ -36,10 +40,9 @@ class ProtectedBranchDropdown {
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
- const { $el, e } = options;
- e.preventDefault();
+ options.e.preventDefault();
this.onSelect();
- }
+ },
});
}
@@ -64,20 +67,22 @@ class ProtectedBranchDropdown {
}
toggleCreateNewButton(branchName) {
- this.selectedBranch = {
- title: branchName,
- id: branchName,
- text: branchName
- };
-
if (branchName) {
+ this.selectedBranch = {
+ title: branchName,
+ id: branchName,
+ text: branchName,
+ };
+
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.text(branchName);
}
- this.$dropdownFooter.toggleClass('hidden', !branchName);
+ this.toggleFooter(!branchName);
}
-}
-window.ProtectedBranchDropdown = ProtectedBranchDropdown;
+ toggleFooter(toggleState) {
+ this.$dropdownFooter.toggleClass('hidden', toggleState);
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js
index 6ef59e94384..3b920942a3f 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js
@@ -1,69 +1,67 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
+/* eslint-disable no-new */
/* global Flash */
-(global => {
- global.gl = global.gl || {};
+import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
- gl.ProtectedBranchEdit = class {
- constructor(options) {
- this.$wrap = options.$wrap;
- this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
- this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+export default class ProtectedBranchEdit {
+ constructor(options) {
+ this.$wrap = options.$wrap;
+ this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
+ this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+ this.onSelectCallback = this.onSelect.bind(this);
- this.buildDropdowns();
- }
+ this.buildDropdowns();
+ }
- buildDropdowns() {
- // Allowed to merge dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: this.$allowedToMergeDropdown,
- data: gon.merge_access_levels,
- onSelect: this.onSelect.bind(this)
- });
+ buildDropdowns() {
+ // Allowed to merge dropdown
+ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelectCallback,
+ });
- // Allowed to push dropdown
- new gl.ProtectedBranchAccessDropdown({
- $dropdown: this.$allowedToPushDropdown,
- data: gon.push_access_levels,
- onSelect: this.onSelect.bind(this)
- });
- }
+ // Allowed to push dropdown
+ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelectCallback,
+ });
+ }
- onSelect() {
- const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
- const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
+ onSelect() {
+ const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
+ const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
- // Do not update if one dropdown has not selected any option
- if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
+ // Do not update if one dropdown has not selected any option
+ if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
- this.$allowedToMergeDropdown.disable();
- this.$allowedToPushDropdown.disable();
+ this.$allowedToMergeDropdown.disable();
+ this.$allowedToPushDropdown.disable();
- $.ajax({
- type: 'POST',
- url: this.$wrap.data('url'),
- dataType: 'json',
- data: {
- _method: 'PATCH',
- protected_branch: {
- merge_access_levels_attributes: [{
- id: this.$allowedToMergeDropdown.data('access-level-id'),
- access_level: $allowedToMergeInput.val()
- }],
- push_access_levels_attributes: [{
- id: this.$allowedToPushDropdown.data('access-level-id'),
- access_level: $allowedToPushInput.val()
- }]
- }
+ $.ajax({
+ type: 'POST',
+ url: this.$wrap.data('url'),
+ dataType: 'json',
+ data: {
+ _method: 'PATCH',
+ protected_branch: {
+ merge_access_levels_attributes: [{
+ id: this.$allowedToMergeDropdown.data('access-level-id'),
+ access_level: $allowedToMergeInput.val(),
+ }],
+ push_access_levels_attributes: [{
+ id: this.$allowedToPushDropdown.data('access-level-id'),
+ access_level: $allowedToPushInput.val(),
+ }],
},
- error() {
- $.scrollTo(0);
- new Flash('Failed to update branch!');
- }
- }).always(() => {
- this.$allowedToMergeDropdown.enable();
- this.$allowedToPushDropdown.enable();
- });
- }
- };
-})(window);
+ },
+ error() {
+ new Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
+ },
+ }).always(() => {
+ this.$allowedToMergeDropdown.enable();
+ this.$allowedToPushDropdown.enable();
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
index 336fa6c57a7..b40d3827c30 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
@@ -1,18 +1,18 @@
-/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
+/* eslint-disable no-new */
-(global => {
- global.gl = global.gl || {};
+import ProtectedBranchEdit from './protected_branch_edit';
- gl.ProtectedBranchEditList = class {
- constructor() {
- this.$wrap = $('.protected-branches-list');
+export default class ProtectedBranchEditList {
+ constructor() {
+ this.$wrap = $('.protected-branches-list');
+ this.initEditForm();
+ }
- // Build edit forms
- this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
- new gl.ProtectedBranchEdit({
- $wrap: $(el)
- });
+ initEditForm() {
+ this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
+ new ProtectedBranchEdit({
+ $wrap: $(el),
});
- }
- };
-})(window);
+ });
+ }
+}
diff --git a/app/assets/javascripts/protected_branches/protected_branches_bundle.js b/app/assets/javascripts/protected_branches/protected_branches_bundle.js
deleted file mode 100644
index 874d70a1431..00000000000
--- a/app/assets/javascripts/protected_branches/protected_branches_bundle.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import './protected_branch_access_dropdown';
-import './protected_branch_create';
-import './protected_branch_dropdown';
-import './protected_branch_edit';
-import './protected_branch_edit_list';
diff --git a/app/assets/javascripts/protected_tags/index.js b/app/assets/javascripts/protected_tags/index.js
index 61e7ba53862..b1618e24e49 100644
--- a/app/assets/javascripts/protected_tags/index.js
+++ b/app/assets/javascripts/protected_tags/index.js
@@ -1,2 +1,9 @@
-export { default as ProtectedTagCreate } from './protected_tag_create';
-export { default as ProtectedTagEditList } from './protected_tag_edit_list';
+/* eslint-disable no-unused-vars */
+
+import ProtectedTagCreate from './protected_tag_create';
+import ProtectedTagEditList from './protected_tag_edit_list';
+
+$(() => {
+ const protectedtTagCreate = new ProtectedTagCreate();
+ const protectedtTagEditList = new ProtectedTagEditList();
+});
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index a4a7f3fa944..e3daa8cf949 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,6 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
-/* global findFileURL */
import Cookies from 'js-cookie';
import findAndFollowLink from './shortcuts_dashboard_navigation';
@@ -20,6 +19,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle');
+ const findFileURL = document.body.dataset.findFile;
$('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts');
@@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
if (Cookies.get(performanceBarCookieName) === 'true') {
Cookies.remove(performanceBarCookieName, { path: '/' });
} else {
- Cookies.set(performanceBarCookieName, true, { path: '/' });
+ Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
gl.utils.refreshCurrentPage();
};
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 5ccfb4ee9c1..721e92221cf 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -28,8 +28,8 @@ export default class SidebarMediator {
fetch() {
this.service.get()
- .then((response) => {
- const data = response.json();
+ .then(response => response.json())
+ .then((data) => {
this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data);
})
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js b/app/assets/javascripts/signin_tabs_memoizer.js
index 2587facc582..20255398047 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/signin_tabs_memoizer.js
@@ -2,56 +2,52 @@
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
-((global) => {
- /**
- * Memorize the last selected tab after reloading a page.
- * Does that setting the current selected tab in the localStorage
- */
- class ActiveTabMemoizer {
- constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
- this.currentTabKey = currentTabKey;
- this.tabSelector = tabSelector;
- this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
-
- this.bootstrap();
- }
-
- bootstrap() {
- const tabs = document.querySelectorAll(this.tabSelector);
- if (tabs.length > 0) {
- tabs[0].addEventListener('click', (e) => {
- if (e.target && e.target.nodeName === 'A') {
- const anchorName = e.target.getAttribute('href');
- this.saveData(anchorName);
- }
- });
- }
-
- this.showTab();
- }
+/**
+ * Memorize the last selected tab after reloading a page.
+ * Does that setting the current selected tab in the localStorage
+ */
+export default class SigninTabsMemoizer {
+ constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
+ this.currentTabKey = currentTabKey;
+ this.tabSelector = tabSelector;
+ this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
+
+ this.bootstrap();
+ }
- showTab() {
- const anchorName = this.readData();
- if (anchorName) {
- const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
- if (tab) {
- tab.click();
+ bootstrap() {
+ const tabs = document.querySelectorAll(this.tabSelector);
+ if (tabs.length > 0) {
+ tabs[0].addEventListener('click', (e) => {
+ if (e.target && e.target.nodeName === 'A') {
+ const anchorName = e.target.getAttribute('href');
+ this.saveData(anchorName);
}
- }
+ });
}
- saveData(val) {
- if (!this.isLocalStorageAvailable) return undefined;
+ this.showTab();
+ }
- return window.localStorage.setItem(this.currentTabKey, val);
+ showTab() {
+ const anchorName = this.readData();
+ if (anchorName) {
+ const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`);
+ if (tab) {
+ tab.click();
+ }
}
+ }
- readData() {
- if (!this.isLocalStorageAvailable) return null;
+ saveData(val) {
+ if (!this.isLocalStorageAvailable) return undefined;
- return window.localStorage.getItem(this.currentTabKey);
- }
+ return window.localStorage.setItem(this.currentTabKey, val);
}
- global.ActiveTabMemoizer = ActiveTabMemoizer;
-})(window);
+ readData() {
+ if (!this.isLocalStorageAvailable) return null;
+
+ return window.localStorage.getItem(this.currentTabKey);
+ }
+}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 9316a2af0b7..4505a79a2df 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -2,99 +2,82 @@
import FilesCommentButton from './files_comment_button';
-(function() {
- window.SingleFileDiff = (function() {
- var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
+const WRAPPER = '<div class="diff-content"></div>';
+const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
+const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
- WRAPPER = '<div class="diff-content"></div>';
-
- LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
-
- ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+export default class SingleFileDiff {
+ constructor(file) {
+ this.file = file;
+ this.toggleDiff = this.toggleDiff.bind(this);
+ this.content = $('.diff-content', this.file);
+ this.$toggleIcon = $('.diff-toggle-caret', this.file);
+ this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
+ this.isOpen = !this.diffForPath;
+ if (this.diffForPath) {
+ this.collapsedContent = this.content;
+ this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+ this.content = null;
+ this.collapsedContent.after(this.loadingContent);
+ this.$toggleIcon.addClass('fa-caret-right');
+ } else {
+ this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+ this.content.after(this.collapsedContent);
+ this.$toggleIcon.addClass('fa-caret-down');
+ }
- COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
+ $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
+ this.toggleDiff($(e.target));
+ }).bind(this));
+ }
- function SingleFileDiff(file) {
- this.file = file;
- this.toggleDiff = this.toggleDiff.bind(this);
- this.content = $('.diff-content', this.file);
- this.$toggleIcon = $('.diff-toggle-caret', this.file);
- this.diffForPath = this.content.find('[data-diff-for-path]').data('diff-for-path');
- this.isOpen = !this.diffForPath;
- if (this.diffForPath) {
- this.collapsedContent = this.content;
- this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
- this.content = null;
- this.collapsedContent.after(this.loadingContent);
- this.$toggleIcon.addClass('fa-caret-right');
- } else {
- this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
- this.content.after(this.collapsedContent);
- this.$toggleIcon.addClass('fa-caret-down');
+ toggleDiff($target, cb) {
+ if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+ this.isOpen = !this.isOpen;
+ if (!this.isOpen && !this.hasError) {
+ this.content.hide();
+ this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
+ this.collapsedContent.show();
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ gl.diffNotesCompileComponents();
}
-
- $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
- this.toggleDiff($(e.target));
- }).bind(this));
+ } else if (this.content) {
+ this.collapsedContent.hide();
+ this.content.show();
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ if (typeof gl.diffNotesCompileComponents !== 'undefined') {
+ gl.diffNotesCompileComponents();
+ }
+ } else {
+ this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ return this.getContentHTML(cb);
}
+ }
- SingleFileDiff.prototype.toggleDiff = function($target, cb) {
- if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
- this.isOpen = !this.isOpen;
- if (!this.isOpen && !this.hasError) {
- this.content.hide();
- this.$toggleIcon.addClass('fa-caret-right').removeClass('fa-caret-down');
- this.collapsedContent.show();
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- gl.diffNotesCompileComponents();
+ getContentHTML(cb) {
+ this.collapsedContent.hide();
+ this.loadingContent.show();
+ $.get(this.diffForPath, (function(_this) {
+ return function(data) {
+ _this.loadingContent.hide();
+ if (data.html) {
+ _this.content = $(data.html);
+ _this.content.syntaxHighlight();
+ } else {
+ _this.hasError = true;
+ _this.content = $(ERROR_HTML);
}
- } else if (this.content) {
- this.collapsedContent.hide();
- this.content.show();
- this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
+ _this.collapsedContent.after(_this.content);
+
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
}
- } else {
- this.$toggleIcon.addClass('fa-caret-down').removeClass('fa-caret-right');
- return this.getContentHTML(cb);
- }
- };
-
- SingleFileDiff.prototype.getContentHTML = function(cb) {
- this.collapsedContent.hide();
- this.loadingContent.show();
- $.get(this.diffForPath, (function(_this) {
- return function(data) {
- _this.loadingContent.hide();
- if (data.html) {
- _this.content = $(data.html);
- _this.content.syntaxHighlight();
- } else {
- _this.hasError = true;
- _this.content = $(ERROR_HTML);
- }
- _this.collapsedContent.after(_this.content);
- if (typeof gl.diffNotesCompileComponents !== 'undefined') {
- gl.diffNotesCompileComponents();
- }
+ FilesCommentButton.init($(_this.file));
- FilesCommentButton.init($(_this.file));
-
- if (cb) cb();
- };
- })(this));
- };
-
- return SingleFileDiff;
- })();
-
- $.fn.singleFileDiff = function() {
- return this.each(function() {
- if (!$.data(this, 'singleFileDiff')) {
- return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
- }
- });
- };
-}).call(window);
+ if (cb) cb();
+ };
+ })(this));
+ }
+}
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index d1bdc353be2..2bf7a3a5d61 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -1,158 +1,157 @@
-/*
-* Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
-* and controllable by a public API.
-*
-* */
-
-(() => {
- class SmartInterval {
- /**
- * @param { function } opts.callback Function to be called on each iteration (required)
- * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
- * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
- * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
- * when the page is hidden
- * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
- * @param { boolean } opts.lazyStart Configure if timer is initialized on
- * instantiation or lazily
- * @param { boolean } opts.immediateExecution Configure if callback should
- * be executed before the first interval.
- */
- constructor(opts = {}) {
- this.cfg = {
- callback: opts.callback,
- startingInterval: opts.startingInterval,
- maxInterval: opts.maxInterval,
- hiddenInterval: opts.hiddenInterval,
- incrementByFactorOf: opts.incrementByFactorOf,
- lazyStart: opts.lazyStart,
- immediateExecution: opts.immediateExecution,
- };
-
- this.state = {
- intervalId: null,
- currentInterval: this.cfg.startingInterval,
- pageVisibility: 'visible',
- };
-
- this.initInterval();
- }
- /* public */
-
- start() {
- const cfg = this.cfg;
- const state = this.state;
-
- if (cfg.immediateExecution) {
- cfg.immediateExecution = false;
- cfg.callback();
- }
+/**
+ * Instances of SmartInterval extend the functionality of `setInterval`, make it configurable
+ * and controllable by a public API.
+ */
+
+class SmartInterval {
+ /**
+ * @param { function } opts.callback Function to be called on each iteration (required)
+ * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially
+ * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this
+ * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this
+ * when the page is hidden
+ * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor
+ * @param { boolean } opts.lazyStart Configure if timer is initialized on
+ * instantiation or lazily
+ * @param { boolean } opts.immediateExecution Configure if callback should
+ * be executed before the first interval.
+ */
+ constructor(opts = {}) {
+ this.cfg = {
+ callback: opts.callback,
+ startingInterval: opts.startingInterval,
+ maxInterval: opts.maxInterval,
+ hiddenInterval: opts.hiddenInterval,
+ incrementByFactorOf: opts.incrementByFactorOf,
+ lazyStart: opts.lazyStart,
+ immediateExecution: opts.immediateExecution,
+ };
+
+ this.state = {
+ intervalId: null,
+ currentInterval: this.cfg.startingInterval,
+ pageVisibility: 'visible',
+ };
+
+ this.initInterval();
+ }
- state.intervalId = window.setInterval(() => {
- cfg.callback();
+ /* public */
- if (this.getCurrentInterval() === cfg.maxInterval) {
- return;
- }
+ start() {
+ const cfg = this.cfg;
+ const state = this.state;
- this.incrementInterval();
- this.resume();
- }, this.getCurrentInterval());
+ if (cfg.immediateExecution) {
+ cfg.immediateExecution = false;
+ cfg.callback();
}
- // cancel the existing timer, setting the currentInterval back to startingInterval
- cancel() {
- this.setCurrentInterval(this.cfg.startingInterval);
- this.stopTimer();
- }
+ state.intervalId = window.setInterval(() => {
+ cfg.callback();
- onVisibilityHidden() {
- if (this.cfg.hiddenInterval) {
- this.setCurrentInterval(this.cfg.hiddenInterval);
- this.resume();
- } else {
- this.cancel();
+ if (this.getCurrentInterval() === cfg.maxInterval) {
+ return;
}
- }
- // start a timer, using the existing interval
- resume() {
- this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
- this.start();
- }
+ this.incrementInterval();
+ this.resume();
+ }, this.getCurrentInterval());
+ }
- onVisibilityVisible() {
- this.cancel();
- this.start();
- }
+ // cancel the existing timer, setting the currentInterval back to startingInterval
+ cancel() {
+ this.setCurrentInterval(this.cfg.startingInterval);
+ this.stopTimer();
+ }
- destroy() {
+ onVisibilityHidden() {
+ if (this.cfg.hiddenInterval) {
+ this.setCurrentInterval(this.cfg.hiddenInterval);
+ this.resume();
+ } else {
this.cancel();
- document.removeEventListener('visibilitychange', this.handleVisibilityChange);
- $(document).off('visibilitychange').off('beforeunload');
}
+ }
- /* private */
+ // start a timer, using the existing interval
+ resume() {
+ this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped
+ this.start();
+ }
- initInterval() {
- const cfg = this.cfg;
+ onVisibilityVisible() {
+ this.cancel();
+ this.start();
+ }
- if (!cfg.lazyStart) {
- this.start();
- }
+ destroy() {
+ this.cancel();
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
+ $(document).off('visibilitychange').off('beforeunload');
+ }
- this.initVisibilityChangeHandling();
- this.initPageUnloadHandling();
- }
+ /* private */
- initVisibilityChangeHandling() {
- // cancel interval when tab no longer shown (prevents cached pages from polling)
- document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
- }
+ initInterval() {
+ const cfg = this.cfg;
- initPageUnloadHandling() {
- // TODO: Consider refactoring in light of turbolinks removal.
- // prevent interval continuing after page change, when kept in cache by Turbolinks
- $(document).on('beforeunload', () => this.cancel());
+ if (!cfg.lazyStart) {
+ this.start();
}
- handleVisibilityChange(e) {
- this.state.pageVisibility = e.target.visibilityState;
- const intervalAction = this.isPageVisible() ?
- this.onVisibilityVisible :
- this.onVisibilityHidden;
+ this.initVisibilityChangeHandling();
+ this.initPageUnloadHandling();
+ }
- intervalAction.apply(this);
- }
+ initVisibilityChangeHandling() {
+ // cancel interval when tab no longer shown (prevents cached pages from polling)
+ document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
+ }
- getCurrentInterval() {
- return this.state.currentInterval;
- }
+ initPageUnloadHandling() {
+ // TODO: Consider refactoring in light of turbolinks removal.
+ // prevent interval continuing after page change, when kept in cache by Turbolinks
+ $(document).on('beforeunload', () => this.cancel());
+ }
- setCurrentInterval(newInterval) {
- this.state.currentInterval = newInterval;
- }
+ handleVisibilityChange(e) {
+ this.state.pageVisibility = e.target.visibilityState;
+ const intervalAction = this.isPageVisible() ?
+ this.onVisibilityVisible :
+ this.onVisibilityHidden;
- incrementInterval() {
- const cfg = this.cfg;
- const currentInterval = this.getCurrentInterval();
- if (cfg.hiddenInterval && !this.isPageVisible()) return;
- let nextInterval = currentInterval * cfg.incrementByFactorOf;
+ intervalAction.apply(this);
+ }
- if (nextInterval > cfg.maxInterval) {
- nextInterval = cfg.maxInterval;
- }
+ getCurrentInterval() {
+ return this.state.currentInterval;
+ }
+
+ setCurrentInterval(newInterval) {
+ this.state.currentInterval = newInterval;
+ }
+
+ incrementInterval() {
+ const cfg = this.cfg;
+ const currentInterval = this.getCurrentInterval();
+ if (cfg.hiddenInterval && !this.isPageVisible()) return;
+ let nextInterval = currentInterval * cfg.incrementByFactorOf;
- this.setCurrentInterval(nextInterval);
+ if (nextInterval > cfg.maxInterval) {
+ nextInterval = cfg.maxInterval;
}
- isPageVisible() { return this.state.pageVisibility === 'visible'; }
+ this.setCurrentInterval(nextInterval);
+ }
- stopTimer() {
- const state = this.state;
+ isPageVisible() { return this.state.pageVisibility === 'visible'; }
- state.intervalId = window.clearInterval(state.intervalId);
- }
+ stopTimer() {
+ const state = this.state;
+
+ state.intervalId = window.clearInterval(state.intervalId);
}
- gl.SmartInterval = SmartInterval;
-})(window.gl || (window.gl = {}));
+}
+
+window.gl.SmartInterval = SmartInterval;
diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js
index 2128007113f..3b6d999b1c3 100644
--- a/app/assets/javascripts/snippets_list.js
+++ b/app/assets/javascripts/snippets_list.js
@@ -1,13 +1,9 @@
-/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
+function SnippetsList() {
+ const $holder = $('.snippets-list-holder');
-(global => {
- global.gl = global.gl || {};
+ $holder.find('.pagination').on('ajax:success', (e, data) => {
+ $holder.replaceWith(data.html);
+ });
+}
- gl.SnippetsList = function() {
- var $holder = $('.snippets-list-holder');
-
- $holder.find('.pagination').on('ajax:success', (e, data) => {
- $holder.replaceWith(data.html);
- });
- };
-})(window);
+window.gl.SnippetsList = SnippetsList;
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index c75b44cc2fd..3a06b477d7c 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,30 +1,28 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
-(function() {
- this.Star = (function() {
- function Star() {
- $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
- var $starIcon, $starSpan, $this, toggleStar;
- $this = $(this);
- $starSpan = $this.find('span');
- $starIcon = $this.find('i');
- toggleStar = function(isStarred) {
- $this.parent().find('.star-count').text(data.star_count);
- if (isStarred) {
- $starSpan.removeClass('starred').text('Star');
- $starIcon.removeClass('fa-star').addClass('fa-star-o');
- } else {
- $starSpan.addClass('starred').text('Unstar');
- $starIcon.removeClass('fa-star-o').addClass('fa-star');
- }
- };
- toggleStar($starSpan.hasClass('starred'));
- }).on('ajax:error', function(e, xhr, status, error) {
- new Flash('Star toggle failed. Try again later.', 'alert');
- });
- }
+import { __, s__ } from './locale';
- return Star;
- })();
-}).call(window);
+export default class Star {
+ constructor() {
+ $('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
+ var $starIcon, $starSpan, $this, toggleStar;
+ $this = $(this);
+ $starSpan = $this.find('span');
+ $starIcon = $this.find('i');
+ toggleStar = function(isStarred) {
+ $this.parent().find('.star-count').text(data.star_count);
+ if (isStarred) {
+ $starSpan.removeClass('starred').text(s__('StarProject|Star'));
+ $starIcon.removeClass('fa-star').addClass('fa-star-o');
+ } else {
+ $starSpan.addClass('starred').text(__('Unstar'));
+ $starIcon.removeClass('fa-star-o').addClass('fa-star');
+ }
+ };
+ toggleStar($starSpan.hasClass('starred'));
+ }).on('ajax:error', function(e, xhr, status, error) {
+ new Flash('Star toggle failed. Try again later.', 'alert');
+ });
+ }
+}
diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js
index 5f9a3e00c22..bb4d68fcd49 100644
--- a/app/assets/javascripts/subscription.js
+++ b/app/assets/javascripts/subscription.js
@@ -1,47 +1,45 @@
-(() => {
- class Subscription {
- constructor(containerElm) {
- this.containerElm = containerElm;
+class Subscription {
+ constructor(containerElm) {
+ this.containerElm = containerElm;
- const subscribeButton = containerElm.querySelector('.js-subscribe-button');
- if (subscribeButton) {
- // remove class so we don't bind twice
- subscribeButton.classList.remove('js-subscribe-button');
- subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
- }
+ const subscribeButton = containerElm.querySelector('.js-subscribe-button');
+ if (subscribeButton) {
+ // remove class so we don't bind twice
+ subscribeButton.classList.remove('js-subscribe-button');
+ subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
+ }
- toggleSubscription(event) {
- const button = event.currentTarget;
- const buttonSpan = button.querySelector('span');
- if (!buttonSpan || button.classList.contains('disabled')) {
- return;
- }
- button.classList.add('disabled');
+ toggleSubscription(event) {
+ const button = event.currentTarget;
+ const buttonSpan = button.querySelector('span');
+ if (!buttonSpan || button.classList.contains('disabled')) {
+ return;
+ }
+ button.classList.add('disabled');
- const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
- const toggleActionUrl = this.containerElm.dataset.url;
+ const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
+ const toggleActionUrl = this.containerElm.dataset.url;
- $.post(toggleActionUrl, () => {
- button.classList.remove('disabled');
+ $.post(toggleActionUrl, () => {
+ button.classList.remove('disabled');
- // hack to allow this to work with the issue boards Vue object
- if (document.querySelector('html').classList.contains('issue-boards-page')) {
- gl.issueBoards.boardStoreIssueSet(
- 'subscribed',
- !gl.issueBoards.BoardsStore.detail.issue.subscribed,
- );
- } else {
- buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
- }
- });
- }
+ // hack to allow this to work with the issue boards Vue object
+ if (document.querySelector('html').classList.contains('issue-boards-page')) {
+ gl.issueBoards.boardStoreIssueSet(
+ 'subscribed',
+ !gl.issueBoards.BoardsStore.detail.issue.subscribed,
+ );
+ } else {
+ buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
+ }
+ });
+ }
- static bindAll(selector) {
- [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
- }
+ static bindAll(selector) {
+ [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
+}
- window.gl = window.gl || {};
- window.gl.Subscription = Subscription;
-})();
+window.gl = window.gl || {};
+window.gl.Subscription = Subscription;
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 0cd591c7320..37e39ce5477 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,34 +1,33 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
-(function() {
- this.SubscriptionSelect = (function() {
- function SubscriptionSelect() {
- $('.js-subscription-event').each(function(i, el) {
- var fieldName;
- fieldName = $(el).data("field-name");
- return $(el).glDropdown({
- selectable: true,
- fieldName: fieldName,
- toggleLabel: (function(_this) {
- return function(selected, el, instance) {
- var $item, label;
- label = 'Subscription';
- $item = instance.dropdown.find('.is-active');
- if ($item.length) {
- label = $item.text();
- }
- return label;
- };
- })(this),
- clicked: function(options) {
- return options.e.preventDefault();
- },
- id: function(obj, el) {
- return $(el).data("id");
- }
- });
+
+class SubscriptionSelect {
+ constructor() {
+ $('.js-subscription-event').each(function(i, el) {
+ var fieldName;
+ fieldName = $(el).data("field-name");
+ return $(el).glDropdown({
+ selectable: true,
+ fieldName: fieldName,
+ toggleLabel: (function(_this) {
+ return function(selected, el, instance) {
+ var $item, label;
+ label = 'Subscription';
+ $item = instance.dropdown.find('.is-active');
+ if ($item.length) {
+ label = $item.text();
+ }
+ return label;
+ };
+ })(this),
+ clicked: function(options) {
+ return options.e.preventDefault();
+ },
+ id: function(obj, el) {
+ return $(el).data("id");
+ }
});
- }
+ });
+ }
+}
- return SubscriptionSelect;
- })();
-}).call(window);
+window.SubscriptionSelect = SubscriptionSelect;
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 7c063fae045..662d6b36c16 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -9,19 +9,18 @@
//
// <div class="js-syntax-highlight"></div>
//
-(function() {
- $.fn.syntaxHighlight = function() {
- var $children;
- if ($(this).hasClass('js-syntax-highlight')) {
- // Given the element itself, apply highlighting
- return $(this).addClass(gon.user_color_scheme);
- } else {
- // Given a parent element, recurse to any of its applicable children
- $children = $(this).find('.js-syntax-highlight');
- if ($children.length) {
- return $children.syntaxHighlight();
- }
+$.fn.syntaxHighlight = function() {
+ var $children;
+
+ if ($(this).hasClass('js-syntax-highlight')) {
+ // Given the element itself, apply highlighting
+ return $(this).addClass(gon.user_color_scheme);
+ } else {
+ // Given a parent element, recurse to any of its applicable children
+ $children = $(this).find('.js-syntax-highlight');
+ if ($children.length) {
+ return $children.syntaxHighlight();
}
- };
-}).call(window);
+ }
+};
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 419c458ff34..c39f569da5e 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -2,7 +2,7 @@
import 'deckar01-task_list';
-class TaskList {
+export default class TaskList {
constructor(options = {}) {
this.selector = options.selector;
this.dataType = options.dataType;
@@ -48,6 +48,3 @@ class TaskList {
});
}
}
-
-window.gl = window.gl || {};
-window.gl.TaskList = TaskList;
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 7230946b484..cd305631c10 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -2,7 +2,7 @@
import UsersSelect from './users_select';
-class Todos {
+export default class Todos {
constructor() {
this.initFilters();
this.bindEvents();
@@ -159,6 +159,3 @@ class Todos {
}
}
}
-
-window.gl = window.gl || {};
-gl.Todos = Todos;
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 76a821c7a17..7777ed1c3dc 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,68 +1,64 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */
-(function() {
- this.TreeView = (function() {
- function TreeView() {
- this.initKeyNav();
- // Code browser tree slider
- // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $(".tree-content-holder .tree-item").on('click', function(e) {
- var $clickedEl, path;
- $clickedEl = $(e.target);
- path = $('.tree-item-file-name a', this).attr('href');
- if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
- if (e.metaKey || e.which === 2) {
- e.preventDefault();
- return window.open(path, '_blank');
- } else {
- return gl.utils.visitUrl(path);
- }
+export default class TreeView {
+ constructor() {
+ this.initKeyNav();
+ // Code browser tree slider
+ // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
+ $(".tree-content-holder .tree-item").on('click', function(e) {
+ var $clickedEl, path;
+ $clickedEl = $(e.target);
+ path = $('.tree-item-file-name a', this).attr('href');
+ if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
+ if (e.metaKey || e.which === 2) {
+ e.preventDefault();
+ return window.open(path, '_blank');
+ } else {
+ return gl.utils.visitUrl(path);
}
- });
- // Show the "Loading commit data" for only the first element
- $('span.log_loading:first').removeClass('hide');
- }
+ }
+ });
+ // Show the "Loading commit data" for only the first element
+ $('span.log_loading:first').removeClass('hide');
+ }
- TreeView.prototype.initKeyNav = function() {
- var li, liSelected;
- li = $("tr.tree-item");
- liSelected = null;
- return $('body').keydown(function(e) {
- var next, path;
- if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
- return false;
- }
- if (e.which === 40) {
- if (liSelected) {
- next = liSelected.next();
- if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
- }
- } else {
- liSelected = li.eq(0).addClass("selected");
- }
- return $(liSelected).focus();
- } else if (e.which === 38) {
- if (liSelected) {
- next = liSelected.prev();
- if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
- }
- } else {
- liSelected = li.last().addClass("selected");
+ initKeyNav() {
+ var li, liSelected;
+ li = $("tr.tree-item");
+ liSelected = null;
+ return $('body').keydown(function(e) {
+ var next, path;
+ if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+ return false;
+ }
+ if (e.which === 40) {
+ if (liSelected) {
+ next = liSelected.next();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
}
- return $(liSelected).focus();
- } else if (e.which === 13) {
- path = $('.tree-item.selected .tree-item-file-name a').attr('href');
- if (path) {
- return gl.utils.visitUrl(path);
+ } else {
+ liSelected = li.eq(0).addClass("selected");
+ }
+ return $(liSelected).focus();
+ } else if (e.which === 38) {
+ if (liSelected) {
+ next = liSelected.prev();
+ if (next.length > 0) {
+ liSelected.removeClass("selected");
+ liSelected = next.addClass("selected");
}
+ } else {
+ liSelected = li.last().addClass("selected");
}
- });
- };
-
- return TreeView;
- })();
-}).call(window);
+ return $(liSelected).focus();
+ } else if (e.which === 13) {
+ path = $('.tree-item.selected .tree-item-file-name a').attr('href');
+ if (path) {
+ return gl.utils.visitUrl(path);
+ }
+ }
+ });
+ }
+}
diff --git a/app/assets/javascripts/usage_ping.js b/app/assets/javascripts/usage_ping.js
index fd3af7d7ab6..2389056bd02 100644
--- a/app/assets/javascripts/usage_ping.js
+++ b/app/assets/javascripts/usage_ping.js
@@ -1,4 +1,4 @@
-function UsagePing() {
+export default function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint');
$.ajax({
@@ -10,6 +10,3 @@ function UsagePing() {
},
});
}
-
-window.gl = window.gl || {};
-window.gl.UsagePing = UsagePing;
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
deleted file mode 100644
index 19c9efe7fbd..00000000000
--- a/app/assets/javascripts/user.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
-
-import Cookies from 'js-cookie';
-
-((global) => {
- global.User = class {
- constructor({ action }) {
- this.action = action;
- this.placeProfileAvatarsToTop();
- this.initTabs();
- this.hideProjectLimitMessage();
- }
-
- placeProfileAvatarsToTop() {
- $('.profile-groups-avatars').tooltip({
- placement: 'top'
- });
- }
-
- initTabs() {
- return new global.UserTabs({
- parentEl: '.user-profile',
- action: this.action
- });
- }
-
- hideProjectLimitMessage() {
- $('.hide-project-limit-message').on('click', e => {
- e.preventDefault();
- Cookies.set('hide_project_limit_message', 'false');
- $(this).parents('.project-limit-message').remove();
- });
- }
- };
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js
deleted file mode 100644
index ce7eb76dc71..00000000000
--- a/app/assets/javascripts/user_tabs.js
+++ /dev/null
@@ -1,175 +0,0 @@
-/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */
-
-/*
-UserTabs
-
-Handles persisting and restoring the current tab selection and lazily-loading
-content on the Users#show page.
-
-### Example Markup
-
- <ul class="nav-links">
- <li class="activity-tab active">
- <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
- Activity
- </a>
- </li>
- <li class="groups-tab">
- <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
- Groups
- </a>
- </li>
- <li class="contributed-tab">
- <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
- Contributed projects
- </a>
- </li>
- <li class="projects-tab">
- <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
- Personal projects
- </a>
- </li>
- <li class="snippets-tab">
- <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
- </a>
- </li>
- </ul>
-
- <div class="tab-content">
- <div class="tab-pane" id="activity">
- Activity Content
- </div>
- <div class="tab-pane" id="groups">
- Groups Content
- </div>
- <div class="tab-pane" id="contributed">
- Contributed projects content
- </div>
- <div class="tab-pane" id="projects">
- Projects content
- </div>
- <div class="tab-pane" id="snippets">
- Snippets content
- </div>
- </div>
-
- <div class="loading-status">
- <div class="loading">
- Loading Animation
- </div>
- </div>
-*/
-((global) => {
- class UserTabs {
- constructor ({ defaultAction, action, parentEl }) {
- this.loaded = {};
- this.defaultAction = defaultAction || 'activity';
- this.action = action || this.defaultAction;
- this.$parentEl = $(parentEl) || $(document);
- this._location = window.location;
- this.$parentEl.find('.nav-links a')
- .each((i, navLink) => {
- this.loaded[$(navLink).attr('data-action')] = false;
- });
- this.actions = Object.keys(this.loaded);
- this.bindEvents();
-
- if (this.action === 'show') {
- this.action = this.defaultAction;
- }
-
- this.activateTab(this.action);
- }
-
- bindEvents() {
- this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
-
- this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
- .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
-
- this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
- }
-
- changeProjectsPage(e) {
- e.preventDefault();
-
- $('.tab-pane.active').empty();
- const endpoint = $(e.target).attr('href');
- this.loadTab(this.getCurrentAction(), endpoint);
- }
-
- tabShown(event) {
- const $target = $(event.target);
- const action = $target.data('action');
- const source = $target.attr('href');
- const endpoint = $target.data('endpoint');
- this.setTab(action, endpoint);
- return this.setCurrentAction(source);
- }
-
- activateTab(action) {
- return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
- .tab('show');
- }
-
- setTab(action, endpoint) {
- if (this.loaded[action]) {
- return;
- }
- if (action === 'activity') {
- this.loadActivities();
- }
-
- const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
- if (loadableActions.indexOf(action) > -1) {
- return this.loadTab(action, endpoint);
- }
- }
-
- loadTab(action, endpoint) {
- return $.ajax({
- beforeSend: () => this.toggleLoading(true),
- complete: () => this.toggleLoading(false),
- dataType: 'json',
- type: 'GET',
- url: endpoint,
- success: (data) => {
- const tabSelector = `div#${action}`;
- this.$parentEl.find(tabSelector).html(data.html);
- this.loaded[action] = true;
- return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
- }
- });
- }
-
- loadActivities() {
- if (this.loaded['activity']) {
- return;
- }
- const $calendarWrap = this.$parentEl.find('.user-calendar');
- $calendarWrap.load($calendarWrap.data('href'));
- new gl.Activities();
- return this.loaded['activity'] = true;
- }
-
- toggleLoading(status) {
- return this.$parentEl.find('.loading-status .loading')
- .toggle(status);
- }
-
- setCurrentAction(source) {
- let new_state = source;
- new_state = new_state.replace(/\/+$/, '');
- new_state += this._location.search + this._location.hash;
- history.replaceState({
- url: new_state
- }, document.title, new_state);
- return new_state;
- }
-
- getCurrentAction() {
- return this.$parentEl.find('.nav-links .active a').data('action');
- }
- }
- global.UserTabs = UserTabs;
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/username_validator.js b/app/assets/javascripts/username_validator.js
index 137cefa3b8e..a348d69153c 100644
--- a/app/assets/javascripts/username_validator.js
+++ b/app/assets/javascripts/username_validator.js
@@ -1,135 +1,131 @@
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
-((global) => {
- const debounceTimeoutDuration = 1000;
- const invalidInputClass = 'gl-field-error-outline';
- const successInputClass = 'gl-field-success-outline';
- const unavailableMessageSelector = '.username .validation-error';
- const successMessageSelector = '.username .validation-success';
- const pendingMessageSelector = '.username .validation-pending';
- const invalidMessageSelector = '.username .gl-field-error';
-
- class UsernameValidator {
- constructor() {
- this.inputElement = $('#new_user_username');
- this.inputDomElement = this.inputElement.get(0);
- this.state = {
- available: false,
- valid: false,
- pending: false,
- empty: true
- };
-
- const debounceTimeout = _.debounce((username) => {
- this.validateUsername(username);
- }, debounceTimeoutDuration);
-
- this.inputElement.on('keyup.username_check', () => {
- const username = this.inputElement.val();
-
- this.state.valid = this.inputDomElement.validity.valid;
- this.state.empty = !username.length;
-
- if (this.state.valid) {
- return debounceTimeout(username);
- }
-
- this.renderState();
- });
-
- // Override generic field validation
- this.inputElement.on('invalid', this.interceptInvalid.bind(this));
- }
-
- renderState() {
- // Clear all state
- this.clearFieldValidationState();
+const debounceTimeoutDuration = 1000;
+const invalidInputClass = 'gl-field-error-outline';
+const successInputClass = 'gl-field-success-outline';
+const unavailableMessageSelector = '.username .validation-error';
+const successMessageSelector = '.username .validation-success';
+const pendingMessageSelector = '.username .validation-pending';
+const invalidMessageSelector = '.username .gl-field-error';
+
+export default class UsernameValidator {
+ constructor() {
+ this.inputElement = $('#new_user_username');
+ this.inputDomElement = this.inputElement.get(0);
+ this.state = {
+ available: false,
+ valid: false,
+ pending: false,
+ empty: true
+ };
+
+ const debounceTimeout = _.debounce((username) => {
+ this.validateUsername(username);
+ }, debounceTimeoutDuration);
+
+ this.inputElement.on('keyup.username_check', () => {
+ const username = this.inputElement.val();
+
+ this.state.valid = this.inputDomElement.validity.valid;
+ this.state.empty = !username.length;
- if (this.state.valid && this.state.available) {
- return this.setSuccessState();
+ if (this.state.valid) {
+ return debounceTimeout(username);
}
- if (this.state.empty) {
- return this.clearFieldValidationState();
- }
+ this.renderState();
+ });
- if (this.state.pending) {
- return this.setPendingState();
- }
+ // Override generic field validation
+ this.inputElement.on('invalid', this.interceptInvalid.bind(this));
+ }
- if (!this.state.available) {
- return this.setUnavailableState();
- }
+ renderState() {
+ // Clear all state
+ this.clearFieldValidationState();
- if (!this.state.valid) {
- return this.setInvalidState();
- }
+ if (this.state.valid && this.state.available) {
+ return this.setSuccessState();
}
- interceptInvalid(event) {
- event.preventDefault();
- event.stopPropagation();
+ if (this.state.empty) {
+ return this.clearFieldValidationState();
}
- validateUsername(username) {
- if (this.state.valid) {
- this.state.pending = true;
- this.state.available = false;
- this.renderState();
- return $.ajax({
- type: 'GET',
- url: `${gon.relative_url_root}/users/${username}/exists`,
- dataType: 'json',
- success: (res) => this.setAvailabilityState(res.exists)
- });
- }
+ if (this.state.pending) {
+ return this.setPendingState();
}
- setAvailabilityState(usernameTaken) {
- if (usernameTaken) {
- this.state.valid = false;
- this.state.available = false;
- } else {
- this.state.available = true;
- }
- this.state.pending = false;
- this.renderState();
+ if (!this.state.available) {
+ return this.setUnavailableState();
}
- clearFieldValidationState() {
- this.inputElement.siblings('p').hide();
-
- this.inputElement.removeClass(invalidInputClass)
- .removeClass(successInputClass);
+ if (!this.state.valid) {
+ return this.setInvalidState();
}
+ }
- setUnavailableState() {
- const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
- this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
- $usernameUnavailableMessage.show();
- }
+ interceptInvalid(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
- setSuccessState() {
- const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
- this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
- $usernameSuccessMessage.show();
+ validateUsername(username) {
+ if (this.state.valid) {
+ this.state.pending = true;
+ this.state.available = false;
+ this.renderState();
+ return $.ajax({
+ type: 'GET',
+ url: `${gon.relative_url_root}/users/${username}/exists`,
+ dataType: 'json',
+ success: (res) => this.setAvailabilityState(res.exists)
+ });
}
+ }
- setPendingState() {
- const $usernamePendingMessage = $(pendingMessageSelector);
- if (this.state.pending) {
- $usernamePendingMessage.show();
- } else {
- $usernamePendingMessage.hide();
- }
+ setAvailabilityState(usernameTaken) {
+ if (usernameTaken) {
+ this.state.valid = false;
+ this.state.available = false;
+ } else {
+ this.state.available = true;
}
+ this.state.pending = false;
+ this.renderState();
+ }
- setInvalidState() {
- const $inputErrorMessage = $(invalidMessageSelector);
- this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
- $inputErrorMessage.show();
+ clearFieldValidationState() {
+ this.inputElement.siblings('p').hide();
+
+ this.inputElement.removeClass(invalidInputClass)
+ .removeClass(successInputClass);
+ }
+
+ setUnavailableState() {
+ const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $usernameUnavailableMessage.show();
+ }
+
+ setSuccessState() {
+ const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector);
+ this.inputElement.addClass(successInputClass).removeClass(invalidInputClass);
+ $usernameSuccessMessage.show();
+ }
+
+ setPendingState() {
+ const $usernamePendingMessage = $(pendingMessageSelector);
+ if (this.state.pending) {
+ $usernamePendingMessage.show();
+ } else {
+ $usernamePendingMessage.hide();
}
}
- global.UsernameValidator = UsernameValidator;
-})(window);
+ setInvalidState() {
+ const $inputErrorMessage = $(invalidMessageSelector);
+ this.inputElement.addClass(invalidInputClass).removeClass(successInputClass);
+ $inputErrorMessage.show();
+ }
+}
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
new file mode 100644
index 00000000000..b7f50cfd083
--- /dev/null
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -0,0 +1,227 @@
+/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */
+
+import d3 from 'd3';
+
+export default class ActivityCalendar {
+ constructor(timestamps, calendar_activities_path) {
+ this.calendar_activities_path = calendar_activities_path;
+ this.clickDay = this.clickDay.bind(this);
+ this.currentSelectedDate = '';
+ this.daySpace = 1;
+ this.daySize = 15;
+ this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
+ this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ this.months = [];
+ // Loop through the timestamps to create a group of objects
+ // The group of objects will be grouped based on the day of the week they are
+ this.timestampsTmp = [];
+ var group = 0;
+
+ var today = new Date();
+ today.setHours(0, 0, 0, 0, 0);
+
+ var oneYearAgo = new Date(today);
+ oneYearAgo.setFullYear(today.getFullYear() - 1);
+
+ var days = gl.utils.getDayDifference(oneYearAgo, today);
+
+ for (var i = 0; i <= days; i += 1) {
+ var date = new Date(oneYearAgo);
+ date.setDate(date.getDate() + i);
+
+ var day = date.getDay();
+ var count = timestamps[date.format('yyyy-mm-dd')];
+
+ // Create a new group array if this is the first day of the week
+ // or if is first object
+ if ((day === 0 && i !== 0) || i === 0) {
+ this.timestampsTmp.push([]);
+ group += 1;
+ }
+
+ var innerArray = this.timestampsTmp[group - 1];
+ // Push to the inner array the values that will be used to render map
+ innerArray.push({
+ count: count || 0,
+ date: date,
+ day: day
+ });
+ }
+
+ // Init color functions
+ this.colorKey = this.initColorKey();
+ this.color = this.initColor();
+ // Init the svg element
+ this.renderSvg(group);
+ this.renderDays();
+ this.renderMonths();
+ this.renderDayTitles();
+ this.renderKey();
+ this.initTooltips();
+ }
+
+ // Add extra padding for the last month label if it is also the last column
+ getExtraWidthPadding(group) {
+ var extraWidthPadding = 0;
+ var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
+ var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
+
+ if (lastColMonth != secondLastColMonth) {
+ extraWidthPadding = 3;
+ }
+
+ return extraWidthPadding;
+ }
+
+ renderSvg(group) {
+ var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
+ return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
+ }
+
+ renderDays() {
+ return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
+ return function(group, i) {
+ _.each(group, function(stamp, a) {
+ var lastMonth, lastMonthX, month, x;
+ if (a === 0 && stamp.day === 0) {
+ month = stamp.date.getMonth();
+ x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
+ lastMonth = _.last(_this.months);
+ if (lastMonth != null) {
+ lastMonthX = lastMonth.x;
+ }
+ if (lastMonth == null) {
+ return _this.months.push({
+ month: month,
+ x: x
+ });
+ } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
+ return _this.months.push({
+ month: month,
+ x: x
+ });
+ }
+ }
+ });
+ return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
+ };
+ })(this)).selectAll('rect').data(function(stamp) {
+ return stamp;
+ }).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
+ return function(stamp, i) {
+ return _this.daySizeWithSpace * stamp.day;
+ };
+ })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
+ return function(stamp) {
+ var contribText, date, dateText;
+ date = new Date(stamp.date);
+ contribText = 'No contributions';
+ if (stamp.count > 0) {
+ contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
+ }
+ dateText = date.format('mmm d, yyyy');
+ return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
+ };
+ })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
+ return function(stamp) {
+ if (stamp.count !== 0) {
+ return _this.color(Math.min(stamp.count, 40));
+ } else {
+ return '#ededed';
+ }
+ };
+ })(this)).attr('data-container', 'body').on('click', this.clickDay);
+ }
+
+ renderDayTitles() {
+ var days;
+ days = [
+ {
+ text: 'M',
+ y: 29 + (this.daySizeWithSpace * 1)
+ }, {
+ text: 'W',
+ y: 29 + (this.daySizeWithSpace * 3)
+ }, {
+ text: 'F',
+ y: 29 + (this.daySizeWithSpace * 5)
+ }
+ ];
+ return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
+ return day.y;
+ }).text(function(day) {
+ return day.text;
+ }).attr('class', 'user-contrib-text');
+ }
+
+ renderMonths() {
+ return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
+ return date.x;
+ }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
+ return function(date) {
+ return _this.monthNames[date.month];
+ };
+ })(this));
+ }
+
+ renderKey() {
+ const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
+ const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+
+ this.svg.append('g')
+ .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
+ .selectAll('rect')
+ .data(keyColors)
+ .enter()
+ .append('rect')
+ .attr('width', this.daySize)
+ .attr('height', this.daySize)
+ .attr('x', (color, i) => this.daySizeWithSpace * i)
+ .attr('y', 0)
+ .attr('fill', color => color)
+ .attr('class', 'js-tooltip')
+ .attr('title', (color, i) => keyValues[i])
+ .attr('data-container', 'body');
+ }
+
+ initColor() {
+ var colorRange;
+ colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+ return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
+ }
+
+ initColorKey() {
+ return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
+ }
+
+ clickDay(stamp) {
+ var formatted_date;
+ if (this.currentSelectedDate !== stamp.date) {
+ this.currentSelectedDate = stamp.date;
+ formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
+ return $.ajax({
+ url: this.calendar_activities_path,
+ data: {
+ date: formatted_date
+ },
+ cache: false,
+ dataType: 'html',
+ beforeSend: function() {
+ return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
+ },
+ success: function(data) {
+ return $('.user-calendar-activities').html(data);
+ }
+ });
+ } else {
+ this.currentSelectedDate = '';
+ return $('.user-calendar-activities').html('');
+ }
+ }
+
+ initTooltips() {
+ return $('.js-contrib-calendar .js-tooltip').tooltip({
+ html: true
+ });
+ }
+}
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
deleted file mode 100644
index b11f691e424..00000000000
--- a/app/assets/javascripts/users/calendar.js
+++ /dev/null
@@ -1,231 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
-
-import d3 from 'd3';
-
-(function() {
- this.Calendar = (function() {
- function Calendar(timestamps, calendar_activities_path) {
- this.calendar_activities_path = calendar_activities_path;
- this.clickDay = this.clickDay.bind(this);
- this.currentSelectedDate = '';
- this.daySpace = 1;
- this.daySize = 15;
- this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
- this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
- this.months = [];
- // Loop through the timestamps to create a group of objects
- // The group of objects will be grouped based on the day of the week they are
- this.timestampsTmp = [];
- var group = 0;
-
- var today = new Date();
- today.setHours(0, 0, 0, 0, 0);
-
- var oneYearAgo = new Date(today);
- oneYearAgo.setFullYear(today.getFullYear() - 1);
-
- var days = gl.utils.getDayDifference(oneYearAgo, today);
-
- for (var i = 0; i <= days; i += 1) {
- var date = new Date(oneYearAgo);
- date.setDate(date.getDate() + i);
-
- var day = date.getDay();
- var count = timestamps[date.format('yyyy-mm-dd')];
-
- // Create a new group array if this is the first day of the week
- // or if is first object
- if ((day === 0 && i !== 0) || i === 0) {
- this.timestampsTmp.push([]);
- group += 1;
- }
-
- var innerArray = this.timestampsTmp[group - 1];
- // Push to the inner array the values that will be used to render map
- innerArray.push({
- count: count || 0,
- date: date,
- day: day
- });
- }
-
- // Init color functions
- this.colorKey = this.initColorKey();
- this.color = this.initColor();
- // Init the svg element
- this.renderSvg(group);
- this.renderDays();
- this.renderMonths();
- this.renderDayTitles();
- this.renderKey();
- this.initTooltips();
- }
-
- // Add extra padding for the last month label if it is also the last column
- Calendar.prototype.getExtraWidthPadding = function(group) {
- var extraWidthPadding = 0;
- var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
- var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
-
- if (lastColMonth != secondLastColMonth) {
- extraWidthPadding = 3;
- }
-
- return extraWidthPadding;
- };
-
- Calendar.prototype.renderSvg = function(group) {
- var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
- return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
- };
-
- Calendar.prototype.renderDays = function() {
- return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
- return function(group, i) {
- _.each(group, function(stamp, a) {
- var lastMonth, lastMonthX, month, x;
- if (a === 0 && stamp.day === 0) {
- month = stamp.date.getMonth();
- x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
- lastMonth = _.last(_this.months);
- if (lastMonth != null) {
- lastMonthX = lastMonth.x;
- }
- if (lastMonth == null) {
- return _this.months.push({
- month: month,
- x: x
- });
- } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
- return _this.months.push({
- month: month,
- x: x
- });
- }
- }
- });
- return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
- };
- })(this)).selectAll('rect').data(function(stamp) {
- return stamp;
- }).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
- return function(stamp, i) {
- return _this.daySizeWithSpace * stamp.day;
- };
- })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
- return function(stamp) {
- var contribText, date, dateText;
- date = new Date(stamp.date);
- contribText = 'No contributions';
- if (stamp.count > 0) {
- contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
- }
- dateText = date.format('mmm d, yyyy');
- return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
- };
- })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
- return function(stamp) {
- if (stamp.count !== 0) {
- return _this.color(Math.min(stamp.count, 40));
- } else {
- return '#ededed';
- }
- };
- })(this)).attr('data-container', 'body').on('click', this.clickDay);
- };
-
- Calendar.prototype.renderDayTitles = function() {
- var days;
- days = [
- {
- text: 'M',
- y: 29 + (this.daySizeWithSpace * 1)
- }, {
- text: 'W',
- y: 29 + (this.daySizeWithSpace * 3)
- }, {
- text: 'F',
- y: 29 + (this.daySizeWithSpace * 5)
- }
- ];
- return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
- return day.y;
- }).text(function(day) {
- return day.text;
- }).attr('class', 'user-contrib-text');
- };
-
- Calendar.prototype.renderMonths = function() {
- return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
- return date.x;
- }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
- return function(date) {
- return _this.monthNames[date.month];
- };
- })(this));
- };
-
- Calendar.prototype.renderKey = function() {
- const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
- const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
-
- this.svg.append('g')
- .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
- .selectAll('rect')
- .data(keyColors)
- .enter()
- .append('rect')
- .attr('width', this.daySize)
- .attr('height', this.daySize)
- .attr('x', (color, i) => this.daySizeWithSpace * i)
- .attr('y', 0)
- .attr('fill', color => color)
- .attr('class', 'js-tooltip')
- .attr('title', (color, i) => keyValues[i])
- .attr('data-container', 'body');
- };
-
- Calendar.prototype.initColor = function() {
- var colorRange;
- colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
- return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
- };
-
- Calendar.prototype.initColorKey = function() {
- return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
- };
-
- Calendar.prototype.clickDay = function(stamp) {
- var formatted_date;
- if (this.currentSelectedDate !== stamp.date) {
- this.currentSelectedDate = stamp.date;
- formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
- return $.ajax({
- url: this.calendar_activities_path,
- data: {
- date: formatted_date
- },
- cache: false,
- dataType: 'html',
- beforeSend: function() {
- return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
- },
- success: function(data) {
- return $('.user-calendar-activities').html(data);
- }
- });
- } else {
- this.currentSelectedDate = '';
- return $('.user-calendar-activities').html('');
- }
- };
-
- Calendar.prototype.initTooltips = function() {
- return $('.js-contrib-calendar .js-tooltip').tooltip({
- html: true
- });
- };
-
- return Calendar;
- })();
-}).call(window);
diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/users/index.js
new file mode 100644
index 00000000000..ecd8e09161e
--- /dev/null
+++ b/app/assets/javascripts/users/index.js
@@ -0,0 +1,7 @@
+import ActivityCalendar from './activity_calendar';
+import User from './user';
+
+// use legacy exports until embedded javascript is refactored
+window.Calendar = ActivityCalendar;
+window.gl = window.gl || {};
+window.gl.User = User;
diff --git a/app/assets/javascripts/users/user.js b/app/assets/javascripts/users/user.js
new file mode 100644
index 00000000000..0b0a3e1afb4
--- /dev/null
+++ b/app/assets/javascripts/users/user.js
@@ -0,0 +1,34 @@
+/* eslint-disable class-methods-use-this */
+
+import Cookies from 'js-cookie';
+import UserTabs from './user_tabs';
+
+export default class User {
+ constructor({ action }) {
+ this.action = action;
+ this.placeProfileAvatarsToTop();
+ this.initTabs();
+ this.hideProjectLimitMessage();
+ }
+
+ placeProfileAvatarsToTop() {
+ $('.profile-groups-avatars').tooltip({
+ placement: 'top',
+ });
+ }
+
+ initTabs() {
+ return new UserTabs({
+ parentEl: '.user-profile',
+ action: this.action,
+ });
+ }
+
+ hideProjectLimitMessage() {
+ $('.hide-project-limit-message').on('click', (e) => {
+ e.preventDefault();
+ Cookies.set('hide_project_limit_message', 'false');
+ $(this).parents('.project-limit-message').remove();
+ });
+ }
+}
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js
new file mode 100644
index 00000000000..f8e23c8624d
--- /dev/null
+++ b/app/assets/javascripts/users/user_tabs.js
@@ -0,0 +1,173 @@
+/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */
+
+/*
+UserTabs
+
+Handles persisting and restoring the current tab selection and lazily-loading
+content on the Users#show page.
+
+### Example Markup
+
+ <ul class="nav-links">
+ <li class="activity-tab active">
+ <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
+ Activity
+ </a>
+ </li>
+ <li class="groups-tab">
+ <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
+ Groups
+ </a>
+ </li>
+ <li class="contributed-tab">
+ <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
+ Contributed projects
+ </a>
+ </li>
+ <li class="projects-tab">
+ <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
+ Personal projects
+ </a>
+ </li>
+ <li class="snippets-tab">
+ <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
+ </a>
+ </li>
+ </ul>
+
+ <div class="tab-content">
+ <div class="tab-pane" id="activity">
+ Activity Content
+ </div>
+ <div class="tab-pane" id="groups">
+ Groups Content
+ </div>
+ <div class="tab-pane" id="contributed">
+ Contributed projects content
+ </div>
+ <div class="tab-pane" id="projects">
+ Projects content
+ </div>
+ <div class="tab-pane" id="snippets">
+ Snippets content
+ </div>
+ </div>
+
+ <div class="loading-status">
+ <div class="loading">
+ Loading Animation
+ </div>
+ </div>
+*/
+
+export default class UserTabs {
+ constructor ({ defaultAction, action, parentEl }) {
+ this.loaded = {};
+ this.defaultAction = defaultAction || 'activity';
+ this.action = action || this.defaultAction;
+ this.$parentEl = $(parentEl) || $(document);
+ this._location = window.location;
+ this.$parentEl.find('.nav-links a')
+ .each((i, navLink) => {
+ this.loaded[$(navLink).attr('data-action')] = false;
+ });
+ this.actions = Object.keys(this.loaded);
+ this.bindEvents();
+
+ if (this.action === 'show') {
+ this.action = this.defaultAction;
+ }
+
+ this.activateTab(this.action);
+ }
+
+ bindEvents() {
+ this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
+
+ this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
+ .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
+
+ this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
+ }
+
+ changeProjectsPage(e) {
+ e.preventDefault();
+
+ $('.tab-pane.active').empty();
+ const endpoint = $(e.target).attr('href');
+ this.loadTab(this.getCurrentAction(), endpoint);
+ }
+
+ tabShown(event) {
+ const $target = $(event.target);
+ const action = $target.data('action');
+ const source = $target.attr('href');
+ const endpoint = $target.data('endpoint');
+ this.setTab(action, endpoint);
+ return this.setCurrentAction(source);
+ }
+
+ activateTab(action) {
+ return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
+ .tab('show');
+ }
+
+ setTab(action, endpoint) {
+ if (this.loaded[action]) {
+ return;
+ }
+ if (action === 'activity') {
+ this.loadActivities();
+ }
+
+ const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
+ if (loadableActions.indexOf(action) > -1) {
+ return this.loadTab(action, endpoint);
+ }
+ }
+
+ loadTab(action, endpoint) {
+ return $.ajax({
+ beforeSend: () => this.toggleLoading(true),
+ complete: () => this.toggleLoading(false),
+ dataType: 'json',
+ type: 'GET',
+ url: endpoint,
+ success: (data) => {
+ const tabSelector = `div#${action}`;
+ this.$parentEl.find(tabSelector).html(data.html);
+ this.loaded[action] = true;
+ return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+ }
+ });
+ }
+
+ loadActivities() {
+ if (this.loaded['activity']) {
+ return;
+ }
+ const $calendarWrap = this.$parentEl.find('.user-calendar');
+ $calendarWrap.load($calendarWrap.data('href'));
+ new gl.Activities();
+ return this.loaded['activity'] = true;
+ }
+
+ toggleLoading(status) {
+ return this.$parentEl.find('.loading-status .loading')
+ .toggle(status);
+ }
+
+ setCurrentAction(source) {
+ let new_state = source;
+ new_state = new_state.replace(/\/+$/, '');
+ new_state += this._location.search + this._location.hash;
+ history.replaceState({
+ url: new_state
+ }, document.title, new_state);
+ return new_state;
+ }
+
+ getCurrentAction() {
+ return this.$parentEl.find('.nav-links .active a').data('action');
+ }
+}
diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js
deleted file mode 100644
index a38ce4eb25e..00000000000
--- a/app/assets/javascripts/users/users_bundle.js
+++ /dev/null
@@ -1 +0,0 @@
-import './calendar';
diff --git a/app/assets/javascripts/version_check_image.js b/app/assets/javascripts/version_check_image.js
index 88ba991af47..ec515e892c6 100644
--- a/app/assets/javascripts/version_check_image.js
+++ b/app/assets/javascripts/version_check_image.js
@@ -3,6 +3,3 @@ export default class VersionCheckImage {
imageElement.off('error').on('error', () => imageElement.hide());
}
}
-
-window.gl = window.gl || {};
-gl.VersionCheckImage = VersionCheckImage;
diff --git a/app/assets/javascripts/visibility_select.js b/app/assets/javascripts/visibility_select.js
index f712d7ba930..0c928d0d5f6 100644
--- a/app/assets/javascripts/visibility_select.js
+++ b/app/assets/javascripts/visibility_select.js
@@ -1,27 +1,21 @@
-(() => {
- const gl = window.gl || (window.gl = {});
-
- class VisibilitySelect {
- constructor(container) {
- if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
- this.container = container;
- this.helpBlock = this.container.querySelector('.help-block');
- this.select = this.container.querySelector('select');
- }
-
- init() {
- if (this.select) {
- this.updateHelpText();
- this.select.addEventListener('change', this.updateHelpText.bind(this));
- } else {
- this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
- }
- }
+export default class VisibilitySelect {
+ constructor(container) {
+ if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
+ this.container = container;
+ this.helpBlock = this.container.querySelector('.help-block');
+ this.select = this.container.querySelector('select');
+ }
- updateHelpText() {
- this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
+ init() {
+ if (this.select) {
+ this.updateHelpText();
+ this.select.addEventListener('change', this.updateHelpText.bind(this));
+ } else {
+ this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
}
- gl.VisibilitySelect = VisibilitySelect;
-})();
+ updateHelpText() {
+ this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
+ }
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index e8e22ad93a5..744a1cd24fa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -108,7 +108,8 @@ export default {
</div>
<mr-widget-memory-usage
v-if="deployment.metrics_url"
- :metricsUrl="deployment.metrics_url"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
index f8b3fb748ae..8430548903c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.js
@@ -92,13 +92,13 @@ export default {
:class="{'label-truncated has-tooltip': isBranchTitleLong(mr.targetBranch)}"
:title="isBranchTitleLong(mr.targetBranch) ? mr.targetBranch : ''"
data-placement="bottom">
- <a :href="mr.targetBranchPath">{{mr.targetBranch}}</a>
+ <a :href="mr.targetBranchTreePath">{{mr.targetBranch}}</a>
</span>
</strong>
<span
v-if="shouldShowCommitsBehindText"
class="diverged-commits-count">
- ({{mr.divergedCommitsCount}} {{commitsText}} behind)
+ (<a :href="mr.targetBranchPath">{{mr.divergedCommitsCount}} {{commitsText}} behind</a>)
</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 76cb71b6c12..534e2a88eff 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -7,7 +7,14 @@ import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MemoryUsage',
props: {
- metricsUrl: { type: String, required: true },
+ metricsUrl: {
+ type: String,
+ required: true,
+ },
+ metricsMonitoringUrl: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -124,7 +131,7 @@ export default {
<p
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
- Memory usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
+ <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
</p>
<p
v-if="shouldShowLoadFailure"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index e8b3cf2f729..c02e10128e2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -17,9 +17,6 @@ export default {
return hasCI && !ciStatus;
},
- hasPipeline() {
- return Object.keys(this.mr.pipeline || {}).length > 0;
- },
svg() {
return statusIconEntityMap.icon_status_failed;
},
@@ -33,11 +30,7 @@ export default {
template: `
<div class="mr-widget-heading">
<div class="ci-widget">
- <template v-if="!hasPipeline">
- <i class="fa fa-spinner fa-spin append-right-10" aria-hidden="true"></i>
- Waiting for pipeline...
- </template>
- <template v-else-if="hasCIError">
+ <template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error">
<span class="js-icon-link icon-link">
<span
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 69bc1436284..72a13108404 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -48,6 +48,7 @@ export default class MergeRequestStore {
this.sourceBranchLink = data.source_branch_with_namespace_link;
this.mergeError = data.merge_error;
this.targetBranchPath = data.target_branch_commits_path;
+ this.targetBranchTreePath = data.target_branch_tree_path;
this.conflictResolutionPath = data.conflict_resolution_path;
this.cancelAutoMergePath = data.cancel_merge_when_pipeline_succeeds_path;
this.removeWIPPath = data.remove_wip_path;
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 8303c556f64..4e10bbc7408 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -44,9 +44,8 @@
text: this.$slots.textarea[0].elm.value,
},
)
- .then((res) => {
- const data = res.json();
-
+ .then(resp => resp.json())
+ .then((data) => {
this.markdownPreviewLoading = false;
this.markdownPreview = data.body;
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 5e7df22dd83..c9dbc048345 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -46,6 +46,8 @@ export default {
},
methods: {
changePage(e) {
+ if (e.target.parentElement.classList.contains('disabled')) return;
+
const text = e.target.innerText;
const { totalPages, nextPage, previousPage } = this.pageInfo;
@@ -82,7 +84,9 @@ export default {
const page = this.pageInfo.page;
const items = [];
- if (page > 1) items.push({ title: FIRST });
+ if (page > 1) {
+ items.push({ title: FIRST, first: true });
+ }
if (page > 1) {
items.push({ title: PREV, prev: true });
@@ -110,7 +114,9 @@ export default {
items.push({ title: NEXT, next: true });
}
- if (total - page >= 1) items.push({ title: LAST, last: true });
+ if (total - page >= 1) {
+ items.push({ title: LAST, last: true });
+ }
return items;
},
@@ -124,13 +130,15 @@ export default {
v-for="item in getItems"
:class="{
page: item.page,
- prev: item.prev,
- next: item.next,
+ 'js-previous-button': item.prev,
+ 'js-next-button': item.next,
+ 'js-last-button': item.last,
+ 'js-first-button': item.first,
separator: item.separator,
active: item.active,
disabled: item.disabled
}">
- <a @click="changePage($event)">{{item.title}}</a>
+ <a @click.prevent="changePage($event)">{{item.title}}</a>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index 740930dce5b..7f8e514fda1 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -14,11 +14,22 @@ Vue.http.interceptors.push((request, next) => {
});
});
-// Inject CSRF token so we don't break any tests.
+// Inject CSRF token and parse headers.
+// New Vue Resource version uses Headers, we are expecting a plain object to render pagination
+// and polling.
Vue.http.interceptors.push((request, next) => {
if ($.rails) {
- // eslint-disable-next-line no-param-reassign
- request.headers['X-CSRF-Token'] = $.rails.csrfToken();
+ request.headers.set('X-CSRF-Token', $.rails.csrfToken());
}
- next();
+
+ next((response) => {
+ // Headers object has a `forEach` property that iterates through all values.
+ const headers = {};
+
+ response.headers.forEach((value, key) => {
+ headers[key] = value;
+ });
+ // eslint-disable-next-line no-param-reassign
+ response.headers = headers;
+ });
});
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js
index 4194c1bc08d..00676bcb0b3 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/wikis.js
@@ -1,69 +1,64 @@
-/* eslint-disable no-param-reassign */
/* global Breakpoints */
import 'vendor/jquery.nicescroll';
import './breakpoints';
-((global) => {
- class Wikis {
- constructor() {
- this.bp = Breakpoints.get();
- this.sidebarEl = document.querySelector('.js-wiki-sidebar');
- this.sidebarExpanded = false;
- $(this.sidebarEl).niceScroll();
+export default class Wikis {
+ constructor() {
+ this.bp = Breakpoints.get();
+ this.sidebarEl = document.querySelector('.js-wiki-sidebar');
+ this.sidebarExpanded = false;
+ $(this.sidebarEl).niceScroll();
- const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
- for (let i = 0; i < sidebarToggles.length; i += 1) {
- sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
- }
-
- this.newWikiForm = document.querySelector('form.new-wiki-page');
- if (this.newWikiForm) {
- this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
- }
+ const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle');
+ for (let i = 0; i < sidebarToggles.length; i += 1) {
+ sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
+ }
- window.addEventListener('resize', () => this.renderSidebar());
- this.renderSidebar();
+ this.newWikiForm = document.querySelector('form.new-wiki-page');
+ if (this.newWikiForm) {
+ this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
}
- handleNewWikiSubmit(e) {
- if (!this.newWikiForm) return;
+ window.addEventListener('resize', () => this.renderSidebar());
+ this.renderSidebar();
+ }
- const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
- const slug = gl.text.slugify(slugInput.value);
+ handleNewWikiSubmit(e) {
+ if (!this.newWikiForm) return;
- if (slug.length > 0) {
- const wikisPath = slugInput.getAttribute('data-wikis-path');
- window.location.href = `${wikisPath}/${slug}`;
- e.preventDefault();
- }
- }
+ const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
+ const slug = gl.text.slugify(slugInput.value);
- handleToggleSidebar(e) {
+ if (slug.length > 0) {
+ const wikisPath = slugInput.getAttribute('data-wikis-path');
+ window.location.href = `${wikisPath}/${slug}`;
e.preventDefault();
- this.sidebarExpanded = !this.sidebarExpanded;
- this.renderSidebar();
}
+ }
- sidebarCanCollapse() {
- const bootstrapBreakpoint = this.bp.getBreakpointSize();
- return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
- }
+ handleToggleSidebar(e) {
+ e.preventDefault();
+ this.sidebarExpanded = !this.sidebarExpanded;
+ this.renderSidebar();
+ }
- renderSidebar() {
- if (!this.sidebarEl) return;
- const { classList } = this.sidebarEl;
- if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
- if (!classList.contains('right-sidebar-expanded')) {
- classList.remove('right-sidebar-collapsed');
- classList.add('right-sidebar-expanded');
- }
- } else if (classList.contains('right-sidebar-expanded')) {
- classList.add('right-sidebar-collapsed');
- classList.remove('right-sidebar-expanded');
+ sidebarCanCollapse() {
+ const bootstrapBreakpoint = this.bp.getBreakpointSize();
+ return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
+ }
+
+ renderSidebar() {
+ if (!this.sidebarEl) return;
+ const { classList } = this.sidebarEl;
+ if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
+ if (!classList.contains('right-sidebar-expanded')) {
+ classList.remove('right-sidebar-collapsed');
+ classList.add('right-sidebar-expanded');
}
+ } else if (classList.contains('right-sidebar-expanded')) {
+ classList.add('right-sidebar-collapsed');
+ classList.remove('right-sidebar-expanded');
}
}
-
- global.Wikis = Wikis;
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index b7fe552dec2..99c7644e4d9 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */
/* global Mousetrap */
// Zen Mode (full screen) textarea
@@ -34,65 +34,62 @@ window.Dropzone = Dropzone;
// **Cancelable** No
// **Target** a.js-zen-leave
//
-(function() {
- this.ZenMode = (function() {
- function ZenMode() {
- this.active_backdrop = null;
- this.active_textarea = null;
- $(document).on('click', '.js-zen-enter', function(e) {
- e.preventDefault();
- return $(e.currentTarget).trigger('zen_mode:enter');
- });
- $(document).on('click', '.js-zen-leave', function(e) {
- e.preventDefault();
- return $(e.currentTarget).trigger('zen_mode:leave');
- });
- $(document).on('zen_mode:enter', (function(_this) {
- return function(e) {
- return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
- };
- })(this));
- $(document).on('zen_mode:leave', (function(_this) {
- return function(e) {
- return _this.exit();
- };
- })(this));
- $(document).on('keydown', function(e) {
- // Esc
- if (e.keyCode === 27) {
- e.preventDefault();
- return $(document).trigger('zen_mode:leave');
- }
- });
- }
-
- ZenMode.prototype.enter = function(backdrop) {
- Mousetrap.pause();
- this.active_backdrop = $(backdrop);
- this.active_backdrop.addClass('fullscreen');
- this.active_textarea = this.active_backdrop.find('textarea');
- // Prevent a user-resized textarea from persisting to fullscreen
- this.active_textarea.removeAttr('style');
- return this.active_textarea.focus();
- };
- ZenMode.prototype.exit = function() {
- if (this.active_textarea) {
- Mousetrap.unpause();
- this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
- this.scrollTo(this.active_textarea);
- this.active_textarea = null;
- this.active_backdrop = null;
- return Dropzone.forElement('.div-dropzone').enable();
+export default class ZenMode {
+ constructor() {
+ this.active_backdrop = null;
+ this.active_textarea = null;
+ $(document).on('click', '.js-zen-enter', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:enter');
+ });
+ $(document).on('click', '.js-zen-leave', function(e) {
+ e.preventDefault();
+ return $(e.currentTarget).trigger('zen_mode:leave');
+ });
+ $(document).on('zen_mode:enter', (function(_this) {
+ return function(e) {
+ return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
+ };
+ })(this));
+ $(document).on('zen_mode:leave', (function(_this) {
+ return function(e) {
+ return _this.exit();
+ };
+ })(this));
+ $(document).on('keydown', function(e) {
+ // Esc
+ if (e.keyCode === 27) {
+ e.preventDefault();
+ return $(document).trigger('zen_mode:leave');
}
- };
+ });
+ }
+
+ enter(backdrop) {
+ Mousetrap.pause();
+ this.active_backdrop = $(backdrop);
+ this.active_backdrop.addClass('fullscreen');
+ this.active_textarea = this.active_backdrop.find('textarea');
+ // Prevent a user-resized textarea from persisting to fullscreen
+ this.active_textarea.removeAttr('style');
+ return this.active_textarea.focus();
+ }
- ZenMode.prototype.scrollTo = function(zen_area) {
- return $.scrollTo(zen_area, 0, {
- offset: -150
- });
- };
+ exit() {
+ if (this.active_textarea) {
+ Mousetrap.unpause();
+ this.active_textarea.closest('.zen-backdrop').removeClass('fullscreen');
+ this.scrollTo(this.active_textarea);
+ this.active_textarea = null;
+ this.active_backdrop = null;
+ return Dropzone.forElement('.div-dropzone').enable();
+ }
+ }
- return ZenMode;
- })();
-}).call(window);
+ scrollTo(zen_area) {
+ return $.scrollTo(zen_area, 0, {
+ offset: -150
+ });
+ }
+}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 83a8eeaafde..0665622fe4a 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -42,4 +42,4 @@
/*
* Styles for JS behaviors.
*/
-@import "behaviors.scss";
+@import "behaviors";
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 9dc9f9a9068..6ce331a9129 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -4,49 +4,49 @@
@import 'framework/tw_bootstrap';
@import "framework/layout";
-@import "framework/animations.scss";
-@import "framework/avatar.scss";
-@import "framework/asciidoctor.scss";
-@import "framework/blocks.scss";
-@import "framework/buttons.scss";
-@import "framework/badges.scss";
-@import "framework/calendar.scss";
-@import "framework/callout.scss";
-@import "framework/common.scss";
-@import "framework/dropdowns.scss";
-@import "framework/files.scss";
-@import "framework/filters.scss";
-@import "framework/flash.scss";
-@import "framework/forms.scss";
-@import "framework/gfm.scss";
-@import "framework/header.scss";
-@import "framework/highlight.scss";
-@import "framework/issue_box.scss";
-@import "framework/jquery.scss";
-@import "framework/lists.scss";
-@import "framework/logo.scss";
-@import "framework/markdown_area.scss";
-@import "framework/mobile.scss";
-@import "framework/modal.scss";
-@import "framework/nav.scss";
-@import "framework/pagination.scss";
-@import "framework/panels.scss";
-@import "framework/selects.scss";
-@import "framework/sidebar.scss";
-@import "framework/tables.scss";
-@import "framework/notes.scss";
-@import "framework/timeline.scss";
-@import "framework/typography.scss";
-@import "framework/zen.scss";
+@import "framework/animations";
+@import "framework/avatar";
+@import "framework/asciidoctor";
+@import "framework/blocks";
+@import "framework/buttons";
+@import "framework/badges";
+@import "framework/calendar";
+@import "framework/callout";
+@import "framework/common";
+@import "framework/dropdowns";
+@import "framework/files";
+@import "framework/filters";
+@import "framework/flash";
+@import "framework/forms";
+@import "framework/gfm";
+@import "framework/header";
+@import "framework/highlight";
+@import "framework/issue_box";
+@import "framework/jquery";
+@import "framework/lists";
+@import "framework/logo";
+@import "framework/markdown_area";
+@import "framework/mobile";
+@import "framework/modal";
+@import "framework/nav";
+@import "framework/pagination";
+@import "framework/panels";
+@import "framework/selects";
+@import "framework/sidebar";
+@import "framework/tables";
+@import "framework/notes";
+@import "framework/timeline";
+@import "framework/typography";
+@import "framework/zen";
@import "framework/blank";
-@import "framework/wells.scss";
-@import "framework/page-header.scss";
-@import "framework/awards.scss";
-@import "framework/images.scss";
+@import "framework/wells";
+@import "framework/page-header";
+@import "framework/awards";
+@import "framework/images";
@import "framework/broadcast-messages";
-@import "framework/emojis.scss";
-@import "framework/emoji-sprites.scss";
-@import "framework/icons.scss";
-@import "framework/snippets.scss";
-@import "framework/memory_graph.scss";
-@import "framework/responsive-tables.scss";
+@import "framework/emojis";
+@import "framework/emoji-sprites";
+@import "framework/icons";
+@import "framework/snippets";
+@import "framework/memory_graph";
+@import "framework/responsive-tables";
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 4ae2b164d2e..0dfa7a31d31 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -35,6 +35,8 @@
width: 40px;
height: 40px;
padding: 0;
+ background: $avatar-background;
+ overflow: hidden;
&.avatar-inline {
float: none;
@@ -60,7 +62,7 @@
}
&:not([href]):hover {
- border-color: rgba($avatar-border, .2);
+ border-color: darken($avatar-border, 10%);
}
}
@@ -99,7 +101,7 @@
.avatar-counter {
background-color: $gray-darkest;
color: $white-light;
- border: 1px solid $border-color;
+ border: 1px solid $avatar-border;
border-radius: 1em;
font-family: $regular_font;
font-size: 9px;
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 19166757e64..bb30da4f4b2 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -24,7 +24,7 @@
opacity: 0;
transform: scale(.2);
transform-origin: 0 -45px;
- transition: .3s cubic-bezier(.67,.06,.19,1.44);
+ transition: .3s cubic-bezier(.67, .06, .19, 1.44);
transition-property: transform, opacity;
&.is-aligned-right {
@@ -231,11 +231,11 @@
.award-control-icon-positive,
.award-control-icon-super-positive {
+ @include transition(opacity, transform);
position: absolute;
left: 10px;
bottom: 6px;
opacity: 0;
- @include transition(opacity, transform);
}
.award-control-text {
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index a2fa2e7769b..6bb096fc5bd 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -1,9 +1,14 @@
-.blank-state-welcome {
- text-align: center;
- border-bottom: 1px solid $border-color;
+.blank-state-parent-container {
+ .section-container {
+ padding: 10px;
+ }
- .blank-state-text {
- margin-bottom: 0;
+ .section-body {
+ width: 100%;
+ height: 100%;
+ padding-bottom: 25px;
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
}
}
@@ -11,41 +16,35 @@
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
-}
-.blank-state-no-icon {
- padding-top: 40px;
- padding-bottom: 40px;
-}
-
-.blank-state-icon {
- padding-bottom: 20px;
- color: $gray-darkest;
- font-size: 56px;
+ &.blank-state-welcome {
+ .blank-state-welcome-title {
+ font-size: 24px;
+ }
- path,
- polygon {
- fill: currentColor;
+ .blank-state-text {
+ margin-bottom: 0;
+ }
}
-}
-.blank-state-title {
- margin-top: 0;
- margin-bottom: 5px;
- font-size: 18px;
- font-weight: normal;
-}
+ .blank-state-icon {
+ padding-bottom: 20px;
-.blank-state-text {
- margin-top: 0;
- margin-bottom: $gl-padding;
- font-size: 14px;
+ svg {
+ display: block;
+ margin: auto;
+ }
+ }
- > strong {
- font-weight: 600;
+ .blank-state-title {
+ margin-top: 0;
+ margin-bottom: 10px;
+ font-size: 18px;
}
-}
-.blank-state-welcome-title {
- font-size: 24px;
+ .blank-state-text {
+ max-width: $container-text-max-width;
+ margin: 0 auto $gl-padding;
+ font-size: 14px;
+ }
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 4369ae78bde..6eabdc63d9e 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -20,17 +20,29 @@
color: $text;
border-color: $border;
+ > .icon {
+ color: $text;
+ }
+
&:hover,
&:focus {
background-color: $hover-background;
border-color: $hover-border;
color: $hover-text;
+
+ > .icon {
+ color: $hover-text;
+ }
}
&:active {
background-color: $active-background;
border-color: $active-border;
color: $hover-text;
+
+ > .icon {
+ color: $hover-text;
+ }
}
}
@@ -163,7 +175,8 @@
@include btn-orange;
}
- &.btn-close {
+ &.btn-close,
+ &.btn-close-color {
@include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
}
@@ -181,7 +194,8 @@
float: right;
}
- &.btn-reopen {
+ &.btn-reopen,
+ .btn-reopen-color {
/* should be same as parent class for now */
}
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index 759401a7806..0ac095f7d8f 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -93,7 +93,7 @@
.is-selected .pika-day,
.pika-day:hover,
- .is-today .pika-day:hover {
+ .is-today .pika-day {
background: $gl-primary;
color: $white-light;
box-shadow: none;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 00c981f64c5..5e374360359 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -336,11 +336,6 @@ table {
text-align: center;
}
-#nprogress .spinner {
- top: 15px !important;
- right: 10px !important;
-}
-
.header-with-avatar {
h3 {
margin: 0;
@@ -450,4 +445,3 @@ table {
pointer-events: none;
opacity: .5;
}
-
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 4f54ca24940..5e410cbf563 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -35,8 +35,8 @@
.open {
.dropdown-menu,
.dropdown-menu-nav {
- display: block;
@include set-visible;
+ display: block;
@media (max-width: $screen-xs-max) {
width: 100%;
@@ -184,13 +184,15 @@
.dropdown-menu,
.dropdown-menu-nav {
+ @include set-invisible;
display: block;
position: absolute;
- width: 100%;
+ width: auto;
top: 100%;
left: 0;
z-index: 9;
min-width: 240px;
+ max-width: 500px;
margin-top: 2px;
margin-bottom: 0;
font-size: 14px;
@@ -200,10 +202,10 @@
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
- @include set-invisible;
@media (max-width: $screen-sm-min) {
width: 100%;
+ min-width: 180px;
}
&.dropdown-open-left {
@@ -287,17 +289,80 @@
padding: 5px 8px;
color: $gl-text-color-secondary;
}
+}
- .badge {
- position: absolute;
- right: 8px;
- top: 5px;
+.droplab-dropdown {
+ .dropdown-toggle > i {
+ pointer-events: none;
+ }
+
+ .dropdown-menu li {
+ padding: $gl-btn-padding;
+ cursor: pointer;
+
+ > a,
+ > button {
+ display: flex;
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
+ text-overflow: inherit;
+ background-color: inherit;
+ color: inherit;
+ border: inherit;
+ text-align: left;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
+
+ &.btn .fa:not(:last-child) {
+ margin-left: 5px;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ background-color: $dropdown-hover-color;
+ color: $white-light;
+ }
+
+ &.droplab-item-selected i {
+ visibility: visible;
+ }
+
+ &.divider {
+ margin: 0 8px;
+ padding: 0;
+ border-top: $gray-darkest;
+ }
+
+ .icon {
+ visibility: hidden;
+ }
+
+ .description {
+ display: inline-block;
+ white-space: normal;
+ margin-left: 5px;
+
+ p {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .icon {
+ display: inline-block;
+ vertical-align: top;
+ padding-top: 2px;
}
}
-.filtered-search-box-input-container .dropdown-menu,
-.filtered-search-box-input-container .dropdown-menu-nav,
-.comment-type-dropdown .dropdown-menu {
+.droplab-dropdown .dropdown-menu,
+.droplab-dropdown .dropdown-menu-nav {
display: none;
opacity: 1;
visibility: visible;
@@ -396,10 +461,6 @@
left: auto;
right: 0;
margin-top: -5px;
-
- @media (max-width: $screen-xs-max) {
- left: 0;
- }
}
.dropdown-menu-selectable {
@@ -610,8 +671,8 @@
}
.pika-single {
- position: relative!important;
- top: 0!important;
+ position: relative !important;
+ top: 0 !important;
border: 0;
box-shadow: none;
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index c7c2684d548..8ad082f7a65 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -163,8 +163,18 @@
td.blame-commit {
padding: 5px 10px;
min-width: 400px;
+ max-width: 400px;
background: $gray-light;
border-left: 3px solid;
+
+ .commit-row-title {
+ display: flex;
+ }
+
+ .item-title {
+ flex: 1;
+ margin-right: 0.5em;
+ }
}
@for $i from 0 through 5 {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 767cf5ffea5..41184907abb 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -70,6 +70,13 @@
.input-token {
max-width: 200px;
+ padding: 0;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
}
.input-token:only-child,
@@ -156,6 +163,16 @@
}
}
+.droplab-dropdown li.filtered-search-token {
+ padding: 0;
+
+ &:hover,
+ &:focus {
+ background-color: inherit;
+ color: inherit;
+ }
+}
+
.filtered-search-term {
.name {
background-color: inherit;
@@ -258,7 +275,7 @@
}
.filtered-search-input-dropdown-menu {
- max-height: 215px;
+ max-height: 225px;
max-width: 280px;
overflow: auto;
@@ -351,7 +368,7 @@
margin-right: 0.3em;
}
- & > .value {
+ > .value {
font-weight: 600;
}
}
@@ -365,10 +382,6 @@
}
}
-.dropdown-menu .filter-dropdown-item {
- padding: 0;
-}
-
@media (max-width: $screen-xs-max) {
.issues-details-filters {
padding: 0 0 10px;
@@ -418,6 +431,7 @@
.fa {
width: 15px;
+ line-height: $line-height-base;
}
.dropdown-label-box {
@@ -450,7 +464,7 @@
-webkit-flex-direction: column;
flex-direction: column;
- &> span {
+ > span {
white-space: normal;
word-break: break-all;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 5bd6c095109..605f4284bb5 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -132,6 +132,22 @@ header {
}
}
+ &.navbar-gitlab-new {
+ .fa-times {
+ display: none;
+ }
+
+ .menu-expanded {
+ .fa-ellipsis-v {
+ display: none;
+ }
+
+ .fa-times {
+ display: block;
+ }
+ }
+ }
+
.global-dropdown {
position: absolute;
left: -10px;
@@ -171,6 +187,19 @@ header {
min-height: $header-height;
padding-left: 30px;
+ &.menu-expanded {
+ @media (max-width: $screen-xs-max) {
+ .header-logo,
+ .title-container {
+ display: none;
+ }
+
+ .navbar-collapse {
+ display: block;
+ }
+ }
+ }
+
.dropdown-menu {
margin-top: -5px;
}
@@ -330,7 +359,7 @@ header {
padding-left: 5px;
.nav > li:not(.hidden-xs) {
- display: table-cell!important;
+ display: table-cell !important;
width: 25%;
a {
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 6d27d7568cf..71d5949b023 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -61,7 +61,7 @@
&:focus {
outline: none;
- & i {
+ i {
visibility: visible;
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 38727e15c6f..868e65a8f46 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -236,6 +236,8 @@ ul.content-list {
ul.controls {
float: right;
list-style: none;
+ display: flex;
+ align-items: center;
.btn {
padding: 10px 14px;
@@ -259,6 +261,12 @@ ul.controls {
}
}
}
+
+ .issuable-pipeline-broken a,
+ .issuable-pipeline-status a,
+ .author_link {
+ display: flex;
+ }
}
ul.indent-list {
@@ -343,6 +351,12 @@ ul.indent-list {
.group-row {
padding: 0;
border: none;
+
+ &:last-of-type {
+ .group-row-contents:not(:hover) {
+ border-bottom: 1px solid transparent;
+ }
+ }
}
.group-row-contents {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index b21bcc22a87..a2de4598167 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -165,8 +165,8 @@
.cur {
.avatar {
- border: 1px solid $white-light;
@include disableAllAnimation;
+ border: 1px solid $white-light;
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 3a98332e46c..6f91d11b369 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -118,3 +118,29 @@
@content;
}
}
+
+/*
+ * Mixin for status badges, as used for pipelines and commit signatures
+ */
+@mixin status-color($color-light, $color-main, $color-dark) {
+ color: $color-main;
+ border-color: $color-main;
+
+ &:not(span):hover {
+ background-color: $color-light;
+ color: $color-dark;
+ border-color: $color-dark;
+
+ svg {
+ fill: $color-dark;
+ }
+ }
+
+ svg {
+ fill: $color-main;
+ }
+}
+
+@mixin green-status-color {
+ @include status-color($green-50, $green-500, $green-700);
+}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 7098203321d..a28f54936be 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -21,3 +21,9 @@ body.modal-open {
width: 860px;
}
}
+
+@media (min-width: $screen-lg-min) {
+ .modal-full {
+ width: 98%;
+ }
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 28b2a7cfacd..35b4d77a5ab 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -182,6 +182,12 @@
}
}
+ &.nav-controls-new-nav {
+ > .dropdown {
+ margin-right: 0;
+ }
+ }
+
> .btn-grouped {
float: none;
}
@@ -190,14 +196,6 @@
display: none;
}
- .btn,
- .dropdown,
- .dropdown-toggle,
- input,
- form {
- height: 35px;
- }
-
input {
display: inline-block;
position: relative;
@@ -325,7 +323,7 @@
position: absolute;
top: 7px;
right: 15px;
- z-index: 2;
+ z-index: 300;
li.active {
font-weight: bold;
diff --git a/app/assets/stylesheets/framework/responsive-tables.scss b/app/assets/stylesheets/framework/responsive-tables.scss
index d2c90908baa..8e653c443cf 100644
--- a/app/assets/stylesheets/framework/responsive-tables.scss
+++ b/app/assets/stylesheets/framework/responsive-tables.scss
@@ -100,9 +100,9 @@
}
.table-mobile-header {
+ @include flex-max-width(40);
color: $gl-text-color-secondary;
text-align: left;
- @include flex-max-width(40);
@media (min-width: $screen-md-min) {
display: none;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 5cf9330b8f8..49b2f0e43a4 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -92,7 +92,6 @@
@mixin maintain-sidebar-dimensions {
display: block;
width: $gutter-width;
- padding: 10px 20px;
}
.issues-bulk-update.right-sidebar {
@@ -104,6 +103,15 @@
&.right-sidebar-expanded {
@include maintain-sidebar-dimensions;
width: $gutter-width;
+
+ .issuable-sidebar-header {
+ // matches `.top-area .nav-controls` for issuable index pages
+ padding: 11px 0;
+ }
+
+ .block:last-of-type {
+ border: none;
+ }
}
&.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 785b09e622f..befd8133be0 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -2,13 +2,26 @@
color: $gl-text-color;
word-wrap: break-word;
+ [dir="auto"] {
+ text-align: initial;
+ }
+
a {
color: $md-link-color;
}
img {
- max-width: 100%;
+ /*max-width: 100%;*/
margin: 0 0 8px;
+ min-width: 200px;
+ min-height: 100px;
+ background-color: $gray-lightest;
+ }
+
+ img.js-lazy-loaded {
+ min-width: none;
+ min-height: none;
+ background-color: none;
}
p a:not(.no-attachment-icon) img {
@@ -112,9 +125,12 @@
blockquote p {
color: $gl-grayish-blue !important;
- margin: 0;
font-size: inherit;
line-height: 1.5;
+
+ &:last-child {
+ margin: 0;
+ }
}
p {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index da4d91511e0..cf0a1ad57d0 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -74,11 +74,17 @@ $red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
-$purple-600: #6e49cb;
-$purple-650: #5c35ae;
-$purple-700: #4a2192;
-$purple-800: #2c0a5c;
-$purple-900: #380d75;
+$indigo-50: #f7f7ff;
+$indigo-100: #ebebfa;
+$indigo-200: #d1d1f0;
+$indigo-300: #a6a6de;
+$indigo-400: #7c7ccc;
+$indigo-500: #6666c4;
+$indigo-600: #5b5bbd;
+$indigo-700: #4b4ba3;
+$indigo-800: #393982;
+$indigo-900: #292961;
+$indigo-950: #1a1a40;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
@@ -104,10 +110,10 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
-$gl-text-color: rgba(0, 0, 0, .85);
-$gl-text-color-light: rgba(0, 0, 0, .7);
-$gl-text-color-secondary: rgba(0, 0, 0, .55);
-$gl-text-color-disabled: rgba(0, 0, 0, .35);
+$gl-text-color: #2e2e2e;
+$gl-text-color-secondary: #707070;
+$gl-text-color-tertiary: #949494;
+$gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1.0);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
$gl-text-green: $green-600;
@@ -121,7 +127,7 @@ $gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
$gl-header-nav-hover-color: #434343;
-$placeholder-text-color: rgba(0, 0, 0, .42);
+$placeholder-text-color: $gl-text-color-tertiary;
/*
* Lists
@@ -129,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42);
$list-font-size: $gl-font-size;
$list-title-color: $gl-text-color;
$list-text-color: $gl-text-color;
-$list-text-disabled-color: $gl-text-color-disabled;
+$list-text-disabled-color: $gl-text-color-tertiary;
$list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
@@ -153,6 +159,7 @@ $code_line_height: 1.6;
* Padding
*/
$gl-padding: 16px;
+$gl-col-padding: 15px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
@@ -169,6 +176,7 @@ $header-height: 50px;
$fixed-layout-width: 1280px;
$limited-layout-width: 990px;
$limited-layout-width-sm: 790px;
+$container-text-max-width: 540px;
$gl-avatar-size: 40px;
$error-exclamation-point: $red-500;
$border-radius-default: 3px;
@@ -264,7 +272,7 @@ $diff-view-modes-border: #c1c1c1;
/*
* Fonts
*/
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
+$monospace_font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
/*
@@ -309,7 +317,7 @@ $badge-color: $gl-text-color-secondary;
/*
* Award emoji
*/
-$award-emoji-menu-shadow: rgba(0,0,0,.175);
+$award-emoji-menu-shadow: rgba(0, 0, 0, .175);
$award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13;
@@ -371,7 +379,9 @@ $issue-boards-card-shadow: rgba(186, 186, 186, 0.5);
* Avatar
*/
$avatar_radius: 50%;
-$avatar-border: rgba(0, 0, 0, .1);
+$avatar-border: $gray-normal;
+$avatar-border-hover: $gray-darker;
+$avatar-background: $gray-lightest;
$gl-avatar-size: 40px;
/*
@@ -443,6 +453,7 @@ $logs-p-color: #333;
/*
* Forms
*/
+$input-height: 34px;
$input-danger-bg: #f2dede;
$input-danger-border: $red-400;
$input-group-addon-bg: #f7f8fa;
@@ -559,7 +570,7 @@ $kdb-color: #555;
$kdb-border: #ccc;
$kdb-border-bottom: #bbb;
$kdb-shadow: #bbb;
-$body-text-shadow: rgba(255,255,255,0.01);
+$body-text-shadow: rgba(255, 255, 255, 0.01);
/*
* UI Dev Kit
@@ -575,6 +586,12 @@ $stage-hover-border: #d1e7fc;
$action-icon-color: #d6d6d6;
/*
+Pipeline Schedules
+*/
+$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
+
+
+/*
Filtered Search
*/
$filter-name-resting-color: #f8f8f8;
@@ -594,3 +611,15 @@ Convdev Index
$color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
+
+/*
+Performance Bar
+*/
+$perf-bar-text: #999;
+$perf-bar-production: #222;
+$perf-bar-staging: #291430;
+$perf-bar-development: #4c1210;
+$perf-bar-bucket-bg: #111;
+$perf-bar-bucket-color: #ccc;
+$perf-bar-bucket-box-shadow-from: rgba($white-light, .2);
+$perf-bar-bucket-box-shadow-to: rgba($black, .25);
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 1c1392f8f67..b1ff2659131 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -3,6 +3,7 @@
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: $border-radius-default;
+ margin-bottom: $gl-padding;
.well-segment {
padding: $gl-padding;
@@ -21,6 +22,11 @@
font-size: 12px;
}
}
+
+ &.admin-well h4 {
+ border-bottom: 1px solid $border-color;
+ padding-bottom: 8px;
+ }
}
.icon-container {
@@ -53,6 +59,14 @@
padding: 15px;
}
+.dark-well {
+ background-color: $gray-normal;
+
+ .btn {
+ width: 100%;
+ }
+}
+
.well-centered {
h1 {
font-weight: normal;
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 1daa10aef24..578f1902cce 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -113,7 +113,7 @@ $white-gc-bg: #eaf2f5;
border-color: $line-removed-dark;
a {
- color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
@@ -122,7 +122,7 @@ $white-gc-bg: #eaf2f5;
border-color: $line-added-dark;
a {
- color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
@@ -163,7 +163,7 @@ $white-gc-bg: #eaf2f5;
background-color: $line-removed;
&::before {
- color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
@@ -175,7 +175,7 @@ $white-gc-bg: #eaf2f5;
background-color: $line-added;
&::before {
- color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index bfb7a0c7e25..360ffda8d71 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -4,7 +4,7 @@
header.navbar-gitlab-new {
color: $white-light;
- background-color: $purple-900;
+ background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0;
.header-content {
@@ -21,14 +21,17 @@ header.navbar-gitlab-new {
padding-right: 0;
color: currentColor;
+ img {
+ height: 28px;
+ margin-right: 10px;
+ }
+
> a {
display: flex;
align-items: center;
- padding-top: 3px;
padding-right: $gl-padding;
padding-left: $gl-padding;
margin-left: -$gl-padding;
- border-bottom: 3px solid transparent;
@media (min-width: $screen-sm-min) {
padding-right: $gl-padding;
@@ -43,11 +46,22 @@ header.navbar-gitlab-new {
}
}
+ .logo-text {
+ line-height: initial;
+
+ svg {
+ width: 55px;
+ height: 15px;
+ margin: 0;
+ fill: $white-light;
+ }
+ }
+
&:hover,
&:focus {
- color: currentColor;
- text-decoration: none;
- border-bottom-color: $white-light;
+ .logo-text svg {
+ fill: $tanuki-yellow;
+ }
}
}
}
@@ -71,7 +85,7 @@ header.navbar-gitlab-new {
.navbar-collapse {
padding-left: 0;
- color: $white-light;
+ color: $indigo-200;
box-shadow: 0;
@media (max-width: $screen-xs-max) {
@@ -101,7 +115,7 @@ header.navbar-gitlab-new {
font-size: 14px;
text-align: center;
color: currentColor;
- border-left: 1px solid lighten($purple-700, 10%);
+ border-left: 1px solid lighten($indigo-700, 10%);
&:hover,
&:focus,
@@ -120,6 +134,7 @@ header.navbar-gitlab-new {
li {
.badge {
box-shadow: none;
+ font-weight: 600;
}
}
}
@@ -133,12 +148,11 @@ header.navbar-gitlab-new {
> a {
background: none;
- opacity: .9;
- will-change: opacity;
+ will-change: color;
&.header-user-dropdown-toggle {
.header-user-avatar {
- border-color: $white-light;
+ border-color: $indigo-200;
}
}
@@ -165,29 +179,34 @@ header.navbar-gitlab-new {
.navbar-sub-nav {
display: flex;
margin-bottom: 0;
- color: $white-light;
+ color: $indigo-200;
> li {
- &.active > a,
- a:hover,
- a:focus {
- border-bottom-color: $white-light;
+ > a:hover,
+ > a:focus {
+ box-shadow: inset 0 -3px 0 rgba($indigo-200, .4);
text-decoration: none;
outline: 0;
- opacity: 1;
+ color: $white-light;
+ }
+
+ &.active > a {
+ box-shadow: inset 0 -3px 0 $indigo-500;
+ color: $white-light;
+ font-weight: 700;
}
> a {
display: block;
- padding: 16px 10px 13px;
+ padding: 16px 10px;
font-size: 13px;
color: currentColor;
- border-bottom: 3px solid transparent;
- opacity: .9;
- will-change: opacity;
+ box-shadow: inset 0 0 0 transparent;
+ will-change: box-shadow;
+ transition: box-shadow 0.15s;
@media (min-width: $screen-sm-min) {
- padding: 15px $gl-padding 12px;
+ padding: 15px $gl-padding;
font-size: 14px;
}
}
@@ -207,55 +226,60 @@ header.navbar-gitlab-new {
.search {
form {
- border-color: $purple-800;
+ border: 0;
+ background-color: rgba($indigo-200, .2);
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
- border-color: rgba($white-light, .6);
+ background-color: rgba($indigo-200, .3);
box-shadow: none;
}
}
&.search-active form {
- border-color: $white-light;
- }
-
- form,
- .search-input {
- background-color: $purple-700;
+ background-color: rgba($indigo-200, .3);
+ box-shadow: none;
}
.search-input {
color: $white-light;
+ background: none;
}
.search-input::placeholder {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
.location-badge {
font-size: 12px;
- color: rgba($white-light, .6);
- background-color: $purple-800;
+ color: $indigo-100;
+ background-color: rgba($indigo-200, .1);
transition: color 0.15s;
will-change: color;
+ margin: -4px 4px -4px -4px;
+ line-height: 25px;
+ padding: 4px 8px;
+ border-radius: 2px 0 0 2px;
+ border-right: 1px solid $indigo-800;
+ height: 34px;
}
.search-input-wrap {
.search-icon,
.clear-icon {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
}
&.search-active {
.location-badge {
color: $white-light;
- background-color: $purple-800;
+ background-color: rgba($indigo-200, .2);
}
.search-input-wrap {
.search-icon {
- color: rgba($white-light, .6);
+ color: rgba($indigo-200, .8);
}
.clear-icon {
@@ -267,9 +291,7 @@ header.navbar-gitlab-new {
.breadcrumbs {
display: flex;
- min-height: 60px;
- padding-top: $gl-padding-top;
- padding-bottom: $gl-padding-top;
+ min-height: 61px;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
@@ -277,7 +299,7 @@ header.navbar-gitlab-new {
position: relative;
top: -1px;
padding: 0 5px;
- color: rgba($black, .65);
+ color: $gl-text-color-secondary;
font-size: 10px;
line-height: 1;
background: none;
@@ -293,6 +315,7 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
+ align-items: center;
.dropdown-menu-projects {
margin-top: -$gl-padding;
@@ -303,10 +326,10 @@ header.navbar-gitlab-new {
.breadcrumbs-links {
flex: 1;
align-self: center;
- color: $black-transparent;
+ color: $gl-text-color-quaternary;
a {
- color: rgba($black, .65);
+ color: $gl-text-color-secondary;
&:not(:first-child),
&.group-path {
@@ -323,7 +346,7 @@ header.navbar-gitlab-new {
white-space: nowrap;
> a {
- &:last-of-type {
+ &:last-of-type:not(:first-child) {
font-weight: 600;
}
}
@@ -361,9 +384,10 @@ header.navbar-gitlab-new {
}
.breadcrumbs-sub-title {
- margin: 2px 0 0;
+ margin: 2px 0;
font-size: 16px;
font-weight: normal;
+ line-height: 1;
ul {
margin: 0;
@@ -376,6 +400,7 @@ header.navbar-gitlab-new {
&::after {
content: "/";
margin: 0 2px 0 5px;
+ color: rgba($black, .65);
}
}
@@ -388,3 +413,13 @@ header.navbar-gitlab-new {
color: $gl-text-color;
}
}
+
+.top-area {
+ .nav-controls-new-nav {
+ .dropdown {
+ @media (min-width: $screen-sm-min) {
+ margin-right: 0;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index 17f23f7fce3..ae43197a1a6 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -2,6 +2,15 @@
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
+$active-background: rgba(0, 0, 0, .04);
+$active-border: $indigo-500;
+$active-color: $indigo-700;
+$active-hover-background: $active-background;
+$active-hover-color: $gl-text-color;
+$inactive-badge-background: rgba(0, 0, 0, .08);
+$hover-background: $indigo-700;
+$hover-color: $white-light;
+$inactive-color: $gl-text-color-secondary;
$new-sidebar-width: 220px;
.page-with-new-sidebar {
@@ -14,22 +23,81 @@ $new-sidebar-width: 220px;
position: fixed;
height: 100%;
}
+
+ .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
+ padding: 10px 0 15px;
+ }
}
.context-header {
- background-color: $gray-normal;
- border-bottom: 1px solid $border-color;
- font-weight: 600;
- display: flex;
- align-items: center;
- padding: 10px 14px;
+ position: relative;
+
+ a {
+ border-bottom: 1px solid $border-color;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ padding: 10px 16px 10px 10px;
+ color: $gl-text-color;
+
+ @media (max-width: $screen-xs-max) {
+ padding-right: 30px;
+ }
+
+ &:hover {
+ background-color: $hover-background;
+ color: $hover-color;
+ border-color: $hover-background;
+
+ .avatar-container {
+ border-color: transparent;
+ }
+
+ .settings-avatar {
+ background-color: $indigo-500;
+
+ i {
+ color: $hover-color;
+ }
+ }
+ }
+ }
.avatar-container {
flex: 0 0 40px;
+ background-color: $white-light;
+ }
+
+ .project-title,
+ .group-title {
+ overflow: hidden;
+ text-overflow: ellipsis;
}
+
&:hover {
- background-color: $border-color;
+ .close-nav-button {
+ color: $white-light;
+ }
+ }
+
+ .close-nav-button {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ background-color: transparent;
+ border: 0;
+ padding: 0 10px;
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+
+ &:hover {
+ color: $gl-text-color;
+ }
}
}
@@ -49,16 +117,25 @@ $new-sidebar-width: 220px;
position: fixed;
z-index: 400;
width: $new-sidebar-width;
- transition: width $sidebar-transition-duration;
+ transition: left $sidebar-transition-duration;
top: 50px;
bottom: 0;
left: 0;
overflow: auto;
- background-color: $gray-light;
- border-right: 1px solid $border-color;
+ background-color: $gray-normal;
+ box-shadow: inset -2px 0 0 $border-color;
+
+ &.nav-sidebar-expanded {
+ left: 0;
+ }
+
+ a {
+ transition: none;
+ text-decoration: none;
+ }
ul {
- padding: 0;
+ padding-left: 0;
list-style: none;
}
@@ -67,38 +144,48 @@ $new-sidebar-width: 220px;
a {
display: block;
- padding: 12px 14px;
+ padding: 12px 16px;
+ color: $inactive-color;
}
}
- a {
- color: $gl-text-color;
- text-decoration: none;
+ li.active {
+ box-shadow: inset 4px 0 0 $active-border;
+
+ > a {
+ color: $active-color;
+ font-weight: 700;
+ }
}
@media (max-width: $screen-xs-max) {
- width: 0;
+ left: (-$new-sidebar-width);
}
}
.sidebar-sub-level-items {
display: none;
+ padding-bottom: 8px;
> li {
a {
- padding: 12px 24px;
- color: $gl-text-color-light;
+ padding: 8px 16px 8px 24px;
- &:hover {
- color: $gl-text-color;
- background-color: $border-color;
+ &:hover,
+ &:focus {
+ background: $active-hover-background;
+ color: $active-hover-color;
}
}
&.active {
- > a {
- color: $purple-650;
- font-weight: 600;
+ a {
+ &,
+ &:hover,
+ &:focus {
+ background: $active-background;
+ color: $active-color;
+ }
}
}
}
@@ -108,40 +195,67 @@ $new-sidebar-width: 220px;
> li {
.badge {
float: right;
- background-color: $border-color;
- color: $gl-text-color;
+ background-color: $inactive-badge-background;
+ color: $inactive-color;
}
&.active {
- > a {
- background-color: $purple-600;
- color: $white-light;
- font-weight: 600;
- }
+ background: $active-background;
.badge {
- background-color: $purple-700;
- color: $white-light;
+ color: $active-color;
+ font-weight: 600;
}
.sidebar-sub-level-items {
- background-color: $gray-normal;
- border-left: 6px solid $purple-600;
display: block;
}
}
- &:not(.active) > a:hover {
- background-color: $border-color;
+ > a:hover {
+ background-color: $hover-background;
+ color: $hover-color;
.badge {
- transition: background-color 100ms linear;
- background-color: $gray-normal;
+ background-color: $indigo-500;
+ color: $hover-color;
}
}
}
}
+.toggle-mobile-nav {
+ display: none;
+ background-color: transparent;
+ border: 0;
+ padding: 6px 16px;
+ margin: 0 16px 0 -15px;
+ height: 46px;
+ border-right: 1px solid $gl-text-color-quaternary;
+
+ i {
+ font-size: 20px;
+ color: $gl-text-color-secondary;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: inline-block;
+ }
+}
+
+.mobile-overlay {
+ display: none;
+
+ &.mobile-nav-open {
+ display: block;
+ position: fixed;
+ background-color: $black-transparent;
+ height: 100%;
+ width: 100%;
+ z-index: 300;
+ }
+}
+
// Make issue boards full-height now that sub-nav is gone
@@ -151,7 +265,17 @@ $new-sidebar-width: 220px;
@media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
// scss-lint:disable DuplicateProperty
- height: calc(100vh - 120px);
+ height: calc(100vh - 180px);
// scss-lint:enable DuplicateProperty
}
}
+
+
+// Change color of all horizontal tabs to match the new indigo color
+.nav-links li.active a {
+ border-bottom-color: $active-border;
+
+ .badge {
+ font-weight: 600;
+ }
+}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 85109fec91a..6039cda96d8 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -11,7 +11,7 @@
.is-dragging {
// Important because plugin sets inline CSS
- opacity: 1!important;
+ opacity: 1 !important;
* {
-webkit-user-select: none;
@@ -19,8 +19,8 @@
-ms-user-select: none;
user-select: none;
// !important to make sure no style can override this when dragging
- cursor: -webkit-grabbing!important;
- cursor: grabbing!important;
+ cursor: -webkit-grabbing !important;
+ cursor: grabbing !important;
}
}
@@ -431,7 +431,10 @@
margin: 5px;
}
-.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar {
+.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar,
+.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
+ position: absolute;
+
&.right-sidebar {
top: 0;
bottom: 0;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 9cff99b839c..b6fc628c02b 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -6,26 +6,26 @@
@keyframes blinking-dots {
0% {
background-color: rgba($white-light, 1);
- box-shadow: 12px 0 0 0 rgba($white-light,0.2),
- 24px 0 0 0 rgba($white-light,0.2);
+ box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
+ 24px 0 0 0 rgba($white-light, 0.2);
}
25% {
background-color: rgba($white-light, 0.4);
- box-shadow: 12px 0 0 0 rgba($white-light,2),
- 24px 0 0 0 rgba($white-light,0.2);
+ box-shadow: 12px 0 0 0 rgba($white-light, 2),
+ 24px 0 0 0 rgba($white-light, 0.2);
}
75% {
background-color: rgba($white-light, 0.4);
- box-shadow: 12px 0 0 0 rgba($white-light,0.2),
- 24px 0 0 0 rgba($white-light,1);
+ box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
+ 24px 0 0 0 rgba($white-light, 1);
}
100% {
background-color: rgba($white-light, 1);
- box-shadow: 12px 0 0 0 rgba($white-light,0.2),
- 24px 0 0 0 rgba($white-light,0.2);
+ box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
+ 24px 0 0 0 rgba($white-light, 0.2);
}
}
@@ -37,65 +37,77 @@
}
.build-page {
- .sticky {
- position: absolute;
- left: 0;
- right: 0;
+ .build-trace-container {
+ position: relative;
}
- .build-trace-container {
- position: absolute;
- top: 225px;
- left: 15px;
- bottom: 10px;
+ .build-trace {
background: $black;
color: $gray-darkest;
- font-family: $monospace_font;
+ white-space: pre;
+ overflow-x: auto;
font-size: 12px;
+ border-radius: 0;
+ border: none;
- &.sidebar-expanded {
- right: 305px;
+ .bash {
+ display: block;
}
+ }
- &.sidebar-collapsed {
- right: 16px;
+ .top-bar {
+ height: 35px;
+ display: flex;
+ justify-content: flex-end;
+ background: $gray-light;
+ border: 1px solid $border-color;
+ color: $gl-text-color;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 50px;
+
+ &.affix {
+ top: 50px;
}
- code {
- background: $black;
- color: $gray-darkest;
+ // with sidebar
+ &.affix.sidebar-expanded {
+ right: 306px;
+ left: 16px;
}
- .top-bar {
- top: 0;
- height: 35px;
- display: flex;
- justify-content: flex-end;
- background: $gray-light;
- border: 1px solid $border-color;
- color: $gl-text-color;
+ // without sidebar
+ &.affix.sidebar-collapsed {
+ right: 16px;
+ left: 16px;
+ }
- .truncated-info {
- margin: 0 auto;
- align-self: center;
+ &.affix-top {
+ position: absolute;
+ right: 0;
+ left: 0;
+ }
- .truncated-info-size {
- margin: 0 5px;
- }
+ .truncated-info {
+ margin: 0 auto;
+ align-self: center;
- .raw-link {
- color: $gl-text-color;
- margin-left: 5px;
- text-decoration: underline;
- }
+ .truncated-info-size {
+ margin: 0 5px;
+ }
+
+ .raw-link {
+ color: $gl-text-color;
+ margin-left: 5px;
+ text-decoration: underline;
}
}
.controllers {
display: flex;
- align-self: center;
font-size: 15px;
- margin-bottom: 4px;
+ justify-content: center;
+ align-items: center;
svg {
height: 15px;
@@ -103,17 +115,9 @@
fill: $gl-text-color;
}
- .controllers-buttons,
- .btn-scroll {
- color: $gl-text-color;
- height: 15px;
- vertical-align: middle;
- padding: 0;
- width: 12px;
- }
-
.controllers-buttons {
- margin: 1px 10px;
+ color: $gl-text-color;
+ margin: 0 10px;
}
.btn-scroll.animate {
@@ -143,15 +147,6 @@
}
}
- .bash {
- top: 35px;
- left: 10px;
- bottom: 0;
- padding: 10px 20px 20px 5px;
- white-space: pre-wrap;
- overflow: auto;
- }
-
.environment-information {
border: 1px solid $border-color;
padding: 8px $gl-padding 12px;
@@ -249,6 +244,10 @@
}
}
+ .block-last {
+ padding: 16px 0;
+ }
+
.trigger-build-variable {
color: $code-color;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 9db0f2075cb..cd9f2d787c5 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -54,7 +54,11 @@
.mr-widget-pipeline-graph {
display: inline-block;
vertical-align: middle;
- margin: 0 -6px 0 0;
+ margin-right: 4px;
+
+ .stage-cell .stage-container {
+ margin: 3px 3px 3px 0;
+ }
.dropdown-menu {
margin-top: 11px;
@@ -250,8 +254,8 @@
}
.committed_ago {
- float: right;
@extend .cgray;
+ float: right;
}
}
}
@@ -279,3 +283,63 @@
color: $gl-text-color;
}
}
+
+
+.gpg-status-box {
+ &.valid {
+ @include green-status-color;
+ }
+
+ &.invalid {
+ @include status-color($gray-dark, $gray, $common-gray-dark);
+ border-color: $common-gray-light;
+ }
+}
+
+.gpg-popover-status {
+ display: flex;
+ align-items: center;
+ font-weight: normal;
+ line-height: 1.5;
+}
+
+.gpg-popover-icon {
+ // same margin as .s32.avatar
+ margin-right: $btn-side-margin;
+
+ &.valid {
+ svg {
+ border: 1px solid $brand-success;
+
+ fill: $brand-success;
+ }
+ }
+
+ &.invalid {
+ svg {
+ border: 1px solid $common-gray-light;
+
+ fill: $common-gray-light;
+ }
+ }
+
+ svg {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ vertical-align: middle;
+ }
+}
+
+.gpg-popover-user-link {
+ display: flex;
+ align-items: center;
+ margin-bottom: $gl-padding / 2;
+ text-decoration: none;
+ color: $gl-text-color;
+}
+
+.commit .gpg-popover-help-link {
+ display: block;
+ color: $link-color;
+}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 3039732ca5b..eeb90759f10 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -24,9 +24,9 @@
.col-headers {
ul {
+ @include clearfix;
margin: 0;
padding: 0;
- @include clearfix;
}
li {
@@ -189,8 +189,8 @@
}
li {
- list-style-type: none;
@include clearfix;
+ list-style-type: none;
}
.stage-nav-item {
@@ -281,11 +281,11 @@
}
.stage-event-item {
+ @include clearfix;
list-style-type: none;
padding: 0 0 $gl-padding;
margin: 0 $gl-padding $gl-padding;
border-bottom: 1px solid $gray-darker;
- @include clearfix;
&:last-child {
border-bottom: none;
@@ -307,9 +307,9 @@
&.issue-title,
&.commit-title,
&.merge-merquest-title {
+ @include text-overflow();
max-width: 100%;
display: block;
- @include text-overflow();
a {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 55011e8a21b..398fd4444ea 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -91,6 +91,7 @@
.old_line,
.new_line {
+ @include user-select(none);
margin: 0;
border: none;
padding: 0 5px;
@@ -99,7 +100,6 @@
min-width: 35px;
max-width: 50px;
width: 35px;
- @include user-select(none);
a {
float: left;
@@ -354,12 +354,12 @@
}
&.active {
+ cursor: default;
+ color: $gl-text-color;
+
&:hover {
text-decoration: none;
}
-
- cursor: default;
- color: $gl-text-color;
}
&.disabled {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index a2be957655f..00ebf4e26ac 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -187,8 +187,7 @@
}
.text-metric {
- font-weight: 600;
- font-size: 14px;
+ font-size: 12px;
}
.selected-metric-line {
@@ -232,10 +231,6 @@
width: 100%;
padding: 0;
padding-bottom: 100%;
-
- .text-metric-bold {
- font-weight: 600;
- }
}
.prometheus-svg-container > svg {
@@ -250,11 +245,15 @@
stroke-width: 0;
}
+ .text-metric-bold {
+ font-weight: 600;
+ }
+
.label-axis-text,
.text-metric-usage {
fill: $black;
font-weight: 500;
- font-size: 14px;
+ font-size: 12px;
}
.legend-axis-text {
@@ -262,7 +261,20 @@
}
.tick > text {
- font-size: 14px;
+ font-size: 12px;
+ }
+
+ .text-metric-title {
+ font-size: 12px;
+ }
+
+ .y-label-text,
+ .x-label-text {
+ fill: $gray-darkest;
+ }
+
+ .axis-tick {
+ stroke: $gray-darker;
}
@media (max-width: $screen-sm-max) {
@@ -277,3 +289,9 @@
}
}
}
+
+.prometheus-row {
+ h5 {
+ font-size: 16px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 057d457b3a2..eb269df46fe 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -200,7 +200,6 @@
right: 0;
transition: width .3s;
background: $gray-light;
- padding: 0 20px;
z-index: 200;
overflow: hidden;
@@ -212,6 +211,10 @@
-webkit-overflow-scrolling: touch;
}
+ &.affix-top .issuable-sidebar {
+ height: 100%;
+ }
+
&.right-sidebar-expanded {
width: $gutter_width;
@@ -224,6 +227,10 @@
}
}
+ .issuable-sidebar {
+ padding: 0 20px;
+ }
+
.issuable-sidebar-header {
padding-top: 10px;
}
@@ -343,13 +350,9 @@
display: none;
}
- .avatar:hover,
- .avatar-counter:hover {
- border-color: $issuable-sidebar-color;
- }
-
.avatar-counter:hover {
color: $issuable-sidebar-color;
+ border-color: $issuable-sidebar-color;
}
.btn-clipboard {
@@ -796,3 +799,26 @@
}
}
}
+
+.issuable-close-button,
+.issuable-close-toggle {
+ @include transition(border-color, color);
+}
+
+.issuable-close-dropdown {
+ .dropdown-menu {
+ min-width: 270px;
+ left: auto;
+ right: 0;
+ }
+
+ .description {
+ .text {
+ margin: 0;
+ }
+ }
+
+ .dropdown-toggle > .icon {
+ margin: 0 3px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index f21005895e4..e7c07ef67f0 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -54,8 +54,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
- width: 400px;
- max-width: 50%;
}
}
@@ -65,7 +63,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
- width: 100%;
margin-top: 3px;
}
}
@@ -81,18 +78,10 @@
.member-form-control {
@media (max-width: $screen-xs-max) {
- padding: 5px 0;
+ padding-bottom: 5px;
margin-left: 0;
margin-right: 0;
}
-
- @media (min-width: $screen-sm-min) {
- width: 50%;
- }
-
- .dropdown-menu-toggle {
- width: 100%;
- }
}
.member-access-text {
@@ -216,3 +205,102 @@
}
}
}
+
+.content-list.members-list li {
+ display: flex;
+ justify-content: space-between;
+
+ .list-item-name {
+ float: none;
+ display: flex;
+ flex: 1;
+ }
+
+ .user-info {
+ padding-right: 10px;
+ }
+
+ .member {
+ font-weight: bold;
+ overflow-wrap: break-word;
+ word-break: break-all;
+ }
+
+ .member-group-link {
+ display: inline-block;
+ }
+
+ .form-control {
+ width: inherit;
+ }
+
+ .btn {
+ align-self: flex-start;
+ }
+
+ .form-horizontal ~ .btn {
+ margin-right: 0;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+
+ .controls > .btn {
+ margin-left: 0;
+ margin-right: 0;
+ display: block;
+ }
+
+ .form-control {
+ width: 100%;
+ }
+
+ .member-access-text {
+ line-height: 0;
+ margin-left: 50px;
+ }
+
+ .member-controls {
+ margin-top: 5px;
+ }
+
+ .form-horizontal {
+ margin-top: 10px;
+ }
+ }
+}
+
+.panel-mobile {
+ .content-list.members-list li {
+ display: block;
+
+ .member-controls {
+ float: none;
+ display: block;
+ }
+
+ .dropdown-menu-toggle,
+ .dropdown-menu,
+ .form-control,
+ .list-item-name {
+ width: 100%;
+ }
+
+ .dropdown-menu {
+ margin-top: 0;
+ }
+
+ .form-horizontal {
+ display: block;
+ }
+
+ .member-form-control {
+ margin: 5px 0;
+ }
+
+ .btn {
+ width: 100%;
+ margin-left: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 59e0624d94e..2db967547dd 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -96,7 +96,7 @@
overflow: visible;
}
- & > span {
+ > span {
padding-right: 4px;
}
@@ -125,7 +125,7 @@
.dropdown-menu {
margin-top: 11px;
- z-index: 200;
+ z-index: 300;
}
.ci-action-icon-wrapper {
@@ -731,11 +731,11 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 100;
+ z-index: 200;
background-color: $white-light;
border-bottom: 1px solid $border-color;
- @media(min-width: $screen-sm-min) {
+ @media (min-width: $screen-sm-min) {
position: sticky;
position: -webkit-sticky;
}
@@ -770,6 +770,12 @@
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
+
+ .inner-page-scroll-tabs {
+ background-color: $white-light;
+ margin-left: -$gl-padding;
+ padding-left: $gl-padding;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 9877ed2cfd6..cdb1e65e4be 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -356,7 +356,6 @@
color: $white-light;
padding-right: 2px;
margin-top: 2px;
- pointer-events: none;
}
}
@@ -366,56 +365,6 @@
width: 298px;
}
- .description {
- display: inline-block;
- white-space: normal;
- margin-left: 8px;
- padding-right: 33px;
- }
-
- li {
- padding-top: 6px;
-
- & > a {
- margin: 0;
- padding: 0;
- color: inherit;
- border-radius: 0;
- text-overflow: inherit;
-
- &:hover,
- &:focus {
- background-color: inherit;
- color: inherit;
- }
- }
-
- &:hover,
- &:focus {
- background-color: $dropdown-hover-color;
- color: $white-light;
- }
-
- &.droplab-item-selected i {
- visibility: visible;
- }
-
- i {
- visibility: hidden;
- }
- }
-
- i {
- display: inline-block;
- vertical-align: top;
- padding-top: 2px;
- }
-
- .divider {
- margin: 0 8px;
- padding: 0;
- border-top: $gray-darkest;
- }
@media (max-width: $screen-xs-max) {
display: flex;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 303425041df..2bb867052f6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -121,10 +121,11 @@ ul.notes {
overflow-y: hidden;
.note-text {
- word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
+ word-wrap: break-word;
+
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
@@ -250,7 +251,7 @@ ul.notes {
}
.note-text {
- & p:first-child {
+ p:first-child {
display: none;
}
diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss
index 595eb40fec7..dc1654e006e 100644
--- a/app/assets/stylesheets/pages/pipeline_schedules.scss
+++ b/app/assets/stylesheets/pages/pipeline_schedules.scss
@@ -1,7 +1,7 @@
.js-pipeline-schedule-form {
.dropdown-select,
.dropdown-menu-toggle {
- width: 100%!important;
+ width: 100% !important;
}
.gl-field-error {
@@ -74,3 +74,84 @@
margin-right: 3px;
}
}
+
+.pipeline-variable-list {
+ margin-left: 0;
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+ clear: both;
+}
+
+.pipeline-variable-row {
+ display: flex;
+ align-items: flex-end;
+
+ &:not(:last-child) {
+ margin-bottom: $gl-btn-padding;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ padding-right: $gl-col-padding;
+ }
+
+ &:last-child {
+ .pipeline-variable-row-remove-button {
+ display: none;
+ }
+
+ @media (max-width: $screen-sm-max) {
+ .pipeline-variable-value-input {
+ margin-right: $pipeline-variable-remove-button-width;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .pipeline-variable-row-body {
+ margin-right: $pipeline-variable-remove-button-width;
+ }
+ }
+ }
+}
+
+.pipeline-variable-row-body {
+ display: flex;
+ width: calc(75% - #{$gl-col-padding});
+ padding-left: $gl-col-padding;
+
+ @media (max-width: $screen-sm-max) {
+ width: 100%;
+ }
+
+ @media (max-width: $screen-xs-max) {
+ display: block;
+ }
+}
+
+.pipeline-variable-key-input {
+ margin-right: $gl-btn-padding;
+
+ @media (max-width: $screen-xs-max) {
+ margin-bottom: $gl-btn-padding;
+ }
+}
+
+.pipeline-variable-row-remove-button {
+ @include transition(color);
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: $pipeline-variable-remove-button-width;
+ height: $input-height;
+ padding: 0;
+ background: transparent;
+ border: 0;
+ color: $gl-text-color-secondary;
+
+ &:hover,
+ &:focus {
+ outline: none;
+ color: $gl-text-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 9637d26e56d..d3862df20d3 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -597,7 +597,7 @@
}
// Dropdown button in mini pipeline graph
-.mini-pipeline-graph-dropdown-toggle {
+button.mini-pipeline-graph-dropdown-toggle {
border-radius: 100px;
background-color: $white-light;
border-width: 1px;
@@ -608,6 +608,7 @@
padding: 0;
transition: all 0.2s linear;
position: relative;
+ vertical-align: middle;
> .fa.fa-caret-down {
position: absolute;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index c207159f606..14ad06b0ac2 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -286,8 +286,7 @@ table.u2f-registrations {
}
.user-callout {
- margin: 0 auto;
- max-width: $screen-lg-min;
+ margin: 20px -5px 0;
.bordered-box {
border: 1px solid $blue-300;
@@ -377,3 +376,41 @@ table.u2f-registrations {
}
}
}
+
+.nav-wip {
+ border: 1px solid $blue-500;
+ background: $blue-25;
+ padding: $gl-padding;
+ margin-bottom: $gl-padding;
+
+ a {
+ color: $blue-500;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.gpg-email-badge {
+ display: inline;
+ margin-right: $gl-padding / 2;
+
+ .gpg-email-badge-email {
+ display: inline;
+ margin-right: $gl-padding / 4;
+ }
+
+ .label-verification-status {
+ border-width: 1px;
+ border-style: solid;
+
+ &.verified {
+ @include green-status-color;
+ }
+
+ &.unverified {
+ @include status-color($gray-dark, $gray, $common-gray-dark);
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 7d7c34115f9..a3e07a36c33 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -26,7 +26,7 @@
margin-bottom: 5px;
}
- & > .form-group {
+ > .form-group {
padding-left: 0;
}
@@ -83,7 +83,7 @@
border: 1px solid $border-color;
}
- & + .select2 a {
+ + .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
@@ -587,9 +587,9 @@ pre.light-well {
}
.project-row {
+ @include basic-list-stats;
display: flex;
align-items: center;
- @include basic-list-stats;
}
h3 {
@@ -742,7 +742,8 @@ pre.light-well {
}
}
-.protected-tags-list {
+.protected-tags-list,
+.protected-branches-list {
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 67ad1ae60af..36f622db136 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -1,22 +1,3 @@
-@mixin status-color($color-light, $color-main, $color-dark) {
- color: $color-main;
- border-color: $color-main;
-
- &:not(span):hover {
- background-color: $color-light;
- color: $color-dark;
- border-color: $color-dark;
-
- svg {
- fill: $color-dark;
- }
- }
-
- svg {
- fill: $color-main;
- }
-}
-
.ci-status {
padding: 2px 7px 4px;
border: 1px solid $gray-darker;
@@ -41,7 +22,7 @@
}
&.ci-success {
- @include status-color($green-50, $green-500, $green-700);
+ @include green-status-color;
}
&.ci-canceled,
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index de652a79369..d7a9dda3770 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -81,7 +81,7 @@
.todo-title {
display: flex;
- & > .title-item {
+ > .title-item {
-webkit-flex: 0 0 auto;
flex: 0 0 auto;
margin: 0 2px;
diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss
index 8c87bc3cafd..798e060a261 100644
--- a/app/assets/stylesheets/pages/ui_dev_kit.scss
+++ b/app/assets/stylesheets/pages/ui_dev_kit.scss
@@ -5,13 +5,13 @@
}
.example {
+ padding: 15px;
+ border: 1px dashed $ui-dev-kit-example-border;
+ margin-bottom: 15px;
+
&::before {
content: "Example";
color: $ui-dev-kit-example-color;
}
-
- padding: 15px;
- border: 1px dashed $ui-dev-kit-example-border;
- margin-bottom: 15px;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 94d0a39f397..45c21c5d274 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -147,13 +147,13 @@
}
ul.wiki-pages-list.content-list {
- & ul {
+ ul {
list-style: none;
margin-left: 0;
padding-left: 15px;
}
- & ul li {
+ ul li {
padding: 5px 0;
}
}
diff --git a/vendor/assets/stylesheets/peek.scss b/app/assets/stylesheets/performance_bar.scss
index f1845fb9044..2890b6b1e49 100644
--- a/vendor/assets/stylesheets/peek.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,47 +1,44 @@
-//= require peek/views/performance_bar
-//= require peek/views/rblineprof
-
-header.navbar-gitlab.with-peek {
- top: 35px;
-}
+@import "framework/variables";
+@import "peek/views/performance_bar";
+@import "peek/views/rblineprof";
#peek {
height: 35px;
- background: #000;
+ background: $black;
line-height: 35px;
- color: #999;
+ color: $perf-bar-text;
&.disabled {
display: none;
}
&.production {
- background-color: #222;
+ background-color: $perf-bar-production;
}
&.staging {
- background-color: #291430;
+ background-color: $perf-bar-staging;
}
&.development {
- background-color: #4c1210;
+ background-color: $perf-bar-development;
}
.wrapper {
- width: 800px;
+ width: 1000px;
margin: 0 auto;
}
// UI Elements
.bucket {
- background: #111;
+ background: $perf-bar-bucket-bg;
display: inline-block;
padding: 4px 6px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
line-height: 1;
- color: #ccc;
+ color: $perf-bar-bucket-color;
border-radius: 3px;
- box-shadow: 0 1px 0 rgba(255,255,255,.2), inset 0 1px 2px rgba(0,0,0,.25);
+ box-shadow: 0 1px 0 $perf-bar-bucket-box-shadow-from, inset 0 1px 2px $perf-bar-bucket-box-shadow-to;
.hidden {
display: none;
@@ -53,12 +50,14 @@ header.navbar-gitlab.with-peek {
}
strong {
- color: #fff;
+ color: $white-light;
}
table {
+ color: $black;
+
strong {
- color: #000;
+ color: $black;
}
}
@@ -90,5 +89,15 @@ header.navbar-gitlab.with-peek {
}
#modal-peek-pg-queries-content {
- color: #000;
+ color: $black;
+}
+
+.peek-rblineprof-file {
+ pre.duration {
+ width: 280px;
+ }
+
+ .data {
+ overflow: visible;
+ }
}
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index 136d0c79467..113e6e86bb5 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -37,7 +37,7 @@ ul.notes-form,
.issuable-details .content-block-small,
.edit-link,
.note-action-button {
- display: none!important;
+ display: none !important;
}
pre {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4d4b8a8425f..4c0f7556894 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -71,14 +71,16 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
+
+ params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(
- application_setting_params_ce
+ application_setting_params_attributes
)
end
- def application_setting_params_ce
+ def application_setting_params_attributes
[
:admin_notification_email,
:after_sign_out_path,
@@ -111,6 +113,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:html_emails_enabled,
:koding_enabled,
:koding_url,
+ :password_authentication_enabled,
:plantuml_enabled,
:plantuml_url,
:max_artifacts_size,
@@ -124,6 +127,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_port,
:metrics_sample_interval,
:metrics_timeout,
+ :performance_bar_allowed_group_id,
+ :performance_bar_enabled,
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
@@ -131,7 +136,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication,
:session_expire_delay,
:sign_in_text,
- :signin_enabled,
:signup_enabled,
:sentry_dsn,
:sentry_enabled,
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index 8360ce08bdc..05e749c00c0 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,6 +1,6 @@
class Admin::DashboardController < Admin::ApplicationController
def index
- @projects = Project.with_route.limit(10)
+ @projects = Project.without_deleted.with_route.limit(10)
@users = User.limit(10)
@groups = Group.with_route.limit(10)
end
diff --git a/app/controllers/admin/hook_logs_controller.rb b/app/controllers/admin/hook_logs_controller.rb
index aa069b89563..3017f96c26f 100644
--- a/app/controllers/admin/hook_logs_controller.rb
+++ b/app/controllers/admin/hook_logs_controller.rb
@@ -10,9 +10,9 @@ class Admin::HookLogsController < Admin::ApplicationController
end
def retry
- status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+ result = hook.execute(hook_log.request_data, hook_log.trigger)
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
redirect_to edit_admin_hook_path(@hook)
end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 054c3500b35..77e3c95d197 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -38,9 +38,9 @@ class Admin::HooksController < Admin::ApplicationController
end
def test
- status, message = hook.execute(sample_hook_data, 'system_hooks')
+ result = TestHooks::SystemService.new(hook, current_user, params[:trigger]).execute
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
redirect_back_or_default
end
@@ -66,15 +66,4 @@ class Admin::HooksController < Admin::ApplicationController
:url
)
end
-
- def sample_hook_data
- {
- event_name: "project_create",
- name: "Ruby",
- path: "ruby",
- project_id: 1,
- owner_name: "Someone",
- owner_email: "example@gitlabhq.com"
- }
- end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index a1975c0e341..0b6cd71e651 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -3,18 +3,9 @@ class Admin::ProjectsController < Admin::ApplicationController
before_action :group, only: [:show, :transfer]
def index
- params[:sort] ||= 'latest_activity_desc'
- @projects = Project.with_statistics
- @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
- @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
- @projects = @projects.with_push if params[:with_push].present?
- @projects = @projects.abandoned if params[:abandoned].present?
- @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
- @projects = @projects.non_archived unless params[:archived].present?
- @projects = @projects.personal(current_user) if params[:personal].present?
- @projects = @projects.search(params[:name]) if params[:name].present?
- @projects = @projects.sort(@sort = params[:sort])
- @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
+ finder = Admin::ProjectsFinder.new(params: params, current_user: current_user)
+ @projects = finder.execute
+ @sort = finder.sort
respond_to do |format|
format.html
@@ -40,14 +31,14 @@ class Admin::ProjectsController < Admin::ApplicationController
::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
- redirect_to admin_namespace_project_path(@project.namespace, @project)
+ redirect_to admin_project_path(@project)
end
def repository_check
RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
redirect_to(
- admin_namespace_project_path(@project.namespace, @project),
+ admin_project_path(@project),
notice: 'Repository check was triggered.'
)
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 824ce845706..43462b13903 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
- include Peek::Rblineprof::CustomControllerHelpers
+ include WithPerformanceBar
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
@@ -68,21 +68,6 @@ class ApplicationController < ActionController::Base
end
end
- def peek_enabled?
- return false unless Gitlab::PerformanceBar.enabled?
- return false unless current_user
-
- if RequestStore.active?
- if RequestStore.store.key?(:peek_enabled)
- RequestStore.store[:peek_enabled]
- else
- RequestStore.store[:peek_enabled] = cookies[:perf_bar_enabled].present?
- end
- else
- cookies[:perf_bar_enabled].present?
- end
- end
-
protected
# This filter handles both private tokens and personal access tokens
@@ -110,6 +95,8 @@ class ApplicationController < ActionController::Base
end
def log_exception(exception)
+ Raven.capture_exception(exception) if sentry_enabled?
+
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
@@ -183,7 +170,7 @@ class ApplicationController < ActionController::Base
end
def check_password_expiration
- if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
+ if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && current_user.allow_password_authentication?
return redirect_to new_profile_password_path
end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index fe331a883c1..3120916c5bb 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -5,10 +5,10 @@ class AutocompleteController < ApplicationController
def users
@users ||= User.none
- @users = @users.search(params[:search]) if params[:search].present?
- @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.active
@users = @users.reorder(:name)
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.page(params[:page]).per(params[:per_page])
if params[:todo_filter].present? && current_user
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index f87db4d9e84..782f0be9c4a 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -78,8 +78,7 @@ module CreatesCommit
end
def new_merge_request_path
- namespace_project_new_merge_request_path(
- @project_to_commit_into.namespace,
+ project_new_merge_request_path(
@project_to_commit_into,
merge_request: {
source_project_id: @project_to_commit_into.id,
@@ -91,7 +90,7 @@ module CreatesCommit
end
def existing_merge_request_path
- namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ project_merge_request_path(@project, @merge_request)
end
def merge_request_exists?
diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb
index 846cd60518f..a22e46b4860 100644
--- a/app/controllers/concerns/hooks_execution.rb
+++ b/app/controllers/concerns/hooks_execution.rb
@@ -3,11 +3,14 @@ module HooksExecution
private
- def set_hook_execution_notice(status, message)
- if status && status >= 200 && status < 400
- flash[:notice] = "Hook executed successfully: HTTP #{status}"
- elsif status
- flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
+ def set_hook_execution_notice(result)
+ http_status = result[:http_status]
+ message = result[:message]
+
+ if http_status && http_status >= 200 && http_status < 400
+ flash[:notice] = "Hook executed successfully: HTTP #{http_status}"
+ elsif http_status
+ flash[:alert] = "Hook executed successfully but returned HTTP #{http_status} #{message}"
else
flash[:alert] = "Hook execution failed: #{message}"
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 650ec1e326a..b43b2c5621f 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -1,6 +1,7 @@
module IssuableCollections
extend ActiveSupport::Concern
include SortingHelper
+ include Gitlab::IssuableMetadata
included do
helper_method :issues_finder
@@ -9,45 +10,12 @@ module IssuableCollections
private
- def issuable_meta_data(issuable_collection, collection_type)
- # map has to be used here since using pluck or select will
- # throw an error when ordering issuables by priority which inserts
- # a new order into the collection.
- # We cannot use reorder to not mess up the paginated collection.
- issuable_ids = issuable_collection.map(&:id)
-
- return {} if issuable_ids.empty?
-
- issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type)
- issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type)
- issuable_merge_requests_count =
- if collection_type == 'Issue'
- MergeRequestsClosingIssues.count_for_collection(issuable_ids)
- else
- []
- end
-
- issuable_ids.each_with_object({}) do |id, issuable_meta|
- downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
- upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
- notes = issuable_note_count.find { |notes| notes.noteable_id == id }
- merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
-
- issuable_meta[id] = Issuable::IssuableMeta.new(
- upvotes.try(:count).to_i,
- downvotes.try(:count).to_i,
- notes.try(:count).to_i,
- merge_requests.try(:last).to_i
- )
- end
- end
-
def issues_collection
issues_finder.execute.preload(:project, :author, :assignees, :labels, :milestone, project: :namespace)
end
def merge_requests_collection
- merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace)
+ merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits)
end
def issues_finder
@@ -64,10 +32,10 @@ module IssuableCollections
def filter_params
set_sort_order_from_cookie
- set_default_scope
set_default_state
- @filter_params = params.dup
+ # Skip irrelevant Rails routing params
+ @filter_params = params.dup.except(:controller, :action, :namespace_id)
@filter_params[:sort] ||= default_sort_order
@sort = @filter_params[:sort]
@@ -87,10 +55,6 @@ module IssuableCollections
@filter_params
end
- def set_default_scope
- params[:scope] = 'all' if params[:scope].blank?
- end
-
def set_default_state
params[:state] = 'opened' if params[:state].blank?
end
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 47d9ae350ae..c6b1e443de6 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -70,7 +70,7 @@ module MembershipActions
def members_page_url
if membershipable.is_a?(Project)
- project_settings_members_path(membershipable)
+ project_project_members_path(membershipable)
else
polymorphic_url([membershipable, :members])
end
diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb
index 1ff785ac2ca..081f3336780 100644
--- a/app/controllers/concerns/milestone_actions.rb
+++ b/app/controllers/concerns/milestone_actions.rb
@@ -45,7 +45,7 @@ module MilestoneActions
def milestone_redirect_path
if @project
- namespace_project_milestone_path(@project.namespace, @project, @milestone)
+ project_milestone_path(@project, @milestone)
elsif @group
group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
else
diff --git a/app/controllers/concerns/repository_settings_redirect.rb b/app/controllers/concerns/repository_settings_redirect.rb
index 0854c73a02f..0576f0e6e70 100644
--- a/app/controllers/concerns/repository_settings_redirect.rb
+++ b/app/controllers/concerns/repository_settings_redirect.rb
@@ -2,6 +2,6 @@ module RepositorySettingsRedirect
extend ActiveSupport::Concern
def redirect_to_repository_settings(project)
- redirect_to namespace_project_settings_repository_path(project.namespace, project)
+ redirect_to project_settings_repository_path(project)
end
end
diff --git a/app/controllers/concerns/requires_health_token.rb b/app/controllers/concerns/requires_health_token.rb
deleted file mode 100644
index 34ab1a97649..00000000000
--- a/app/controllers/concerns/requires_health_token.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module RequiresHealthToken
- extend ActiveSupport::Concern
- included do
- before_action :validate_health_check_access!
- end
-
- private
-
- def validate_health_check_access!
- render_404 unless token_valid?
- end
-
- def token_valid?
- token = params[:token].presence || request.headers['TOKEN']
- token.present? &&
- ActiveSupport::SecurityUtils.variable_size_secure_compare(
- token,
- current_application_settings.health_check_access_token
- )
- end
-
- def render_404
- render file: Rails.root.join('public', '404'), layout: false, status: '404'
- end
-end
diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
new file mode 100644
index 00000000000..ad2f4bbc486
--- /dev/null
+++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
@@ -0,0 +1,33 @@
+module RequiresWhitelistedMonitoringClient
+ extend ActiveSupport::Concern
+ included do
+ before_action :validate_ip_whitelisted_or_valid_token!
+ end
+
+ private
+
+ def validate_ip_whitelisted_or_valid_token!
+ render_404 unless client_ip_whitelisted? || valid_token?
+ end
+
+ def client_ip_whitelisted?
+ ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) }
+ end
+
+ def ip_whitelist
+ @ip_whitelist ||= Settings.monitoring.ip_whitelist.map(&IPAddr.method(:new))
+ end
+
+ def valid_token?
+ token = params[:token].presence || request.headers['TOKEN']
+ token.present? &&
+ ActiveSupport::SecurityUtils.variable_size_secure_compare(
+ token,
+ current_application_settings.health_check_access_token
+ )
+ end
+
+ def render_404
+ render file: Rails.root.join('public', '404'), layout: false, status: '404'
+ end
+end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index b68d76aeff0..ada0dde87fb 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -9,9 +9,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+ redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
- redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
end
end
@@ -25,7 +25,7 @@ module SpammableActions
def recaptcha_check_with_fallback(&fallback)
if spammable.valid?
- redirect_to spammable
+ redirect_to spammable_path
elsif render_recaptcha?
ensure_spam_config_loaded!
@@ -56,6 +56,10 @@ module SpammableActions
raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
+ def spammable_path
+ raise NotImplementedError, "#{self.class} does not implement #{__method__}"
+ end
+
def authorize_submit_spammable!
access_denied! unless current_user.admin?
end
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
new file mode 100644
index 00000000000..ed253042701
--- /dev/null
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -0,0 +1,17 @@
+module WithPerformanceBar
+ extend ActiveSupport::Concern
+
+ included do
+ include Peek::Rblineprof::CustomControllerHelpers
+ end
+
+ def peek_enabled?
+ return false unless Gitlab::PerformanceBar.enabled?(current_user)
+
+ if RequestStore.active?
+ RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? }
+ else
+ cookies[:perf_bar_enabled].present?
+ end
+ end
+end
diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb
index dd1d46a68c7..9dcb3a0eb6d 100644
--- a/app/controllers/dashboard/labels_controller.rb
+++ b/app/controllers/dashboard/labels_controller.rb
@@ -1,9 +1,14 @@
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
- labels = LabelsFinder.new(current_user).execute
-
respond_to do |format|
format.json { render json: LabelSerializer.new.represent_appearance(labels) }
end
end
+
+ def labels
+ finder_params = { project_ids: projects.select(:id) }
+ labels = LabelsFinder.new(current_user, finder_params).execute
+
+ GlobalLabel.build_collection(labels)
+ end
end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 28c90548cc1..59e5b5e4775 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,6 +1,7 @@
class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
+ before_action :authorize_read_project!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
def index
@@ -49,6 +50,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController
private
+ def authorize_read_project!
+ project_id = params[:project_id]
+
+ if project_id.present?
+ project = Project.find(project_id)
+ render_404 unless can?(current_user, :read_project, project)
+ end
+ end
+
def find_todos
@todos ||= TodosFinder.new(current_user, params).execute
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 6b1d418fc9a..5c10d7bc261 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -2,13 +2,13 @@ class Groups::MilestonesController < Groups::ApplicationController
include MilestoneActions
before_action :group_projects
- before_action :milestone, only: [:show, :update, :merge_requests, :participants, :labels]
- before_action :authorize_admin_milestones!, only: [:new, :create, :update]
+ before_action :milestone, only: [:edit, :show, :update, :merge_requests, :participants, :labels]
+ before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update]
def index
respond_to do |format|
format.html do
- @milestone_states = GlobalMilestone.states_count(@projects)
+ @milestone_states = GlobalMilestone.states_count(group_projects, group)
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
@@ -22,49 +22,41 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def create
- project_ids = params[:milestone][:project_ids].reject(&:blank?)
- title = milestone_params[:title]
+ @milestone = Milestones::CreateService.new(group, current_user, milestone_params).execute
- if create_milestones(project_ids)
- redirect_to milestone_path(title)
+ if @milestone.persisted?
+ redirect_to milestone_path
else
- render_new_with_error(project_ids.empty?)
+ render "new"
end
end
def show
end
- def update
- @milestone.milestones.each do |milestone|
- Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone)
- end
-
- redirect_back_or_default(default: milestone_path(@milestone.title))
+ def edit
+ render_404 if @milestone.is_legacy_group_milestone?
end
- private
-
- def create_milestones(project_ids)
- return false unless project_ids.present?
+ def update
+ # Keep this compatible with legacy group milestones where we have to update
+ # all projects milestones states at once.
+ if @milestone.is_legacy_group_milestone?
+ update_params = milestone_params.select { |key| key == "state_event" }
+ milestones = @milestone.milestones
+ else
+ update_params = milestone_params
+ milestones = [@milestone]
+ end
- ActiveRecord::Base.transaction do
- @projects.where(id: project_ids).each do |project|
- Milestones::CreateService.new(project, current_user, milestone_params).execute
- end
+ milestones.each do |milestone|
+ Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
end
- true
- rescue ActiveRecord::ActiveRecordError => e
- flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}"
- false
+ redirect_to milestone_path
end
- def render_new_with_error(empty_project_ids)
- @milestone = Milestone.new(milestone_params)
- @milestone.errors.add(:base, "Please select at least one project.") if empty_project_ids
- render :new
- end
+ private
def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
@@ -74,16 +66,31 @@ class Groups::MilestonesController < Groups::ApplicationController
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
- def milestone_path(title)
- group_milestone_path(@group, title.to_slug.to_s, title: title)
+ def milestone_path
+ if @milestone.is_legacy_group_milestone?
+ group_milestone_path(group, @milestone.safe_title, title: @milestone.title)
+ else
+ group_milestone_path(group, @milestone.iid)
+ end
end
def milestones
- @milestones = GroupMilestone.build_collection(@group, @projects, params)
+ search_params = params.merge(group_ids: group.id)
+
+ milestones = MilestonesFinder.new(search_params).execute
+ legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
+
+ milestones + legacy_milestones
end
def milestone
- @milestone = GroupMilestone.build(@group, @projects, params[:title])
+ @milestone =
+ if params[:title]
+ GroupMilestone.build(group, group_projects, params[:title])
+ else
+ group.milestones.find_by_iid(params[:id])
+ end
+
render_404 unless @milestone
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
new file mode 100644
index 00000000000..0142ad8278c
--- /dev/null
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -0,0 +1,24 @@
+module Groups
+ module Settings
+ class CiCdController < Groups::ApplicationController
+ before_action :authorize_admin_pipeline!
+
+ def show
+ define_secret_variables
+ end
+
+ private
+
+ def define_secret_variables
+ @variable = Ci::GroupVariable.new(group: group)
+ .present(current_user: current_user)
+ @variables = group.variables.order_key_asc
+ .map { |variable| variable.present(current_user: current_user) }
+ end
+
+ def authorize_admin_pipeline!
+ return render_404 unless can?(current_user, :admin_pipeline, group)
+ end
+ end
+ end
+end
diff --git a/app/controllers/groups/variables_controller.rb b/app/controllers/groups/variables_controller.rb
new file mode 100644
index 00000000000..10038ff3ad9
--- /dev/null
+++ b/app/controllers/groups/variables_controller.rb
@@ -0,0 +1,64 @@
+module Groups
+ class VariablesController < Groups::ApplicationController
+ before_action :variable, only: [:show, :update, :destroy]
+ before_action :authorize_admin_build!
+
+ def index
+ redirect_to group_settings_ci_cd_path(group)
+ end
+
+ def show
+ end
+
+ def update
+ if variable.update(variable_params)
+ redirect_to group_variables_path(group),
+ notice: 'Variable was successfully updated.'
+ else
+ render "show"
+ end
+ end
+
+ def create
+ @variable = group.variables.create(variable_params)
+ .present(current_user: current_user)
+
+ if @variable.persisted?
+ redirect_to group_settings_ci_cd_path(group),
+ notice: 'Variable was successfully created.'
+ else
+ render "show"
+ end
+ end
+
+ def destroy
+ if variable.destroy
+ redirect_to group_settings_ci_cd_path(group),
+ status: 302,
+ notice: 'Variable was successfully removed.'
+ else
+ redirect_to group_settings_ci_cd_path(group),
+ status: 302,
+ notice: 'Failed to remove the variable.'
+ end
+ end
+
+ private
+
+ def variable_params
+ params.require(:variable).permit(*variable_params_attributes)
+ end
+
+ def variable_params_attributes
+ %i[key value protected]
+ end
+
+ def variable
+ @variable ||= group.variables.find(params[:id]).present(current_user: current_user)
+ end
+
+ def authorize_admin_build!
+ return render_404 unless can?(current_user, :admin_build, group)
+ end
+ end
+end
diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
index 5d3109b7187..c3d18991fd4 100644
--- a/app/controllers/health_check_controller.rb
+++ b/app/controllers/health_check_controller.rb
@@ -1,3 +1,3 @@
class HealthCheckController < HealthCheck::HealthCheckController
- include RequiresHealthToken
+ include RequiresWhitelistedMonitoringClient
end
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index abc832e6ddc..98c2aaa3526 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,10 +1,13 @@
class HealthController < ActionController::Base
protect_from_forgery with: :exception
- include RequiresHealthToken
+ include RequiresWhitelistedMonitoringClient
CHECKS = [
Gitlab::HealthChecks::DbCheck,
- Gitlab::HealthChecks::RedisCheck,
+ Gitlab::HealthChecks::Redis::RedisCheck,
+ Gitlab::HealthChecks::Redis::CacheCheck,
+ Gitlab::HealthChecks::Redis::QueuesCheck,
+ Gitlab::HealthChecks::Redis::SharedStateCheck,
Gitlab::HealthChecks::FsShardsCheck
].freeze
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 7625187c7be..0982a61902b 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -63,7 +63,7 @@ class InvitesController < ApplicationController
when Project
project = member.source
label = "project #{project.name_with_namespace}"
- path = namespace_project_path(project.namespace, project)
+ path = project_path(project)
when Group
group = member.source
label = "group #{group.name}"
diff --git a/app/controllers/metrics_controller.rb b/app/controllers/metrics_controller.rb
index 0e9a19c0b6f..37587a52eaf 100644
--- a/app/controllers/metrics_controller.rb
+++ b/app/controllers/metrics_controller.rb
@@ -1,12 +1,12 @@
class MetricsController < ActionController::Base
- include RequiresHealthToken
+ include RequiresWhitelistedMonitoringClient
protect_from_forgery with: :exception
before_action :validate_prometheus_metrics
def index
- render text: metrics_service.metrics_text, content_type: 'text/plain; verssion=0.0.4'
+ render text: metrics_service.metrics_text, content_type: 'text/plain; version=0.0.4'
end
private
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index b82681b197e..323d5d26eb6 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,5 +1,6 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
+ include Devise::Controllers::Rememberable
protect_from_forgery except: [:kerberos, :saml, :cas3]
@@ -115,8 +116,10 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
if @user.two_factor_enabled?
+ params[:remember_me] = '1' if remember_me?
prompt_for_two_factor(@user)
else
+ remember_me(@user) if remember_me?
sign_in_and_redirect(@user)
end
else
@@ -147,4 +150,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
AuditEventService.new(user, user, options)
.for_authentication.security_event
end
+
+ def remember_me?
+ request_params = request.env['omniauth.params']
+ (request_params['remember_me'] == '1') if request_params.present?
+ end
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index a8575e037e4..aa8cf630032 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -1,6 +1,8 @@
class PasswordsController < Devise::PasswordsController
+ include Gitlab::CurrentSettings
+
before_action :resource_from_email, only: [:create]
- before_action :prevent_ldap_reset, only: [:create]
+ before_action :check_password_authentication_available, only: [:create]
before_action :throttle_reset, only: [:create]
def edit
@@ -25,7 +27,7 @@ class PasswordsController < Devise::PasswordsController
def update
super do |resource|
- if resource.valid? && resource.require_password?
+ if resource.valid? && resource.require_password_creation?
resource.update_attribute(:password_automatically_set, false)
end
end
@@ -38,11 +40,11 @@ class PasswordsController < Devise::PasswordsController
self.resource = resource_class.find_by_email(email)
end
- def prevent_ldap_reset
- return unless resource && resource.ldap_user?
+ def check_password_authentication_available
+ return if current_application_settings.password_authentication_enabled? && (resource.nil? || resource.allow_password_authentication?)
redirect_to after_sending_reset_password_instructions_path_for(resource_name),
- alert: "Cannot reset password for LDAP user."
+ alert: "Password authentication is unavailable."
end
def throttle_reset
diff --git a/app/controllers/profiles/gpg_keys_controller.rb b/app/controllers/profiles/gpg_keys_controller.rb
new file mode 100644
index 00000000000..6779cc6ddac
--- /dev/null
+++ b/app/controllers/profiles/gpg_keys_controller.rb
@@ -0,0 +1,47 @@
+class Profiles::GpgKeysController < Profiles::ApplicationController
+ before_action :set_gpg_key, only: [:destroy, :revoke]
+
+ def index
+ @gpg_keys = current_user.gpg_keys
+ @gpg_key = GpgKey.new
+ end
+
+ def create
+ @gpg_key = current_user.gpg_keys.new(gpg_key_params)
+
+ if @gpg_key.save
+ redirect_to profile_gpg_keys_path
+ else
+ @gpg_keys = current_user.gpg_keys.select(&:persisted?)
+ render :index
+ end
+ end
+
+ def destroy
+ @gpg_key.destroy
+
+ respond_to do |format|
+ format.html { redirect_to profile_gpg_keys_url, status: 302 }
+ format.js { head :ok }
+ end
+ end
+
+ def revoke
+ @gpg_key.revoke
+
+ respond_to do |format|
+ format.html { redirect_to profile_gpg_keys_url, status: 302 }
+ format.js { head :ok }
+ end
+ end
+
+ private
+
+ def gpg_key_params
+ params.require(:gpg_key).permit(:key)
+ end
+
+ def set_gpg_key
+ @gpg_key = current_user.gpg_keys.find(params[:id])
+ end
+end
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 10145bae0d3..c423761ab24 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -77,7 +77,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
end
def authorize_change_password!
- return render_404 if @user.ldap_user?
+ render_404 unless @user.allow_password_authentication?
end
def user_params
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 3d7ce4f0222..221e01b415a 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -22,6 +22,7 @@ class Projects::ApplicationController < ApplicationController
def project
return @project if @project
+ return nil unless params[:project_id] || params[:id]
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
@@ -76,13 +77,13 @@ class Projects::ApplicationController < ApplicationController
def require_non_empty_project
# Be sure to return status code 303 to avoid a double DELETE:
# http://api.rubyonrails.org/classes/ActionController/Redirecting.html
- redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
+ redirect_to project_path(@project), status: 303 if @project.empty_repo?
end
def require_branch_head
unless @repository.branch_exists?(@ref)
redirect_to(
- namespace_project_tree_path(@project.namespace, @project, @ref),
+ project_tree_path(@project, @ref),
notice: "This action is not allowed unless you are on a branch"
)
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index ea036b1f705..f637a9a803b 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -46,7 +46,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def keep
build.keep_artifacts!
- redirect_to namespace_project_job_path(project.namespace, project, build)
+ redirect_to project_job_path(project, build)
end
def latest_succeeded
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 6c25cd83a24..06ba73d8e8d 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -3,11 +3,11 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :authorize_admin_project!, only: [:index]
before_action :no_cache_headers, except: [:index]
- def build
- build_status = Gitlab::Badge::Build::Status
+ def pipeline
+ pipeline_status = Gitlab::Badge::Pipeline::Status
.new(project, params[:ref])
- render_badge build_status
+ render_badge pipeline_status
end
def coverage
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index a82d6fd5a4a..49ea2945675 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -27,9 +27,9 @@ class Projects::BlobController < Projects::ApplicationController
def create
create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
- success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
+ success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
- failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
+ failure_path: project_new_blob_path(@project, @ref))
end
def show
@@ -63,7 +63,7 @@ class Projects::BlobController < Projects::ApplicationController
@path = params[:file_path] if params[:file_path].present?
create_commit(Files::UpdateService, success_path: -> { after_edit_path },
failure_view: :edit,
- failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+ failure_path: project_blob_path(@project, @id))
rescue Files::UpdateService::FileChangedError
@conflict = true
@@ -83,9 +83,9 @@ class Projects::BlobController < Projects::ApplicationController
def destroy
create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
- success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
+ success_path: -> { project_tree_path(@project, @branch_name) },
failure_view: :show,
- failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+ failure_path: project_blob_path(@project, @id))
end
def diff
@@ -118,7 +118,7 @@ class Projects::BlobController < Projects::ApplicationController
else
if tree = @repository.tree(@commit.id, @path)
if tree.entries.any?
- return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path))
+ return redirect_to project_tree_path(@project, File.join(@ref, @path))
end
end
@@ -143,10 +143,10 @@ class Projects::BlobController < Projects::ApplicationController
def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @branch_name == @ref
- diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
+ diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}"
else
- namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
+ project_blob_path(@project, File.join(@branch_name, @path))
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 94a752c21eb..86058531179 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -52,7 +52,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name)
else
- redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name)
+ redirect_to project_tree_path(@project, branch_name)
end
else
@error = result[:message]
@@ -62,7 +62,7 @@ class Projects::BranchesController < Projects::ApplicationController
format.json do
if result[:status] == :success
- render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) }
+ render json: { name: branch_name, url: project_tree_url(@project, branch_name) }
else
render json: result[:messsage], status: :unprocessable_entity
end
@@ -79,7 +79,7 @@ class Projects::BranchesController < Projects::ApplicationController
flash_type = result[:status] == :error ? :alert : :notice
flash[flash_type] = result[:message]
- redirect_to namespace_project_branches_path(@project.namespace, @project), status: 303
+ redirect_to project_branches_path(@project), status: 303
end
format.js { render nothing: true, status: result[:return_code] }
@@ -90,7 +90,7 @@ class Projects::BranchesController < Projects::ApplicationController
def destroy_all_merged
DeleteMergedBranchesService.new(@project, current_user).async_execute
- redirect_to namespace_project_branches_path(@project.namespace, @project),
+ redirect_to project_branches_path(@project),
notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
end
@@ -106,8 +106,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def url_to_autodeploy_setup(project, branch_name)
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
branch_name,
file_name: '.gitlab-ci.yml',
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index f34a198634e..b45e5d7ff43 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -7,23 +7,23 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
before_action :validate_artifacts!
def download
- redirect_to download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ redirect_to download_project_job_artifacts_path(project, job)
end
def browse
- redirect_to browse_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to browse_project_job_artifacts_path(project, job, path: params[:path])
end
def file
- redirect_to file_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to file_project_job_artifacts_path(project, job, path: params[:path])
end
def raw
- redirect_to raw_namespace_project_job_artifacts_path(project.namespace, project, job, path: params[:path])
+ redirect_to raw_project_job_artifacts_path(project, job, path: params[:path])
end
def latest_succeeded
- redirect_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
+ redirect_to latest_succeeded_project_artifacts_path(project, job, ref_name_and_path: params[:ref_name_and_path], job: params[:job])
end
private
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 1334a231788..230b072dcea 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -2,15 +2,15 @@ class Projects::BuildsController < Projects::ApplicationController
before_action :authorize_read_build!
def index
- redirect_to namespace_project_jobs_path(project.namespace, project)
+ redirect_to project_jobs_path(project)
end
def show
- redirect_to namespace_project_job_path(project.namespace, project, job)
+ redirect_to project_job_path(project, job)
end
def raw
- redirect_to raw_namespace_project_job_path(project.namespace, project, job)
+ redirect_to raw_project_job_path(project, job)
end
private
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 7c3cce1c241..6de125e7e80 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -38,9 +38,14 @@ class Projects::CommitController < Projects::ApplicationController
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
- render json: PipelineSerializer
- .new(project: @project, current_user: @current_user)
- .represent(@pipelines)
+ render json: {
+ pipelines: PipelineSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(@pipelines),
+ count: {
+ all: @pipelines.count
+ }
+ }
end
end
end
@@ -80,16 +85,16 @@ class Projects::CommitController < Projects::ApplicationController
end
def successful_change_path
- referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
+ referenced_merge_request_url || project_commits_url(@project, @branch_name)
end
def failed_change_path
- referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id])
+ referenced_merge_request_url || project_commit_url(@project, params[:id])
end
def referenced_merge_request_url
if merge_request = @commit.merged_merge_request(current_user)
- namespace_project_merge_request_url(merge_request.target_project.namespace, merge_request.target_project, merge_request)
+ project_merge_request_url(merge_request.target_project, merge_request)
end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 37b5a6e9d48..2de9900d449 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -6,18 +6,9 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
+ before_action :set_commits
def show
- @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
- search = params[:search]
-
- @commits =
- if search.present?
- @repository.find_commits_by_message(search, @ref, @path, @limit, @offset)
- else
- @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
- end
-
@note_counts = project.notes.where(commit_id: @commits.map(&:id))
.group(:commit_id).count
@@ -37,4 +28,33 @@ class Projects::CommitsController < Projects::ApplicationController
end
end
end
+
+ def signatures
+ respond_to do |format|
+ format.json do
+ render json: {
+ signatures: @commits.select(&:has_signature?).map do |commit|
+ {
+ commit_sha: commit.sha,
+ html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
+ }
+ end
+ }
+ end
+ end
+ end
+
+ private
+
+ def set_commits
+ @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
+ search = params[:search]
+
+ @commits =
+ if search.present?
+ @repository.find_commits_by_message(search, @ref, @path, @limit, @offset)
+ else
+ @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
+ end
+ end
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index ef400c4d745..c8613c0d634 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -31,9 +31,9 @@ class Projects::CompareController < Projects::ApplicationController
from: params[:from].presence,
to: params[:to].presence
}
- redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars)
+ redirect_to project_compare_index_path(@project, from_to_vars)
else
- redirect_to namespace_project_compare_path(@project.namespace, @project,
+ redirect_to project_compare_path(@project,
params[:from], params[:to])
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index f88a1ffd1e9..29e223a5273 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -15,6 +15,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
+ Gitlab::PollingInterval.set_header(response, interval: 3_000)
+
render json: {
environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user)
@@ -63,7 +65,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment = project.environments.create(environment_params)
if @environment.persisted?
- redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ redirect_to project_environment_path(project, @environment)
else
render :new
end
@@ -71,7 +73,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def update
if @environment.update(environment_params)
- redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ redirect_to project_environment_path(project, @environment)
else
render :edit
end
@@ -86,7 +88,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
if stop_action
polymorphic_url([project.namespace.becomes(Namespace), project, stop_action])
else
- namespace_project_environment_url(project.namespace, project, @environment)
+ project_environment_url(project, @environment)
end
respond_to do |format|
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 1eb3800e49d..3f83bef2c79 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -44,12 +44,12 @@ class Projects::ForksController < Projects::ApplicationController
if @forked_project.saved? && @forked_project.forked?
if @forked_project.import_in_progress?
- redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
+ redirect_to project_import_path(@forked_project, continue: continue_params)
else
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
+ redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked."
end
end
else
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index df5221fe95f..57372f9e79d 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -29,7 +29,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def ci
- redirect_to charts_namespace_project_pipelines_path(@project.namespace, @project)
+ redirect_to charts_project_pipelines_path(@project)
end
private
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index deb33a2f0ff..f59200d3b1f 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -22,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = 'Please select a group.'
end
- redirect_to namespace_project_settings_members_path(project.namespace, project)
+ redirect_to project_project_members_path(project)
end
def update
@@ -36,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_settings_members_path(project.namespace, project), status: 302
+ redirect_to project_project_members_path(project), status: 302
end
format.js { head :ok }
end
diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb
index 354f0d6db3a..745e89fc843 100644
--- a/app/controllers/projects/hook_logs_controller.rb
+++ b/app/controllers/projects/hook_logs_controller.rb
@@ -14,11 +14,11 @@ class Projects::HookLogsController < Projects::ApplicationController
end
def retry
- status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+ result = hook.execute(hook_log.request_data, hook_log.trigger)
- set_hook_execution_notice(status, message)
+ set_hook_execution_notice(result)
- redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
+ redirect_to edit_project_hook_path(@project, @hook)
end
private
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index f5143280154..85d35900c71 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -9,6 +9,10 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings"
+ def index
+ redirect_to project_settings_integrations_path(@project)
+ end
+
def create
@hook = @project.hooks.new(hook_params)
@hook.save
@@ -17,7 +21,7 @@ class Projects::HooksController < Projects::ApplicationController
@hooks = @project.hooks.select(&:persisted?)
flash[:alert] = @hook.errors.full_messages.join.html_safe
end
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+ redirect_to project_settings_integrations_path(@project)
end
def edit
@@ -26,20 +30,16 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update_attributes(hook_params)
flash[:notice] = 'Hook was successfully updated.'
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
+ redirect_to project_settings_integrations_path(@project)
else
render 'edit'
end
end
def test
- if !@project.empty_repo?
- status, message = TestHookService.new.execute(hook, current_user)
+ result = TestHooks::ProjectService.new(hook, current_user, params[:trigger]).execute
- set_hook_execution_notice(status, message)
- else
- flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
- end
+ set_hook_execution_notice(result)
redirect_back_or_default(default: { action: 'index' })
end
@@ -47,7 +47,7 @@ class Projects::HooksController < Projects::ApplicationController
def destroy
hook.destroy
- redirect_to namespace_project_settings_integrations_path(@project.namespace, @project), status: 302
+ redirect_to project_settings_integrations_path(@project), status: 302
end
private
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 4b143434ea5..49aa32119ef 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -17,7 +17,7 @@ class Projects::ImportsController < Projects::ApplicationController
@project.reload.import_schedule
end
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
end
def show
@@ -25,10 +25,10 @@ class Projects::ImportsController < Projects::ApplicationController
if continue_params
redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to namespace_project_path(@project.namespace, @project), notice: finished_notice
+ redirect_to project_path(@project), notice: finished_notice
end
elsif @project.import_failed?
- redirect_to new_namespace_project_import_path(@project.namespace, @project)
+ redirect_to new_project_import_path(@project)
else
if continue_params && continue_params[:notice_now]
flash.now[:notice] = continue_params[:notice_now]
@@ -50,19 +50,19 @@ class Projects::ImportsController < Projects::ApplicationController
def require_no_repo
if @project.repository_exists?
- redirect_to namespace_project_path(@project.namespace, @project)
+ redirect_to project_path(@project)
end
end
def redirect_if_progress
if @project.import_in_progress?
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
end
end
def redirect_if_no_import
if @project.repository_exists? && @project.no_import?
- redirect_to namespace_project_path(@project.namespace, @project)
+ redirect_to project_path(@project)
end
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index ca483c105b6..e2ccabb22db 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -8,7 +8,6 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new]
- before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
@@ -238,20 +237,24 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :awardable, :issue
alias_method :spammable, :issue
+ def spammable_path
+ project_issue_path(@project, @issue)
+ end
+
def authorize_update_issue!
- return render_404 unless can?(current_user, :update_issue, @issue)
+ render_404 unless can?(current_user, :update_issue, @issue)
end
def authorize_admin_issues!
- return render_404 unless can?(current_user, :admin_issue, @project)
+ render_404 unless can?(current_user, :admin_issue, @project)
end
def authorize_create_merge_request!
- return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
+ render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
def check_issues_available!
- return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
+ return render_404 unless @project.feature_available?(:issues, current_user)
end
def redirect_to_external_issue_tracker
@@ -262,7 +265,7 @@ class Projects::IssuesController < Projects::ApplicationController
if action_name == 'new'
redirect_to external.new_issue_path
else
- redirect_to external.project_path
+ redirect_to external.issue_tracker_path
end
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index cb4f46388fd..96abdac91b6 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -38,7 +38,7 @@ class Projects::JobsController < Projects::ApplicationController
build.cancel if can?(current_user, :update_build, build)
end
- redirect_to namespace_project_jobs_path(project.namespace, project)
+ redirect_to project_jobs_path(project)
end
def show
@@ -108,7 +108,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
- redirect_to namespace_project_job_path(project.namespace, project, @build),
+ redirect_to project_job_path(project, @build),
notice: "Build has been successfully erased!"
else
respond_422
@@ -137,6 +137,6 @@ class Projects::JobsController < Projects::ApplicationController
end
def build_path(build)
- namespace_project_job_path(build.project.namespace, build.project, build)
+ project_job_path(build.project, build)
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index daa973c9281..480a2dff262 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -33,7 +33,7 @@ class Projects::LabelsController < Projects::ApplicationController
if @label.valid?
respond_to do |format|
- format.html { redirect_to namespace_project_labels_path(@project.namespace, @project) }
+ format.html { redirect_to project_labels_path(@project) }
format.json { render json: @label }
end
else
@@ -51,7 +51,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
- redirect_to namespace_project_labels_path(@project.namespace, @project)
+ redirect_to project_labels_path(@project)
else
render :edit
end
@@ -61,12 +61,11 @@ class Projects::LabelsController < Projects::ApplicationController
Gitlab::IssuesLabels.generate(@project)
if params[:redirect] == 'issues'
- redirect_to namespace_project_issues_path(@project.namespace, @project)
+ redirect_to project_issues_path(@project)
elsif params[:redirect] == 'merge_requests'
- redirect_to namespace_project_merge_requests_path(@project.namespace,
- @project)
+ redirect_to project_merge_requests_path(@project)
else
- redirect_to namespace_project_labels_path(@project.namespace, @project)
+ redirect_to project_labels_path(@project)
end
end
@@ -74,7 +73,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
@labels = find_labels
- redirect_to namespace_project_labels_path(@project.namespace, @project),
+ redirect_to project_labels_path(@project),
status: 302,
notice: 'Label was removed'
end
@@ -114,7 +113,7 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404 unless promote_service.execute(@label)
respond_to do |format|
format.html do
- redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ redirect_to(project_labels_path(@project),
notice: 'Label was promoted to a Group Label')
end
format.js
@@ -125,7 +124,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ redirect_to(project_labels_path(@project),
notice: 'Failed to promote label due to internal error. Please contact administrators.')
end
format.js
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 38f7e6eb5e9..0f6add3e287 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -16,12 +16,10 @@ class Projects::MattermostsController < Projects::ApplicationController
if result
flash[:notice] = 'This service is now configured'
- redirect_to edit_namespace_project_service_path(
- @project.namespace, @project, service)
+ redirect_to edit_project_service_path(@project, service)
else
flash[:alert] = message || 'Failed to configure service'
- redirect_to new_namespace_project_mattermost_path(
- @project.namespace, @project)
+ redirect_to new_project_mattermost_path(@project)
end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 5de0f828010..6602b204fcb 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -17,8 +17,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
end
def merge_request_params
- params.require(:merge_request)
- .permit(merge_request_params_attributes)
+ params.require(:merge_request).permit(merge_request_params_attributes)
end
def merge_request_params_attributes
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index a71f23e790d..28afef101a9 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
- render json: { redirect_to: namespace_project_merge_request_url(@project.namespace, @project, @merge_request, resolved_conflicts: true) }
+ render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Conflict::ResolutionError => e
render status: :bad_request, json: { message: e.message }
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index da058da795e..f35d53896ba 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -107,7 +107,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project
@source_project = @merge_request.source_project
- @commits = @merge_request.compare_commits.reverse
+ @commits = @merge_request.commits
@commit = @merge_request.diff_head_commit
@note_counts = Note.where(commit_id: @commits.map(&:id))
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 04f8e95aa09..d361e661d0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -112,9 +112,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
Gitlab::PollingInterval.set_header(response, interval: 10_000)
- render json: PipelineSerializer
- .new(project: @project, current_user: @current_user)
- .represent(@pipelines)
+ render json: {
+ pipelines: PipelineSerializer
+ .new(project: @project, current_user: @current_user)
+ .represent(@pipelines),
+ count: {
+ all: @pipelines.count
+ }
+ }
end
def edit
@@ -211,22 +216,25 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
stop_url =
if environment.stop_action? && can?(current_user, :create_deployment, environment)
- stop_namespace_project_environment_path(project.namespace, project, environment)
+ stop_project_environment_path(project, environment)
end
metrics_url =
if can?(current_user, :read_environment, environment) && environment.has_metrics?
- metrics_namespace_project_environment_deployment_path(environment.project.namespace,
- environment.project,
- environment,
- deployment)
+ metrics_project_environment_deployment_path(environment.project, environment, deployment)
+ end
+
+ metrics_monitoring_url =
+ if can?(current_user, :read_environment, environment)
+ environment_metrics_path(environment)
end
{
id: environment.id,
name: environment.name,
- url: namespace_project_environment_path(project.namespace, project, environment),
+ url: project_environment_path(project, environment),
metrics_url: metrics_url,
+ metrics_monitoring_url: metrics_monitoring_url,
stop_url: stop_url,
external_url: environment.external_url,
external_url_formatted: environment.formatted_external_url,
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 953b1e83e49..c94384d2a1a 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -13,20 +13,16 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to :html
def index
- @milestones =
- case params[:state]
- when 'all' then @project.milestones
- when 'closed' then @project.milestones.closed
- else @project.milestones.active
- end
-
@sort = params[:sort] || 'due_date_asc'
- @milestones = @milestones.sort(@sort)
+ @milestones = milestones.sort(@sort)
respond_to do |format|
format.html do
@project_namespace = @project.namespace.becomes(Namespace)
- @milestones = @milestones.includes(:project)
+ # We need to show group milestones in the JSON response
+ # so that people can filter by and assign group milestones,
+ # but we don't need to show them on the project milestones page itself.
+ @milestones = @milestones.for_projects
@milestones = @milestones.page(params[:page])
end
format.json do
@@ -45,14 +41,14 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def show
+ @project_namespace = @project.namespace.becomes(Namespace)
end
def create
@milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute
- if @milestone.save
- redirect_to namespace_project_milestone_path(@project.namespace,
- @project, @milestone)
+ if @milestone.valid?
+ redirect_to project_milestone_path(@project, @milestone)
else
render "new"
end
@@ -65,8 +61,7 @@ class Projects::MilestonesController < Projects::ApplicationController
format.js
format.html do
if @milestone.valid?
- redirect_to namespace_project_milestone_path(@project.namespace,
- @project, @milestone)
+ redirect_to project_milestone_path(@project, @milestone)
else
render :edit
end
@@ -87,6 +82,18 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
+ def milestones
+ @milestones ||= begin
+ if @project.group && can?(current_user, :read_group, @project.group)
+ group = @project.group
+ end
+
+ search_params = params.merge(project_ids: @project.id, group_ids: group&.id)
+
+ MilestonesFinder.new(search_params).execute
+ end
+ end
+
def milestone
@milestone ||= @project.milestones.find_by!(iid: params[:id])
end
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 33a152ad34f..dfa5e4f7f46 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -8,8 +8,8 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit
def show
- @url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
- @commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
+ @url = project_network_path(@project, @ref, @options.merge(format: :json))
+ @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html do
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 28b383e69eb..d421b1a8eb5 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -15,7 +15,7 @@ class Projects::PagesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_pages_path(@project.namespace, @project),
+ redirect_to project_pages_path(@project),
status: 302,
notice: 'Pages were removed'
end
diff --git a/app/controllers/projects/pages_domains_controller.rb b/app/controllers/projects/pages_domains_controller.rb
index dbd011f6c5d..15e77d854dc 100644
--- a/app/controllers/projects/pages_domains_controller.rb
+++ b/app/controllers/projects/pages_domains_controller.rb
@@ -16,7 +16,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
@domain = @project.pages_domains.create(pages_domain_params)
if @domain.valid?
- redirect_to namespace_project_pages_path(@project.namespace, @project)
+ redirect_to project_pages_path(@project)
else
render 'new'
end
@@ -27,7 +27,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_pages_path(@project.namespace, @project),
+ redirect_to project_pages_path(@project),
status: 302,
notice: 'Domain was removed'
end
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index 60db179277b..ec7c645df5a 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -1,11 +1,11 @@
class Projects::PipelineSchedulesController < Projects::ApplicationController
+ before_action :schedule, except: [:index, :new, :create]
+
before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
- before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update]
+ before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
- before_action :schedule, only: [:edit, :update, :destroy, :take_ownership]
-
def index
@scope = params[:scope]
@all_schedules = PipelineSchedulesFinder.new(@project).execute
@@ -34,7 +34,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def update
if schedule.update(schedule_params)
- redirect_to namespace_project_pipeline_schedules_path(@project.namespace.becomes(Namespace), @project)
+ redirect_to project_pipeline_schedules_path(@project)
else
render :edit
end
@@ -53,7 +53,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
redirect_to pipeline_schedules_path(@project), status: 302
else
redirect_to pipeline_schedules_path(@project),
- status: 302,
+ status: :forbidden,
alert: _("Failed to remove the pipeline schedule")
end
end
@@ -66,6 +66,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
def schedule_params
params.require(:schedule)
- .permit(:description, :cron, :cron_timezone, :ref, :active)
+ .permit(:description, :cron, :cron_timezone, :ref, :active,
+ variables_attributes: [:id, :key, :value, :_destroy] )
+ end
+
+ def authorize_update_pipeline_schedule!
+ return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
+ end
+
+ def authorize_admin_pipeline_schedule!
+ return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 303e91a8dc0..a3bfbf0694e 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -60,7 +60,7 @@ class Projects::PipelinesController < Projects::ApplicationController
.execute(:web, ignore_skip_ci: true, save_on_errors: false)
if @pipeline.persisted?
- redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
+ redirect_to project_pipeline_path(project, @pipeline)
else
render 'new'
end
@@ -111,7 +111,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }
@@ -123,7 +123,7 @@ class Projects::PipelinesController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
+ redirect_back_or_default default: project_pipelines_path(project)
end
format.json { head :no_content }
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 38a47651000..9d24ebe2138 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -2,13 +2,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project, params: params)
+ redirect_to project_settings_ci_cd_path(@project, params: params)
end
def update
if @project.update_attributes(update_params)
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params
params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
- :public_builds, :auto_cancel_pending_pipelines
+ :public_builds, :auto_cancel_pending_pipelines, :ci_config_path
)
end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d2d26738582..f8ff7413b53 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -6,8 +6,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
def index
- sort = params[:sort].presence || sort_value_name
- redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort)
+ @sort = params[:sort].presence || sort_value_name
+ @group_links = @project.project_group_links
+
+ @skip_groups = @group_links.pluck(:group_id)
+ @skip_groups << @project.namespace_id unless @project.personal?
+ @skip_groups += @project.group.ancestors.pluck(:id) if @project.group
+
+ @project_members = MembersFinder.new(@project, current_user).execute
+
+ if params[:search].present?
+ @project_members = @project_members.joins(:user).merge(User.search(params[:search]))
+ @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
+ end
+
+ @project_members = @project_members.sort(@sort).page(params[:page])
+ @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+ @project_member = @project.project_members.new
end
def update
@@ -19,7 +34,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def resend_invite
- redirect_path = namespace_project_settings_members_path(@project.namespace, @project)
+ redirect_path = project_project_members_path(@project)
@project_member = @project.project_members.find(params[:id])
@@ -42,7 +57,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
return render_404
end
- redirect_to(namespace_project_settings_members_path(project.namespace, project),
+ redirect_to(project_project_members_path(project),
notice: notice)
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 2a0b58fae7c..1eb78d8b522 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -13,21 +13,21 @@ class Projects::RefsController < Projects::ApplicationController
new_path =
case params[:destination]
when "tree"
- namespace_project_tree_path(@project.namespace, @project, @id)
+ project_tree_path(@project, @id)
when "blob"
- namespace_project_blob_path(@project.namespace, @project, @id)
+ project_blob_path(@project, @id)
when "graph"
- namespace_project_network_path(@project.namespace, @project, @id, @options)
+ project_network_path(@project, @id, @options)
when "graphs"
- namespace_project_graph_path(@project.namespace, @project, @id)
+ project_graph_path(@project, @id)
when "find_file"
- namespace_project_find_file_path(@project.namespace, @project, @id)
+ project_find_file_path(@project, @id)
when "graphs_commits"
- commits_namespace_project_graph_path(@project.namespace, @project, @id)
+ commits_project_graph_path(@project, @id)
when "badges"
- namespace_project_pipelines_settings_path(@project.namespace, @project, ref: @id)
+ project_pipelines_settings_path(@project, ref: @id)
else
- namespace_project_commits_path(@project.namespace, @project, @id)
+ project_commits_path(@project, @id)
end
redirect_to new_path
@@ -62,7 +62,7 @@ class Projects::RefsController < Projects::ApplicationController
offset = (@offset + @limit)
if contents.size > offset
- @more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: offset)
+ @more_log_url = logs_file_project_ref_path(@project, @ref, @path || '', offset: offset)
end
respond_to do |format|
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index 98e78585be8..71e7dc70a4d 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -10,11 +10,11 @@ module Projects
def destroy
if image.destroy
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Image repository has been removed successfully!'
else
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove image repository!'
end
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index 5050dba3aab..ae72bd03cfb 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -5,11 +5,11 @@ module Projects
def destroy
if tag.delete
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
notice: 'Registry tag has been removed successfully!'
else
- redirect_to project_container_registry_path(@project),
+ redirect_to project_container_registry_index_path(@project),
status: 302,
alert: 'Failed to remove registry tag!'
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 2c097cb4d8d..3e0a530fdb9 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -19,7 +19,7 @@ class Projects::ReleasesController < Projects::ApplicationController
release.destroy
end
- redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+ redirect_to project_tag_path(@project, @tag.name)
end
private
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 160e632648a..9f9773575a5 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,7 +5,7 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def edit
@@ -49,7 +49,7 @@ class Projects::RunnersController < Projects::ApplicationController
def toggle_shared_runners
project.toggle!(:shared_runners_enabled)
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
protected
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 704f8cc8a79..d54a1111f11 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -15,7 +15,7 @@ class Projects::ServicesController < Projects::ApplicationController
def update
if @service.save(context: :manual_change)
- redirect_to(namespace_project_settings_integrations_path(@project.namespace, @project), notice: success_message)
+ redirect_to(project_settings_integrations_path(@project), notice: success_message)
else
render 'edit'
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 24fe78bc1bd..15a2ff56b92 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -21,7 +21,10 @@ module Projects
end
def define_secret_variables
- @variable = Ci::Variable.new
+ @variable = Ci::Variable.new(project: project)
+ .present(current_user: current_user)
+ @variables = project.variables.order_key_asc
+ .map { |variable| variable.present(current_user: current_user) }
end
def define_triggers_variables
@@ -32,7 +35,7 @@ module Projects
def define_badges_variables
@ref = params[:ref] || @project.default_branch || 'master'
- @badges = [Gitlab::Badge::Build::Status,
+ @badges = [Gitlab::Badge::Pipeline::Status,
Gitlab::Badge::Coverage::Report]
@badges.map! do |badge|
diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb
deleted file mode 100644
index 54f9dceddef..00000000000
--- a/app/controllers/projects/settings/members_controller.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-module Projects
- module Settings
- class MembersController < Projects::ApplicationController
- include SortingHelper
-
- def show
- @sort = params[:sort].presence || sort_value_name
- @group_links = @project.project_group_links
-
- @skip_groups = @group_links.pluck(:group_id)
- @skip_groups << @project.namespace_id unless @project.personal?
- @skip_groups += @project.group.ancestors.pluck(:id) if @project.group
-
- @project_members = MembersFinder.new(@project, current_user).execute
-
- if params[:search].present?
- @project_members = @project_members.joins(:user).merge(User.search(params[:search]))
- @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
- end
-
- @project_members = @project_members.sort(@sort).page(params[:page])
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
- @project_member = @project.project_members.new
- end
- end
- end
-end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 98dd307bd9d..d07143d294f 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -30,7 +30,7 @@ class Projects::SnippetsController < Projects::ApplicationController
).execute
@snippets = @snippets.page(params[:page])
if @snippets.out_of_range? && @snippets.total_pages != 0
- redirect_to namespace_project_snippets_path(page: @snippets.total_pages)
+ redirect_to project_snippets_path(@project, page: @snippets.total_pages)
end
end
@@ -79,7 +79,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet.destroy
- redirect_to namespace_project_snippets_path(@project.namespace, @project), status: 302
+ redirect_to project_snippets_path(@project), status: 302
end
protected
@@ -90,6 +90,10 @@ class Projects::SnippetsController < Projects::ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
+ def spammable_path
+ project_snippet_path(@project, @snippet)
+ end
+
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index ebc9f4edab4..b62d7d9b7c5 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -35,7 +35,7 @@ class Projects::TagsController < Projects::ApplicationController
if result[:status] == :success
@tag = result[:tag]
- redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name)
+ redirect_to project_tag_path(@project, @tag.name)
else
@error = result[:message]
@message = params[:message]
@@ -50,7 +50,7 @@ class Projects::TagsController < Projects::ApplicationController
respond_to do |format|
if result[:status] == :success
format.html do
- redirect_to namespace_project_tags_path(@project.namespace, @project), status: 303
+ redirect_to project_tags_path(@project), status: 303
end
format.js
@@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController
@error = result[:message]
format.html do
- redirect_to namespace_project_tags_path(@project.namespace, @project),
+ redirect_to project_tags_path(@project),
alert: @error, status: 303
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 266a15c1cf9..30181ac3bdf 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -16,7 +16,7 @@ class Projects::TreeController < Projects::ApplicationController
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
return redirect_to(
- namespace_project_blob_path(@project.namespace, @project,
+ project_blob_path(@project,
File.join(@ref, @path))
)
elsif @path.present?
@@ -37,8 +37,8 @@ class Projects::TreeController < Projects::ApplicationController
return render_404 unless @commit_params.values.all?
create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
- success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
- failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
+ success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
+ failure_path: project_tree_path(@project, @ref))
end
private
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index e86adddd77f..e04145dd0b3 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -7,7 +7,7 @@ class Projects::TriggersController < Projects::ApplicationController
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def create
@@ -19,7 +19,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not create a new trigger.'
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def take_ownership
@@ -29,7 +29,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = 'You could not take ownership of trigger.'
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def edit
@@ -37,7 +37,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project), notice: 'Trigger was successfully updated.'
else
render action: "edit"
end
@@ -50,7 +50,7 @@ class Projects::TriggersController < Projects::ApplicationController
flash[:alert] = "Could not remove the trigger."
end
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project), status: 302
+ redirect_to project_settings_ci_cd_path(@project), status: 302
end
private
@@ -69,8 +69,7 @@ class Projects::TriggersController < Projects::ApplicationController
def trigger_params
params.require(:trigger).permit(
- :description,
- trigger_schedule_attributes: [:id, :active, :cron, :cron_timezone, :ref]
+ :description
)
end
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 50e25a00f03..6a825137564 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -1,50 +1,60 @@
class Projects::VariablesController < Projects::ApplicationController
+ before_action :variable, only: [:show, :update, :destroy]
before_action :authorize_admin_build!
layout 'project_settings'
def index
- redirect_to namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ redirect_to project_settings_ci_cd_path(@project)
end
def show
- @variable = @project.variables.find(params[:id])
end
def update
- @variable = @project.variables.find(params[:id])
-
- if @variable.update_attributes(project_params)
- redirect_to namespace_project_variables_path(project.namespace, project), notice: 'Variable was successfully updated.'
+ if variable.update(variable_params)
+ redirect_to project_variables_path(project),
+ notice: 'Variable was successfully updated.'
else
- render action: "show"
+ render "show"
end
end
def create
- @variable = Ci::Variable.new(project_params)
+ @variable = project.variables.create(variable_params)
+ .present(current_user: current_user)
- if @variable.valid? && @project.variables << @variable
- flash[:notice] = 'Variables were successfully updated.'
- redirect_to namespace_project_settings_ci_cd_path(project.namespace, project)
+ if @variable.persisted?
+ redirect_to project_settings_ci_cd_path(project),
+ notice: 'Variable was successfully created.'
else
render "show"
end
end
def destroy
- @key = @project.variables.find(params[:id])
- @key.destroy
-
- redirect_to namespace_project_settings_ci_cd_path(project.namespace, project),
- status: 302,
- notice: 'Variable was successfully removed.'
+ if variable.destroy
+ redirect_to project_settings_ci_cd_path(project),
+ status: 302,
+ notice: 'Variable was successfully removed.'
+ else
+ redirect_to project_settings_ci_cd_path(project),
+ status: 302,
+ notice: 'Failed to remove the variable.'
+ end
end
private
- def project_params
- params.require(:variable)
- .permit([:id, :key, :value, :protected, :_destroy])
+ def variable_params
+ params.require(:variable).permit(*variable_params_attributes)
+ end
+
+ def variable_params_attributes
+ %i[id key value protected _destroy]
+ end
+
+ def variable
+ @variable ||= project.variables.find(params[:id]).present(current_user: current_user)
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index e54b90b8d52..ac98470c2b1 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, @page),
+ project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@@ -62,7 +62,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, @page),
+ project_wiki_path(@project, @page),
notice: 'Wiki was successfully updated.'
)
else
@@ -75,7 +75,7 @@ class Projects::WikisController < Projects::ApplicationController
unless @page
redirect_to(
- namespace_project_wiki_path(@project.namespace, @project, :home),
+ project_wiki_path(@project, :home),
notice: "Page not found"
)
end
@@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
@page = @project_wiki.find_page(params[:id])
WikiPages::DestroyService.new(@project, current_user).execute(@page)
- redirect_to namespace_project_wiki_path(@project.namespace, @project, :home),
+ redirect_to project_wiki_path(@project, :home),
status: 302,
notice: "Page was successfully deleted"
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 450895cdf3a..2d7cbd4614e 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -50,10 +50,13 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
if result[:status] == :success
flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name }
+
format.html do
redirect_to(edit_project_path(@project))
end
else
+ flash[:alert] = result[:message]
+
format.html { render 'edit' }
end
@@ -92,7 +95,7 @@ class ProjectsController < Projects::ApplicationController
def show
if @project.import_in_progress?
- redirect_to namespace_project_import_path(@project.namespace, @project)
+ redirect_to project_import_path(@project)
return
end
@@ -293,10 +296,10 @@ class ProjectsController < Projects::ApplicationController
def project_params
params.require(:project)
- .permit(project_params_ce)
+ .permit(project_params_attributes)
end
- def project_params_ce
+ def project_params_attributes
[
:avatar,
:build_allow_git_fetch,
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 4a579601785..d58c8d14a75 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -44,7 +44,7 @@ class SearchController < ApplicationController
query = params[:search].strip.downcase
found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
- redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
+ redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha
end
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index f39441a281e..69513f4dadc 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -5,6 +5,14 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_two_factor_requirement, only: [:destroy]
+ # Explicitly call protect from forgery before anything else. Otherwise the
+ # CSFR-token might be cleared before authentication is done. This was the case
+ # when LDAP was enabled and the `OmniauthCallbacksController` is loaded
+ #
+ # *Note:* `prepend: true` is the default for rails4, but this will be changed
+ # to `prepend: false` in rails5.
+ protect_from_forgery prepend: true, with: :exception
+
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create]
@@ -48,7 +56,7 @@ class SessionsController < Devise::SessionsController
private
def login_counter
- @login_counter ||= Gitlab::Metrics.counter(:user_session_logins, 'User sign in count')
+ @login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
# Handle an "initial setup" state, where there's only one user, it's an admin,
@@ -58,7 +66,7 @@ class SessionsController < Devise::SessionsController
user = User.admins.last
- return unless user && user.require_password?
+ return unless user && user.require_password_creation?
Users::UpdateService.new(user).execute do |user|
@token = user.generate_reset_token
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 3d86dd2ea2c..8c3abd0a085 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -107,6 +107,10 @@ class SnippetsController < ApplicationController
alias_method :awardable, :snippet
alias_method :spammable, :snippet
+ def spammable_path
+ snippet_path(@snippet)
+ end
+
def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/finders/admin/projects_finder.rb b/app/finders/admin/projects_finder.rb
new file mode 100644
index 00000000000..a5ba791a513
--- /dev/null
+++ b/app/finders/admin/projects_finder.rb
@@ -0,0 +1,33 @@
+class Admin::ProjectsFinder
+ attr_reader :sort, :namespace_id, :visibility_level, :with_push,
+ :abandoned, :last_repository_check_failed, :archived,
+ :personal, :name, :page, :current_user
+
+ def initialize(params:, current_user:)
+ @current_user = current_user
+ @sort = params.fetch(:sort) { 'latest_activity_desc' }
+ @namespace_id = params[:namespace_id]
+ @visibility_level = params[:visibility_level]
+ @with_push = params[:with_push]
+ @abandoned = params[:abandoned]
+ @last_repository_check_failed = params[:last_repository_check_failed]
+ @archived = params[:archived]
+ @personal = params[:personal]
+ @name = params[:name]
+ @page = params[:page]
+ end
+
+ def execute
+ items = Project.with_statistics
+ items = items.in_namespace(namespace_id) if namespace_id.present?
+ items = items.where(visibility_level: visibility_level) if visibility_level.present?
+ items = items.with_push if with_push.present?
+ items = items.abandoned if abandoned.present?
+ items = items.where(last_repository_check_failed: true) if last_repository_check_failed.present?
+ items = items.non_archived unless archived.present?
+ items = items.personal(current_user) if personal.present?
+ items = items.search(name) if name.present?
+ items = items.sort(sort)
+ items.includes(:namespace).order("namespaces.path, projects.name ASC").page(page)
+ end
+end
diff --git a/app/finders/concerns/created_at_filter.rb b/app/finders/concerns/created_at_filter.rb
new file mode 100644
index 00000000000..ac9ac77732c
--- /dev/null
+++ b/app/finders/concerns/created_at_filter.rb
@@ -0,0 +1,8 @@
+module CreatedAtFilter
+ def by_created_at(items)
+ items = items.created_before(params[:created_before]) if params[:created_before].present?
+ items = items.created_after(params[:created_after]) if params[:created_after].present?
+
+ items
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 7bc2117f61e..6fe17a2b99d 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -11,6 +11,7 @@
# group_id: integer
# project_id: integer
# milestone_title: string
+# author_id: integer
# assignee_id: integer
# search: string
# label_name: string
@@ -19,8 +20,10 @@
# iids: integer[]
#
class IssuableFinder
+ include CreatedAtFilter
+
NONE = '0'.freeze
- IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
+ IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page state].freeze
attr_accessor :current_user, :params
@@ -32,6 +35,7 @@ class IssuableFinder
def execute
items = init_collection
items = by_scope(items)
+ items = by_created_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
@@ -42,7 +46,6 @@ class IssuableFinder
items = by_iids(items)
items = by_milestone(items)
items = by_label(items)
- items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items)
@@ -87,8 +90,14 @@ class IssuableFinder
execute.find_by!(*params)
end
- def state_counter_cache_key(state)
- Digest::SHA1.hexdigest(state_counter_cache_key_components(state).flatten.join('-'))
+ def state_counter_cache_key
+ cache_key(state_counter_cache_key_components)
+ end
+
+ def clear_caches!
+ state_counter_cache_key_components_permutations.each do |components|
+ Rails.cache.delete(cache_key(components))
+ end
end
def group
@@ -147,9 +156,17 @@ class IssuableFinder
@milestones =
if milestones?
- scope = Milestone.where(project_id: projects)
+ if project?
+ group_id = project.group&.id
+ project_id = project.id
+ end
+
+ group_id = group.id if group
+
+ search_params =
+ { title: params[:milestone_title], project_ids: project_id, group_ids: group_id }
- scope.where(title: params[:milestone_title])
+ MilestonesFinder.new(search_params).execute
else
Milestone.none
end
@@ -331,11 +348,6 @@ class IssuableFinder
items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
else
items = items.with_milestone(params[:milestone_title])
- items_projects = projects(items)
-
- if items_projects
- items = items.where(milestones: { project_id: items_projects })
- end
end
end
@@ -408,28 +420,23 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items
end
- def by_created_at(items)
- if params[:created_after].present?
- items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
- end
-
- if params[:created_before].present?
- items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
- end
-
- items
- end
-
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
- def state_counter_cache_key_components(state)
+ def state_counter_cache_key_components
opts = params.with_indifferent_access
- opts[:state] = state
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
opts.delete_if { |_, value| value.blank? }
['issuables_count', klass.to_ability_name, opts.sort]
end
+
+ def state_counter_cache_key_components_permutations
+ [state_counter_cache_key_components]
+ end
+
+ def cache_key(components)
+ Digest::SHA1.hexdigest(components.flatten.join('-'))
+ end
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 85230ff1293..0ec42a4e6eb 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -75,7 +75,7 @@ class IssuesFinder < IssuableFinder
current_user.blank? || for_counting || params[:for_counting]
end
- def state_counter_cache_key_components(state)
+ def state_counter_cache_key_components
extra_components = [
user_can_see_all_confidential_issues?,
user_cannot_see_confidential_issues?(for_counting: true)
@@ -84,6 +84,16 @@ class IssuesFinder < IssuableFinder
super + extra_components
end
+ def state_counter_cache_key_components_permutations
+ # Ignore the last two, as we'll provide both options for them.
+ components = super.first[0..-3]
+
+ [
+ components + [false, true],
+ components + [true, false]
+ ]
+ end
+
def by_assignee(items)
if assignee
items.assigned_to(assignee)
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index 630c73c2a94..0a5a0ea2f35 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,12 +1,56 @@
+# Search for milestones
+#
+# params - Hash
+# project_ids: Array of project ids or single project id.
+# group_ids: Array of group ids or single group id.
+# order - Orders by field default due date asc.
+# title - filter by title.
+# state - filters by state.
+
class MilestonesFinder
- def execute(projects, params)
- milestones = Milestone.of_projects(projects)
- milestones = milestones.reorder("due_date ASC")
-
- case params[:state]
- when 'closed' then milestones.closed
- when 'all' then milestones
- else milestones.active
+ attr_reader :params, :project_ids, :group_ids
+
+ def initialize(params = {})
+ @project_ids = Array(params[:project_ids])
+ @group_ids = Array(params[:group_ids])
+ @params = params
+ end
+
+ def execute
+ return Milestone.none if project_ids.empty? && group_ids.empty?
+
+ items = Milestone.all
+ items = by_groups_and_projects(items)
+ items = by_title(items)
+ items = by_state(items)
+
+ order(items)
+ end
+
+ private
+
+ def by_groups_and_projects(items)
+ items.for_projects_and_groups(project_ids, group_ids)
+ end
+
+ def by_title(items)
+ if params[:title]
+ items.where(title: params[:title])
+ else
+ items
+ end
+ end
+
+ def by_state(items)
+ Milestone.filter_by_state(items, params[:state])
+ end
+
+ def order(items)
+ if params.has_key?(:order)
+ items.reorder(params[:order])
+ else
+ order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
+ items.reorder(order_statement)
end
end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 8bfbe37c543..aa80dfc3f37 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -28,7 +28,14 @@ class ProjectsFinder < UnionFinder
end
def execute
- collection = init_collection
+ user = params.delete(:user)
+ collection =
+ if user
+ PersonalProjectsFinder.new(user).execute(current_user)
+ else
+ init_collection
+ end
+
collection = by_ids(collection)
collection = by_personal(collection)
collection = by_starred(collection)
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 07deceb827b..33f7ae90598 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -14,6 +14,8 @@
# external: boolean
#
class UsersFinder
+ include CreatedAtFilter
+
attr_accessor :current_user, :params
def initialize(current_user, params = {})
@@ -29,6 +31,7 @@ class UsersFinder
users = by_active(users)
users = by_external_identity(users)
users = by_external(users)
+ users = by_created_at(users)
users
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 7be8e3b96cf..1c165700b19 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -298,10 +298,6 @@ module ApplicationHelper
end
end
- def can_toggle_new_nav?
- Rails.env.development?
- end
-
def show_new_nav?
cookies["new_nav"] == "true"
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index ca326dd0627..29b88c60dab 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -1,7 +1,7 @@
module ApplicationSettingsHelper
delegate :gravatar_enabled?,
:signup_enabled?,
- :signin_enabled?,
+ :password_authentication_enabled?,
:akismet_enabled?,
:koding_enabled?,
to: :current_application_settings
@@ -34,17 +34,17 @@ module ApplicationSettingsHelper
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
- def restricted_level_checkboxes(help_block_id)
- Gitlab::VisibilityLevel.options.map do |name, level|
+ def restricted_level_checkboxes(help_block_id, checkbox_name)
+ Gitlab::VisibilityLevel.values.map do |level|
checked = restricted_visibility_levels(true).include?(level)
css_class = checked ? 'active' : ''
- checkbox_name = "application_setting[restricted_visibility_levels][]"
+ tag_name = "application_setting_visibility_level_#{level}"
- label_tag(name, class: css_class) do
+ label_tag(tag_name, class: css_class) do
check_box_tag(checkbox_name, level, checked,
autocomplete: 'off',
'aria-describedby' => help_block_id,
- id: name) + visibility_level_icon(level) + name
+ id: tag_name) + visibility_level_icon(level) + visibility_level_label(level)
end
end
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index bbe7f3c8fb4..0e068d4b51c 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -11,17 +11,12 @@ module AvatarsHelper
def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
- css_class = options[:css_class] || ''
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
data_attributes = { container: 'body' }
- if options[:lazy]
- data_attributes[:src] = avatar_url
- end
-
image_tag(
- options[:lazy] ? '' : avatar_url,
- class: "avatar has-tooltip s#{avatar_size} #{css_class}",
+ avatar_url,
+ class: %W[avatar has-tooltip s#{avatar_size}].push(*options[:css_class]),
alt: "#{user_name}'s avatar",
title: user_name,
data: data_attributes
diff --git a/app/helpers/award_emoji_helper.rb b/app/helpers/award_emoji_helper.rb
index 024cf38469e..86b19368cfd 100644
--- a/app/helpers/award_emoji_helper.rb
+++ b/app/helpers/award_emoji_helper.rb
@@ -7,7 +7,7 @@ module AwardEmojiHelper
if awardable.for_personal_snippet?
toggle_award_emoji_snippet_note_path(awardable.noteable, awardable)
else
- toggle_award_emoji_namespace_project_note_path(@project.namespace, @project, awardable.id)
+ toggle_award_emoji_project_note_path(@project, awardable.id)
end
else
url_for([:toggle_award_emoji, @project.namespace.becomes(Namespace), @project, awardable])
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index ee36617ba9a..e964d7a5e16 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -9,7 +9,7 @@ module BlobHelper
end
def edit_path(project = @project, ref = @ref, path = @path, options = {})
- namespace_project_edit_blob_path(project.namespace, project,
+ project_edit_blob_path(project,
tree_join(ref, path),
options[:link_opts])
end
@@ -33,7 +33,7 @@ module BlobHelper
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag 'Edit',
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -62,7 +62,7 @@ module BlobHelper
notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
+ fork_path = project_forks_path(project, namespace_key: current_user.namespace.id, continue: continue_params)
button_tag label,
class: "#{common_classes} js-edit-blob-link-fork-toggler",
@@ -120,15 +120,15 @@ module BlobHelper
def blob_raw_url
if @build && @entry
- raw_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: @entry.path)
+ raw_project_job_artifacts_path(@project, @build, path: @entry.path)
elsif @snippet
if @snippet.project_id
- raw_namespace_project_snippet_path(@project.namespace, @project, @snippet)
+ raw_project_snippet_path(@project, @snippet)
else
raw_snippet_path(@snippet)
end
elsif @blob
- namespace_project_raw_path(@project.namespace, @project, @id)
+ project_raw_path(@project, @id)
end
end
@@ -279,12 +279,12 @@ module BlobHelper
options = []
if can?(current_user, :create_issue, project)
- options << link_to("submit an issue", new_namespace_project_issue_path(project.namespace, project))
+ options << link_to("submit an issue", new_project_issue_path(project))
end
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
if merge_project
- options << link_to("create a merge request", namespace_project_new_merge_request_path(project.namespace, project))
+ options << link_to("create a merge request", project_new_merge_request_path(project))
end
options
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index e2df52e3833..8b33c362a9c 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -3,12 +3,12 @@ module BoardsHelper
board = @board || @boards.first
{
- endpoint: namespace_project_boards_path(@project.namespace, @project),
+ endpoint: project_boards_path(@project),
board_id: board.id,
disabled: "#{!can?(current_user, :admin_list, @project)}",
- issue_link_base: namespace_project_issues_path(@project.namespace, @project),
+ issue_link_base: project_issues_path(@project),
root_path: root_path,
- bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
+ bulk_update_path: bulk_update_project_issues_path(@project),
default_avatar: image_path(default_avatar)
}
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 59519c1335b..686437fc99a 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -7,7 +7,7 @@ module BranchesHelper
options = exist_opts.merge(options)
- namespace_project_branches_path(@project.namespace, @project, @id, options)
+ project_branches_path(@project, @id, options)
end
def can_push_branch?(project, branch_name)
diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb
new file mode 100644
index 00000000000..abe8edd6a8c
--- /dev/null
+++ b/app/helpers/breadcrumbs_helper.rb
@@ -0,0 +1,25 @@
+module BreadcrumbsHelper
+ def add_to_breadcrumbs(text, link)
+ @breadcrumbs_extra_links ||= []
+ @breadcrumbs_extra_links.push({
+ text: text,
+ link: link
+ })
+ end
+
+ def breadcrumb_title_link
+ return @breadcrumb_link if @breadcrumb_link
+
+ if controller.available_action?(:index)
+ url_for(action: "index")
+ else
+ request.path
+ end
+ end
+
+ def breadcrumb_title(title)
+ return if defined?(@breadcrumb_title)
+
+ @breadcrumb_title = title
+ end
+end
diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb
index f0a0d245dc0..85bc784d53c 100644
--- a/app/helpers/builds_helper.rb
+++ b/app/helpers/builds_helper.rb
@@ -20,8 +20,8 @@ module BuildsHelper
def javascript_build_options
{
- page_url: namespace_project_job_url(@project.namespace, @project, @build),
- build_url: namespace_project_job_url(@project.namespace, @project, @build, :json),
+ page_url: project_job_url(@project, @build),
+ build_url: project_job_url(@project, @build, :json),
build_status: @build.status,
build_stage: @build.stage,
log_state: ''
@@ -31,7 +31,7 @@ module BuildsHelper
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
- description: namespace_project_job_url(@project.namespace, @project, @build)
+ description: project_job_url(@project, @build)
}
end
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index ba84dbe4a7a..bf9ad95b7c2 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -50,12 +50,12 @@ module ButtonHelper
def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector'
- klass << ' has-tooltip' if current_user.try(:require_password?) || current_user.try(:require_personal_access_token?)
+ klass << ' has-tooltip' if current_user.try(:require_password_creation?) || current_user.try(:require_personal_access_token_creation_for_git_auth?)
protocol = gitlab_config.protocol.upcase
tooltip_title =
- if current_user.try(:require_password?)
+ if current_user.try(:require_password_creation?)
_("Set a password on your account to pull or push via %{protocol}.") % { protocol: protocol }
else
_("Create a personal access token on your account to pull or push via %{protocol}.") % { protocol: protocol }
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 21c0eb8b54c..8022547a6ad 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -8,7 +8,7 @@
module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
- namespace_project_pipeline_path(project.namespace, project, pipeline)
+ project_pipeline_path(project, pipeline)
end
def ci_label_for_status(status)
@@ -99,10 +99,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- pipeline_status.sha)
+ path = pipelines_project_commit_path(project, pipeline_status.sha)
render_status_with_link(
'commit',
@@ -113,10 +110,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
- path = pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- commit)
+ path = pipelines_project_commit_path(project, commit)
render_status_with_link(
'commit',
@@ -127,7 +121,7 @@ module CiStatusHelper
def render_pipeline_status(pipeline, tooltip_placement: 'auto left')
project = pipeline.project
- path = namespace_project_pipeline_path(project.namespace, project, pipeline)
+ path = project_pipeline_path(project, pipeline)
render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement)
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 0accd1f8d77..69220a1c0f6 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -30,7 +30,7 @@ module CommitsHelper
crumbs = content_tag(:li) do
link_to(
@project.path,
- namespace_project_commits_path(@project.namespace, @project, @ref)
+ project_commits_path(@project, @ref)
)
end
@@ -42,8 +42,7 @@ module CommitsHelper
# The text is just the individual part, but the link needs all the parts before it
link_to(
part,
- namespace_project_commits_path(
- @project.namespace,
+ project_commits_path(
@project,
tree_join(@ref, parts[0..i].join('/'))
)
@@ -86,20 +85,20 @@ module CommitsHelper
if @path.blank?
return link_to(
_("Browse Files"),
- namespace_project_tree_path(project.namespace, project, commit),
+ project_tree_path(project, commit),
class: "btn btn-default"
)
elsif @repo.blob_at(commit.id, @path)
return link_to(
_("Browse File"),
- namespace_project_blob_path(project.namespace, project,
+ project_blob_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
elsif @path.present?
return link_to(
_("Browse Directory"),
- namespace_project_tree_path(project.namespace, project,
+ project_tree_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
@@ -114,6 +113,10 @@ module CommitsHelper
commit_action_link('cherry-pick', commit, continue_to_path, btn_class: btn_class, has_tooltip: has_tooltip)
end
+ def commit_signature_badge_classes(additional_classes)
+ %w(btn status-box gpg-status-box) + Array(additional_classes)
+ end
+
protected
# Private: Returns a link to a person. If the person has a matching user and
@@ -165,7 +168,7 @@ module CommitsHelper
notice: "#{edit_in_new_fork_notice} Try to #{action} this commit again.",
notice_now: edit_in_new_fork_notice_now
}
- fork_path = namespace_project_forks_path(@project.namespace, @project,
+ fork_path = project_forks_path(@project,
namespace_key: current_user.namespace.id,
continue: continue_params)
@@ -175,7 +178,7 @@ module CommitsHelper
def view_file_button(commit_sha, diff_new_path, project)
link_to(
- namespace_project_blob_path(project.namespace, project,
+ project_blob_path(project,
tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file'
) do
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 424ded2b69d..2c28dd81c87 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -9,8 +9,7 @@ module CompareHelper
end
def create_mr_path(from = params[:from], to = params[:to], project = @project)
- namespace_project_new_merge_request_path(
- project.namespace,
+ project_new_merge_request_path(
project,
merge_request: {
source_branch: to,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 16a99addd0b..926502bf239 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -103,18 +103,18 @@ module DiffHelper
end
def diff_file_blob_raw_path(diff_file)
- namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+ project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
end
def diff_file_old_blob_raw_path(diff_file)
sha = diff_file.old_content_sha
return unless sha
- namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path))
+ project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
- blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
+ blob_diff_path: project_blob_diff_path(project,
tree_join(diff_commit_id, diff_file_path)),
view: diff_view
}
@@ -142,7 +142,7 @@ module DiffHelper
diff_file = viewer.diff_file
options = []
- blob_url = namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+ blob_url = project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.file_path))
options << link_to('view the blob', blob_url)
options
@@ -163,17 +163,17 @@ module DiffHelper
end
def commit_diff_whitespace_link(project, commit, options)
- url = namespace_project_commit_path(project.namespace, project, commit.id, params_with_whitespace)
+ url = project_commit_path(project, commit.id, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_merge_request_whitespace_link(project, merge_request, options)
- url = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, params_with_whitespace)
+ url = diffs_project_merge_request_path(project, merge_request, params_with_whitespace)
toggle_whitespace_link(url, options)
end
def diff_compare_whitespace_link(project, from, to, options)
- url = namespace_project_compare_path(project.namespace, project, from, to, params_with_whitespace)
+ url = project_compare_path(project, from, to, params_with_whitespace)
toggle_whitespace_link(url, options)
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index fdbca789d21..5f11fe62030 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -61,8 +61,8 @@ module EmailsHelper
else
image_tag(
image_url('mailers/gitlab_header_logo.gif'),
- size: "55x50",
- alt: "GitLab"
+ size: '55x50',
+ alt: 'GitLab'
)
end
end
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index ff8550439d0..1e78a189c08 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -8,7 +8,7 @@ module EnvironmentHelper
def environment_link_for_build(project, build)
environment = environment_for_build(project, build)
if environment
- link_to environment.name, namespace_project_environment_path(project.namespace, project, environment)
+ link_to environment.name, project_environment_path(project, environment)
else
content_tag :span, build.expanded_environment_name
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 515e802e01e..4ce89f89fa9 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -1,7 +1,7 @@
module EnvironmentsHelper
def environments_list_data
{
- endpoint: namespace_project_environments_path(@project.namespace, @project, format: :json)
+ endpoint: project_environments_path(@project, format: :json)
}
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 751d61955b7..48c87dca217 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -99,13 +99,12 @@ module EventsHelper
def event_feed_url(event)
if event.issue?
- namespace_project_issue_url(event.project.namespace, event.project,
+ project_issue_url(event.project,
event.issue)
elsif event.merge_request?
- namespace_project_merge_request_url(event.project.namespace,
- event.project, event.merge_request)
+ project_merge_request_url(event.project, event.merge_request)
elsif event.commit_note?
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
event.note_target)
elsif event.note?
if event.note_target
@@ -119,15 +118,15 @@ module EventsHelper
def push_event_feed_url(event)
if event.push_with_commits? && event.md_ref?
if event.commits_count > 1
- namespace_project_compare_url(event.project.namespace, event.project,
+ project_compare_url(event.project,
from: event.commit_from, to:
event.commit_to)
else
- namespace_project_commit_url(event.project.namespace, event.project,
+ project_commit_url(event.project,
id: event.commit_to)
end
else
- namespace_project_commits_url(event.project.namespace, event.project,
+ project_commits_url(event.project,
event.ref_name)
end
end
@@ -146,15 +145,9 @@ module EventsHelper
def event_note_target_path(event)
if event.commit_note?
- namespace_project_commit_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_commit_path(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.project_snippet_note?
- namespace_project_snippet_path(event.project.namespace,
- event.project,
- event.note_target,
- anchor: dom_id(event.target))
+ project_snippet_path(event.project, event.note_target, anchor: dom_id(event.target))
else
polymorphic_path([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index defd87d6bbe..8cf890b74a8 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -4,7 +4,7 @@ module ExternalWikiHelper
if external_wiki_service
external_wiki_service.properties['external_wiki_url']
else
- namespace_project_wiki_path(project.namespace, project, :home)
+ project_wiki_path(project, :home)
end
end
end
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 8c7af62e199..0517a699ae0 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -1,144 +1,93 @@
-# Shorter routing method for project and project items
-# Since update to rails 4.1.9 we are now allowed to use `/` in project routing
-# so we use nested routing for project resources which include project and
-# project namespace. To avoid writing long methods every time we define shortcuts for
-# some of routing.
-#
-# For example instead of this:
-#
-# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
-#
-# We can simply use shortcut:
-#
-# merge_request_path(merge_request)
-#
+# Shorter routing method for some project items
module GitlabRoutingHelper
- # Project
- def project_path(project, *args)
- namespace_project_path(project.namespace, project, *args)
- end
-
- def project_url(project, *args)
- namespace_project_url(project.namespace, project, *args)
- end
-
- def edit_project_path(project, *args)
- edit_namespace_project_path(project.namespace, project, *args)
- end
-
- def edit_project_url(project, *args)
- edit_namespace_project_url(project.namespace, project, *args)
- end
+ extend ActiveSupport::Concern
- def project_files_path(project, *args)
- namespace_project_tree_path(project.namespace, project, @ref || project.repository.root_ref)
+ included do
+ Gitlab::Routing.includes_helpers(self)
end
- def project_commits_path(project, *args)
- namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
- end
-
- def project_pipelines_path(project, *args)
- namespace_project_pipelines_path(project.namespace, project, *args)
- end
-
- def project_environments_path(project, *args)
- namespace_project_environments_path(project.namespace, project, *args)
- end
-
- def project_cycle_analytics_path(project, *args)
- namespace_project_cycle_analytics_path(project.namespace, project, *args)
+ # Project
+ def project_tree_path(project, ref = nil, *args)
+ namespace_project_tree_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
- def project_jobs_path(project, *args)
- namespace_project_jobs_path(project.namespace, project, *args)
+ def project_commits_path(project, ref = nil, *args)
+ namespace_project_commits_path(project.namespace, project, ref || @ref || project.repository.root_ref, *args) # rubocop:disable Cop/ProjectPathHelper
end
def project_ref_path(project, ref_name, *args)
- namespace_project_commits_path(project.namespace, project, ref_name, *args)
- end
-
- def project_container_registry_path(project, *args)
- namespace_project_container_registry_index_path(project.namespace, project, *args)
- end
-
- def activity_project_path(project, *args)
- activity_namespace_project_path(project.namespace, project, *args)
+ project_commits_path(project, ref_name, *args)
end
def runners_path(project, *args)
- namespace_project_runners_path(project.namespace, project, *args)
+ project_runners_path(project, *args)
end
def runner_path(runner, *args)
- namespace_project_runner_path(@project.namespace, @project, runner, *args)
+ project_runner_path(@project, runner, *args)
end
def environment_path(environment, *args)
- namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ project_environment_path(environment.project, environment, *args)
end
def environment_metrics_path(environment, *args)
- metrics_namespace_project_environment_path(environment.project.namespace, environment.project, environment, *args)
+ metrics_project_environment_path(environment.project, environment, *args)
end
def issue_path(entity, *args)
- namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args)
+ project_issue_path(entity.project, entity, *args)
end
def merge_request_path(entity, *args)
- namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_path(entity.project, entity, *args)
end
def pipeline_path(pipeline, *args)
- namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+ project_pipeline_path(pipeline.project, pipeline.id, *args)
end
def milestone_path(entity, *args)
- namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args)
+ project_milestone_path(entity.project, entity, *args)
end
def issue_url(entity, *args)
- namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args)
+ project_issue_url(entity.project, entity, *args)
end
def merge_request_url(entity, *args)
- namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args)
+ project_merge_request_url(entity.project, entity, *args)
end
def pipeline_url(pipeline, *args)
- namespace_project_pipeline_url(pipeline.project.namespace, pipeline.project, pipeline.id, *args)
+ project_pipeline_url(pipeline.project, pipeline.id, *args)
end
def pipeline_job_url(pipeline, build, *args)
- namespace_project_job_url(pipeline.project.namespace, pipeline.project, build.id, *args)
+ project_job_url(pipeline.project, build.id, *args)
end
def commits_url(entity, *args)
- namespace_project_commits_url(entity.project.namespace, entity.project, entity.ref, *args)
+ project_commits_url(entity.project, entity.ref, *args)
end
def commit_url(entity, *args)
- namespace_project_commit_url(entity.project.namespace, entity.project, entity.sha, *args)
- end
-
- def project_snippet_url(entity, *args)
- namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
+ project_commit_url(entity.project, entity.sha, *args)
end
def preview_markdown_path(project, *args)
if @snippet.is_a?(PersonalSnippet)
preview_markdown_snippets_path
else
- preview_markdown_namespace_project_path(project.namespace, project, *args)
+ preview_markdown_project_path(project, *args)
end
end
def toggle_subscription_path(entity, *args)
if entity.is_a?(Issue)
- toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_issue_path(entity.project, entity)
else
- toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
+ toggle_subscription_project_merge_request_path(entity.project, entity)
end
end
@@ -152,32 +101,27 @@ module GitlabRoutingHelper
## Members
def project_members_url(project, *args)
- namespace_project_project_members_url(project.namespace, project)
+ project_project_members_url(project, *args)
end
def project_member_path(project_member, *args)
- namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ project_project_member_path(project_member.source, project_member)
end
def request_access_project_members_path(project, *args)
- request_access_namespace_project_project_members_path(project.namespace, project)
+ request_access_project_project_members_path(project)
end
def leave_project_members_path(project, *args)
- leave_namespace_project_project_members_path(project.namespace, project)
+ leave_project_project_members_path(project)
end
def approve_access_request_project_member_path(project_member, *args)
- approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
+ approve_access_request_project_project_member_path(project_member.source, project_member)
end
def resend_invite_project_member_path(project_member, *args)
- resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member)
- end
-
- # Snippets
- def personal_snippet_url(snippet, *args)
- snippet_url(snippet)
+ resend_invite_project_project_member_path(project_member.source, project_member)
end
# Groups
@@ -211,50 +155,37 @@ module GitlabRoutingHelper
def artifacts_action_path(path, project, build)
action, path_params = path.split('/', 2)
- args = [project.namespace, project, build, path_params]
+ args = [project, build, path_params]
case action
when 'download'
- download_namespace_project_job_artifacts_path(*args)
+ download_project_job_artifacts_path(*args)
when 'browse'
- browse_namespace_project_job_artifacts_path(*args)
+ browse_project_job_artifacts_path(*args)
when 'file'
- file_namespace_project_job_artifacts_path(*args)
+ file_project_job_artifacts_path(*args)
when 'raw'
- raw_namespace_project_job_artifacts_path(*args)
+ raw_project_job_artifacts_path(*args)
end
end
# Pipeline Schedules
def pipeline_schedules_path(project, *args)
- namespace_project_pipeline_schedules_path(project.namespace, project, *args)
+ project_pipeline_schedules_path(project, *args)
end
def pipeline_schedule_path(schedule, *args)
project = schedule.project
- namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
+ project_pipeline_schedule_path(project, schedule, *args)
end
def edit_pipeline_schedule_path(schedule)
project = schedule.project
- edit_namespace_project_pipeline_schedule_path(project.namespace, project, schedule)
+ edit_project_pipeline_schedule_path(project, schedule)
end
def take_ownership_pipeline_schedule_path(schedule, *args)
project = schedule.project
- take_ownership_namespace_project_pipeline_schedule_path(project.namespace, project, schedule, *args)
- end
-
- # Settings
- def project_settings_integrations_path(project, *args)
- namespace_project_settings_integrations_path(project.namespace, project, *args)
- end
-
- def project_settings_members_path(project, *args)
- namespace_project_settings_members_path(project.namespace, project, *args)
- end
-
- def project_settings_ci_cd_path(project, *args)
- namespace_project_settings_ci_cd_path(project.namespace, project, *args)
+ take_ownership_project_pipeline_schedule_path(project, schedule, *args)
end
end
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
new file mode 100644
index 00000000000..551b9cca6b1
--- /dev/null
+++ b/app/helpers/hooks_helper.rb
@@ -0,0 +1,17 @@
+module HooksHelper
+ def link_to_test_hook(hook, trigger)
+ path = case hook
+ when ProjectHook
+ project = hook.project
+ test_project_hook_path(project, hook, trigger: trigger)
+ when SystemHook
+ test_admin_hook_path(hook, trigger: trigger)
+ end
+
+ trigger_human_name = trigger.to_s.tr('_', ' ').camelize
+
+ link_to path, rel: 'nofollow' do
+ content_tag(:span, trigger_human_name)
+ end
+ end
+end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 05177e58c5a..425af547330 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -26,9 +26,9 @@ module IssuablesHelper
project = issuable.project
if issuable.is_a?(MergeRequest)
- namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
+ project_merge_request_path(project, issuable.iid, :json)
else
- namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
+ project_issue_path(project, issuable.iid, :json)
end
end
@@ -197,7 +197,7 @@ module IssuablesHelper
def issuable_initial_data(issuable)
data = {
- endpoint: namespace_project_issue_path(@project.namespace, @project, issuable),
+ endpoint: project_issue_path(@project, issuable),
canUpdate: can?(current_user, :update_issue, issuable),
canDestroy: can?(current_user, :destroy_issue, issuable),
canMove: current_user ? issuable.can_move?(current_user) : false,
@@ -235,7 +235,7 @@ module IssuablesHelper
def issuables_count_for_state(issuable_type, state, finder: nil)
finder ||= public_send("#{issuable_type}_finder")
- cache_key = finder.state_counter_cache_key(state)
+ cache_key = finder.state_counter_cache_key
@counts ||= {}
@counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
@@ -245,6 +245,53 @@ module IssuablesHelper
@counts[cache_key][state]
end
+ def close_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :close))
+ end
+
+ def reopen_issuable_url(issuable)
+ issuable_url(issuable, close_reopen_params(issuable, :reopen))
+ end
+
+ def close_reopen_issuable_url(issuable, should_inverse = false)
+ issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
+ end
+
+ def issuable_url(issuable, *options)
+ case issuable
+ when Issue
+ issue_url(issuable, *options)
+ when MergeRequest
+ merge_request_url(issuable, *options)
+ end
+ end
+
+ def issuable_button_visibility(issuable, closed)
+ case issuable
+ when Issue
+ issue_button_visibility(issuable, closed)
+ when MergeRequest
+ merge_request_button_visibility(issuable, closed)
+ end
+ end
+
+ def issuable_close_reopen_button_method(issuable)
+ case issuable
+ when Issue
+ ''
+ when MergeRequest
+ 'put'
+ end
+ end
+
+ def issuable_author_is_current_user(issuable)
+ issuable.author == current_user
+ end
+
+ def issuable_display_type(issuable)
+ issuable.model_name.human.downcase
+ end
+
private
def sidebar_gutter_collapsed?
@@ -270,8 +317,6 @@ module IssuablesHelper
issue_template_names
when MergeRequest
merge_request_template_names
- else
- raise 'Unknown issuable type!'
end
end
@@ -295,10 +340,18 @@ module IssuablesHelper
mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
- url: namespace_project_todos_path(@project.namespace, @project),
+ url: project_todos_path(@project),
delete_path: (dashboard_todo_path(todo) if todo),
placement: (is_collapsed ? 'left' : nil),
container: (is_collapsed ? 'body' : nil)
}
end
+
+ def close_reopen_params(issuable, action)
+ {
+ issuable.model_name.to_s.underscore => { state_event: action }
+ }.tap do |params|
+ params[:format] = :json if issuable.is_a?(Issue)
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 82288f1da35..7e1ccb23e9e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -17,10 +17,10 @@ module IssuesHelper
return '' if project.nil?
url =
- if options[:only_path]
- project.issues_tracker.issue_path(issue_iid)
+ if options[:internal]
+ url_for_internal_issue(issue_iid, project, options)
else
- project.issues_tracker.issue_url(issue_iid)
+ url_for_tracker_issue(issue_iid, project, options)
end
# Ensure we return a valid URL to prevent possible XSS.
@@ -29,6 +29,24 @@ module IssuesHelper
''
end
+ def url_for_tracker_issue(issue_iid, project, options)
+ if options[:only_path]
+ project.issues_tracker.issue_path(issue_iid)
+ else
+ project.issues_tracker.issue_url(issue_iid)
+ end
+ end
+
+ def url_for_internal_issue(issue_iid, project = @project, options = {})
+ helpers = Gitlab::Routing.url_helpers
+
+ if options[:only_path]
+ helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ else
+ helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue_iid)
+ end
+ end
+
def bulk_update_milestone_options
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
milestones.unshift(Milestone::None)
@@ -150,7 +168,7 @@ module IssuesHelper
Gitlab::UrlBuilder.build(single_discussion.first_note)
else
project = merge_request.project
- namespace_project_merge_request_path(project.namespace, project, merge_request)
+ project_merge_request_path(project, merge_request)
end
link_to link_text, path
@@ -158,4 +176,6 @@ module IssuesHelper
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
+ module_function :url_for_internal_issue
+ module_function :url_for_tracker_issue
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 6baf6f31d8f..4b99de1b6a5 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -57,14 +57,14 @@ module LabelsHelper
def edit_label_path(label)
case label
when GroupLabel then edit_group_label_path(label.group, label)
- when ProjectLabel then edit_namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then edit_project_label_path(label.project, label)
end
end
def destroy_label_path(label)
case label
when GroupLabel then group_label_path(label.group, label)
- when ProjectLabel then namespace_project_label_path(label.project.namespace, label.project, label)
+ when ProjectLabel then project_label_path(label.project, label)
end
end
@@ -127,7 +127,7 @@ module LabelsHelper
project = @target_project || @project
if project
- namespace_project_labels_path(project.namespace, project, :json)
+ project_labels_path(project, :json)
else
dashboard_labels_path(:json)
end
@@ -149,8 +149,8 @@ module LabelsHelper
case label_subscription_status(label, project)
when 'group-level' then toggle_subscription_group_label_path(label.group, label)
- when 'project-level' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
- when 'unsubscribed' then toggle_subscription_namespace_project_label_path(project.namespace, project, label)
+ when 'project-level' then toggle_subscription_project_label_path(project, label)
+ when 'unsubscribed' then toggle_subscription_project_label_path(project, label)
end
end
diff --git a/app/helpers/lazy_image_tag_helper.rb b/app/helpers/lazy_image_tag_helper.rb
new file mode 100644
index 00000000000..2c5619ac41b
--- /dev/null
+++ b/app/helpers/lazy_image_tag_helper.rb
@@ -0,0 +1,24 @@
+module LazyImageTagHelper
+ def placeholder_image
+ "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+ end
+
+ # Override the default ActionView `image_tag` helper to support lazy-loading
+ def image_tag(source, options = {})
+ options = options.symbolize_keys
+
+ unless options.delete(:lazy) == false
+ options[:data] ||= {}
+ options[:data][:src] = path_to_image(source)
+ options[:class] ||= ""
+ options[:class] << " lazy"
+
+ source = placeholder_image
+ end
+
+ super(source, options)
+ end
+
+ # Required for Banzai::Filter::ImageLazyLoadFilter
+ module_function :placeholder_image
+end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 54d6f86fa11..78cf7b26a31 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -1,8 +1,7 @@
module MergeRequestsHelper
def new_mr_path_from_push_event(event)
target_project = event.project.default_merge_request_target
- namespace_project_new_merge_request_path(
- event.project.namespace,
+ project_new_merge_request_path(
event.project,
new_mr_from_push_event(event, target_project)
)
@@ -48,8 +47,8 @@ module MergeRequestsHelper
end
def mr_change_branches_path(merge_request)
- namespace_project_new_merge_request_path(
- @project.namespace, @project,
+ project_new_merge_request_path(
+ @project,
merge_request: {
source_project_id: merge_request.source_project_id,
target_project_id: merge_request.target_project_id,
@@ -82,9 +81,7 @@ module MergeRequestsHelper
end
def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil)
- diffs_namespace_project_merge_request_path(
- project.namespace, project, merge_request,
- diff_id: merge_request_diff.id, start_sha: start_sha)
+ diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha)
end
def version_index(merge_request_diff)
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index f346e20e807..f8860bfee99 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -1,7 +1,7 @@
module MilestonesHelper
def milestones_filter_path(opts = {})
if @project
- namespace_project_milestones_path(@project.namespace, @project, opts)
+ project_milestones_path(@project, opts)
elsif @group
group_milestones_path(@group, opts)
else
@@ -11,7 +11,7 @@ module MilestonesHelper
def milestones_label_path(opts = {})
if @project
- namespace_project_issues_path(@project.namespace, @project, opts)
+ project_issues_path(@project, opts)
elsif @group
issues_group_path(@group, opts)
else
@@ -54,8 +54,10 @@ module MilestonesHelper
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
+ elsif param == check
+ 'active'
else
- 'active' if param == check
+ check
end
end
@@ -73,7 +75,7 @@ module MilestonesHelper
def milestones_filter_dropdown_path
project = @target_project || @project
if project
- namespace_project_milestones_path(project.namespace, project, :json)
+ project_milestones_path(project, :json)
elsif @group
group_milestones_path(@group, :json)
else
@@ -120,7 +122,7 @@ module MilestonesHelper
def milestone_merge_request_tab_path(milestone)
if @project
- merge_requests_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ merge_requests_project_milestone_path(@project, milestone, format: :json)
elsif @group
merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
@@ -130,7 +132,7 @@ module MilestonesHelper
def milestone_participants_tab_path(milestone)
if @project
- participants_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ participants_project_milestone_path(@project, milestone, format: :json)
elsif @group
participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
@@ -140,11 +142,21 @@ module MilestonesHelper
def milestone_labels_tab_path(milestone)
if @project
- labels_namespace_project_milestone_path(@project.namespace, @project, milestone, format: :json)
+ labels_project_milestone_path(@project, milestone, format: :json)
elsif @group
labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
end
end
+
+ def group_milestone_route(milestone, params = {})
+ params = nil if params.empty?
+
+ if milestone.is_legacy_group_milestone?
+ group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: params)
+ else
+ group_milestone_path(@group, milestone.iid, milestone: params)
+ end
+ end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index e589ed4e56d..b769462abc2 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -23,7 +23,6 @@ module NavHelper
def nav_header_class
class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav
- class_name << " with-peek" if peek_enabled?
class_name
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index ecc6cd6c6c5..0a0881d95cf 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -81,11 +81,11 @@ module NotesHelper
path_params = version_params.merge(anchor: discussion.line_code)
- diffs_namespace_project_merge_request_path(discussion.project.namespace, discussion.project, discussion.noteable, path_params)
+ diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params)
elsif discussion.for_commit?
anchor = discussion.line_code if discussion.diff_discussion?
- namespace_project_commit_path(discussion.project.namespace, discussion.project, discussion.noteable, anchor: anchor)
+ project_commit_path(discussion.project, discussion.noteable, anchor: anchor)
end
end
@@ -93,12 +93,7 @@ module NotesHelper
if @snippet.is_a?(PersonalSnippet)
snippet_notes_path(@snippet)
else
- namespace_project_noteable_notes_path(
- namespace_id: @project.namespace,
- project_id: @project,
- target_id: @noteable.id,
- target_type: @noteable.class.name.underscore
- )
+ project_noteable_notes_path(@project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)
end
end
@@ -106,7 +101,7 @@ module NotesHelper
if note.noteable.is_a?(PersonalSnippet)
snippet_note_path(note.noteable, note)
else
- namespace_project_note_path(project.namespace, project, note)
+ project_note_path(project, note)
end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 3286a92a8a7..b30b2eb1d03 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -4,6 +4,10 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any?
+ if show_new_nav? && titles.any? && !defined?(@breadcrumb_title)
+ @breadcrumb_title = @page_title.last
+ end
+
# Segments are seperated by middot
@page_title.join(" \u00b7 ")
end
diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb
new file mode 100644
index 00000000000..d24efe37f5f
--- /dev/null
+++ b/app/helpers/performance_bar_helper.rb
@@ -0,0 +1,7 @@
+module PerformanceBarHelper
+ # This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?`
+ # in WithPerformanceBar breaks tests (but works in the browser).
+ def performance_bar_enabled?
+ peek_enabled?
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 53d95c2de94..9a8d296d514 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -195,7 +195,7 @@ module ProjectsHelper
controller.controller_name,
controller.action_name,
current_application_settings.cache_key,
- 'v2.4'
+ 'v2.5'
]
key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
@@ -214,11 +214,11 @@ module ProjectsHelper
def show_no_password_message?
cookies[:hide_no_password_message].blank? && !current_user.hide_no_password &&
- ( current_user.require_password? || current_user.require_personal_access_token? )
+ ( current_user.require_password_creation? || current_user.require_personal_access_token_creation_for_git_auth? )
end
def link_to_set_password
- if current_user.require_password?
+ if current_user.require_password_creation?
link_to s_('SetPasswordToCloneLink|set a password'), edit_profile_password_path
else
link_to s_('CreateTokenToCloneLink|create a personal access token'), profile_personal_access_tokens_path
@@ -267,15 +267,15 @@ module ProjectsHelper
def tab_ability_map
{
- environments: :read_environment,
- milestones: :read_milestone,
- snippets: :read_project_snippet,
- settings: :admin_project,
- builds: :read_build,
- labels: :read_label,
- issues: :read_issue,
- team: :read_project_member,
- wiki: :read_wiki
+ environments: :read_environment,
+ milestones: :read_milestone,
+ snippets: :read_project_snippet,
+ settings: :admin_project,
+ builds: :read_build,
+ labels: :read_label,
+ issues: :read_issue,
+ project_members: :read_project_member,
+ wiki: :read_wiki
}
end
@@ -347,8 +347,7 @@ module ProjectsHelper
def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil)
commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase }
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: file_name,
@@ -359,8 +358,7 @@ module ProjectsHelper
end
def add_koding_stack_path(project)
- namespace_project_new_blob_path(
- project.namespace,
+ project_new_blob_path(
project,
project.default_branch || 'master',
file_name: '.koding.yml',
@@ -414,8 +412,7 @@ module ProjectsHelper
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
- namespace_project_blob_path(
- project.namespace,
+ project_blob_path(
project,
tree_join(project.default_branch,
contribution_guide.name)
@@ -445,7 +442,7 @@ module ProjectsHelper
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
- namespace_project_wiki_path(proj.namespace, proj, page, url_params)
+ project_wiki_path(proj, page, url_params)
end
def project_status_css_class(status)
@@ -470,8 +467,7 @@ module ProjectsHelper
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
- namespace_project_blob_path(
- project.namespace,
+ project_blob_path(
project,
tree_join(project.default_branch, blob.name)
)
@@ -522,4 +518,12 @@ module ProjectsHelper
current_application_settings.restricted_visibility_levels || []
end
+
+ def find_file_path
+ return unless @project && !@project.empty_repo?
+
+ ref = @ref || @project.repository.root_ref
+
+ project_find_file_path(@project, ref)
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index f39a3bb55a8..fd7ab59ce64 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -67,16 +67,16 @@ module SearchHelper
ref = @ref || @project.repository.root_ref
[
- { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Network", url: namespace_project_network_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) },
- { category: "Current Project", label: "Issues", url: namespace_project_issues_path(@project.namespace, @project) },
- { category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
- { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
- { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) },
- { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }
+ { category: "Current Project", label: "Files", url: project_tree_path(@project, ref) },
+ { category: "Current Project", label: "Commits", url: project_commits_path(@project, ref) },
+ { category: "Current Project", label: "Network", url: project_network_path(@project, ref) },
+ { category: "Current Project", label: "Graph", url: project_graph_path(@project, ref) },
+ { category: "Current Project", label: "Issues", url: project_issues_path(@project) },
+ { category: "Current Project", label: "Merge Requests", url: project_merge_requests_path(@project) },
+ { category: "Current Project", label: "Milestones", url: project_milestones_path(@project) },
+ { category: "Current Project", label: "Snippets", url: project_snippets_path(@project) },
+ { category: "Current Project", label: "Members", url: project_project_members_path(@project) },
+ { category: "Current Project", label: "Wiki", url: project_wikis_path(@project) }
]
else
[]
@@ -104,7 +104,7 @@ module SearchHelper
id: p.id,
value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.name_with_namespace)}",
- url: namespace_project_path(p.namespace, p)
+ url: project_path(p)
}
end
end
@@ -133,7 +133,7 @@ module SearchHelper
data: {
'project-id' => @project.id,
'username-params' => @users.to_json(only: [:id, :username]),
- 'base-endpoint' => namespace_project_path(@project.namespace, @project)
+ 'base-endpoint' => project_path(@project)
}
}
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 2fd64b3441e..b447d4952e7 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,8 +1,7 @@
module SnippetsHelper
def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id?
- namespace_project_snippet_path(snippet.project.namespace,
- snippet.project, snippet, opts)
+ project_snippet_path(snippet.project, snippet, opts)
else
snippet_path(snippet, opts)
end
@@ -10,7 +9,7 @@ module SnippetsHelper
def download_snippet_path(snippet)
if snippet.project_id
- raw_namespace_project_snippet_path(@project.namespace, @project, snippet, inline: false)
+ raw_project_snippet_path(@project, snippet, inline: false)
else
raw_snippet_path(snippet, inline: false)
end
@@ -21,7 +20,7 @@ module SnippetsHelper
# @returns String, path to snippet index
def subject_snippets_path(subject = nil, opts = nil)
if subject.is_a?(Project)
- namespace_project_snippets_path(subject.namespace, subject, opts)
+ project_snippets_path(subject, opts)
else # assume subject === User
dashboard_snippets_path(opts)
end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index 209bd56b78a..08fd97cd048 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -18,7 +18,8 @@ module SystemNoteHelper
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
'moved' => 'icon_arrow_circle_o_right',
- 'outdated' => 'icon_edit'
+ 'outdated' => 'icon_edit',
+ 'duplicate' => 'icon_clone'
}.freeze
def icon_for_system_note(note)
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index 1a55ee05996..ee701076a14 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -107,8 +107,7 @@ module TabHelper
def branches_tab_class
if current_controller?(:protected_branches) ||
current_controller?(:branches) ||
- current_page?(namespace_project_repository_path(@project.namespace,
- @project))
+ current_page?(project_repository_path(@project))
'active'
end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
index 31aaf9e5607..d000d6b1c0a 100644
--- a/app/helpers/tags_helper.rb
+++ b/app/helpers/tags_helper.rb
@@ -10,7 +10,7 @@ module TagsHelper
}
options = exist_opts.merge(options)
- namespace_project_tags_path(@project.namespace, @project, @id, options)
+ project_tags_path(@project, @id, options)
end
def tag_list(project)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 3d1b3a4711a..2a7aa299e83 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -39,7 +39,7 @@ module TodosHelper
anchor = dom_id(todo.note) if todo.note.present?
if todo.for_commit?
- namespace_project_commit_path(todo.project.namespace.becomes(Namespace), todo.project,
+ project_commit_path(todo.project,
todo.target, anchor: anchor)
else
path = [todo.project.namespace.becomes(Namespace), todo.project, todo.target]
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index a48d4475e97..ce435ca2241 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -8,6 +8,6 @@ module TriggersHelper
end
def service_trigger_url(service)
- "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger"
+ "#{Settings.gitlab.url}/api/v4/projects/#{service.project_id}/services/#{service.to_param}/trigger"
end
end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 456598b4c28..3b175251446 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -2,7 +2,7 @@ module VersionCheckHelper
def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled
image_url = VersionCheck.new.url
- image_tag image_url, class: 'js-version-status-badge'
+ image_tag image_url, class: 'js-version-status-badge', lazy: false
end
end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 0f847841295..64ca2d2eacf 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -31,7 +31,7 @@ module Emails
setup_issue_mail(issue_id, recipient_id)
@label_names = label_names
- @labels_url = namespace_project_labels_url(@project.namespace, @project)
+ @labels_url = project_labels_url(@project)
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
end
@@ -56,7 +56,7 @@ module Emails
def setup_issue_mail(issue_id, recipient_id)
@issue = Issue.find(issue_id)
@project = @issue.project
- @target_url = namespace_project_issue_url(@project.namespace, @project, @issue)
+ @target_url = project_issue_url(@project, @issue)
@sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index ec27ac517db..3626f8ce416 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -22,7 +22,7 @@ module Emails
setup_merge_request_mail(merge_request_id, recipient_id)
@label_names = label_names
- @labels_url = namespace_project_labels_url(@project.namespace, @project)
+ @labels_url = project_labels_url(@project)
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
end
@@ -59,7 +59,7 @@ module Emails
def setup_merge_request_mail(merge_request_id, recipient_id)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
- @target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request)
+ @target_url = project_merge_request_url(@project, @merge_request)
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 00707a0023e..77a82b895ce 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -4,7 +4,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@commit = @note.noteable
- @target_url = namespace_project_commit_url(*note_target_url_options)
+ @target_url = project_commit_url(*note_target_url_options)
mail_answer_thread(@commit, note_thread_options(recipient_id))
end
@@ -12,7 +12,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@issue = @note.noteable
- @target_url = namespace_project_issue_url(*note_target_url_options)
+ @target_url = project_issue_url(*note_target_url_options)
mail_answer_thread(@issue, note_thread_options(recipient_id))
end
@@ -20,7 +20,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@merge_request = @note.noteable
- @target_url = namespace_project_merge_request_url(*note_target_url_options)
+ @target_url = project_merge_request_url(*note_target_url_options)
mail_answer_thread(@merge_request, note_thread_options(recipient_id))
end
@@ -28,7 +28,7 @@ module Emails
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
- @target_url = namespace_project_snippet_url(*note_target_url_options)
+ @target_url = project_snippet_url(*note_target_url_options)
mail_answer_thread(@snippet, note_thread_options(recipient_id))
end
@@ -43,7 +43,7 @@ module Emails
private
def note_target_url_options
- [@project.namespace, @project, @note.noteable, anchor: "note_#{@note.id}"]
+ [@project, @note.noteable, anchor: "note_#{@note.id}"]
end
def note_thread_options(recipient_id)
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 256cbcd73a1..c401030e34a 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -14,7 +14,7 @@ module Emails
end
def new_ssh_key_email(key_id)
- @key = Key.find_by_id(key_id)
+ @key = Key.find_by(id: key_id)
return unless @key
@@ -22,5 +22,15 @@ module Emails
@target_url = user_url(@user)
mail(to: @user.notification_email, subject: subject("SSH key was added to your account"))
end
+
+ def new_gpg_key_email(gpg_key_id)
+ @gpg_key = GpgKey.find_by(id: gpg_key_id)
+
+ return unless @gpg_key
+
+ @current_user = @user = @gpg_key.user
+ @target_url = user_url(@user)
+ mail(to: @user.notification_email, subject: subject("GPG key was added to your account"))
+ end
end
end
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index e0af7081411..761d873c01c 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -3,7 +3,7 @@ module Emails
def project_was_moved_email(project_id, user_id, old_path_with_namespace)
@current_user = @user = User.find user_id
@project = Project.find project_id
- @target_url = namespace_project_url(@project.namespace, @project)
+ @target_url = project_url(@project)
@old_path_with_namespace = old_path_with_namespace
mail(to: @user.notification_email,
subject: subject("Project was moved"))
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index f315e38bcaa..eaac6fcb548 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,5 +1,6 @@
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
+ include GitlabRoutingHelper
include Emails::Issues
include Emails::MergeRequests
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index c79326e8427..f9c48482be7 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -10,5 +10,5 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 668caef0d2c..898ce45f60e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -13,13 +13,13 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters
}x
- serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize
- serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize
- serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize
- serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize
- serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiveRecordSerialize
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
@@ -184,6 +184,9 @@ class ApplicationSetting < ActiveRecord::Base
Rails.cache.fetch(CACHE_KEY) do
ApplicationSetting.last
end
+ rescue
+ # Fall back to an uncached value if there are any problems (e.g. redis down)
+ ApplicationSetting.last
end
def self.expire
@@ -234,6 +237,8 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
+ password_authentication_enabled: Settings.gitlab['password_authentication_enabled'],
+ performance_bar_allowed_group_id: nil,
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
@@ -247,7 +252,6 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_text: nil,
sidekiq_throttling_enabled: false,
sign_in_text: nil,
- signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0,
two_factor_grace_period: 48,
@@ -336,6 +340,48 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
+ def performance_bar_allowed_group_id=(group_full_path)
+ group_full_path = nil if group_full_path.blank?
+
+ if group_full_path.nil?
+ if group_full_path != performance_bar_allowed_group_id
+ super(group_full_path)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ return
+ end
+
+ group = Group.find_by_full_path(group_full_path)
+
+ if group
+ if group.id != performance_bar_allowed_group_id
+ super(group.id)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ else
+ super(nil)
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+ end
+
+ def performance_bar_allowed_group
+ Group.find_by_id(performance_bar_allowed_group_id)
+ end
+
+ # Return true if the Performance Bar is enabled for a given group
+ def performance_bar_enabled
+ performance_bar_allowed_group_id.present?
+ end
+
+ # - If `enable` is true, we early return since the actual attribute that holds
+ # the enabling/disabling is `performance_bar_allowed_group_id`
+ # - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil`
+ def performance_bar_enabled=(enable)
+ return if enable
+
+ self.performance_bar_allowed_group_id = nil
+ end
+
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 46d412fbd72..112a8778b4e 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,5 +1,5 @@
class AuditEvent < ActiveRecord::Base
- serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
diff --git a/app/models/blob_viewer/readme.rb b/app/models/blob_viewer/readme.rb
index 75c373a03bb..4604a9934a0 100644
--- a/app/models/blob_viewer/readme.rb
+++ b/app/models/blob_viewer/readme.rb
@@ -10,5 +10,11 @@ module BlobViewer
def visible_to?(current_user)
can?(current_user, :read_wiki, project)
end
+
+ def render_error
+ return if project.has_external_wiki? || (project.wiki_enabled? && project.wiki.has_home_page?)
+
+ :no_wiki
+ end
end
end
diff --git a/app/models/board.rb b/app/models/board.rb
index 18081a32157..97d0f550925 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -1,7 +1,7 @@
class Board < ActiveRecord::Base
belongs_to :project
- has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
+ has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
validates :project, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a300536532b..416a2a33378 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -19,8 +19,8 @@ module Ci
)
end
- serialize :options # rubocop:disable Cop/ActiverecordSerialize
- serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize
+ serialize :options # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize
delegate :name, to: :project, prefix: true
@@ -96,6 +96,14 @@ module Ci
BuildSuccessWorker.perform_async(id)
end
end
+
+ before_transition any => [:failed] do |build|
+ next if build.retries_max.zero?
+
+ if build.retries_count < build.retries_max
+ Ci::Build.retry(build, build.user)
+ end
+ end
end
def detailed_status(current_user)
@@ -130,6 +138,14 @@ module Ci
success? || failed? || canceled?
end
+ def retries_count
+ pipeline.builds.retried.where(name: self.name).count
+ end
+
+ def retries_max
+ self.options.fetch(:retry, 0).to_i
+ end
+
def latest?
!retried?
end
@@ -176,13 +192,22 @@ module Ci
# * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes
+ # * First/Last Character is not a hyphen
def ref_slug
- slugified = ref.to_s.downcase
- slugified.gsub(/[^a-z0-9]/, '-')[0..62]
+ ref.to_s
+ .downcase
+ .gsub(/[^a-z0-9]/, '-')[0..62]
+ .gsub(/(\A-+|-+\z)/, '')
end
# Variables whose value does not depend on environment
def simple_variables
+ variables(environment: nil)
+ end
+
+ # All variables, including those dependent on environment, which could
+ # contain unexpanded variables.
+ def variables(environment: persisted_environment)
variables = predefined_variables
variables += project.predefined_variables
variables += pipeline.predefined_variables
@@ -191,15 +216,13 @@ module Ci
variables += project.deployment_variables if has_environment?
variables += yaml_variables
variables += user_variables
- variables += project.secret_variables_for(ref).map(&:to_runner_variable)
+ variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
+ variables += secret_variables(environment: environment)
variables += trigger_request.user_variables if trigger_request
- variables
- end
+ variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule
+ variables += persisted_environment_variables if environment
- # All variables, including those dependent on environment, which could
- # contain unexpanded variables.
- def variables
- simple_variables.concat(persisted_environment_variables)
+ variables
end
def merge_request
@@ -213,7 +236,7 @@ module Ci
.reorder(iid: :desc)
merge_requests.find do |merge_request|
- merge_request.commits_sha.include?(pipeline.sha)
+ merge_request.commit_shas.include?(pipeline.sha)
end
end
end
@@ -367,6 +390,11 @@ module Ci
]
end
+ def secret_variables(environment: persisted_environment)
+ project.secret_variables_for(ref: ref, environment: environment)
+ .map(&:to_runner_variable)
+ end
+
def steps
[Gitlab::Ci::Build::Step.from_commands(self),
Gitlab::Ci::Build::Step.from_after_script(self)].compact
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
new file mode 100644
index 00000000000..f64bc245a67
--- /dev/null
+++ b/app/models/ci/group_variable.rb
@@ -0,0 +1,13 @@
+module Ci
+ class GroupVariable < ActiveRecord::Base
+ extend Ci::Model
+ include HasVariable
+ include Presentable
+
+ belongs_to :group
+
+ validates :key, uniqueness: { scope: :group_id }
+
+ scope :unprotected, -> { where(protected: false) }
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 364858964b0..e5b615a7cc0 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -14,14 +14,14 @@ module Ci
has_many :stages
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, foreign_key: :commit_id
- has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
+ has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
has_many :merge_requests, foreign_key: "head_pipeline_id"
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
- has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
+ has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
has_many :manual_actions, -> { latest.manual_actions.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :artifacts, -> { latest.with_artifacts_not_expired.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build'
@@ -326,10 +326,24 @@ module Ci
end
end
+ def ci_yaml_file_path
+ if project.ci_config_path.blank?
+ '.gitlab-ci.yml'
+ else
+ project.ci_config_path
+ end
+ end
+
def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file)
- @ci_yaml_file = project.repository.gitlab_ci_yml_for(sha) rescue nil
+ @ci_yaml_file = begin
+ project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
+ rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal
+ self.yaml_errors =
+ "Failed to load CI/CD config file at #{ci_yaml_file_path}"
+ nil
+ end
end
def has_yaml_errors?
@@ -377,7 +391,8 @@ module Ci
def predefined_variables
[
- { key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
+ { key: 'CI_PIPELINE_ID', value: id.to_s, public: true },
+ { key: 'CI_CONFIG_PATH', value: ci_yaml_file_path, public: true }
]
end
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 45d8cd34359..085eeeae157 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -9,17 +9,21 @@ module Ci
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
has_many :pipelines
+ has_many :variables, class_name: 'Ci::PipelineScheduleVariable'
validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :description, presence: true
+ validates :variables, variable_duplicates: true
before_save :set_next_run_at
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
+ accepts_nested_attributes_for :variables, allow_destroy: true
+
def owned_by?(current_user)
owner == current_user
end
@@ -36,10 +40,6 @@ module Ci
update_attribute(:active, false)
end
- def runnable_by_owner?
- Ability.allowed?(owner, :create_pipeline, project)
- end
-
def set_next_run_at
self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now)
end
@@ -56,5 +56,9 @@ module Ci
Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone)
.next_time_from(next_run_at)
end
+
+ def job_variables
+ variables&.map(&:to_runner_variable) || []
+ end
end
end
diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb
new file mode 100644
index 00000000000..1ff177616e8
--- /dev/null
+++ b/app/models/ci/pipeline_schedule_variable.rb
@@ -0,0 +1,8 @@
+module Ci
+ class PipelineScheduleVariable < ActiveRecord::Base
+ extend Ci::Model
+ include HasVariable
+
+ belongs_to :pipeline_schedule
+ end
+end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index d12f96f3d0b..c6d23898560 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -3,12 +3,12 @@ module Ci
extend Ci::Model
RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
- LAST_CONTACT_TIME = 1.hour.ago
+ ONLINE_CONTACT_TIMEOUT = 1.hour
AVAILABLE_SCOPES = %w[specific shared active paused online].freeze
FORM_EDITABLE = %i[description tag_list active run_untagged locked].freeze
has_many :builds
- has_many :runner_projects, dependent: :destroy
+ has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects
has_one :last_build, ->() { order('id DESC') }, class_name: 'Ci::Build'
@@ -19,7 +19,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
- scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
+ scope :online, ->() { where('contacted_at > ?', contact_time_deadline) }
scope :ordered, ->() { order(id: :desc) }
scope :owned_or_shared, ->(project_id) do
@@ -59,6 +59,10 @@ module Ci
where(t[:token].matches(pattern).or(t[:description].matches(pattern)))
end
+ def self.contact_time_deadline
+ ONLINE_CONTACT_TIMEOUT.ago
+ end
+
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
end
@@ -80,7 +84,7 @@ module Ci
end
def online?
- contacted_at && contacted_at > LAST_CONTACT_TIME
+ contacted_at && contacted_at > self.class.contact_time_deadline
end
def status
@@ -145,7 +149,7 @@ module Ci
private
def cleanup_runner_queue
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Queues.with do |redis|
redis.del(runner_queue_key)
end
end
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 564334ad1ad..c58ce5c3717 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -6,7 +6,7 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id
has_many :builds
- serialize :variables # rubocop:disable Cop/ActiverecordSerialize
+ serialize :variables # rubocop:disable Cop/ActiveRecordSerialize
def user_variables
return [] unless variables
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 0b8d0ff881a..cf0fe04ddaf 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -2,6 +2,7 @@ module Ci
class Variable < ActiveRecord::Base
extend Ci::Model
include HasVariable
+ include Presentable
belongs_to :project
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 20206d57c4c..7940733f557 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -1,5 +1,6 @@
class Commit
extend ActiveModel::Naming
+ extend Gitlab::Cache::RequestCache
include ActiveModel::Conversion
include Noteable
@@ -138,7 +139,7 @@ class Commit
safe_message.split("\n", 2)[1].try(:chomp)
end
-
+
def description?
description.present?
end
@@ -169,19 +170,9 @@ class Commit
end
def author
- if RequestStore.active?
- key = "commit_author:#{author_email.downcase}"
- # nil is a valid value since no author may exist in the system
- if RequestStore.store.key?(key)
- @author = RequestStore.store[key]
- else
- @author = find_author_by_any_email
- RequestStore.store[key] = @author
- end
- else
- @author ||= find_author_by_any_email
- end
+ User.find_by_any_email(author_email.downcase)
end
+ request_cache(:author) { author_email.downcase }
def committer
@committer ||= User.find_by_any_email(committer_email.downcase)
@@ -243,6 +234,14 @@ class Commit
@statuses[ref] = pipelines.latest_status(ref)
end
+ def signature
+ return @signature if defined?(@signature)
+
+ @signature = gpg_commit.signature
+ end
+
+ delegate :has_signature?, to: :gpg_commit
+
def revert_branch_name
"revert-#{short_id}"
end
@@ -322,7 +321,7 @@ class Commit
def raw_diffs(*args)
if Gitlab::GitalyClient.feature_enabled?(:commit_raw_diffs)
- Gitlab::GitalyClient::Commit.new(project.repository).diff_from_parent(self, *args)
+ Gitlab::GitalyClient::CommitService.new(project.repository).diff_from_parent(self, *args)
else
raw.diffs(*args)
end
@@ -331,7 +330,7 @@ class Commit
def raw_deltas
@deltas ||= Gitlab::GitalyClient.migrate(:commit_deltas) do |is_enabled|
if is_enabled
- Gitlab::GitalyClient::Commit.new(project.repository).commit_deltas(self)
+ Gitlab::GitalyClient::CommitService.new(project.repository).commit_deltas(self)
else
raw.deltas
end
@@ -368,10 +367,6 @@ class Commit
end
end
- def find_author_by_any_email
- User.find_by_any_email(author_email.downcase)
- end
-
def repo_changes
changes = { added: [], modified: [], removed: [] }
@@ -395,4 +390,8 @@ class Commit
def merged_merge_request_no_cache(user)
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
end
+
+ def gpg_commit
+ @gpg_commit ||= Gitlab::Gpg::Commit.new(self)
+ end
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index a7fd0a15f0f..f4f9b037957 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,7 +2,7 @@ module Awardable
extend ActiveSupport::Concern
included do
- has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy
+ has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
if self < Participable
# By default we always load award_emoji user association
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index eb32bf3d32a..48547a938fc 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -11,7 +11,7 @@ module CacheMarkdownField
extend ActiveSupport::Concern
# Increment this number every time the renderer changes its output
- CACHE_VERSION = 1
+ CACHE_VERSION = 2
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
@@ -78,7 +78,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field)
- cached = !cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil?
+ cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present?
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false
diff --git a/app/models/concerns/created_at_filterable.rb b/app/models/concerns/created_at_filterable.rb
new file mode 100644
index 00000000000..e8a3e41203d
--- /dev/null
+++ b/app/models/concerns/created_at_filterable.rb
@@ -0,0 +1,12 @@
+module CreatedAtFilterable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :created_before, ->(date) { where(scoped_table[:created_at].lteq(date)) }
+ scope :created_after, ->(date) { where(scoped_table[:created_at].gteq(date)) }
+
+ def self.scoped_table
+ arel_table.alias(table_name)
+ end
+ end
+end
diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb
new file mode 100644
index 00000000000..6ddbb8da1a9
--- /dev/null
+++ b/app/models/concerns/each_batch.rb
@@ -0,0 +1,81 @@
+module EachBatch
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Iterates over the rows in a relation in batches, similar to Rails'
+ # `in_batches` but in a more efficient way.
+ #
+ # Unlike `in_batches` provided by Rails this method does not support a
+ # custom start/end range, nor does it provide support for the `load:`
+ # keyword argument.
+ #
+ # This method will yield an ActiveRecord::Relation to the supplied block, or
+ # return an Enumerator if no block is given.
+ #
+ # Example:
+ #
+ # User.each_batch do |relation|
+ # relation.update_all(updated_at: Time.now)
+ # end
+ #
+ # The supplied block is also passed an optional batch index:
+ #
+ # User.each_batch do |relation, index|
+ # puts index # => 1, 2, 3, ...
+ # end
+ #
+ # You can also specify an alternative column to use for ordering the rows:
+ #
+ # User.each_batch(column: :created_at) do |relation|
+ # ...
+ # end
+ #
+ # This will produce SQL queries along the lines of:
+ #
+ # User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
+ # (0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
+ #
+ # of - The number of rows to retrieve per batch.
+ # column - The column to use for ordering the batches.
+ def each_batch(of: 1000, column: primary_key)
+ unless column
+ raise ArgumentError,
+ 'the column: argument must be set to a column name to use for ordering rows'
+ end
+
+ start = except(:select)
+ .select(column)
+ .reorder(column => :asc)
+ .take
+
+ return unless start
+
+ start_id = start[column]
+ arel_table = self.arel_table
+
+ 1.step do |index|
+ stop = except(:select)
+ .select(column)
+ .where(arel_table[column].gteq(start_id))
+ .reorder(column => :asc)
+ .offset(of)
+ .limit(1)
+ .take
+
+ relation = where(arel_table[column].gteq(start_id))
+
+ if stop
+ stop_id = stop[column]
+ start_id = stop_id
+ relation = relation.where(arel_table[column].lt(stop_id))
+ end
+
+ # Any ORDER BYs are useless for this relation and can lead to less
+ # efficient UPDATE queries, hence we get rid of it.
+ yield relation.except(:order), index
+
+ break unless stop
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/editable.rb b/app/models/concerns/editable.rb
index c62c7e1e936..28623d257a6 100644
--- a/app/models/concerns/editable.rb
+++ b/app/models/concerns/editable.rb
@@ -4,4 +4,8 @@ module Editable
def is_edited?
last_edited_at.present? && last_edited_at != created_at
end
+
+ def last_edited_by
+ super || User.ghost
+ end
end
diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb
index 5382dde6765..67a0adfcd56 100644
--- a/app/models/concerns/internal_id.rb
+++ b/app/models/concerns/internal_id.rb
@@ -8,7 +8,8 @@ module InternalId
def set_iid
if iid.blank?
- records = project.send(self.class.name.tableize)
+ parent = project || group
+ records = parent.send(self.class.name.tableize)
records = records.with_deleted if self.paranoid?
max_iid = records.maximum(:iid)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 41c8b525273..13fe9d09c69 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -30,7 +30,8 @@ module Issuable
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
belongs_to :milestone
- has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
+
+ has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do # rubocop:disable Cop/ActiveRecordDependent
def authors_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
@@ -42,9 +43,9 @@ module Issuable
end
end
- has_many :label_links, as: :target, dependent: :destroy
+ has_many :label_links, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, through: :label_links
- has_many :todos, as: :target, dependent: :destroy
+ has_many :todos, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :metrics
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 01599ce49c6..f0998465822 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -70,6 +70,22 @@ module Milestoneish
due_date && due_date.past?
end
+ def is_group_milestone?
+ false
+ end
+
+ def is_project_milestone?
+ false
+ end
+
+ def is_legacy_group_milestone?
+ false
+ end
+
+ def is_dashboard_milestone?
+ false
+ end
+
private
def count_issues_by_state(user)
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index 47e71c58557..ef95d6b0f98 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -17,7 +17,13 @@ module ProtectedRef
class_methods do
def protected_ref_access_levels(*types)
types.each do |type|
- has_many :"#{type}_access_levels", dependent: :destroy
+ # We need to set `inverse_of` to make sure the `belongs_to`-object is set
+ # when creating children using `accepts_nested_attributes_for`.
+ #
+ # If we don't `protected_branch` or `protected_tag` would be empty and
+ # `project` cannot be delegated to it, which in turn would cause validations
+ # to fail.
+ has_many :"#{type}_access_levels", dependent: :destroy, inverse_of: self.model_name.singular # rubocop:disable Cop/ActiveRecordDependent
validates :"#{type}_access_levels", length: { is: 1, message: "are restricted to a single instance per #{self.model_name.human}." }
@@ -25,8 +31,8 @@ module ProtectedRef
end
end
- def protected_ref_accessible_to?(ref, user, action:)
- access_levels_for_ref(ref, action: action).any? do |access_level|
+ def protected_ref_accessible_to?(ref, user, action:, protected_refs: nil)
+ access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level|
access_level.check_access(user)
end
end
@@ -37,8 +43,9 @@ module ProtectedRef
end
end
- def access_levels_for_ref(ref, action:)
- self.matching(ref).map(&:"#{action}_access_levels").flatten
+ def access_levels_for_ref(ref, action:, protected_refs: nil)
+ self.matching(ref, protected_refs: protected_refs)
+ .map(&:"#{action}_access_levels").flatten
end
def matching(ref_name, protected_refs: nil)
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index ee108f010a6..f5048d17d80 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -4,8 +4,8 @@ module Routable
extend ActiveSupport::Concern
included do
- has_one :route, as: :source, autosave: true, dependent: :destroy
- has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy
+ has_one :route, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates_associated :route
validates :route, presence: true
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index c28974a3cdf..67ecf470f7e 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -3,6 +3,8 @@ module ShaAttribute
module ClassMethods
def sha_attribute(name)
+ return unless table_exists?
+
column = columns.find { |c| c.name == name.to_s }
# In case the table doesn't exist we won't be able to find the column,
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 647a6cad3d7..bd75f25a210 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -8,7 +8,7 @@ module Spammable
end
included do
- has_one :user_agent_detail, as: :subject, dependent: :destroy
+ has_one :user_agent_detail, as: :subject, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
attr_accessor :spam
attr_accessor :spam_log
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
index f60a0f8f438..274b38a7708 100644
--- a/app/models/concerns/subscribable.rb
+++ b/app/models/concerns/subscribable.rb
@@ -9,7 +9,7 @@ module Subscribable
extend ActiveSupport::Concern
included do
- has_many :subscriptions, dependent: :destroy, as: :subscribable
+ has_many :subscriptions, dependent: :destroy, as: :subscribable # rubocop:disable Cop/ActiveRecordDependent
end
def subscribed?(user, project = nil)
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
index 9cf83440784..b517ddaebd7 100644
--- a/app/models/concerns/time_trackable.rb
+++ b/app/models/concerns/time_trackable.rb
@@ -18,7 +18,7 @@ module TimeTrackable
validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false
validate :check_negative_time_spent
- has_many :timelogs, dependent: :destroy
+ has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end
def spend_time(options)
diff --git a/app/models/dashboard_milestone.rb b/app/models/dashboard_milestone.rb
index 646c1e5ce1a..fac7c5e5c85 100644
--- a/app/models/dashboard_milestone.rb
+++ b/app/models/dashboard_milestone.rb
@@ -2,4 +2,8 @@ class DashboardMilestone < GlobalMilestone
def issues_finder_params
{ authorized_only: true }
end
+
+ def is_dashboard_milestone?
+ true
+ end
end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 053f2a11aa0..51768dd96bc 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,5 +1,5 @@
class DeployKey < Key
- has_many :deploy_keys_projects, dependent: :destroy
+ has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 20ef1378500..e9a60e6ce09 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -6,9 +6,9 @@ class DiffNote < Note
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
- serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
- serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
- serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
validates :original_position, presence: true
validates :position, presence: true
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 66c96d0f586..e9ebf0637f3 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -6,7 +6,7 @@ class Environment < ActiveRecord::Base
belongs_to :project, required: true, validate: true
- has_many :deployments, dependent: :destroy
+ has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment'
before_validation :nullify_external_url
@@ -218,8 +218,7 @@ class Environment < ActiveRecord::Base
end
def etag_cache_key
- Gitlab::Routing.url_helpers.namespace_project_environments_path(
- project.namespace,
+ Gitlab::Routing.url_helpers.project_environments_path(
project,
format: :json)
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 29bc141c5cd..8d93a228494 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -50,7 +50,7 @@ class Event < ActiveRecord::Base
belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
# For Hash only
- serialize :data # rubocop:disable Cop/ActiverecordSerialize
+ serialize :data # rubocop:disable Cop/ActiveRecordSerialize
# Callbacks
after_create :reset_project_activity
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 698a7bbd327..2a1b7564962 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,7 +2,7 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
- delegate :color, :description, to: :@first_label
+ delegate :color, :text_color, :description, to: :@first_label
def for_display
@first_label
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 538615130a7..c0864769314 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -2,6 +2,7 @@ class GlobalMilestone
include Milestoneish
EPOCH = DateTime.parse('1970-01-01')
+ STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -11,7 +12,10 @@ class GlobalMilestone
end
def self.build_collection(projects, params)
- child_milestones = MilestonesFinder.new.execute(projects, params)
+ params =
+ { project_ids: projects.map(&:id), state: params[:state] }
+
+ child_milestones = MilestonesFinder.new(params).execute
milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped|
milestones_relation = Milestone.where(id: grouped.map(&:id))
@@ -28,13 +32,42 @@ class GlobalMilestone
new(title, child_milestones)
end
- def self.states_count(projects)
- relation = MilestonesFinder.new.execute(projects, state: 'all')
- milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
+ def self.states_count(projects, group = nil)
+ legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
+ group_milestones_count = group_milestones_states_count(group)
+
+ legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count|
+ legacy_group_milestones_count + group_milestones_count
+ end
+ end
+
+ def self.group_milestones_states_count(group)
+ return STATE_COUNT_HASH unless group
+
+ params = { group_ids: [group.id], state: 'all', order: nil }
+
+ relation = MilestonesFinder.new(params).execute
+ grouped_by_state = relation.group(:state).count
+
+ {
+ opened: grouped_by_state['active'] || 0,
+ closed: grouped_by_state['closed'] || 0,
+ all: grouped_by_state.values.sum
+ }
+ end
+
+ # Counts the legacy group milestones which must be grouped by title
+ def self.legacy_group_milestone_states_count(projects)
+ return STATE_COUNT_HASH unless projects
+
+ params = { project_ids: projects.map(&:id), state: 'all', order: nil }
+
+ relation = MilestonesFinder.new(params).execute
+ project_milestones_by_state_and_title = relation.group(:state, :title).count
- opened = count_by_state(milestones_by_state_and_title, 'active')
- closed = count_by_state(milestones_by_state_and_title, 'closed')
- all = milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
+ opened = count_by_state(project_milestones_by_state_and_title, 'active')
+ closed = count_by_state(project_milestones_by_state_and_title, 'closed')
+ all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count
{
opened: opened,
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
new file mode 100644
index 00000000000..3df60ddc950
--- /dev/null
+++ b/app/models/gpg_key.rb
@@ -0,0 +1,107 @@
+class GpgKey < ActiveRecord::Base
+ KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
+ KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
+
+ include ShaAttribute
+
+ sha_attribute :primary_keyid
+ sha_attribute :fingerprint
+
+ belongs_to :user
+ has_many :gpg_signatures
+
+ validates :user, presence: true
+
+ validates :key,
+ presence: true,
+ uniqueness: true,
+ format: {
+ with: /\A#{KEY_PREFIX}((?!#{KEY_PREFIX})(?!#{KEY_SUFFIX}).)+#{KEY_SUFFIX}\Z/m,
+ message: "is invalid. A valid public GPG key begins with '#{KEY_PREFIX}' and ends with '#{KEY_SUFFIX}'"
+ }
+
+ validates :fingerprint,
+ presence: true,
+ uniqueness: true,
+ # only validate when the `key` is valid, as we don't want the user to show
+ # the error about the fingerprint
+ unless: -> { errors.has_key?(:key) }
+
+ validates :primary_keyid,
+ presence: true,
+ uniqueness: true,
+ # only validate when the `key` is valid, as we don't want the user to show
+ # the error about the fingerprint
+ unless: -> { errors.has_key?(:key) }
+
+ before_validation :extract_fingerprint, :extract_primary_keyid
+ after_commit :update_invalid_gpg_signatures, on: :create
+ after_commit :notify_user, on: :create
+
+ def primary_keyid
+ super&.upcase
+ end
+
+ def fingerprint
+ super&.upcase
+ end
+
+ def key=(value)
+ super(value&.strip)
+ end
+
+ def user_infos
+ @user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
+ end
+
+ def verified_user_infos
+ user_infos.select do |user_info|
+ user_info[:email] == user.email
+ end
+ end
+
+ def emails_with_verified_status
+ user_infos.map do |user_info|
+ [
+ user_info[:email],
+ user_info[:email] == user.email
+ ]
+ end.to_h
+ end
+
+ def verified?
+ emails_with_verified_status.any? { |_email, verified| verified }
+ end
+
+ def update_invalid_gpg_signatures
+ InvalidGpgSignatureUpdateWorker.perform_async(self.id)
+ end
+
+ def revoke
+ GpgSignature.where(gpg_key: self, valid_signature: true).update_all(
+ gpg_key_id: nil,
+ valid_signature: false,
+ updated_at: Time.zone.now
+ )
+
+ destroy
+ end
+
+ private
+
+ def extract_fingerprint
+ # we can assume that the result only contains one item as the validation
+ # only allows one key
+ self.fingerprint = Gitlab::Gpg.fingerprints_from_key(key).first
+ end
+
+ def extract_primary_keyid
+ # we can assume that the result only contains one item as the validation
+ # only allows one key
+ self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
+ end
+
+ def notify_user
+ NotificationService.new.new_gpg_key(self)
+ end
+end
diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb
new file mode 100644
index 00000000000..1ac0e123ff1
--- /dev/null
+++ b/app/models/gpg_signature.rb
@@ -0,0 +1,21 @@
+class GpgSignature < ActiveRecord::Base
+ include ShaAttribute
+
+ sha_attribute :commit_sha
+ sha_attribute :gpg_key_primary_keyid
+
+ belongs_to :project
+ belongs_to :gpg_key
+
+ validates :commit_sha, presence: true
+ validates :project_id, presence: true
+ validates :gpg_key_primary_keyid, presence: true
+
+ def gpg_key_primary_keyid
+ super&.upcase
+ end
+
+ def commit
+ project.commit(commit_sha)
+ end
+end
diff --git a/app/models/group.rb b/app/models/group.rb
index a6fdb30f84c..bd5735ed82e 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -2,13 +2,12 @@ require 'carrierwave/orm/activerecord'
class Group < Namespace
include Gitlab::ConfigHelper
- include Gitlab::VisibilityLevel
include AccessRequestable
include Avatarable
include Referable
include SelectForProjectAuthorization
- has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
+ has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
has_many :users, through: :group_members
has_many :owners,
@@ -16,12 +15,14 @@ class Group < Namespace
through: :group_members,
source: :user
- has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember'
+ has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
- has_many :project_group_links, dependent: :destroy
+ has_many :milestones
+ has_many :project_group_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :shared_projects, through: :project_group_links, source: :project
- has_many :notification_settings, dependent: :destroy, as: :source
+ has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, class_name: 'GroupLabel'
+ has_many :variables, class_name: 'Ci::GroupVariable'
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects
@@ -31,7 +32,7 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
after_create :post_create_hook
after_destroy :post_destroy_hook
@@ -101,10 +102,6 @@ class Group < Namespace
full_name
end
- def visibility_level_field
- :visibility_level
- end
-
def visibility_level_allowed_by_projects
allowed_by_projects = self.projects.where('visibility_level > ?', self.visibility_level).none?
@@ -170,10 +167,14 @@ class Group < Namespace
end
def has_owner?(user)
+ return false unless user
+
members_with_parents.owners.where(user_id: user).any?
end
def has_master?(user)
+ return false unless user
+
members_with_parents.masters.where(user_id: user).any?
end
@@ -215,7 +216,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.non_request.where(source_id: ancestors.pluck(:id).push(id))
+ GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil)
end
def users_with_parents
@@ -248,6 +249,14 @@ class Group < Namespace
}
end
+ def secret_variables_for(ref, project)
+ list_of_ids = [self] + ancestors
+ variables = Ci::GroupVariable.where(group: list_of_ids)
+ variables = variables.unprotected unless project.protected_for?(ref)
+ variables = variables.group_by(&:group_id)
+ list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
+ end
+
protected
def update_two_factor_requirement
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 86d38e5468b..65249bd7bfc 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -16,4 +16,8 @@ class GroupMilestone < GlobalMilestone
def issues_finder_params
{ group_id: group.id }
end
+
+ def is_legacy_group_milestone?
+ true
+ end
end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index ee6165fd32d..a8c424a6614 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,11 +1,20 @@
class ProjectHook < WebHook
- belongs_to :project
+ TRIGGERS = {
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events,
+ issue_hooks: :issues_events,
+ confidential_issue_hooks: :confidential_issues_events,
+ note_hooks: :note_events,
+ merge_request_hooks: :merge_requests_events,
+ job_hooks: :job_events,
+ pipeline_hooks: :pipeline_events,
+ wiki_page_hooks: :wiki_page_events
+ }.freeze
+
+ TRIGGERS.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
- scope :issue_hooks, -> { where(issues_events: true) }
- scope :confidential_issue_hooks, -> { where(confidential_issues_events: true) }
- scope :note_hooks, -> { where(note_events: true) }
- scope :merge_request_hooks, -> { where(merge_requests_events: true) }
- scope :job_hooks, -> { where(job_events: true) }
- scope :pipeline_hooks, -> { where(pipeline_events: true) }
- scope :wiki_page_hooks, -> { where(wiki_page_events: true) }
+ belongs_to :project
+ validates :project, presence: true
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 40e43c27f91..aef11514945 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -1,5 +1,6 @@
class ServiceHook < WebHook
belongs_to :service
+ validates :service, presence: true
def execute(data)
WebHookService.new(self, data, 'service_hook').execute
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 1584235ab00..180c479c41b 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,5 +1,13 @@
class SystemHook < WebHook
- scope :repository_update_hooks, -> { where(repository_update_events: true) }
+ TRIGGERS = {
+ repository_update_hooks: :repository_update_events,
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events
+ }.freeze
+
+ TRIGGERS.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
default_value_for :push_events, false
default_value_for :repository_update_events, true
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7503f3739c3..5a70e114f56 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,21 +1,7 @@
class WebHook < ActiveRecord::Base
include Sortable
- default_value_for :push_events, true
- default_value_for :issues_events, false
- default_value_for :confidential_issues_events, false
- default_value_for :note_events, false
- default_value_for :merge_requests_events, false
- default_value_for :tag_push_events, false
- default_value_for :job_events, false
- default_value_for :pipeline_events, false
- default_value_for :repository_update_events, false
- default_value_for :enable_ssl_verification, true
-
- has_many :web_hook_logs, dependent: :destroy
-
- scope :push_hooks, -> { where(push_events: true) }
- scope :tag_push_hooks, -> { where(tag_push_events: true) }
+ has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, url: true
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index d73cfcf630d..e72c125fb69 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,9 +1,9 @@
class WebHookLog < ActiveRecord::Base
belongs_to :web_hook
- serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
- serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize
- serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :request_data, Hash # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :response_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
validates :web_hook, presence: true
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 3a9a6dba601..400bb55d2f0 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys
include RelativePositioning
include IgnorableColumn
+ include CreatedAtFilterable
ignore_column :position
@@ -23,9 +24,14 @@ class Issue < ActiveRecord::Base
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ has_many :merge_requests_closing_issues,
+ class_name: 'MergeRequestsClosingIssues',
+ dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
+ has_many :issue_assignees
+ has_many :assignees, class_name: "User", through: :issue_assignees
has_many :issue_assignees
has_many :assignees, class_name: "User", through: :issue_assignees
@@ -45,8 +51,6 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
- scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
-
scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache
@@ -295,11 +299,7 @@ class Issue < ActiveRecord::Base
end
def expire_etag_cache
- key = Gitlab::Routing.url_helpers.realtime_changes_namespace_project_issue_path(
- project.namespace,
- project,
- self
- )
+ key = Gitlab::Routing.url_helpers.realtime_changes_project_issue_path(project, self)
Gitlab::EtagCaching::Store.new.touch(key)
end
end
diff --git a/app/models/label.rb b/app/models/label.rb
index ed6a8411da9..674bb3f2720 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -15,9 +15,9 @@ class Label < ActiveRecord::Base
default_value_for :color, DEFAULT_COLOR
- has_many :lists, dependent: :destroy
+ has_many :lists, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :priorities, class_name: 'LabelPriority'
- has_many :label_links, dependent: :destroy
+ has_many :label_links, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 2d5909ab25e..c36be956ff0 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -7,7 +7,7 @@
class LegacyDiffNote < Note
include NoteOnDiff
- serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize
+ serialize :st_diff # rubocop:disable Cop/ActiveRecordSerialize
validates :line_code, presence: true, line_code: true
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 7712d5783e0..b7cf96abe83 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,5 +1,5 @@
class LfsObject < ActiveRecord::Base
- has_many :lfs_objects_projects, dependent: :destroy
+ has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :lfs_objects_projects
validates :oid, presence: true, uniqueness: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 808212c780c..a910099b4c1 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -5,6 +5,7 @@ class MergeRequest < ActiveRecord::Base
include Referable
include Sortable
include IgnorableColumn
+ include CreatedAtFilterable
ignore_column :position
@@ -12,26 +13,25 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
- has_many :merge_request_diffs, dependent: :destroy
+ has_many :merge_request_diffs
has_one :merge_request_diff,
- -> { order('merge_request_diffs.id DESC') }
+ -> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+ has_many :merge_requests_closing_issues,
+ class_name: 'MergeRequestsClosingIssues',
+ dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
belongs_to :assignee, class_name: "User"
- serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize
+ serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
- delegate :commits, :real_size, :commits_sha, :commits_count,
- to: :merge_request_diff, prefix: nil
-
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
@@ -221,6 +221,36 @@ class MergeRequest < ActiveRecord::Base
"#{project.to_reference(from, full: full)}#{reference}"
end
+ def commits
+ if persisted?
+ merge_request_diff.commits
+ elsif compare_commits
+ compare_commits.reverse
+ else
+ []
+ end
+ end
+
+ def commits_count
+ if persisted?
+ merge_request_diff.commits_count
+ elsif compare_commits
+ compare_commits.size
+ else
+ 0
+ end
+ end
+
+ def commit_shas
+ if persisted?
+ merge_request_diff.commit_shas
+ elsif compare_commits
+ compare_commits.reverse.map(&:sha)
+ else
+ []
+ end
+ end
+
def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
@@ -243,9 +273,7 @@ class MergeRequest < ActiveRecord::Base
def diff_size
# Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here.
- return real_size if merge_request_diff
-
- diffs.real_size
+ merge_request_diff&.real_size || diffs.real_size
end
def diff_base_commit
@@ -516,7 +544,7 @@ class MergeRequest < ActiveRecord::Base
def related_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
- commit_ids = commits.last(commits_for_notes_limit).map(&:id)
+ commit_ids = commit_shas.take(commits_for_notes_limit)
Note.where(
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
@@ -568,7 +596,7 @@ class MergeRequest < ActiveRecord::Base
# running `ReferenceExtractor` on each of them separately.
# This optimization does not apply to issues from external sources.
def cache_merge_request_closes_issues!(current_user)
- return if project.has_external_issue_tracker?
+ return unless project.issues_enabled?
transaction do
self.merge_requests_closing_issues.delete_all
@@ -839,15 +867,18 @@ class MergeRequest < ActiveRecord::Base
return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines
- .where(sha: all_commits_sha, ref: source_branch)
+ .where(sha: all_commit_shas, ref: source_branch)
.order(id: :desc)
end
# Note that this could also return SHA from now dangling commits
#
- def all_commits_sha
+ def all_commit_shas
if persisted?
- merge_request_diffs.flat_map(&:commits_sha).uniq
+ column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).pluck('DISTINCT(sha)')
+ serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas)
+
+ (column_shas + serialised_shas).uniq
elsif compare_commits
compare_commits.to_a.reverse.map(&:id)
else
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index f1ee4d3f7a9..ec87aee9310 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -11,9 +11,10 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
+ has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
- serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
- serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
+ serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize
state_machine :state, initial: :empty do
state :collected
@@ -47,14 +48,13 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
- ensure_commits_sha
+ ensure_commit_shas
save_commits
- reload_commits
save_diffs
keep_around_commits
end
- def ensure_commits_sha
+ def ensure_commit_shas
merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
@@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base
# created before version 8.4 that does not store head_commit_sha in separate db field.
def head_commit_sha
if persisted? && super.nil?
- last_commit.try(:sha)
+ last_commit_sha
else
super
end
@@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits
- @commits ||= load_commits(st_commits)
+ @commits ||= load_commits
end
- def reload_commits
- @commits = nil
- commits
- end
-
- def last_commit
- commits.first
+ def last_commit_sha
+ commit_shas.first
end
def first_commit
@@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(head_commit_sha)
end
- def commits_sha
- st_commits.map { |commit| commit[:id] }
+ def commit_shas
+ if st_commits.present?
+ st_commits.map { |commit| commit[:id] }
+ else
+ merge_request_diff_commits.map(&:sha)
+ end
end
def diff_refs=(new_diff_refs)
@@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
- st_commits.count
+ if st_commits.present?
+ st_commits.size
+ else
+ merge_request_diff_commits.size
+ end
end
def utf8_st_diffs
@@ -231,35 +234,23 @@ class MergeRequestDiff < ActiveRecord::Base
raw.any? { |element| VALID_CLASSES.include?(element.class) }
end
- def dump_commits(commits)
- commits.map(&:to_hash)
- end
-
- def load_commits(array)
- array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
- end
-
- # Load all commits related to current merge request diff from repo
- # and save it as array of hashes in st_commits db field
- def save_commits
- new_attributes = {}
-
- commits = compare.commits
-
- if commits.present?
- commits = Commit.decorate(commits, merge_request.source_project).reverse
- new_attributes[:st_commits] = dump_commits(commits)
- end
-
- update_columns_serialized(new_attributes)
- end
-
def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index|
- diff.to_hash.merge(
+ diff_hash = diff.to_hash.merge(
+ binary: false,
merge_request_diff_id: self.id,
relative_order: index
)
+
+ # Compatibility with old diffs created with Psych.
+ diff_hash.tap do |hash|
+ diff_text = hash[:diff]
+
+ if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
+ hash[:binary] = true
+ hash[:diff] = [diff_text].pack('m0')
+ end
+ end
end
Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
@@ -288,18 +279,22 @@ class MergeRequestDiff < ActiveRecord::Base
st_diffs
end
elsif merge_request_diff_files.present?
- merge_request_diff_files
- .as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS)
- .map(&:with_indifferent_access)
+ merge_request_diff_files.map(&:to_hash)
end
end
- # Load diffs between branches related to current merge request diff from repo
- # and save it as array of hashes in st_diffs db field
+ def load_commits
+ commits = st_commits.presence || merge_request_diff_commits
+
+ commits.map do |commit|
+ Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project)
+ end
+ end
+
def save_diffs
new_attributes = {}
- if commits.size.zero?
+ if compare.commits.size.zero?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
@@ -319,7 +314,13 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
- update_columns_serialized(new_attributes)
+ update(new_attributes)
+ end
+
+ def save_commits
+ MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
+
+ merge_request_diff_commits.reload
end
def repository
@@ -332,29 +333,6 @@ class MergeRequestDiff < ActiveRecord::Base
project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end
- #
- # #save or #update_attributes providing changes on serialized attributes do a lot of
- # serialization and deserialization calls resulting in bad performance.
- # Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide.
- # As a tradeoff we need to reload the current instance to properly manage time objects on those serialized
- # attributes. So to keep the same behaviour as the attribute assignment we reload the instance.
- # The difference is in the usage of
- # #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns)
- #
- # Ex:
- #
- # new_attributes[:st_commits].first.slice(:committed_date)
- # => {:committed_date=>2014-02-27 11:01:38 +0200}
- # YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date)))
- # => {:committed_date=>2014-02-27 10:01:38 +0100}
- #
- def update_columns_serialized(new_attributes)
- return unless new_attributes.any?
-
- update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
- reload
- end
-
def keep_around_commits
[repository, merge_request.source_project.repository].each do |repo|
repo.keep_around(start_commit_sha)
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
new file mode 100644
index 00000000000..cafdbe11849
--- /dev/null
+++ b/app/models/merge_request_diff_commit.rb
@@ -0,0 +1,38 @@
+class MergeRequestDiffCommit < ActiveRecord::Base
+ include ShaAttribute
+
+ belongs_to :merge_request_diff
+
+ sha_attribute :sha
+ alias_attribute :id, :sha
+
+ def self.create_bulk(merge_request_diff_id, commits)
+ sha_attribute = Gitlab::Database::ShaAttribute.new
+
+ rows = commits.map.with_index do |commit, index|
+ # See #parent_ids.
+ commit_hash = commit.to_hash.except(:parent_ids)
+ sha = commit_hash.delete(:id)
+
+ commit_hash.merge(
+ merge_request_diff_id: merge_request_diff_id,
+ relative_order: index,
+ sha: sha_attribute.type_cast_for_database(sha)
+ )
+ end
+
+ Gitlab::Database.bulk_insert(self.table_name, rows)
+ end
+
+ def to_hash
+ Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash|
+ hash[key] = public_send(key)
+ end
+ end
+
+ # We don't save these, because they would need a table or a serialised
+ # field. They aren't used anywhere, so just pretend the commit has no parents.
+ def parent_ids
+ []
+ end
+end
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
index 598ebd4d829..1199ff5af22 100644
--- a/app/models/merge_request_diff_file.rb
+++ b/app/models/merge_request_diff_file.rb
@@ -8,4 +8,14 @@ class MergeRequestDiffFile < ActiveRecord::Base
encode_utf8(diff) if diff.respond_to?(:encoding)
end
+
+ def diff
+ binary? ? super.unpack('m0').first : super
+ end
+
+ def to_hash
+ keys = Gitlab::Git::Diff::SERIALIZE_KEYS - [:diff]
+
+ as_json(only: keys).merge(diff: diff).with_indifferent_access
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d2e2749f70d..48d00764965 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -18,17 +18,32 @@ class Milestone < ActiveRecord::Base
cache_markdown_field :description
belongs_to :project
+ belongs_to :group
+
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :events, as: :target, dependent: :destroy
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :of_groups, ->(ids) { where(group_id: ids) }
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
- scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :for_projects, -> { where(group: nil).includes(:project) }
+
+ scope :for_projects_and_groups, -> (project_ids, group_ids) do
+ conditions = []
+ conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any?
+ conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any?
+
+ where(conditions.reduce(:or))
+ end
+
+ validates :group, presence: true, unless: :project
+ validates :project, presence: true, unless: :group
- validates :title, presence: true, uniqueness: { scope: :project_id }
- validates :project, presence: true
+ validate :uniqueness_of_title, if: :title_changed?
+ validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
strip_attributes :title
@@ -63,6 +78,14 @@ class Milestone < ActiveRecord::Base
where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
end
+
+ def filter_by_state(milestones, state)
+ case state
+ when 'closed' then milestones.closed
+ when 'all' then milestones
+ else milestones.active
+ end
+ end
end
def self.reference_prefix
@@ -138,6 +161,8 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
def to_reference(from_project = nil, format: :iid, full: false)
+ return if is_group_milestone?
+
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
@@ -152,6 +177,10 @@ class Milestone < ActiveRecord::Base
id
end
+ def for_display
+ self
+ end
+
def can_be_closed?
active? && issues.opened.count.zero?
end
@@ -164,8 +193,45 @@ class Milestone < ActiveRecord::Base
write_attribute(:title, sanitize_title(value)) if value.present?
end
+ def safe_title
+ title.to_slug.normalize.to_s
+ end
+
+ def parent
+ group || project
+ end
+
+ def is_group_milestone?
+ group_id.present?
+ end
+
+ def is_project_milestone?
+ project_id.present?
+ end
+
private
+ # Milestone titles must be unique across project milestones and group milestones
+ def uniqueness_of_title
+ if project
+ relation = Milestone.for_projects_and_groups([project_id], [project.group&.id])
+ elsif group
+ project_ids = group.projects.map(&:id)
+ relation = Milestone.for_projects_and_groups(project_ids, [group.id])
+ end
+
+ title_exists = relation.find_by_title(title)
+ errors.add(:title, "already being used for another group or project milestone.") if title_exists
+ end
+
+ # Milestone should be either a project milestone or a group milestone
+ def milestone_type_check
+ if group_id && project_id
+ field = project_id_changed? ? :project_id : :group_id
+ errors.add(field, "milestone should belong either to a project or a group.")
+ end
+ end
+
def milestone_format_reference(format = :iid)
raise ArgumentError, 'Unknown format' unless [:iid, :name].include?(format)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 672eab94c07..0bb04194bdb 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -5,6 +5,7 @@ class Namespace < ActiveRecord::Base
include Sortable
include Gitlab::ShellAdapter
include Gitlab::CurrentSettings
+ include Gitlab::VisibilityLevel
include Routable
include AfterCommitQueue
@@ -15,13 +16,13 @@ class Namespace < ActiveRecord::Base
cache_markdown_field :description, pipeline: :description
- has_many :projects, dependent: :destroy
+ has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
has_many :children, class_name: "Namespace", foreign_key: :parent_id
- has_one :chat_team, dependent: :destroy
+ has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
@@ -105,6 +106,10 @@ class Namespace < ActiveRecord::Base
end
end
+ def visibility_level_field
+ :visibility_level
+ end
+
def to_param
full_path
end
diff --git a/app/models/note.rb b/app/models/note.rb
index ca6999427c0..d0e3bc0bfed 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -46,8 +46,8 @@ class Note < ActiveRecord::Base
belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User'
- has_many :todos, dependent: :destroy
- has_many :events, as: :target, dependent: :destroy
+ has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata
delegate :gfm_reference, :local_reference, to: :noteable
@@ -190,7 +190,7 @@ class Note < ActiveRecord::Base
# override to return commits, which are not active record
def noteable
if for_commit?
- project.commit(commit_id)
+ @commit ||= project.commit(commit_id)
else
super
end
@@ -330,8 +330,7 @@ class Note < ActiveRecord::Base
def expire_etag_cache
return unless for_issue?
- key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
- noteable.project.namespace,
+ key = Gitlab::Routing.url_helpers.project_noteable_notes_path(
noteable.project,
target_type: noteable_type.underscore,
target_id: noteable.id
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 6e13f9b2089..654be927ed8 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable
add_authentication_token_field :token
- serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize
+ serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user
diff --git a/app/models/project.rb b/app/models/project.rb
index 241e7e60dd2..d827bfaa806 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -59,6 +59,7 @@ class Project < ActiveRecord::Base
update_column(:last_repository_updated_at, self.created_at)
end
+ before_destroy :remove_private_deploy_keys
after_destroy :remove_pages
# update visibility_level of forks
@@ -80,96 +81,108 @@ class Project < ActiveRecord::Base
belongs_to :namespace
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
- has_many :boards, before_add: :validate_board_limit, dependent: :destroy
+ has_many :boards, before_add: :validate_board_limit
# Project services
- has_one :campfire_service, dependent: :destroy
- has_one :drone_ci_service, dependent: :destroy
- has_one :emails_on_push_service, dependent: :destroy
- has_one :pipelines_email_service, dependent: :destroy
- has_one :irker_service, dependent: :destroy
- has_one :pivotaltracker_service, dependent: :destroy
- has_one :hipchat_service, dependent: :destroy
- has_one :flowdock_service, dependent: :destroy
- has_one :assembla_service, dependent: :destroy
- has_one :asana_service, dependent: :destroy
- has_one :gemnasium_service, dependent: :destroy
- has_one :mattermost_slash_commands_service, dependent: :destroy
- has_one :mattermost_service, dependent: :destroy
- has_one :slack_slash_commands_service, dependent: :destroy
- has_one :slack_service, dependent: :destroy
- has_one :buildkite_service, dependent: :destroy
- has_one :bamboo_service, dependent: :destroy
- has_one :teamcity_service, dependent: :destroy
- has_one :pushover_service, dependent: :destroy
- has_one :jira_service, dependent: :destroy
- has_one :redmine_service, dependent: :destroy
- has_one :custom_issue_tracker_service, dependent: :destroy
- has_one :bugzilla_service, dependent: :destroy
- has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
- has_one :external_wiki_service, dependent: :destroy
- has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
- has_one :prometheus_service, dependent: :destroy, inverse_of: :project
- has_one :mock_ci_service, dependent: :destroy
- has_one :mock_deployment_service, dependent: :destroy
- has_one :mock_monitoring_service, dependent: :destroy
- has_one :microsoft_teams_service, dependent: :destroy
-
- has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+ has_one :campfire_service
+ has_one :drone_ci_service
+ has_one :emails_on_push_service
+ has_one :pipelines_email_service
+ has_one :irker_service
+ has_one :pivotaltracker_service
+ has_one :hipchat_service
+ has_one :flowdock_service
+ has_one :assembla_service
+ has_one :asana_service
+ has_one :gemnasium_service
+ has_one :mattermost_slash_commands_service
+ has_one :mattermost_service
+ has_one :slack_slash_commands_service
+ has_one :slack_service
+ has_one :buildkite_service
+ has_one :bamboo_service
+ has_one :teamcity_service
+ has_one :pushover_service
+ has_one :jira_service
+ has_one :redmine_service
+ has_one :custom_issue_tracker_service
+ has_one :bugzilla_service
+ has_one :gitlab_issue_tracker_service, inverse_of: :project
+ has_one :external_wiki_service
+ has_one :kubernetes_service, inverse_of: :project
+ has_one :prometheus_service, inverse_of: :project
+ has_one :mock_ci_service
+ has_one :mock_deployment_service
+ has_one :mock_monitoring_service
+ has_one :microsoft_teams_service
+
+ has_one :forked_project_link, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
has_many :forked_project_links, foreign_key: "forked_from_project_id"
has_many :forks, through: :forked_project_links, source: :forked_to_project
# Merge Requests for target project should be removed with it
- has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
- has_many :issues, dependent: :destroy
- has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
- has_many :services, dependent: :destroy
- has_many :events, dependent: :destroy
- has_many :milestones, dependent: :destroy
- has_many :notes, dependent: :destroy
- has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet'
- has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
- has_many :protected_branches, dependent: :destroy
- has_many :protected_tags, dependent: :destroy
+ has_many :merge_requests, foreign_key: 'target_project_id'
+ has_many :issues
+ has_many :labels, class_name: 'ProjectLabel'
+ has_many :services
+ has_many :events
+ has_many :milestones
+ has_many :notes
+ has_many :snippets, class_name: 'ProjectSnippet'
+ has_many :hooks, class_name: 'ProjectHook'
+ has_many :protected_branches
+ has_many :protected_tags
has_many :project_authorizations
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
- has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
+ has_many :project_members, -> { where(requested_at: nil) },
+ as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
alias_method :members, :project_members
has_many :users, through: :project_members
- has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember'
+ has_many :requesters, -> { where.not(requested_at: nil) },
+ as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- has_many :deploy_keys_projects, dependent: :destroy
+ has_many :deploy_keys_projects
has_many :deploy_keys, through: :deploy_keys_projects
- has_many :users_star_projects, dependent: :destroy
+ has_many :users_star_projects
has_many :starrers, through: :users_star_projects, source: :user
- has_many :releases, dependent: :destroy
- has_many :lfs_objects_projects, dependent: :destroy
+ has_many :releases
+ has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :lfs_objects, through: :lfs_objects_projects
- has_many :project_group_links, dependent: :destroy
+ has_many :project_group_links
has_many :invited_groups, through: :project_group_links, source: :group
- has_many :pages_domains, dependent: :destroy
- has_many :todos, dependent: :destroy
- has_many :notification_settings, dependent: :destroy, as: :source
-
- has_one :import_data, dependent: :delete, class_name: 'ProjectImportData'
- has_one :project_feature, dependent: :destroy
- has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
- has_many :container_repositories, dependent: :destroy
-
- has_many :commit_statuses, dependent: :destroy
- has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline'
- has_many :builds, class_name: 'Ci::Build' # the builds are created from the commit_statuses
- has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
+ has_many :pages_domains
+ has_many :todos
+ has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
+ has_one :import_data, class_name: 'ProjectImportData'
+ has_one :project_feature
+ has_one :statistics, class_name: 'ProjectStatistics'
+
+ # Container repositories need to remove data from the container registry,
+ # which is not managed by the DB. Hence we're still using dependent: :destroy
+ # here.
+ has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+
+ has_many :commit_statuses
+ has_many :pipelines, class_name: 'Ci::Pipeline'
+
+ # Ci::Build objects store data on the file system such as artifact files and
+ # build traces. Currently there's no efficient way of removing this data in
+ # bulk that doesn't involve loading the rows into memory. As a result we're
+ # still using `dependent: :destroy` here.
+ has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, class_name: 'Ci::Variable'
- has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger'
- has_many :environments, dependent: :destroy
- has_many :deployments, dependent: :destroy
- has_many :pipeline_schedules, dependent: :destroy, class_name: 'Ci::PipelineSchedule'
+ has_many :triggers, class_name: 'Ci::Trigger'
+ has_many :environments
+ has_many :deployments
+ has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
@@ -186,6 +199,11 @@ class Project < ActiveRecord::Base
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
+ validates :ci_config_path,
+ format: { without: /\.{2}/,
+ message: 'cannot include directory traversal.' },
+ length: { maximum: 255 },
+ allow_blank: true
validates :name,
presence: true,
length: { maximum: 255 },
@@ -219,7 +237,7 @@ class Project < ActiveRecord::Base
before_save :ensure_runners_token
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes
scope :pending_delete, -> { where(pending_delete: true) }
@@ -468,7 +486,9 @@ class Project < ActiveRecord::Base
end
def has_container_registry_tags?
- container_repositories.to_a.any?(&:has_tags?) ||
+ return @images if defined?(@images)
+
+ @images = container_repositories.to_a.any?(&:has_tags?) ||
has_root_container_repository_tags?
end
@@ -518,9 +538,19 @@ class Project < ActiveRecord::Base
ProjectCacheWorker.perform_async(self.id)
end
+ remove_import_data
+ end
+
+ # This method is overriden in EE::Project model
+ def remove_import_data
import_data&.destroy
end
+ def ci_config_path=(value)
+ # Strip all leading slashes so that //foo -> foo
+ super(value&.sub(%r{\A/+}, '')&.delete("\0"))
+ end
+
def import_url=(value)
return super(value) unless Gitlab::UrlSanitizer.valid?(value)
@@ -675,7 +705,7 @@ class Project < ActiveRecord::Base
end
def web_url
- Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
+ Gitlab::Routing.url_helpers.project_url(self)
end
def new_issue_address(author)
@@ -704,9 +734,11 @@ class Project < ActiveRecord::Base
end
def get_issue(issue_id, current_user)
- if default_issues_tracker?
- IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
- else
+ issue = IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) if issues_enabled?
+
+ if issue
+ issue
+ elsif external_issue_tracker
ExternalIssue.new(issue_id, self)
end
end
@@ -728,7 +760,7 @@ class Project < ActiveRecord::Base
end
def external_issue_reference_pattern
- external_issue_tracker.class.reference_pattern
+ external_issue_tracker.class.reference_pattern(only_long: issues_enabled?)
end
def default_issues_tracker?
@@ -773,10 +805,12 @@ class Project < ActiveRecord::Base
update_column(:has_external_wiki, services.external_wikis.any?)
end
- def find_or_initialize_services
+ def find_or_initialize_services(exceptions: [])
services_templates = Service.where(template: true)
- Service.available_services_names.map do |service_name|
+ available_services_names = Service.available_services_names - exceptions
+
+ available_services_names.map do |service_name|
service = find_service(services, service_name)
if service
@@ -851,7 +885,7 @@ class Project < ActiveRecord::Base
def avatar_url(**args)
# We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
- avatar_path(args) || (Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self) if avatar_in_git)
+ avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git)
end
# For compatibility with old code
@@ -947,8 +981,6 @@ class Project < ActiveRecord::Base
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
- expire_caches_before_rename(old_path_with_namespace)
-
if has_container_registry_tags?
Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!"
@@ -956,6 +988,8 @@ class Project < ActiveRecord::Base
raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
end
+ expire_caches_before_rename(old_path_with_namespace)
+
if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
@@ -1015,7 +1049,8 @@ class Project < ActiveRecord::Base
namespace: namespace.name,
visibility_level: visibility_level,
path_with_namespace: path_with_namespace,
- default_branch: default_branch
+ default_branch: default_branch,
+ ci_config_path: ci_config_path
}
# Backward compatibility
@@ -1229,7 +1264,13 @@ class Project < ActiveRecord::Base
File.join(pages_path, 'public')
end
+ def remove_private_deploy_keys
+ deploy_keys.where(public: false).delete_all
+ end
+
def remove_pages
+ ::Projects::UpdatePagesConfigurationService.new(self).execute
+
# 1. We rename pages to temporary directory
# 2. We wait 5 minutes, due to NFS caching
# 3. We asynchronously remove pages with force
@@ -1315,7 +1356,8 @@ class Project < ActiveRecord::Base
variables
end
- def secret_variables_for(ref)
+ def secret_variables_for(ref:, environment: nil)
+ # EE would use the environment
if protected_for?(ref)
variables
else
@@ -1348,15 +1390,15 @@ class Project < ActiveRecord::Base
end
def pushes_since_gc
- Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i }
+ Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i }
end
def increment_pushes_since_gc
- Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) }
+ Gitlab::Redis::SharedState.with { |redis| redis.incr(pushes_since_gc_redis_shared_state_key) }
end
def reset_pushes_since_gc
- Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) }
+ Gitlab::Redis::SharedState.with { |redis| redis.del(pushes_since_gc_redis_shared_state_key) }
end
def route_map_for(commit_sha)
@@ -1419,7 +1461,7 @@ class Project < ActiveRecord::Base
from && self != from
end
- def pushes_since_gc_redis_key
+ def pushes_since_gc_redis_shared_state_key
"projects/#{id}/pushes_since_gc"
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index e3cafd4d1c6..37730474324 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base
insecure_mode: true,
algorithm: 'aes-256-cbc'
- serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :data, JSON # rubocop:disable Cop/ActiveRecordSerialize
validates :project, presence: true
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index ad4eb9536e1..88c428b4aae 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -1,5 +1,5 @@
class GitlabIssueTrackerService < IssueTrackerService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
@@ -12,26 +12,26 @@ class GitlabIssueTrackerService < IssueTrackerService
end
def project_url
- namespace_project_issues_url(project.namespace, project)
+ project_issues_url(project)
end
def new_issue_url
- new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project)
+ new_project_issue_url(project)
end
def issue_url(iid)
- namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid)
+ project_issue_url(project, id: iid)
end
- def project_path
- namespace_project_issues_path(project.namespace, project)
+ def issue_tracker_path
+ project_issues_path(project)
end
def new_issue_path
- new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project)
+ new_project_issue_path(project)
end
def issue_path(iid)
- namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid)
+ project_issue_path(project, id: iid)
end
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index fcc7c4bec06..31984c5d7ed 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -8,8 +8,12 @@ class IssueTrackerService < Service
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN
- def self.reference_pattern
- @reference_pattern ||= %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
+ def self.reference_pattern(only_long: false)
+ if only_long
+ %r{(\b[A-Z][A-Z0-9_]+-)(?<issue>\d+)}
+ else
+ %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?<issue>\d+)}
+ end
end
def default?
@@ -20,7 +24,7 @@ class IssueTrackerService < Service
self.issues_url.gsub(':id', iid.to_s)
end
- def project_path
+ def issue_tracker_path
project_url
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 00328892b4a..37f2c96a22f 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -1,12 +1,10 @@
class JiraService < IssueTrackerService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
- validates :project_key, presence: true, if: :activated?
- prop_accessor :username, :password, :url, :api_url, :project_key,
- :jira_issue_transition_id, :title, :description
+ prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
before_update :reset_password
@@ -18,7 +16,7 @@ class JiraService < IssueTrackerService
end
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
- def self.reference_pattern
+ def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
@@ -54,10 +52,6 @@ class JiraService < IssueTrackerService
@client ||= JIRA::Client.new(options)
end
- def jira_project
- @jira_project ||= jira_request { client.Project.find(project_key) }
- end
-
def help
"You need to configure JIRA before enabling this service. For more details
read the
@@ -88,18 +82,12 @@ class JiraService < IssueTrackerService
[
{ type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com', required: true },
{ type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
- { type: 'text', name: 'project_key', placeholder: 'Project Key', required: true },
{ type: 'text', name: 'username', placeholder: '', required: true },
{ type: 'password', name: 'password', placeholder: '', required: true },
- { type: 'text', name: 'jira_issue_transition_id', placeholder: '' }
+ { type: 'text', name: 'jira_issue_transition_id', title: 'Transition ID', placeholder: '' }
]
end
- # URLs to redirect from Gitlab issues pages to jira issue tracker
- def project_url
- "#{url}/issues/?jql=project=#{project_key}"
- end
-
def issues_url
"#{url}/browse/:id"
end
@@ -152,8 +140,8 @@ class JiraService < IssueTrackerService
url: resource_url(user_path(author))
},
project: {
- name: self.project.path_with_namespace,
- url: resource_url(namespace_project_path(project.namespace, self.project))
+ name: project.path_with_namespace,
+ url: resource_url(namespace_project_path(project.namespace, project)) # rubocop:disable Cop/ProjectPathHelper
},
entity: {
name: noteable_type.humanize.downcase,
@@ -184,7 +172,7 @@ class JiraService < IssueTrackerService
def test_settings
return unless client_url.present?
# Test settings by getting the project
- jira_request { jira_project.present? }
+ jira_request { client.ServerInfo.all.attrs }
end
private
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 48e7802c557..dee99bbb859 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -59,21 +59,21 @@ class KubernetesService < DeploymentService
def fields
[
{ type: 'text',
- name: 'namespace',
- title: 'Kubernetes namespace',
- placeholder: namespace_placeholder },
- { type: 'text',
name: 'api_url',
title: 'API URL',
placeholder: 'Kubernetes API URL, like https://kube.example.com/' },
- { type: 'text',
- name: 'token',
- title: 'Service token',
- placeholder: 'Service token' },
{ type: 'textarea',
name: 'ca_pem',
- title: 'Custom CA bundle',
- placeholder: 'Certificate Authority bundle (PEM format)' }
+ title: 'CA Certificate',
+ placeholder: 'Certificate Authority bundle (PEM format)' },
+ { type: 'text',
+ name: 'namespace',
+ title: 'Project namespace (optional/unique)',
+ placeholder: namespace_placeholder },
+ { type: 'text',
+ name: 'token',
+ title: 'Token',
+ placeholder: 'Service token' }
]
end
@@ -96,10 +96,13 @@ class KubernetesService < DeploymentService
end
def predefined_variables
+ config = YAML.dump(kubeconfig)
+
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }
+ { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true },
+ { key: 'KUBECONFIG', value: config, public: false, file: true }
]
if ca_pem.present?
@@ -135,6 +138,14 @@ class KubernetesService < DeploymentService
private
+ def kubeconfig
+ to_kubeconfig(
+ url: api_url,
+ namespace: actual_namespace,
+ token: token,
+ ca_pem: ca_pem)
+ end
+
def namespace_placeholder
default_namespace || TEMPLATE_PLACEHOLDER
end
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 4592cb747a0..eb4da68bb7e 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -5,7 +5,7 @@ class SlashCommandsService < Service
prop_accessor :token
- has_many :chat_names, foreign_key: :service_id, dependent: :destroy
+ has_many :chat_names, foreign_key: :service_id, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
def valid_token?(token)
self.respond_to?(:token) &&
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f26ee57510c..dfca0031af8 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -31,7 +31,7 @@ class ProjectWiki
end
def web_url
- Gitlab::Routing.url_helpers.namespace_project_wiki_url(@project.namespace, @project, :home)
+ Gitlab::Routing.url_helpers.project_wiki_url(@project, :home)
end
def url_to_repo
@@ -63,6 +63,10 @@ class ProjectWiki
!!repository.exists?
end
+ def has_home_page?
+ !!find_page('home')
+ end
+
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 8c24e722a8b..d27eeff9fb4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -123,6 +123,7 @@ class Repository
commits
end
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
unless exists? && has_visible_content? && query.present?
return []
@@ -470,8 +471,17 @@ class Repository
end
cache_method :root_ref
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
def exists?
- refs_directory_exists?
+ return false unless path_with_namespace
+
+ Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
+ if enabled
+ raw_repository.exists?
+ else
+ refs_directory_exists?
+ end
+ end
end
cache_method :exists?
@@ -610,6 +620,7 @@ class Repository
commit(sha)
end
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/383
def last_commit_id_for_path(sha, path)
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
@@ -931,7 +942,7 @@ class Repository
def is_ancestor?(ancestor_id, descendant_id)
return false if ancestor_id.nil? || descendant_id.nil?
-
+
Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
if is_enabled
raw_repository.is_ancestor?(ancestor_id, descendant_id)
@@ -1078,8 +1089,8 @@ class Repository
blob_data_at(sha, '.gitlab/route-map.yml')
end
- def gitlab_ci_yml_for(sha)
- blob_data_at(sha, '.gitlab-ci.yml')
+ def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
+ blob_data_at(sha, path)
end
private
@@ -1093,8 +1104,6 @@ class Repository
end
def refs_directory_exists?
- return false unless path_with_namespace
-
File.exist?(File.join(path_to_repo, 'refs'))
end
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index edde7bedbab..298569cb7a6 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,5 +1,5 @@
class SentNotification < ActiveRecord::Base
- serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
+ serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :project
belongs_to :noteable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/service.rb b/app/models/service.rb
index 6a0b0a5c522..6b64079215f 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,7 +2,7 @@
# and implement a set of methods
class Service < ActiveRecord::Base
include Sortable
- serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize
default_value_for :active, false
default_value_for :push_events, true
@@ -51,6 +51,14 @@ class Service < ActiveRecord::Base
active
end
+ def show_active_box?
+ true
+ end
+
+ def editable?
+ true
+ end
+
def template?
template
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 54014df43b0..09d5ff46618 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -30,16 +30,14 @@ class Snippet < ActiveRecord::Base
belongs_to :author, class_name: 'User'
belongs_to :project
- has_many :notes, as: :noteable, dependent: :destroy
+ has_many :notes, as: :noteable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
validates :file_name,
- length: { maximum: 255 },
- format: { with: Gitlab::Regex.file_name_regex,
- message: Gitlab::Regex.file_name_regex_message }
+ length: { maximum: 255 }
validates :content, presence: true
validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 414c95f7705..0b33e45473b 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,7 +1,8 @@
class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
commit description merge confidential visible label assignee cross_reference
- title time_tracking branch milestone discussion task moved opened closed merged
+ title time_tracking branch milestone discussion task moved
+ opened closed merged duplicate
outdated
].freeze
diff --git a/app/models/user.rb b/app/models/user.rb
index 0febae84873..6e66c587a1f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -12,6 +12,7 @@ class User < ActiveRecord::Base
include TokenAuthenticatable
include IgnorableColumn
include FeatureGate
+ include CreatedAtFilterable
DEFAULT_NOTIFICATION_LEVEL = :participating
@@ -41,7 +42,7 @@ class User < ActiveRecord::Base
otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
- serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize
+ serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
@@ -67,24 +68,25 @@ class User < ActiveRecord::Base
#
# Namespace for personal projects
- has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true
+ has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
has_many :keys, -> do
type = Key.arel_table[:type]
where(type.not_eq('DeployKey').or(type.eq(nil)))
- end, dependent: :destroy
- has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy
+ end, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :gpg_keys
- has_many :emails, dependent: :destroy
- has_many :personal_access_tokens, dependent: :destroy
- has_many :identities, dependent: :destroy, autosave: true
- has_many :u2f_registrations, dependent: :destroy
- has_many :chat_names, dependent: :destroy
+ has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
+ has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Groups
- has_many :members, dependent: :destroy
- has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember'
+ has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, source: 'GroupMember' # rubocop:disable Cop/ActiveRecordDependent
has_many :groups, through: :group_members
has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
@@ -92,35 +94,35 @@ class User < ActiveRecord::Base
# Projects
has_many :groups_projects, through: :groups, source: :projects
has_many :personal_projects, through: :namespace, source: :projects
- has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy
+ has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :project_members
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
- has_many :users_star_projects, dependent: :destroy
+ has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :starred_projects, through: :users_star_projects, source: :project
has_many :project_authorizations
has_many :authorized_projects, through: :project_authorizations, source: :project
- has_many :snippets, dependent: :destroy, foreign_key: :author_id
- has_many :notes, dependent: :destroy, foreign_key: :author_id
- has_many :issues, dependent: :destroy, foreign_key: :author_id
- has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
- has_many :events, dependent: :destroy, foreign_key: :author_id
- has_many :subscriptions, dependent: :destroy
+ has_many :snippets, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event"
- has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
- has_one :abuse_report, dependent: :destroy, foreign_key: :user_id
- has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport"
- has_many :spam_logs, dependent: :destroy
- has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
- has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline'
- has_many :todos, dependent: :destroy
- has_many :notification_settings, dependent: :destroy
- has_many :award_emoji, dependent: :destroy
- has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
+ has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :reported_abuse_reports, dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent
+ has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent
+ has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent
+ has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issue_assignees
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
- has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
+ has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
#
# Validations
@@ -156,6 +158,7 @@ class User < ActiveRecord::Base
before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed?
after_save :ensure_namespace_correct
+ after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
after_initialize :set_projects_limit
after_destroy :post_destroy_hook
@@ -211,7 +214,7 @@ class User < ActiveRecord::Base
end
mount_uploader :avatar, AvatarUploader
- has_many :uploads, as: :model, dependent: :destroy
+ has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes
scope :admins, -> { where(admin: true) }
@@ -313,7 +316,7 @@ class User < ActiveRecord::Base
table[:name].matches(pattern)
.or(table[:email].matches(pattern))
.or(table[:username].matches(pattern))
- ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
+ ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
# searches user by given pattern
@@ -384,9 +387,11 @@ class User < ActiveRecord::Base
# Return (create if necessary) the ghost user. The ghost user
# owns records previously belonging to deleted users.
def ghost
- unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u|
+ email = 'ghost%s@example.com'
+ unique_internal(where(ghost: true), 'ghost', email) do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.name = 'Ghost User'
+ u.notification_email = email
end
end
end
@@ -509,6 +514,10 @@ class User < ActiveRecord::Base
end
end
+ def update_invalid_gpg_signatures
+ gpg_keys.each(&:update_invalid_gpg_signatures)
+ end
+
# Returns the groups a user has access to
def authorized_groups
union = Gitlab::SQL::Union
@@ -579,16 +588,20 @@ class User < ActiveRecord::Base
keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end
- def require_password?
- password_automatically_set? && !ldap_user? && current_application_settings.signin_enabled?
+ def require_password_creation?
+ password_automatically_set? && allow_password_authentication?
end
- def require_personal_access_token?
- return false if current_application_settings.signin_enabled? || ldap_user?
+ def require_personal_access_token_creation_for_git_auth?
+ return false if allow_password_authentication? || ldap_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end
+ def allow_password_authentication?
+ !ldap_user? && current_application_settings.password_authentication_enabled?
+ end
+
def can_change_username?
gitlab_config.username_changing_enabled
end
@@ -698,7 +711,7 @@ class User < ActiveRecord::Base
end
def sanitize_attrs
- %w[name username skype linkedin twitter].each do |attr|
+ %w[username skype linkedin twitter].each do |attr|
value = public_send(attr)
public_send("#{attr}=", Sanitize.clean(value)) if value.present?
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index a886efc1360..984e5482288 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,13 +1,15 @@
module Ci
class BuildPolicy < CommitStatusPolicy
- condition(:protected_action) do
- next false unless @subject.action?
+ condition(:protected_ref) do
+ access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
- !::Gitlab::UserAccess
- .new(@user, project: @subject.project)
- .can_merge_to_branch?(@subject.ref)
+ if @subject.tag?
+ !access.can_create_tag?(@subject.ref)
+ else
+ !access.can_update_branch?(@subject.ref)
+ end
end
- rule { protected_action }.prevent :update_build
+ rule { protected_ref }.prevent :update_build
end
end
diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb
index a2dde95dbc8..4e689a9efd5 100644
--- a/app/policies/ci/pipeline_policy.rb
+++ b/app/policies/ci/pipeline_policy.rb
@@ -1,5 +1,17 @@
module Ci
class PipelinePolicy < BasePolicy
delegate { @subject.project }
+
+ condition(:protected_ref) do
+ access = ::Gitlab::UserAccess.new(@user, project: @subject.project)
+
+ if @subject.tag?
+ !access.can_create_tag?(@subject.ref)
+ else
+ !access.can_update_branch?(@subject.ref)
+ end
+ end
+
+ rule { protected_ref }.prevent :update_pipeline
end
end
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index 1877e89bb23..6b7598e1821 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -1,4 +1,14 @@
module Ci
class PipelineSchedulePolicy < PipelinePolicy
+ alias_method :pipeline_schedule, :subject
+
+ condition(:owner_of_schedule) do
+ can?(:developer_access) && pipeline_schedule.owned_by?(@user)
+ end
+
+ rule { can?(:master_access) | owner_of_schedule }.policy do
+ enable :update_pipeline_schedule
+ enable :admin_pipeline_schedule
+ end
end
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 55eefa76d3f..1c91425f589 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -44,7 +44,7 @@ class GlobalPolicy < BasePolicy
prevent :log_in
end
- rule { ~restricted_public_level }.policy do
+ rule { admin | ~restricted_public_level }.policy do
enable :read_users_list
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index dcb37416ca3..6defab75fce 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -31,6 +31,8 @@ class GroupPolicy < BasePolicy
rule { master }.policy do
enable :create_projects
enable :admin_milestones
+ enable :admin_pipeline
+ enable :admin_build
end
rule { owner }.policy do
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 7cbca63fab4..0133091db57 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -10,7 +10,8 @@ class ProjectPolicy < BasePolicy
desc "User is a project owner"
condition :owner do
- @user && project.owner == @user || (project.group && project.group.has_owner?(@user))
+ (project.owner.present? && project.owner == @user) ||
+ project.group&.has_owner?(@user)
end
desc "Project has public builds enabled"
@@ -162,7 +163,6 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
- enable :update_pipeline_schedule
enable :create_merge_request
enable :create_wiki
enable :push_code
@@ -188,7 +188,6 @@ class ProjectPolicy < BasePolicy
enable :admin_build
enable :admin_container_image
enable :admin_pipeline
- enable :admin_pipeline_schedule
enable :admin_environment
enable :admin_deployment
enable :admin_pages
@@ -289,9 +288,6 @@ class ProjectPolicy < BasePolicy
prevent :create_issue
prevent :update_issue
prevent :admin_issue
- end
-
- rule { issues_disabled & default_issues_tracker }.policy do
prevent :read_issue
end
diff --git a/app/presenters/ci/group_variable_presenter.rb b/app/presenters/ci/group_variable_presenter.rb
new file mode 100644
index 00000000000..81fea106a5c
--- /dev/null
+++ b/app/presenters/ci/group_variable_presenter.rb
@@ -0,0 +1,25 @@
+module Ci
+ class GroupVariablePresenter < Gitlab::View::Presenter::Delegated
+ presents :variable
+
+ def placeholder
+ 'GROUP_VARIABLE'
+ end
+
+ def form_path
+ if variable.persisted?
+ group_variable_path(group, variable)
+ else
+ group_variables_path(group)
+ end
+ end
+
+ def edit_path
+ group_variable_path(group, variable)
+ end
+
+ def delete_path
+ group_variable_path(group, variable)
+ end
+ end
+end
diff --git a/app/presenters/ci/variable_presenter.rb b/app/presenters/ci/variable_presenter.rb
new file mode 100644
index 00000000000..5d7998393a6
--- /dev/null
+++ b/app/presenters/ci/variable_presenter.rb
@@ -0,0 +1,25 @@
+module Ci
+ class VariablePresenter < Gitlab::View::Presenter::Delegated
+ presents :variable
+
+ def placeholder
+ 'PROJECT_VARIABLE'
+ end
+
+ def form_path
+ if variable.persisted?
+ project_variable_path(project, variable)
+ else
+ project_variables_path(project)
+ end
+ end
+
+ def edit_path
+ project_variable_path(project, variable)
+ end
+
+ def delete_path
+ project_variable_path(project, variable)
+ end
+ end
+end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 8bf35953d29..2df84e58575 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -20,30 +20,25 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def cancel_merge_when_pipeline_succeeds_path
if can_cancel_merge_when_pipeline_succeeds?(current_user)
- cancel_merge_when_pipeline_succeeds_namespace_project_merge_request_path(
- project.namespace,
- project,
- merge_request)
+ cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
end
end
def create_issue_to_resolve_discussions_path
if can?(current_user, :create_issue, project) && project.issues_enabled?
- new_namespace_project_issue_path(project.namespace,
- project,
- merge_request_to_resolve_discussions_of: iid)
+ new_project_issue_path(project, merge_request_to_resolve_discussions_of: iid)
end
end
def remove_wip_path
if can?(current_user, :update_merge_request, merge_request.project)
- remove_wip_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ remove_wip_project_merge_request_path(project, merge_request)
end
end
def merge_path
if can_be_merged_by?(current_user)
- merge_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ merge_project_merge_request_path(project, merge_request)
end
end
@@ -55,7 +50,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
notice_now: edit_in_new_fork_notice_now
}
- namespace_project_forks_path(merge_request.project.namespace, merge_request.project,
+ project_forks_path(merge_request.project,
namespace_key: current_user.namespace.id,
continue: continue_params)
end
@@ -69,7 +64,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
notice_now: edit_in_new_fork_notice_now
}
- namespace_project_forks_path(project.namespace, project,
+ project_forks_path(project,
namespace_key: current_user.namespace.id,
continue: continue_params)
end
@@ -77,19 +72,25 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
def conflict_resolution_path
if conflicts.can_be_resolved_in_ui? && conflicts.can_be_resolved_by?(current_user)
- conflicts_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ conflicts_project_merge_request_path(project, merge_request)
+ end
+ end
+
+ def target_branch_tree_path
+ if target_branch_exists?
+ project_tree_path(project, target_branch)
end
end
def target_branch_commits_path
if target_branch_exists?
- namespace_project_commits_path(project.namespace, project, target_branch)
+ project_commits_path(project, target_branch)
end
end
def source_branch_path
if source_branch_exists?
- namespace_project_branch_path(source_project.namespace, source_project, source_branch)
+ project_branch_path(source_project, source_branch)
end
end
@@ -99,7 +100,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
if source_branch_exists?
namespace = link_to(namespace, project_path(source_project))
- branch = link_to(branch, namespace_project_commits_path(source_project.namespace, source_project, source_branch))
+ branch = link_to(branch, project_tree_path(source_project, source_branch))
end
if for_fork?
@@ -136,7 +137,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
merge_request: merge_request,
closes_issues: closing_issues
).assignable_issues
- path = assign_related_issues_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ path = assign_related_issues_project_merge_request_path(project, merge_request)
if issues.present?
pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue"
link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
index 301b718d060..f2d76a8ad81 100644
--- a/app/serializers/build_action_entity.rb
+++ b/app/serializers/build_action_entity.rb
@@ -6,10 +6,7 @@ class BuildActionEntity < Grape::Entity
end
expose :path do |build|
- play_namespace_project_job_path(
- build.project.namespace,
- build.project,
- build)
+ play_project_job_path(build.project, build)
end
expose :playable?, as: :playable
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
index cb55c98f7c6..6e0e33bc09b 100644
--- a/app/serializers/build_artifact_entity.rb
+++ b/app/serializers/build_artifact_entity.rb
@@ -9,24 +9,15 @@ class BuildArtifactEntity < Grape::Entity
expose :artifacts_expire_at, as: :expire_at
expose :path do |job|
- download_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ download_project_job_artifacts_path(project, job)
end
expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job|
- keep_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ keep_project_job_artifacts_path(project, job)
end
expose :browse_path do |job|
- browse_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ browse_project_job_artifacts_path(project, job)
end
private
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index eeb5399aa8b..743a08acefe 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -7,7 +7,7 @@ class BuildDetailsEntity < JobEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
- erase_namespace_project_job_path(project.namespace, project, build)
+ erase_project_job_path(project, build)
end
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
@@ -16,23 +16,24 @@ class BuildDetailsEntity < JobEntity
end
expose :path do |build|
- namespace_project_merge_request_path(project.namespace, project, build.merge_request)
+ project_merge_request_path(build.merge_request.project,
+ build.merge_request)
end
end
expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build|
- new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options)
+ new_project_issue_path(project, issue: build_failed_issue_options)
end
expose :raw_path do |build|
- raw_namespace_project_job_path(project.namespace, project, build)
+ raw_project_job_path(project, build)
end
private
def build_failed_issue_options
{ title: "Build Failed ##{build.id}",
- description: namespace_project_job_path(project.namespace, project, build) }
+ description: project_job_path(project, build) }
end
def current_user
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index 31763955f97..e4e9d8ef90a 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -8,16 +8,10 @@ class CommitEntity < API::Entities::RepoCommit
end
expose :commit_url do |commit|
- namespace_project_commit_url(
- request.project.namespace,
- request.project,
- commit)
+ project_commit_url(request.project, commit)
end
expose :commit_path do |commit|
- namespace_project_commit_path(
- request.project.namespace,
- request.project,
- commit)
+ project_commit_path(request.project, commit)
end
end
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index 068013c8829..c75431a79ae 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -9,7 +9,7 @@ class DeployKeyEntity < Grape::Entity
expose :created_at
expose :updated_at
expose :projects, using: ProjectEntity do |deploy_key|
- deploy_key.projects.select { |project| options[:user].can?(:read_project, project) }
+ deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) }
end
expose :can_edit
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index e493c9162fd..241c689bccd 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -11,10 +11,7 @@ class DeploymentEntity < Grape::Entity
end
expose :ref_path do |deployment|
- namespace_project_tree_path(
- deployment.project.namespace,
- deployment.project,
- id: deployment.ref)
+ project_tree_path(deployment.project, id: deployment.ref)
end
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 4e8a3c67b21..dcaccc3007d 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -10,32 +10,20 @@ class EnvironmentEntity < Grape::Entity
expose :stop_action?
expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
- metrics_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ metrics_project_environment_path(environment.project, environment)
end
expose :environment_path do |environment|
- namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ project_environment_path(environment.project, environment)
end
expose :stop_path do |environment|
- stop_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ stop_project_environment_path(environment.project, environment)
end
expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment|
can?(request.current_user, :admin_environment, environment.project) &&
- terminal_namespace_project_environment_path(
- environment.project.namespace,
- environment.project,
- environment)
+ terminal_project_environment_path(environment.project, environment)
end
expose :created_at, :updated_at
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 35df95549b7..c189a4992da 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -11,6 +11,6 @@ class IssueEntity < IssuableEntity
expose :labels, using: LabelEntity
expose :web_url do |issue|
- namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ project_issue_path(issue.project, issue)
end
end
diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb
index ad565654342..4452161051e 100644
--- a/app/serializers/label_entity.rb
+++ b/app/serializers/label_entity.rb
@@ -1,5 +1,6 @@
class LabelEntity < Grape::Entity
- expose :id
+ expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) }
+
expose :title
expose :color
expose :description
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 7bb981041cc..7f17f2bf604 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -97,11 +97,13 @@ class MergeRequestEntity < IssuableEntity
presenter(merge_request).target_branch_commits_path
end
+ expose :target_branch_tree_path do |merge_request|
+ presenter(merge_request).target_branch_tree_path
+ end
+
expose :new_blob_path do |merge_request|
if can?(current_user, :push_code, merge_request.project)
- namespace_project_new_blob_path(merge_request.project.namespace,
- merge_request.project,
- merge_request.source_branch)
+ project_new_blob_path(merge_request.project, merge_request.source_branch)
end
end
@@ -134,30 +136,19 @@ class MergeRequestEntity < IssuableEntity
end
expose :email_patches_path do |merge_request|
- namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- format: :patch)
+ project_merge_request_path(merge_request.project, merge_request, format: :patch)
end
expose :plain_diff_path do |merge_request|
- namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- format: :diff)
+ project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
expose :status_path do |merge_request|
- namespace_project_merge_request_path(merge_request.target_project.namespace,
- merge_request.target_project,
- merge_request,
- format: :json)
+ project_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :ci_environments_status_path do |merge_request|
- ci_environments_status_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request)
+ ci_environments_status_project_merge_request_path(merge_request.project, merge_request)
end
expose :merge_commit_message_with_description do |merge_request|
@@ -173,9 +164,7 @@ class MergeRequestEntity < IssuableEntity
end
expose :commit_change_content_path do |merge_request|
- commit_change_content_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request)
+ commit_change_content_project_merge_request_path(merge_request.project, merge_request)
end
private
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 6d1fd9d459f..c4f000b0ca3 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -10,10 +10,7 @@ class PipelineEntity < Grape::Entity
expose :created_at, :updated_at
expose :path do |pipeline|
- namespace_project_pipeline_path(
- pipeline.project.namespace,
- pipeline.project,
- pipeline)
+ project_pipeline_path(pipeline.project, pipeline)
end
expose :flags do
@@ -48,15 +45,11 @@ class PipelineEntity < Grape::Entity
expose :commit, using: CommitEntity
expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
- retry_namespace_project_pipeline_path(pipeline.project.namespace,
- pipeline.project,
- pipeline.id)
+ retry_project_pipeline_path(pipeline.project, pipeline)
end
expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline|
- cancel_namespace_project_pipeline_path(pipeline.project.namespace,
- pipeline.project,
- pipeline.id)
+ cancel_project_pipeline_path(pipeline.project, pipeline)
end
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index a471a7e6a88..dc283ba3e7a 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -5,7 +5,7 @@ class ProjectEntity < Grape::Entity
expose :name
expose :full_path do |project|
- namespace_project_path(project.namespace, project)
+ project_path(project)
end
expose :full_name do |project|
diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb
index ed7dacc2dbd..e9999a36d8a 100644
--- a/app/serializers/runner_entity.rb
+++ b/app/serializers/runner_entity.rb
@@ -5,7 +5,7 @@ class RunnerEntity < Grape::Entity
expose :edit_path,
if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner|
- edit_namespace_project_runner_path(project.namespace, project, runner)
+ edit_project_runner_path(project, runner)
end
private
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index cee0089056f..4523b15152e 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -14,16 +14,14 @@ class StageEntity < Grape::Entity
expose :detailed_status, as: :status, with: StatusEntity
expose :path do |stage|
- namespace_project_pipeline_path(
- stage.pipeline.project.namespace,
+ project_pipeline_path(
stage.pipeline.project,
stage.pipeline,
anchor: stage.name)
end
expose :dropdown_path do |stage|
- stage_namespace_project_pipeline_path(
- stage.pipeline.project.namespace,
+ stage_project_pipeline_path(
stage.pipeline.project,
stage.pipeline,
stage: stage.name,
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index b2a543daa00..9c00ea789ec 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -5,10 +5,11 @@ class AccessTokenValidationService
REVOKED = :revoked
INSUFFICIENT_SCOPE = :insufficient_scope
- attr_reader :token
+ attr_reader :token, :request
- def initialize(token)
+ def initialize(token, request: nil)
@token = token
+ @request = request
end
def validate(scopes: [])
@@ -27,12 +28,23 @@ class AccessTokenValidationService
end
# True if the token's scope contains any of the passed scopes.
- def include_any_scope?(scopes)
- if scopes.blank?
+ def include_any_scope?(required_scopes)
+ if required_scopes.blank?
true
else
- # Check whether the token is allowed access to any of the required scopes.
- Set.new(scopes).intersection(Set.new(token.scopes)).present?
+ # We're comparing each required_scope against all token scopes, which would
+ # take quadratic time. This consideration is irrelevant here because of the
+ # small number of records involved.
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12300/#note_33689006
+ token_scopes = token.scopes.map(&:to_sym)
+
+ required_scopes.any? do |scope|
+ if scope.respond_to?(:sufficient?)
+ scope.sufficient?(token_scopes, request)
+ else
+ API::Scope.new(scope).sufficient?(token_scopes, request)
+ end
+ end
end
end
end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 68f6a8619e5..9eedb9e65a2 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -1,19 +1,22 @@
module Boards
class CreateService < BaseService
def execute
- if project.boards.empty?
- create_board!
- else
- project.boards.first
- end
+ create_board! if can_create_board?
end
private
+ def can_create_board?
+ project.boards.size == 0
+ end
+
def create_board!
- board = project.boards.create
- board.lists.create(list_type: :backlog)
- board.lists.create(list_type: :closed)
+ board = project.boards.create(params)
+
+ if board.persisted?
+ board.lists.create(list_type: :backlog)
+ board.lists.create(list_type: :closed)
+ end
board
end
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index a1d67cbc244..eb345fead2d 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -33,17 +33,12 @@ module Boards
end
def filter_params
- set_default_scope
set_project
set_state
params
end
- def set_default_scope
- params[:scope] = 'all'
- end
-
def set_project
params[:project_id] = project.id
end
diff --git a/app/services/chat_names/authorize_user_service.rb b/app/services/chat_names/authorize_user_service.rb
index 321bf3a9205..7256466c9e8 100644
--- a/app/services/chat_names/authorize_user_service.rb
+++ b/app/services/chat_names/authorize_user_service.rb
@@ -1,6 +1,6 @@
module ChatNames
class AuthorizeUserService
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
def initialize(service, params)
@service = service
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 942145c4a8c..21e2ef153de 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -15,12 +15,40 @@ module Ci
pipeline_schedule: schedule
)
+ result = validate(current_user || trigger_request.trigger.owner,
+ ignore_skip_ci: ignore_skip_ci,
+ save_on_errors: save_on_errors)
+
+ return result if result
+
+ Ci::Pipeline.transaction do
+ update_merge_requests_head_pipeline if pipeline.save
+
+ Ci::CreatePipelineStagesService
+ .new(project, current_user)
+ .execute(pipeline)
+ end
+
+ cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
+
+ pipeline_created_counter.increment(source: source)
+
+ pipeline.tap(&:process!)
+ end
+
+ private
+
+ def validate(triggering_user, ignore_skip_ci:, save_on_errors:)
unless project.builds_enabled?
return error('Pipeline is disabled')
end
- unless trigger_request || can?(current_user, :create_pipeline, project)
- return error('Insufficient permissions to create a new pipeline')
+ unless allowed_to_trigger_pipeline?(triggering_user)
+ if can?(triggering_user, :create_pipeline, project)
+ return error("Insufficient permissions for protected ref '#{ref}'")
+ else
+ return error('Insufficient permissions to create a new pipeline')
+ end
end
unless branch? || tag?
@@ -33,7 +61,7 @@ module Ci
unless pipeline.config_processor
unless pipeline.ci_yaml_file
- return error('Missing .gitlab-ci.yml file')
+ return error("Missing #{pipeline.ci_yaml_file_path} file")
end
return error(pipeline.yaml_errors, save: save_on_errors)
end
@@ -46,24 +74,29 @@ module Ci
unless pipeline.has_stage_seeds?
return error('No stages / jobs for this pipeline.')
end
+ end
- Ci::Pipeline.transaction do
- update_merge_requests_head_pipeline if pipeline.save
-
- Ci::CreatePipelineStagesService
- .new(project, current_user)
- .execute(pipeline)
+ def allowed_to_trigger_pipeline?(triggering_user)
+ if triggering_user
+ allowed_to_create?(triggering_user)
+ else # legacy triggers don't have a corresponding user
+ !project.protected_for?(ref)
end
+ end
- cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
-
- pipeline_created_counter.increment(source: source)
+ def allowed_to_create?(triggering_user)
+ access = Gitlab::UserAccess.new(triggering_user, project: project)
- pipeline.tap(&:process!)
+ can?(triggering_user, :create_pipeline, project) &&
+ if branch?
+ access.can_update_branch?(ref)
+ elsif tag?
+ access.can_create_tag?(ref)
+ else
+ true # Allow it for now and we'll reject when we check ref existence
+ end
end
- private
-
def update_merge_requests_head_pipeline
return unless pipeline.latest?
@@ -113,15 +146,21 @@ module Ci
end
def branch?
- project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
+ return @is_branch if defined?(@is_branch)
+
+ @is_branch =
+ project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
end
def tag?
- project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
+ return @is_tag if defined?(@is_tag)
+
+ @is_tag =
+ project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
end
def ref
- Gitlab::Git.ref_name(origin_ref)
+ @ref ||= Gitlab::Git.ref_name(origin_ref)
end
def valid_sha?
@@ -135,7 +174,7 @@ module Ci
end
def pipeline_created_counter
- @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_count, "Pipelines created count")
+ @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created")
end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index cf3d4aee2bc..a43d0e4593c 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,12 +1,14 @@
module Ci
- class CreateTriggerRequestService
- def execute(project, trigger, ref, variables = nil)
+ module CreateTriggerRequestService
+ Result = Struct.new(:trigger_request, :pipeline)
+
+ def self.execute(project, trigger, ref, variables = nil)
trigger_request = trigger.trigger_requests.create(variables: variables)
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref)
.execute(:trigger, ignore_skip_ci: true, trigger_request: trigger_request)
- trigger_request if pipeline.persisted?
+ Result.new(trigger_request, pipeline)
end
end
end
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
index 43636fde0be..32925e9c1f2 100644
--- a/app/services/git_operation_service.rb
+++ b/app/services/git_operation_service.rb
@@ -129,6 +129,7 @@ class GitOperationService
end
end
+ # Gitaly note: JV: wait with migrating #update_ref until we know how to migrate its call sites.
def update_ref(ref, newrev, oldrev)
# We use 'git update-ref' because libgit2/rugged currently does not
# offer 'compare and swap' ref updates. Without compare-and-swap we can
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 20d1fb29289..bb7680c5054 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -56,6 +56,8 @@ class GitPushService < BaseService
perform_housekeeping
update_caches
+
+ update_signatures
end
def update_gitattributes
@@ -80,6 +82,12 @@ class GitPushService < BaseService
ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size])
end
+ def update_signatures
+ @push_commits.each do |commit|
+ CreateGpgSignatureWorker.perform_async(commit.sha, @project.id)
+ end
+ end
+
# Schedules processing of commit messages.
def process_commit_messages
default = is_default_branch?
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 8dd0846f3bc..ea497729115 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -2,8 +2,11 @@ class IssuableBaseService < BaseService
private
def create_milestone_note(issuable)
+ milestone = issuable.milestone
+ return if milestone && milestone.is_group_milestone?
+
SystemNoteService.change_milestone(
- issuable, issuable.project, current_user, issuable.milestone)
+ issuable, issuable.project, current_user, milestone)
end
def create_labels_note(issuable, old_labels)
@@ -55,6 +58,7 @@ class IssuableBaseService < BaseService
params.delete(:assignee_ids)
params.delete(:assignee_id)
params.delete(:due_date)
+ params.delete(:canonical_issue_id)
end
filter_assignee(issuable)
@@ -89,10 +93,12 @@ class IssuableBaseService < BaseService
milestone_id = params[:milestone_id]
return unless milestone_id
- if milestone_id == IssuableFinder::NONE ||
- project.milestones.find_by(id: milestone_id).nil?
- params[:milestone_id] = ''
- end
+ params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
+
+ milestone =
+ Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
+
+ params[:milestone_id] = '' unless milestone
end
def filter_labels
@@ -178,7 +184,7 @@ class IssuableBaseService < BaseService
after_create(issuable)
issuable.create_cross_references!(current_user)
execute_hooks(issuable)
- invalidate_cache_counts(issuable.assignees, issuable)
+ invalidate_cache_counts(issuable, users: issuable.assignees)
end
issuable
@@ -235,12 +241,12 @@ class IssuableBaseService < BaseService
old_assignees: old_assignees
)
- if old_assignees != issuable.assignees
- new_assignees = issuable.assignees.to_a
- affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees)
- invalidate_cache_counts(affected_assignees.compact, issuable)
- end
+ new_assignees = issuable.assignees.to_a
+ affected_assignees = (old_assignees + new_assignees) - (old_assignees & new_assignees)
+ # Don't clear the project cache, because it will be handled by the
+ # appropriate service (close / reopen / merge / etc.).
+ invalidate_cache_counts(issuable, users: affected_assignees.compact, skip_project_cache: true)
after_update(issuable)
issuable.create_new_cross_references!(current_user)
execute_hooks(issuable, 'update')
@@ -334,9 +340,18 @@ class IssuableBaseService < BaseService
create_labels_note(issuable, old_labels) if issuable.labels != old_labels
end
- def invalidate_cache_counts(users, issuable)
+ def invalidate_cache_counts(issuable, users: [], skip_project_cache: false)
users.each do |user|
user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts")
end
+
+ unless skip_project_cache
+ case issuable
+ when Issue
+ IssuesFinder.new(nil, project_id: issuable.project_id).clear_caches!
+ when MergeRequest
+ MergeRequestsFinder.new(nil, project_id: issuable.target_project_id).clear_caches!
+ end
+ end
end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 34199eb5d13..4c198fc96ea 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -7,6 +7,14 @@ module Issues
issue_data
end
+ def reopen_service
+ Issues::ReopenService
+ end
+
+ def close_service
+ Issues::CloseService
+ end
+
private
def create_assignee_note(issue, old_assignees)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 85c616ca576..74459c3342c 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -16,19 +16,19 @@ module Issues
# The code calling this method is responsible for ensuring that a user is
# allowed to close the given issue.
def close_issue(issue, commit: nil, notifications: true, system_note: true)
- if project.jira_tracker? && project.jira_service.active
+ if project.jira_tracker? && project.jira_service.active && issue.is_a?(ExternalIssue)
project.jira_service.close_issue(commit, issue)
todo_service.close_issue(issue, current_user)
return issue
end
- if project.default_issues_tracker? && issue.close
+ if project.issues_enabled? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
- invalidate_cache_counts(issue.assignees, issue)
+ invalidate_cache_counts(issue, users: issue.assignees)
end
issue
diff --git a/app/services/issues/duplicate_service.rb b/app/services/issues/duplicate_service.rb
new file mode 100644
index 00000000000..5c0854e664d
--- /dev/null
+++ b/app/services/issues/duplicate_service.rb
@@ -0,0 +1,24 @@
+module Issues
+ class DuplicateService < Issues::BaseService
+ def execute(duplicate_issue, canonical_issue)
+ return if canonical_issue == duplicate_issue
+ return unless can?(current_user, :update_issue, duplicate_issue)
+ return unless can?(current_user, :create_note, canonical_issue)
+
+ create_issue_duplicate_note(duplicate_issue, canonical_issue)
+ create_issue_canonical_note(canonical_issue, duplicate_issue)
+
+ close_service.new(project, current_user, {}).execute(duplicate_issue)
+ end
+
+ private
+
+ def create_issue_duplicate_note(duplicate_issue, canonical_issue)
+ SystemNoteService.mark_duplicate_issue(duplicate_issue, duplicate_issue.project, current_user, canonical_issue)
+ end
+
+ def create_issue_canonical_note(canonical_issue, duplicate_issue)
+ SystemNoteService.mark_canonical_issue_of_duplicate(canonical_issue, canonical_issue.project, current_user, duplicate_issue)
+ end
+ end
+end
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index 711f4035c55..29def25719d 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -61,8 +61,18 @@ module Issues
end
def cloneable_milestone_id
- @new_project.milestones
- .find_by(title: @old_issue.milestone.try(:title)).try(:id)
+ title = @old_issue.milestone&.title
+ return unless title
+
+ if @new_project.group && can?(current_user, :read_group, @new_project.group)
+ group_id = @new_project.group.id
+ end
+
+ params =
+ { title: title, project_ids: @new_project.id, group_ids: group_id }
+
+ milestones = MilestonesFinder.new(params).execute
+ milestones.first&.id
end
def rewrite_notes
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 80ea6312768..73b2e85cba3 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -8,7 +8,7 @@ module Issues
create_note(issue)
notification_service.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen')
- invalidate_cache_counts(issue.assignees, issue)
+ invalidate_cache_counts(issue, users: issue.assignees)
end
issue
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index cd9f9a4a16e..8d918ccc635 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -5,6 +5,7 @@ module Issues
def execute(issue)
handle_move_between_iids(issue)
filter_spam_check_params
+ change_issue_duplicate(issue)
update(issue)
end
@@ -53,14 +54,6 @@ module Issues
end
end
- def reopen_service
- Issues::ReopenService
- end
-
- def close_service
- Issues::CloseService
- end
-
def handle_move_between_iids(issue)
return unless params[:move_between_iids]
@@ -72,6 +65,15 @@ module Issues
issue.move_between(issue_before, issue_after)
end
+ def change_issue_duplicate(issue)
+ canonical_issue_id = params.delete(:canonical_issue_id)
+ canonical_issue = IssuesFinder.new(current_user).find_by(id: canonical_issue_id)
+
+ if canonical_issue
+ Issues::DuplicateService.new(project, current_user).execute(issue, canonical_issue)
+ end
+ end
+
private
def get_issue_if_allowed(project, iid)
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index 2ffc989ed71..c0ce01f7523 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -13,7 +13,7 @@ module MergeRequests
notification_service.close_mr(merge_request, current_user)
todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close')
- invalidate_cache_counts(merge_request.assignees, merge_request)
+ invalidate_cache_counts(merge_request, users: merge_request.assignees)
end
merge_request
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 71d37797bb4..19189e64acf 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -7,9 +7,8 @@ module MergeRequests
source_project = @project
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
- params[:target_project_id] ||= source_project.id
-
merge_request = MergeRequest.new
+ merge_request.target_project = @project
merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 5dd40e07c0d..668a1741736 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -49,13 +49,13 @@ module MergeRequests
def url_for_new_merge_request(branch_name)
merge_request_params = { source_branch: branch_name }
- url = Gitlab::Routing.url_helpers.namespace_project_new_merge_request_url(project.namespace, project, merge_request: merge_request_params)
+ url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params)
{ branch_name: branch_name, url: url, new_merge_request: true }
end
def url_for_existing_merge_request(merge_request)
target_project = merge_request.target_project
- url = Gitlab::Routing.url_helpers.namespace_project_merge_request_url(target_project.namespace, target_project, merge_request)
+ url = Gitlab::Routing.url_helpers.project_merge_request_url(target_project, merge_request)
{ branch_name: merge_request.source_branch, url: url, new_merge_request: false }
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index b247cb89e5e..bc846e07f24 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -61,8 +61,12 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
- DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
- .execute(merge_request.source_branch)
+ # Verify again that the source branch can be removed, since branch may be protected,
+ # or the source branch may have been updated.
+ if @merge_request.can_remove_source_branch?(branch_deletion_user)
+ DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
+ .execute(merge_request.source_branch)
+ end
end
end
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index f0d998731d7..261a8bfa200 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -13,7 +13,7 @@ module MergeRequests
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
- invalidate_cache_counts(merge_request.assignees, merge_request)
+ invalidate_cache_counts(merge_request, users: merge_request.assignees)
end
private
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index e0e7c43f802..bc4a13cf4bc 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -35,11 +35,12 @@ module MergeRequests
# target branch manually
def close_merge_requests
commit_ids = @commits.map(&:id)
- merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
+ merge_requests = @project.merge_requests.preload(:merge_request_diff).opened.where(target_branch: @branch_name).to_a
merge_requests = merge_requests.select(&:diff_head_commit)
merge_requests = merge_requests.select do |merge_request|
- commit_ids.include?(merge_request.diff_head_sha)
+ commit_ids.include?(merge_request.diff_head_sha) &&
+ merge_request.merge_request_diff.state != 'empty'
end
filter_merge_requests(merge_requests).each do |merge_request|
@@ -68,7 +69,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff(current_user)
else
- mr_commit_ids = merge_request.commits_sha
+ mr_commit_ids = merge_request.commit_shas
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff(current_user) if matches.any?
@@ -128,7 +129,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- mr_commit_ids = Set.new(merge_request.commits_sha)
+ mr_commit_ids = Set.new(merge_request.commit_shas)
new_commits, existing_commits = @commits.partition do |commit|
mr_commit_ids.include?(commit.id)
@@ -144,7 +145,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
- commit_shas = merge_request.commits_sha
+ commit_shas = merge_request.commit_shas
wip_commit = @commits.detect do |commit|
commit.work_in_progress? && commit_shas.include?(commit.sha)
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index f2fddf7f345..52f6d511f98 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -10,7 +10,7 @@ module MergeRequests
execute_hooks(merge_request, 'reopen')
merge_request.reload_diff(current_user)
merge_request.mark_as_unchecked
- invalidate_cache_counts(merge_request.assignees, merge_request)
+ invalidate_cache_counts(merge_request, users: merge_request.assignees)
end
merge_request
diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb
index d726db4e99b..a02eee4961b 100644
--- a/app/services/metrics_service.rb
+++ b/app/services/metrics_service.rb
@@ -3,7 +3,10 @@ require 'prometheus/client/formats/text'
class MetricsService
CHECKS = [
Gitlab::HealthChecks::DbCheck,
- Gitlab::HealthChecks::RedisCheck,
+ Gitlab::HealthChecks::Redis::RedisCheck,
+ Gitlab::HealthChecks::Redis::CacheCheck,
+ Gitlab::HealthChecks::Redis::QueuesCheck,
+ Gitlab::HealthChecks::Redis::SharedStateCheck,
Gitlab::HealthChecks::FsShardsCheck
].freeze
@@ -28,6 +31,6 @@ class MetricsService
end
def multiprocess_metrics_path
- @multiprocess_metrics_path ||= Rails.root.join(ENV['prometheus_multiproc_dir']).freeze
+ ::Prometheus::Client.configuration.multiprocess_files_dir
end
end
diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb
index 176ab9f1ab5..4963601ea8b 100644
--- a/app/services/milestones/base_service.rb
+++ b/app/services/milestones/base_service.rb
@@ -1,4 +1,10 @@
module Milestones
class BaseService < ::BaseService
+ # Parent can either a group or a project
+ attr_accessor :parent, :current_user, :params
+
+ def initialize(parent, user, params = {})
+ @parent, @current_user, @params = parent, user, params.dup
+ end
end
end
diff --git a/app/services/milestones/close_service.rb b/app/services/milestones/close_service.rb
index 608fc49d766..776ec4b287b 100644
--- a/app/services/milestones/close_service.rb
+++ b/app/services/milestones/close_service.rb
@@ -1,7 +1,7 @@
module Milestones
class CloseService < Milestones::BaseService
def execute(milestone)
- if milestone.close
+ if milestone.close && milestone.is_project_milestone?
event_service.close_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index b8e08c9f1eb..aef3124c7e3 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -1,9 +1,9 @@
module Milestones
class CreateService < Milestones::BaseService
def execute
- milestone = project.milestones.new(params)
+ milestone = parent.milestones.new(params)
- if milestone.save
+ if milestone.save && milestone.is_project_milestone?
event_service.open_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb
index e457212508f..600ebcfbecb 100644
--- a/app/services/milestones/destroy_service.rb
+++ b/app/services/milestones/destroy_service.rb
@@ -1,15 +1,17 @@
module Milestones
class DestroyService < Milestones::BaseService
def execute(milestone)
+ return unless milestone.is_project_milestone?
+
Milestone.transaction do
update_params = { milestone: nil }
milestone.issues.each do |issue|
- Issues::UpdateService.new(project, current_user, update_params).execute(issue)
+ Issues::UpdateService.new(parent, current_user, update_params).execute(issue)
end
milestone.merge_requests.each do |merge_request|
- MergeRequests::UpdateService.new(project, current_user, update_params).execute(merge_request)
+ MergeRequests::UpdateService.new(parent, current_user, update_params).execute(merge_request)
end
event_service.destroy_milestone(milestone, current_user)
diff --git a/app/services/milestones/reopen_service.rb b/app/services/milestones/reopen_service.rb
index 573f9ee5c21..5b8b682caaf 100644
--- a/app/services/milestones/reopen_service.rb
+++ b/app/services/milestones/reopen_service.rb
@@ -1,7 +1,7 @@
module Milestones
class ReopenService < Milestones::BaseService
def execute(milestone)
- if milestone.activate
+ if milestone.activate && milestone.is_project_milestone?
event_service.reopen_milestone(milestone, current_user)
end
diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb
index ed64847f429..31b441ed476 100644
--- a/app/services/milestones/update_service.rb
+++ b/app/services/milestones/update_service.rb
@@ -5,9 +5,9 @@ module Milestones
case state
when 'activate'
- Milestones::ReopenService.new(project, current_user, {}).execute(milestone)
+ Milestones::ReopenService.new(parent, current_user, {}).execute(milestone)
when 'close'
- Milestones::CloseService.new(project, current_user, {}).execute(milestone)
+ Milestones::CloseService.new(parent, current_user, {}).execute(milestone)
end
if params.present?
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 3a98a5f6b64..b94921d2a08 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -17,6 +17,16 @@ class NotificationService
end
end
+ # Always notify the user about gpg key added
+ #
+ # This is a security email so it will be sent even if the user user disabled
+ # notifications
+ def new_gpg_key(gpg_key)
+ if gpg_key.user
+ mailer.new_gpg_key_email(gpg_key.id).deliver_later
+ end
+ end
+
# Always notify user about email added to profile
def new_email(email)
if email.user
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index e2b2660ea71..f6e8b6655f2 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -15,40 +15,48 @@ module Projects
def execute
return false unless can?(current_user, :remove_project, project)
- repo_path = project.path_with_namespace
- wiki_path = repo_path + '.wiki'
-
# Flush the cache for both repositories. This has to be done _before_
# removing the physical repositories as some expiration code depends on
# Git data (e.g. a list of branch names).
- flush_caches(project, wiki_path)
+ flush_caches(project)
Projects::UnlinkForkService.new(project, current_user).execute
- Project.transaction do
- project.team.truncate
- project.destroy!
-
- unless remove_legacy_registry_tags
- raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
- end
-
- unless remove_repository(repo_path)
- raise_error('Failed to remove project repository. Please try again or contact administrator.')
- end
+ attempt_destroy_transaction(project)
- unless remove_repository(wiki_path)
- raise_error('Failed to remove wiki repository. Please try again or contact administrator.')
- end
- end
-
- log_info("Project \"#{project.path_with_namespace}\" was removed")
system_hook_service.execute_hooks_for(project, :destroy)
+ log_info("Project \"#{project.full_path}\" was removed")
+
true
+ rescue => error
+ attempt_rollback(project, error.message)
+ false
+ rescue Exception => error # rubocop:disable Lint/RescueException
+ # Project.transaction can raise Exception
+ attempt_rollback(project, error.message)
+ raise
end
private
+ def repo_path
+ project.path_with_namespace
+ end
+
+ def wiki_path
+ repo_path + '.wiki'
+ end
+
+ def trash_repositories!
+ unless remove_repository(repo_path)
+ raise_error('Failed to remove project repository. Please try again or contact administrator.')
+ end
+
+ unless remove_repository(wiki_path)
+ raise_error('Failed to remove wiki repository. Please try again or contact administrator.')
+ end
+ end
+
def remove_repository(path)
# Skip repository removal. We use this flag when remove user or group
return true if params[:skip_repo] == true
@@ -70,6 +78,26 @@ module Projects
end
end
+ def attempt_rollback(project, message)
+ return unless project
+
+ project.update_attributes(delete_error: message, pending_delete: false)
+ log_error("Deletion failed on #{project.full_path} with the following message: #{message}")
+ end
+
+ def attempt_destroy_transaction(project)
+ Project.transaction do
+ unless remove_legacy_registry_tags
+ raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
+ end
+
+ trash_repositories!
+
+ project.team.truncate
+ project.destroy!
+ end
+ end
+
##
# This method makes sure that we correctly remove registry tags
# for legacy image repository (when repository path equals project path).
@@ -96,7 +124,7 @@ module Projects
"#{path}+#{project.id}#{DELETED_FLAG}"
end
- def flush_caches(project, wiki_path)
+ def flush_caches(project)
project.repository.before_delete
Repository.new(wiki_path, project).before_delete
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index e60b854f916..749a1cc56d8 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -130,7 +130,11 @@ module Projects
end
def max_size
- current_application_settings.max_pages_size.megabytes || MAX_SIZE
+ max_pages_size = current_application_settings.max_pages_size.megabytes
+
+ return MAX_SIZE if max_pages_size.zero?
+
+ [max_pages_size, MAX_SIZE].min
end
def tmp_path
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 55d9cb13ae4..d81035e4eba 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -1,22 +1,16 @@
module Projects
class UpdateService < BaseService
def execute
- # check that user is allowed to set specified visibility_level
- new_visibility = params[:visibility_level]
-
- if new_visibility && new_visibility.to_i != project.visibility_level
- unless can?(current_user, :change_visibility_level, project) &&
- Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
-
- deny_visibility_level(project, new_visibility)
- return error('Visibility level unallowed')
- end
+ unless visibility_level_allowed?
+ return error('New visibility level not allowed!')
end
- new_branch = params[:default_branch]
+ if renaming_project_with_container_registry_tags?
+ return error('Cannot rename project because it contains container registry tags!')
+ end
- if project.repository.exists? && new_branch && new_branch != project.default_branch
- project.change_head(new_branch)
+ if changing_default_branch?
+ project.change_head(params[:default_branch])
end
if project.update_attributes(params.except(:default_branch))
@@ -28,8 +22,40 @@ module Projects
success
else
- error('Project could not be updated')
+ error('Project could not be updated!')
+ end
+ end
+
+ private
+
+ def visibility_level_allowed?
+ # check that user is allowed to set specified visibility_level
+ new_visibility = params[:visibility_level]
+
+ if new_visibility && new_visibility.to_i != project.visibility_level
+ unless can?(current_user, :change_visibility_level, project) &&
+ Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
+
+ deny_visibility_level(project, new_visibility)
+ return false
+ end
end
+
+ true
+ end
+
+ def renaming_project_with_container_registry_tags?
+ new_path = params[:path]
+
+ new_path && new_path != project.path &&
+ project.has_container_registry_tags?
+ end
+
+ def changing_default_branch?
+ new_branch = params[:default_branch]
+
+ project.repository.exists? &&
+ new_branch && new_branch != project.default_branch
end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index e4dfe87e614..5dc1b91d2c0 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -146,32 +146,6 @@ module QuickActions
end
end
- desc do
- "Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
- end
- explanation do |users|
- users = issuable.allows_multiple_assignees? ? users : users.take(1)
- "Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
- end
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
- end
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |assignee_param|
- extract_users(assignee_param)
- end
- command :reassign do |users|
- @updates[:assignee_ids] =
- if issuable.allows_multiple_assignees?
- users.map(&:id)
- else
- [users.last.id]
- end
- end
-
desc 'Set milestone'
explanation do |milestone|
"Sets the milestone to #{milestone.to_reference}." if milestone
@@ -497,6 +471,24 @@ module QuickActions
end
end
+ desc 'Mark this issue as a duplicate of another issue'
+ explanation do |duplicate_reference|
+ "Marks this issue as a duplicate of #{duplicate_reference}."
+ end
+ params '#issue'
+ condition do
+ issuable.is_a?(Issue) &&
+ issuable.persisted? &&
+ current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
+ end
+ command :duplicate do |duplicate_param|
+ canonical_issue = extract_references(duplicate_param, :issue).first
+
+ if canonical_issue.present?
+ @updates[:canonical_issue_id] = canonical_issue.id
+ end
+ end
+
def extract_users(params)
return [] if params.nil?
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index ed476fc9d0c..bd58a54592f 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -4,7 +4,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
- SystemHook.send(hooks_scope).each do |hook|
+ SystemHook.public_send(hooks_scope).find_each do |hook|
hook.async_execute(data, 'system_hooks')
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0837c07e6aa..2dbee9c246e 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -282,7 +282,7 @@ module SystemNoteService
body = "changed this line in"
if version_params = merge_request.version_params_for(diff_refs)
line_code = change_position.line_code(project.repository)
- url = url_helpers.diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, version_params.merge(anchor: line_code))
+ url = url_helpers.diffs_project_merge_request_url(project, merge_request, version_params.merge(anchor: line_code))
body << " [version #{version_index} of the diff](#{url})"
else
@@ -413,7 +413,7 @@ module SystemNoteService
#
# "created branch `201-issue-branch-button`"
def new_issue_branch(issue, project, author, branch)
- link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
+ link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link})"
@@ -552,6 +552,44 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end
+ # Called when a Noteable has been marked as a duplicate of another Issue
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # canonical_issue - Issue that this is a duplicate of
+ #
+ # Example Note text:
+ #
+ # "marked this issue as a duplicate of #1234"
+ #
+ # "marked this issue as a duplicate of other_project#5678"
+ #
+ # Returns the created Note object
+ def mark_duplicate_issue(noteable, project, author, canonical_issue)
+ body = "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}"
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
+ end
+
+ # Called when a Noteable has been marked as the canonical Issue of a duplicate
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # duplicate_issue - Issue that was a duplicate of this
+ #
+ # Example Note text:
+ #
+ # "marked #1234 as a duplicate of this issue"
+ #
+ # "marked other_project#5678 as a duplicate of this issue"
+ #
+ # Returns the created Note object
+ def mark_canonical_issue_of_duplicate(noteable, project, author, duplicate_issue)
+ body = "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue"
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
+ end
+
private
def notes_for_mentioner(mentioner, noteable, notes)
@@ -630,10 +668,9 @@ module SystemNoteService
def diff_comparison_url(merge_request, project, oldrev)
diff_id = merge_request.merge_request_diff.id
- url_helpers.diffs_namespace_project_merge_request_url(
- project.namespace,
+ url_helpers.diffs_project_merge_request_url(
project,
- merge_request.iid,
+ merge_request,
diff_id: diff_id,
start_sha: oldrev
)
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
deleted file mode 100644
index 280c81f7d2d..00000000000
--- a/app/services/test_hook_service.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-class TestHookService
- def execute(hook, current_user)
- data = Gitlab::DataBuilder::Push.build_sample(hook.project, current_user)
- hook.execute(data, 'push_hooks')
- end
-end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
new file mode 100644
index 00000000000..74ba814afff
--- /dev/null
+++ b/app/services/test_hooks/base_service.rb
@@ -0,0 +1,41 @@
+module TestHooks
+ class BaseService
+ attr_accessor :hook, :current_user, :trigger
+
+ def initialize(hook, current_user, trigger)
+ @hook = hook
+ @current_user = current_user
+ @trigger = trigger
+ end
+
+ def execute
+ trigger_data_method = "#{trigger}_data"
+
+ if !self.respond_to?(trigger_data_method, true) ||
+ !hook.class::TRIGGERS.value?(trigger.to_sym)
+
+ return error('Testing not available for this hook')
+ end
+
+ error_message = catch(:validation_error) do
+ sample_data = self.__send__(trigger_data_method)
+
+ return hook.execute(sample_data, trigger)
+ end
+
+ error(error_message)
+ end
+
+ private
+
+ def error(message, http_status = nil)
+ result = {
+ message: message,
+ status: :error
+ }
+
+ result[:http_status] = http_status if http_status
+ result
+ end
+ end
+end
diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb
new file mode 100644
index 00000000000..01d5d774cd5
--- /dev/null
+++ b/app/services/test_hooks/project_service.rb
@@ -0,0 +1,63 @@
+module TestHooks
+ class ProjectService < TestHooks::BaseService
+ private
+
+ def project
+ @project ||= hook.project
+ end
+
+ def push_events_data
+ throw(:validation_error, 'Ensure the project has at least one commit.') if project.empty_repo?
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ alias_method :tag_push_events_data, :push_events_data
+
+ def note_events_data
+ note = project.notes.first
+ throw(:validation_error, 'Ensure the project has notes.') unless note.present?
+
+ Gitlab::DataBuilder::Note.build(note, current_user)
+ end
+
+ def issues_events_data
+ issue = project.issues.first
+ throw(:validation_error, 'Ensure the project has issues.') unless issue.present?
+
+ issue.to_hook_data(current_user)
+ end
+
+ alias_method :confidential_issues_events_data, :issues_events_data
+
+ def merge_requests_events_data
+ merge_request = project.merge_requests.first
+ throw(:validation_error, 'Ensure the project has merge requests.') unless merge_request.present?
+
+ merge_request.to_hook_data(current_user)
+ end
+
+ def job_events_data
+ build = project.builds.first
+ throw(:validation_error, 'Ensure the project has CI jobs.') unless build.present?
+
+ Gitlab::DataBuilder::Build.build(build)
+ end
+
+ def pipeline_events_data
+ pipeline = project.pipelines.first
+ throw(:validation_error, 'Ensure the project has CI pipelines.') unless pipeline.present?
+
+ Gitlab::DataBuilder::Pipeline.build(pipeline)
+ end
+
+ def wiki_page_events_data
+ page = project.wiki.pages.first
+ if !project.wiki_enabled? || page.blank?
+ throw(:validation_error, 'Ensure the wiki is enabled and has pages.')
+ end
+
+ Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
+ end
+ end
+end
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
new file mode 100644
index 00000000000..76c3c19bd74
--- /dev/null
+++ b/app/services/test_hooks/system_service.rb
@@ -0,0 +1,48 @@
+module TestHooks
+ class SystemService < TestHooks::BaseService
+ private
+
+ def project
+ @project ||= begin
+ project = Project.first
+
+ throw(:validation_error, 'Ensure that at least one project exists.') unless project
+
+ project
+ end
+ end
+
+ def push_events_data
+ if project.empty_repo?
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
+ end
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ def tag_push_events_data
+ if project.repository.tags.empty?
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has tags.")
+ end
+
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ def repository_update_events_data
+ commit = project.commit
+ ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
+
+ unless commit
+ throw(:validation_error, "Ensure project \"#{project.human_name}\" has commits.")
+ end
+
+ change = Gitlab::DataBuilder::Repository.single_change(
+ commit.parent_id || Gitlab::Git::BLANK_SHA,
+ commit.id,
+ ref
+ )
+
+ Gitlab::DataBuilder::Repository.update(project, current_user, [change], [ref])
+ end
+ end
+end
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 4628c4c6f6e..3a9c151cf9b 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -50,10 +50,12 @@ module Users
def migrate_issues
user.issues.update_all(author_id: ghost_user.id)
+ Issue.where(last_edited_by_id: user.id).update_all(last_edited_by_id: ghost_user.id)
end
def migrate_merge_requests
user.merge_requests.update_all(author_id: ghost_user.id)
+ MergeRequest.where(merge_user_id: user.id).update_all(merge_user_id: ghost_user.id)
end
def migrate_notes
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 4241b912d5b..a5110a23cad 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -39,7 +39,11 @@ class WebHookService
execution_duration: Time.now - start_time
)
- [response.code, response.to_s]
+ {
+ status: :success,
+ http_status: response.code,
+ message: response.to_s
+ }
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
log_execution(
trigger: hook_name,
@@ -52,7 +56,10 @@ class WebHookService
Rails.logger.error("WebHook Error => #{e}")
- [nil, e.to_s]
+ {
+ status: :error,
+ message: e.to_s
+ }
end
def async_execute
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
index 14317ea65c8..260c04a8b94 100644
--- a/app/services/wiki_pages/base_service.rb
+++ b/app/services/wiki_pages/base_service.rb
@@ -1,23 +1,9 @@
module WikiPages
class BaseService < ::BaseService
- def hook_data(page, action)
- hook_data = {
- object_kind: page.class.name.underscore,
- user: current_user.hook_attrs,
- project: @project.hook_attrs,
- wiki: @project.wiki.hook_attrs,
- object_attributes: page.hook_attrs
- }
-
- page_url = Gitlab::UrlBuilder.build(page)
- hook_data[:object_attributes].merge!(url: page_url, action: action)
- hook_data
- end
-
private
def execute_hooks(page, action = 'create')
- page_data = hook_data(page, action)
+ page_data = Gitlab::DataBuilder::WikiPage.build(page, current_user, action)
@project.execute_hooks(page_data, :wiki_page_hooks)
@project.execute_services(page_data, :wiki_page_hooks)
end
diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb
index 0da7a025591..05a2091633a 100644
--- a/app/uploaders/gitlab_uploader.rb
+++ b/app/uploaders/gitlab_uploader.rb
@@ -16,7 +16,7 @@ class GitlabUploader < CarrierWave::Uploader::Base
def self.base_dir
return root_dir unless file_storage?
- File.join(root_dir, 'system')
+ File.join(root_dir, '-', 'system')
end
def self.file_storage?
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 7f857765fbf..ef70871624b 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -3,6 +3,10 @@ class PersonalFileUploader < FileUploader
File.join(CarrierWave.root, model_path(model))
end
+ def self.base_dir
+ File.join(root_dir, 'system')
+ end
+
private
def secure_url
diff --git a/app/validators/variable_duplicates_validator.rb b/app/validators/variable_duplicates_validator.rb
new file mode 100644
index 00000000000..8a9d8892e9b
--- /dev/null
+++ b/app/validators/variable_duplicates_validator.rb
@@ -0,0 +1,13 @@
+# VariableDuplicatesValidator
+#
+# This validtor is designed for especially the following condition
+# - Use `accepts_nested_attributes_for :xxx` in a parent model
+# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
+class VariableDuplicatesValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
+ if duplicates.any?
+ record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}")
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index b21d5665970..8bb2a563990 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -22,7 +22,9 @@
.form-group
= f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
.col-sm-10
- - restricted_level_checkboxes('restricted-visibility-help').each do |level|
+ - checkbox_name = 'application_setting[restricted_visibility_levels][]'
+ = hidden_field_tag(checkbox_name)
+ - restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level|
.checkbox
= level
%span.help-block#restricted-visibility-help
@@ -143,9 +145,9 @@
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
- = f.label :signin_enabled do
- = f.check_box :signin_enabled
- Sign-in enabled
+ = f.label :password_authentication_enabled do
+ = f.check_box :password_authentication_enabled
+ Password authentication enabled
- if omniauth_enabled? && button_based_providers.any?
.form-group
= f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2'
@@ -313,7 +315,9 @@
%fieldset
%legend Metrics - Prometheus
%p
- Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
+ Enable a Prometheus metrics endpoint at
+ %code= metrics_path
+ to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
= link_to 'here', admin_health_check_path
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
@@ -325,10 +329,29 @@
= f.label :prometheus_metrics_enabled do
= f.check_box :prometheus_metrics_enabled
Enable Prometheus Metrics
- - unless Gitlab::Metrics.metrics_folder_present?
- .help-block
- %strong.cred WARNING:
- Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
+ - unless Gitlab::Metrics.metrics_folder_present?
+ .help-block
+ %strong.cred WARNING:
+ Environment variable
+ %code prometheus_multiproc_dir
+ does not exist or is not pointing to a valid directory.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/gitlab_metrics', anchor: 'metrics-shared-directory')
+
+ %fieldset
+ %legend Profiling - Performance Bar
+ %p
+ Enable the Performance Bar for a given group.
+ = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :performance_bar_enabled do
+ = f.check_box :performance_bar_enabled
+ Enable the Performance Bar
+ .form-group
+ = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
%fieldset
%legend Background Jobs
diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml
index c596866bde2..13b583e6072 100644
--- a/app/views/admin/applications/edit.html.haml
+++ b/app/views/admin/applications/edit.html.haml
@@ -1,4 +1,5 @@
- page_title "Edit", @application.name, "Applications"
+
%h3.page-title Edit application
- @url = admin_application_path(@application)
= render 'form', application: @application
diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml
index 6310d89bd6b..346c58877d9 100644
--- a/app/views/admin/applications/new.html.haml
+++ b/app/views/admin/applications/new.html.haml
@@ -1,4 +1,6 @@
+- breadcrumb_title "Applications"
- page_title "New Application"
+
%h3.page-title New application
- @url = admin_applications_path
= render 'form', application: @application
diff --git a/app/views/admin/broadcast_messages/edit.html.haml b/app/views/admin/broadcast_messages/edit.html.haml
index 45e053eb31d..8cbc4597e32 100644
--- a/app/views/admin/broadcast_messages/edit.html.haml
+++ b/app/views/admin/broadcast_messages/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Messages"
- page_title "Broadcast Messages"
= render 'form'
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 4f2ae081d7a..b806882eee3 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Messages"
- page_title "Broadcast Messages"
%h3.page-title
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 3c9f932a225..8e94e68bc11 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -5,182 +5,182 @@
.admin-dashboard.prepend-top-default
.row
.col-md-4
- %h4 Statistics
- %hr
- %p
- Forks
- %span.light.pull-right
- = number_with_delimiter(ForkedProjectLink.count)
- %p
- Issues
- %span.light.pull-right
- = number_with_delimiter(Issue.count)
- %p
- Merge Requests
- %span.light.pull-right
- = number_with_delimiter(MergeRequest.count)
- %p
- Notes
- %span.light.pull-right
- = number_with_delimiter(Note.count)
- %p
- Snippets
- %span.light.pull-right
- = number_with_delimiter(Snippet.count)
- %p
- SSH Keys
- %span.light.pull-right
- = number_with_delimiter(Key.count)
- %p
- Milestones
- %span.light.pull-right
- = number_with_delimiter(Milestone.count)
- %p
- Active Users
- %span.light.pull-right
- = number_with_delimiter(User.active.count)
+ .info-well
+ .well-segment.admin-well
+ %h4 Statistics
+ %p
+ Forks
+ %span.light.pull-right
+ = number_with_delimiter(ForkedProjectLink.count)
+ %p
+ Issues
+ %span.light.pull-right
+ = number_with_delimiter(Issue.count)
+ %p
+ Merge Requests
+ %span.light.pull-right
+ = number_with_delimiter(MergeRequest.count)
+ %p
+ Notes
+ %span.light.pull-right
+ = number_with_delimiter(Note.count)
+ %p
+ Snippets
+ %span.light.pull-right
+ = number_with_delimiter(Snippet.count)
+ %p
+ SSH Keys
+ %span.light.pull-right
+ = number_with_delimiter(Key.count)
+ %p
+ Milestones
+ %span.light.pull-right
+ = number_with_delimiter(Milestone.count)
+ %p
+ Active Users
+ %span.light.pull-right
+ = number_with_delimiter(User.active.count)
.col-md-4
- %h4
- Features
- %hr
- - sign_up = "Sign up"
- %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
- = sign_up
- %span.light.pull-right
- = boolean_to_icon signup_enabled?
- - ldap = "LDAP"
- %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
- = ldap
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.ldap.enabled
- - gravatar = "Gravatar"
- %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
- = gravatar
- %span.light.pull-right
- = boolean_to_icon gravatar_enabled?
- - omniauth = "OmniAuth"
- %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
- = omniauth
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.omniauth.enabled
- - reply_email = "Reply by email"
- %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
- = reply_email
- %span.light.pull-right
- = boolean_to_icon Gitlab::IncomingEmail.enabled?
- - container_reg = "Container Registry"
- %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
- = container_reg
- %span.light.pull-right
- = boolean_to_icon Gitlab.config.registry.enabled
- - gitlab_pages = 'GitLab Pages'
- - gitlab_pages_enabled = Gitlab.config.pages.enabled
- %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
- = gitlab_pages
- %span.light.pull-right
- = boolean_to_icon gitlab_pages_enabled
- - gitlab_shared_runners = 'Shared Runners'
- - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
- %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
- = gitlab_shared_runners
- %span.light.pull-right
- = boolean_to_icon gitlab_shared_runners_enabled
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Features
+ - sign_up = "Sign up"
+ %p{ "aria-label" => "#{sign_up}: status " + (signup_enabled? ? "on" : "off") }
+ = sign_up
+ %span.light.pull-right
+ = boolean_to_icon signup_enabled?
+ - ldap = "LDAP"
+ %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
+ = ldap
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.ldap.enabled
+ - gravatar = "Gravatar"
+ %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
+ = gravatar
+ %span.light.pull-right
+ = boolean_to_icon gravatar_enabled?
+ - omniauth = "OmniAuth"
+ %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") }
+ = omniauth
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.omniauth.enabled
+ - reply_email = "Reply by email"
+ %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
+ = reply_email
+ %span.light.pull-right
+ = boolean_to_icon Gitlab::IncomingEmail.enabled?
+ - container_reg = "Container Registry"
+ %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
+ = container_reg
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.registry.enabled
+ - gitlab_pages = 'GitLab Pages'
+ - gitlab_pages_enabled = Gitlab.config.pages.enabled
+ %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
+ = gitlab_pages
+ %span.light.pull-right
+ = boolean_to_icon gitlab_pages_enabled
+ - gitlab_shared_runners = 'Shared Runners'
+ - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
+ %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
+ = gitlab_shared_runners
+ %span.light.pull-right
+ = boolean_to_icon gitlab_shared_runners_enabled
.col-md-4
- %h4
- Components
- - if current_application_settings.version_check_enabled
- .pull-right
- = version_status_badge
-
- %hr
- %p
- GitLab
- %span.pull-right
- = Gitlab::VERSION
- %p
- GitLab Shell
- %span.pull-right
- = Gitlab::Shell.new.version
- %p
- GitLab Workhorse
- %span.pull-right
- = gitlab_workhorse_version
- %p
- GitLab API
- %span.pull-right
- = API::API::version
- %p
- Git
- %span.pull-right
- = Gitlab::Git.version
- %p
- Ruby
- %span.pull-right
- #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
-
- %p
- Rails
- %span.pull-right
- #{Rails::VERSION::STRING}
-
- %p
- = Gitlab::Database.adapter_name
- %span.pull-right
- = Gitlab::Database.version
- %hr
+ .info-well
+ .well-segment.admin-well
+ %h4
+ Components
+ - if current_application_settings.version_check_enabled
+ .pull-right
+ = version_status_badge
+ %p
+ GitLab
+ %span.pull-right
+ = Gitlab::VERSION
+ %p
+ GitLab Shell
+ %span.pull-right
+ = Gitlab::Shell.new.version
+ %p
+ GitLab Workhorse
+ %span.pull-right
+ = gitlab_workhorse_version
+ %p
+ GitLab API
+ %span.pull-right
+ = API::API::version
+ %p
+ Git
+ %span.pull-right
+ = Gitlab::Git.version
+ %p
+ Ruby
+ %span.pull-right
+ #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+ %p
+ Rails
+ %span.pull-right
+ #{Rails::VERSION::STRING}
+ %p
+ = Gitlab::Database.adapter_name
+ %span.pull-right
+ = Gitlab::Database.version
.row
.col-sm-4
- .light-well.well-centered
- %h4 Projects
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_projects_path do
- %h1= number_with_delimiter(Project.cached_count)
+ %h3.text-center
+ Projects:
+ = number_with_delimiter(Project.cached_count)
%hr
= link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4
- .light-well.well-centered
- %h4 Users
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_users_path do
- %h1= number_with_delimiter(User.count)
+ %h3.text-center
+ Users:
+ = number_with_delimiter(User.count)
%hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4
- .light-well.well-centered
- %h4 Groups
- .data
+ .info-well.dark-well
+ .well-segment.well-centered
= link_to admin_groups_path do
- %h1= number_with_delimiter(Group.count)
+ %h3.text-center
+ Groups:
+ = number_with_delimiter(Group.count)
%hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new"
-
- .row.prepend-top-10
+ .row
.col-md-4
- %h4 Latest projects
- %hr
- - @projects.each do |project|
- %p
- = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
- %span.light.pull-right
- #{time_ago_with_tooltip(project.created_at)}
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest projects
+ - @projects.each do |project|
+ %p
+ = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
+ %span.light.pull-right
+ #{time_ago_with_tooltip(project.created_at)}
.col-md-4
- %h4 Latest users
- %hr
- - @users.each do |user|
- %p
- = link_to [:admin, user], class: 'str-truncated-60' do
- = user.name
- %span.light.pull-right
- #{time_ago_with_tooltip(user.created_at)}
-
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest users
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated-60' do
+ = user.name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(user.created_at)}
.col-md-4
- %h4 Latest groups
- %hr
- - @groups.each do |group|
- %p
- = link_to [:admin, group], class: 'str-truncated-60' do
- = group.full_name
- %span.light.pull-right
- #{time_ago_with_tooltip(group.created_at)}
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest groups
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated-60' do
+ = group.full_name
+ %span.light.pull-right
+ #{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 9149b8e7fb9..843c71af466 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -107,8 +107,7 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
-
- = render 'shared/members/requests', membership_source: @group, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
@@ -117,7 +116,7 @@
%span.badge= @group.members.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
- %ul.well-list.group-users-list.content-list
+ %ul.well-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 0e35a1905bf..665e8c7e74f 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -12,7 +12,7 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
- = link_to 'Test hook', test_admin_hook_path(@hook), class: 'btn btn-default'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index e92b8bc39f4..fed6002528d 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -22,12 +22,12 @@
- @hooks.each do |hook|
%li
.controls
- = link_to 'Test hook', test_admin_hook_path(hook), class: 'btn btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-small'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url
%div
- - %w(repository_update_events push_events tag_push_events issues_events note_events merge_requests_events job_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray= trigger.titleize
+ - SystemHook::TRIGGERS.each_value do |event|
+ - if hook.public_send(event)
+ %span.label.label-gray= event.to_s.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml
index 596f367a00d..c69c2761189 100644
--- a/app/views/admin/projects/_projects.html.haml
+++ b/app/views/admin/projects/_projects.html.haml
@@ -4,7 +4,7 @@
- @projects.each_with_index do |project|
%li.project-row{ class: ('no-description' if project.description.blank?) }
.controls
- = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn"
= link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove"
.stats
%span.badge
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 08a8f627113..7b1b15cfeb8 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -108,7 +108,7 @@
.panel-heading
Transfer project
.panel-body
- = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f|
+ = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
.form-group
= f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
@@ -128,7 +128,7 @@
.panel-heading
Repository check
.panel-body
- = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
+ = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f|
.form-group
- if @project.last_repository_check_at.nil?
This repository has never been checked.
@@ -160,12 +160,12 @@
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage access')
- %ul.well-list.content-list
+ %ul.well-list.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
@@ -174,7 +174,7 @@
%span.badge= @project.users.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
- %ul.well-list.project_members.content-list
+ %ul.well-list.project_members.content-list.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 2da8f615470..126550ee10e 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -2,26 +2,6 @@
= render "admin/dashboard/head"
%div{ class: container_class }
-
- %p.prepend-top-default
- %span
- To register a new Runner you should enter the following registration
- token.
- With this token the Runner will request a unique Runner token and use
- that for future communication.
- %br
- Registration token is
- %code#runners-token= current_application_settings.runners_registration_token
-
- .bs-callout.clearfix
- .pull-left
- %p
- You can reset runners registration token by pressing a button below.
- .prepend-top-10
- = button_to "Reset runners registration token", reset_runners_token_admin_application_settings_path,
- method: :put, class: 'btn btn-default',
- data: { confirm: 'Are you sure you want to reset registration token?' }
-
.bs-callout
%p
A 'Runner' is a process which runs a job.
@@ -46,6 +26,19 @@
%span.label.label-danger paused
\- Runner will not receive any new jobs
+ .bs-callout.clearfix
+ .pull-left
+ %p
+ You can reset runners registration token by pressing a button below.
+ .prepend-top-10
+ = button_to _("Reset runners registration token"), reset_runners_token_admin_application_settings_path,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: _("Are you sure you want to reset registration token?") }
+
+ = render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: current_application_settings.runners_registration_token,
+ type: 'shared' }
+
.append-bottom-20.clearfix
.pull-left
= form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 801430e525e..df2bf27be9d 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -85,7 +85,7 @@
%tr.build
%td.id
- if project
- = link_to namespace_project_job_path(project.namespace, project, build) do
+ = link_to project_job_path(project, build) do
%strong ##{build.id}
- else
%strong ##{build.id}
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index 15eaf1c0e67..4a440f3f6d4 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -33,7 +33,7 @@
- member = project.team.find_member(@user.id)
%li.project_member
.list-item-name
- = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
+ = link_to admin_project_path(project), class: dom_class(project) do
= project.name_with_namespace
- if member
@@ -44,5 +44,5 @@
%span.light.vertical-align-middle= member.human_access
- if member.respond_to? :project
- = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
+ = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do
%i.fa.fa-times
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
new file mode 100644
index 00000000000..b75dab0acc5
--- /dev/null
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -0,0 +1,16 @@
+- link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'
+.bs-callout.help-callout
+ %h4= _("How to setup a #{type} Runner for a new project")
+
+ %ol
+ %li
+ = _("Install a Runner compatible with GitLab CI")
+ = (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe
+ %li
+ = _("Specify the following URL during the Runner setup:")
+ %code= root_url(only_path: false)
+ %li
+ = _("Use the following registration token during setup:")
+ %code#registration_token= registration_token
+ %li
+ = _("Start the Runner!")
diff --git a/app/views/projects/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index 98f618ca3b8..98f618ca3b8 100644
--- a/app/views/projects/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
diff --git a/app/views/projects/variables/_form.html.haml b/app/views/ci/variables/_form.html.haml
index 0a70a301cb4..eebd0955c80 100644
--- a/app/views/projects/variables/_form.html.haml
+++ b/app/views/ci/variables/_form.html.haml
@@ -1,12 +1,12 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @variable] do |f|
+= form_for @variable, as: :variable, url: @variable.form_path do |f|
= form_errors(@variable)
.form-group
= f.label :key, "Key", class: "label-light"
- = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true
+ = f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true
.form-group
= f.label :value, "Value", class: "label-light"
- = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE"
+ = f.text_area :value, class: "form-control", placeholder: @variable.placeholder
.form-group
.checkbox
= f.label :protected do
diff --git a/app/views/projects/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index 5e6786f6698..007c2344b5a 100644
--- a/app/views/projects/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -1,16 +1,16 @@
.row.prepend-top-default.append-bottom-default
.col-lg-4
- = render "projects/variables/content"
+ = render "ci/variables/content"
.col-lg-8
%h5.prepend-top-0
Add a variable
- = render "projects/variables/form", btn_text: "Add new variable"
+ = render "ci/variables/form", btn_text: "Add new variable"
%hr
%h5.prepend-top-0
- Your variables (#{@project.variables.size})
- - if @project.variables.empty?
+ Your variables (#{@variables.size})
+ - if @variables.empty?
%p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above.
- else
- = render "projects/variables/table"
+ = render "ci/variables/table"
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
diff --git a/app/views/ci/variables/_show.html.haml b/app/views/ci/variables/_show.html.haml
new file mode 100644
index 00000000000..2bfb290629d
--- /dev/null
+++ b/app/views/ci/variables/_show.html.haml
@@ -0,0 +1,9 @@
+- page_title "Variables"
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ = render "ci/variables/content"
+ .col-lg-9
+ %h5.prepend-top-0
+ Update variable
+ = render "ci/variables/form", btn_text: "Save variable"
diff --git a/app/views/projects/variables/_table.html.haml b/app/views/ci/variables/_table.html.haml
index 59cd3c4b592..71a0b56c4f4 100644
--- a/app/views/projects/variables/_table.html.haml
+++ b/app/views/ci/variables/_table.html.haml
@@ -11,18 +11,18 @@
%th Protected
%th
%tbody
- - @project.variables.order_key_asc.each do |variable|
+ - @variables.each do |variable|
- if variable.id?
%tr
%td.variable-key= variable.key
%td.variable-value{ "data-value" => variable.value }******
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
%td.variable-menu
- = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do
+ = link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
%span.sr-only
Update
= icon("pencil")
- = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
+ = link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
%span.sr-only
Remove
= icon("trash")
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 4594c52b34b..5a379eae8f4 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,3 +1,7 @@
+- if show_new_nav? && current_user.can_create_group?
+ - content_for :breadcrumbs_extra do
+ = link_to "New group", new_group_path, class: "btn btn-new"
+
.top-area
%ul.nav-links
= nav_link(page: dashboard_groups_path) do
@@ -6,9 +10,8 @@
= nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups
- .nav-controls
+ .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- if current_user.can_create_group?
- = link_to new_group_path, class: "btn btn-new" do
- New group
+ = link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 64b737ee886..1f9a5b401b6 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,5 +1,10 @@
= content_for :flash_message do
= render 'shared/project_limit'
+
+- if show_new_nav? && current_user.can_create_project?
+ - content_for :breadcrumbs_extra do
+ = link_to "New project", new_project_path, class: 'btn btn-new'
+
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
@@ -14,9 +19,8 @@
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore projects
- .nav-controls
+ .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
- = link_to new_project_path, class: 'btn btn-new' do
- New project
+ = link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}"
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 02e90bbfa55..fd5389106bb 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,3 +1,7 @@
+- if show_new_nav? && current_user
+ - content_for :breadcrumbs_extra do
+ = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
+
.top-area
%ul.nav-links
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
@@ -8,6 +12,5 @@
Explore Snippets
- if current_user
- .nav-controls.hidden-xs
- = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do
- New snippet
+ .nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) }
+ = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index d6b46dee0e4..52e0012fd7d 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,11 +1,18 @@
+- @hide_top_links = true
- page_title "Issues"
- header_title "Issues", issues_dashboard_path(assignee_id: current_user.id)
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
+- if show_new_nav?
+ - content_for :breadcrumbs_extra do
+ = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
+ = icon('rss')
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues'
+
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 6f6afe161d1..c3fe14da2b2 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -1,9 +1,14 @@
+- @hide_top_links = true
- page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
+- if show_new_nav?
+ - content_for :breadcrumbs_extra do
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests'
+
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests'
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index ef1467c4d78..37dbcaf5cb8 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -2,10 +2,14 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
+- if show_new_nav?
+ - content_for :breadcrumbs_extra do
+ = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true
+
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true
.milestones
diff --git a/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
new file mode 100644
index 00000000000..209afd4aab4
--- /dev/null
+++ b/app/views/dashboard/projects/_blank_state_admin_welcome.html.haml
@@ -0,0 +1,33 @@
+.blank-state
+ .blank-state-icon
+ = custom_icon("add_new_user", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Add user
+ %p.blank-state-text
+ Add your team members and others to GitLab.
+ = link_to new_admin_user_path, class: "btn btn-new" do
+ New user
+
+.blank-state
+ .blank-state-icon
+ = custom_icon("configure_server", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Configure GitLab
+ %p.blank-state-text
+ Make adjustments to how your GitLab instance is set up.
+ = link_to admin_root_path, class: "btn btn-new" do
+ Configure
+
+- if current_user.can_create_group?
+ .blank-state
+ .blank-state-icon
+ = custom_icon("add_new_group", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Create a group
+ %p.blank-state-text
+ Groups are a great way to organise projects and people.
+ = link_to new_group_path, class: "btn btn-new" do
+ New group
diff --git a/app/views/dashboard/projects/_blank_state_welcome.html.haml b/app/views/dashboard/projects/_blank_state_welcome.html.haml
new file mode 100644
index 00000000000..a93a3415ee1
--- /dev/null
+++ b/app/views/dashboard/projects/_blank_state_welcome.html.haml
@@ -0,0 +1,48 @@
+- public_project_count = ProjectsFinder.new(current_user: current_user).execute.count
+
+- if current_user.can_create_group?
+ .blank-state
+ .blank-state-icon
+ = custom_icon("add_new_group", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Create a group for several dependent projects.
+ %p.blank-state-text
+ Groups are the best way to manage projects and members.
+ = link_to new_group_path, class: "btn btn-new" do
+ New group
+
+.blank-state
+ .blank-state-icon
+ = custom_icon("add_new_project", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Create a project
+ %p.blank-state-text
+ - if current_user.can_create_project?
+ You don't have access to any projects right now.
+ You can create up to
+ %strong= number_with_delimiter(current_user.projects_limit)
+ = succeed "." do
+ = "project".pluralize(current_user.projects_limit)
+ - else
+ If you are added to a project, it will be displayed here.
+ - if current_user.can_create_project?
+ = link_to new_project_path, class: "btn btn-new" do
+ New project
+
+- if public_project_count > 0
+ .blank-state
+ .blank-state-icon
+ = custom_icon("globe", size: 50)
+ .blank-state-body
+ %h3.blank-state-title
+ Explore public projects
+ %p.blank-state-text
+ There are
+ = number_with_delimiter(public_project_count)
+ public projects on this server.
+ Public projects are an easy way to allow
+ everyone to have read-only access.
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
+ Browse projects
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index 8843d4e7c84..ad3fac6d164 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -1,47 +1,12 @@
-- publicish_project_count = ProjectsFinder.new(current_user: current_user).execute.count
-.blank-state.blank-state-welcome
- %h2.blank-state-welcome-title
- Welcome to GitLab
- %p.blank-state-text
- Code, test, and deploy together
-
-- if current_user.can_create_group?
- .blank-state
- .blank-state-icon
- = custom_icon("group", size: 50)
- %h3.blank-state-title
- You can create a group for several dependent projects.
- %p.blank-state-text
- Groups are the best way to manage projects and members.
- = link_to new_group_path, class: "btn btn-new" do
- New group
-
-.blank-state
- .blank-state-icon
- = custom_icon("project", size: 50)
- %h3.blank-state-title
- You don't have access to any projects right now
- %p.blank-state-text
- - if current_user.can_create_project?
- You can create up to
- %strong= number_with_delimiter(current_user.projects_limit)
- = succeed "." do
- = "project".pluralize(current_user.projects_limit)
- - else
- If you are added to a project, it will be displayed here.
- - if current_user.can_create_project?
- = link_to new_project_path, class: "btn btn-new" do
- New project
-
-- if publicish_project_count > 0
- .blank-state
- .blank-state-icon
- = icon("globe")
- %h3.blank-state-title
- There are
- = number_with_delimiter(publicish_project_count)
- public projects on this server.
- %p.blank-state-text
- Public projects are an easy way to allow everyone to have read-only access.
- = link_to trending_explore_projects_path, class: "btn btn-new" do
- Browse projects
+.row.blank-state-parent-container
+ .section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" }
+ .container.section-body
+ .blank-state.blank-state-welcome
+ %h2.blank-state-welcome-title
+ Welcome to GitLab
+ %p.blank-state-text
+ Code, test, and deploy together
+ - if current_user.admin?
+ = render "blank_state_admin_welcome"
+ - else
+ = render "blank_state_welcome"
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 7ac6cf06fb9..ec6cb1a9624 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- @hide_top_links = true
-- @breadcrumb_title = "Projects"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 99efe9c9b86..ae1d733a516 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,5 +1,6 @@
+- @hide_top_links = true
- @no_container = true
-
+- breadcrumb_title "Projects"
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 52d6ebd8a14..9b615ec999e 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index af87129e49e..dd61dcf2a7b 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -6,15 +6,15 @@
- else
= render 'devise/shared/tabs_normal'
.tab-content
- - if signin_enabled? || ldap_enabled? || crowd_enabled?
+ - if password_authentication_enabled? || ldap_enabled? || crowd_enabled?
= render 'devise/shared/signin_box'
-# Signup only makes sense if you can also sign-in
- - if signin_enabled? && signup_enabled?
+ - if password_authentication_enabled? && signup_enabled?
= render 'devise/shared/signup_box'
-# Show a message if none of the mechanisms above are enabled
- - if !signin_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
+ - if !password_authentication_enabled? && !ldap_enabled? && !(omniauth_enabled? && devise_mapping.omniauthable?)
%div
No authentication methods configured.
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index f92f89e73ff..bfd7dd25a7d 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -6,4 +6,7 @@
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
- = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn')
+ = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}"
+ %fieldset.prepend-top-10
+ = check_box_tag :remember_me
+ = label_tag :remember_me, 'Remember me'
diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml
index da4769e214e..3b06008febe 100644
--- a/app/views/devise/shared/_signin_box.html.haml
+++ b/app/views/devise/shared/_signin_box.html.haml
@@ -7,12 +7,12 @@
.login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i.zero? && !crowd_enabled?) }
.login-body
= render 'devise/sessions/new_ldap', server: server
- - if signin_enabled?
+ - if password_authentication_enabled?
.login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' }
.login-body
= render 'devise/sessions/new_base'
-- elsif signin_enabled?
+- elsif password_authentication_enabled?
.login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
= render 'devise/sessions/new_base'
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index dd34600490e..6d0243a325d 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -5,9 +5,9 @@
- @ldap_servers.each_with_index do |server, i|
%li{ class: active_when(i.zero? && !crowd_enabled?) }
= link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab'
- - if signin_enabled?
+ - if password_authentication_enabled?
%li
= link_to 'Standard', '#ldap-standard', 'data-toggle' => 'tab'
- - if signin_enabled? && signup_enabled?
+ - if password_authentication_enabled? && signup_enabled?
%li
= link_to 'Register', '#register-pane', 'data-toggle' => 'tab'
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index c225d800a98..212856c0676 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,6 +1,6 @@
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- - if signin_enabled? && signup_enabled?
+ - if password_authentication_enabled? && signup_enabled?
%li{ role: 'presentation' }
%a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register
diff --git a/app/views/discussions/_new_issue_for_all_discussions.html.haml b/app/views/discussions/_new_issue_for_all_discussions.html.haml
index ca9e0e8728a..cab346fb514 100644
--- a/app/views/discussions/_new_issue_for_all_discussions.html.haml
+++ b/app/views/discussions/_new_issue_for_all_discussions.html.haml
@@ -3,4 +3,4 @@
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve all discussions in new issue",
"aria-label" => "Resolve all discussions in a new issue",
"data-container" => "body" }
- = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
+ = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid), title: "Resolve all discussions in new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/discussions/_new_issue_for_discussion.html.haml b/app/views/discussions/_new_issue_for_discussion.html.haml
index df5546a1e32..a9bc317b8f8 100644
--- a/app/views/discussions/_new_issue_for_discussion.html.haml
+++ b/app/views/discussions/_new_issue_for_discussion.html.haml
@@ -5,4 +5,4 @@
.btn.btn-default.discussion-create-issue-btn.has-tooltip{ title: "Resolve this discussion in a new issue",
"aria-label" => "Resolve this discussion in a new issue",
"data-container" => "body" }
- = link_to custom_icon('icon_mr_issue'), new_namespace_project_issue_path(@project.namespace, @project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
+ = link_to custom_icon('icon_mr_issue'), new_project_issue_path(@project, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id), title: "Resolve this discussion in a new issue", class: 'new-issue-for-discussion'
diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml
index 3c64f1be5ff..ad434a64556 100644
--- a/app/views/events/_commit.html.haml
+++ b/app/views/events/_commit.html.haml
@@ -1,5 +1,5 @@
%li.commit
.commit-row-title
- = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
+ = link_to truncate_sha(commit[:id]), project_commit_path(project, commit[:id]), class: "commit-sha", alt: '', title: truncate_sha(commit[:id])
&middot;
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line, author: event.author
diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml
index f8f0bcb7608..9fcacfbbf36 100644
--- a/app/views/events/_event_push.atom.haml
+++ b/app/views/events/_event_push.atom.haml
@@ -2,7 +2,7 @@
- event.commits.first(15).each do |commit|
%p
%strong= commit[:author][:name]
- = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id])
+ = link_to "(##{truncate_sha(commit[:id])})", project_commit_path(event.project, id: commit[:id])
%i
at
= commit[:timestamp].to_time.to_s(:short)
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 769ac655d0a..54b414cc62a 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -6,7 +6,7 @@
%span.author_name= link_to_author event
%span.pushed #{event.action_name} #{event.ref_type}
%strong
- - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
+ - commits_link = project_commits_path(project, event.ref_name)
= link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
@@ -31,7 +31,7 @@
- from = project.default_branch
- from_label = from
- = link_to namespace_project_compare_path(project.namespace, project, from: from, to: event.commit_to) do
+ = link_to project_compare_path(project, from: from, to: event.commit_to) do
Compare #{from_label}...#{truncate_sha(event.commit_to)}
- if create_mr
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index ffe07b217a7..2651ef37e67 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index ec461755103..f00802e0af7 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index ec461755103..f00802e0af7 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index ec461755103..f00802e0af7 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml
index e5706d04736..94fc4ac21d2 100644
--- a/app/views/explore/snippets/index.html.haml
+++ b/app/views/explore/snippets/index.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- page_title "Snippets"
- header_title "Snippets", snippets_path
diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml
index 2454e7355a7..623d233a46a 100644
--- a/app/views/groups/_settings_head.html.haml
+++ b/app/views/groups/_settings_head.html.haml
@@ -12,3 +12,8 @@
= link_to projects_group_path(@group), title: 'Projects' do
%span
Projects
+
+ = nav_link(controller: :ci_cd) do
+ = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
+ %span
+ Pipelines
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 2e4e4511bb6..ad9d5562ded 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -27,6 +27,6 @@
Members with access to
%strong= @group.name
%span.badge= @members.total_count
- %ul.content-list
+ %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 182dbe2f98a..735d9390699 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,12 +1,19 @@
- page_title "Issues"
+- group_issues_exists = group_issues(@group).exists?
= render "head_issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
-- if group_issues(@group).exists?
+- if show_new_nav? && group_issues_exists
+ - content_for :breadcrumbs_extra do
+ = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
+ = icon('rss')
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue"
+
+- if group_issues_exists
.top-area
= render 'shared/issuable/nav', type: :issues
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= link_to params.merge(rss_url_options), class: 'btn' do
= icon('rss')
%span.icon-label
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 2bc00fb16c8..50179a47797 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,14 +1,18 @@
- page_title 'Labels'
+- if show_new_nav? && can?(current_user, :admin_label, @group)
+ - content_for :breadcrumbs_extra do
+ = link_to "New label", new_group_label_path(@group), class: "btn btn-new"
+
= render "groups/head_issues"
+
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
- if can?(current_user, :admin_label, @group)
- = link_to new_group_label_path(@group), class: "btn btn-new" do
- New label
+ = link_to "New label", new_group_label_path(@group), class: "btn btn-new"
.labels
.other-labels
diff --git a/app/views/groups/labels/new.html.haml b/app/views/groups/labels/new.html.haml
index 2be87460b1d..ae240490bbd 100644
--- a/app/views/groups/labels/new.html.haml
+++ b/app/views/groups/labels/new.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Labels"
- page_title 'New Label'
- header_title group_title(@group, 'Labels', group_labels_path(@group))
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 45e39252e16..997c82c77d9 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,12 +1,16 @@
- page_title "Merge Requests"
+- if show_new_nav? && current_user
+ - content_for :breadcrumbs_extra do
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request"
+
- if @group_merge_requests.empty?
= render 'shared/empty_states/merge_requests', project_select_button: true
- else
.top-area
= render 'shared/issuable/nav', type: :merge_requests
- if current_user
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request"
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml
new file mode 100644
index 00000000000..7f450cd9a93
--- /dev/null
+++ b/app/views/groups/milestones/_form.html.haml
@@ -0,0 +1,27 @@
+= form_for [@group, @milestone], html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
+ .row
+ = form_errors(@milestone)
+
+ .col-md-6
+ .form-group
+ = f.label :title, "Title", class: "control-label"
+ .col-sm-10
+ = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
+ .form-group.milestone-description
+ = f.label :description, "Description", class: "control-label"
+ .col-sm-10
+ = render layout: 'projects/md_preview', locals: { url: '' } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
+ .clearfix
+ .error-alert
+
+ = render "shared/milestones/form_dates", f: f
+
+ .form-actions
+ - if @milestone.new_record?
+ = f.submit 'Create milestone', class: "btn-create btn"
+ = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
+ - else
+ = f.submit 'Update milestone', class: "btn-create btn"
+ = link_to "Cancel", group_milestone_path(@group, @milestone), class: "btn btn-cancel"
+
diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index 4c4e0a26728..bae8997e24c 100644
--- a/app/views/groups/milestones/_milestone.html.haml
+++ b/app/views/groups/milestones/_milestone.html.haml
@@ -1,5 +1,6 @@
+
= render 'shared/milestones/milestone',
- milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
+ milestone_path: group_milestone_route(milestone),
issues_path: issues_group_path(@group, milestone_title: milestone.title),
merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
milestone: milestone
diff --git a/app/views/groups/milestones/edit.html.haml b/app/views/groups/milestones/edit.html.haml
new file mode 100644
index 00000000000..5f6d7d209d0
--- /dev/null
+++ b/app/views/groups/milestones/edit.html.haml
@@ -0,0 +1,7 @@
+- page_title "Milestones"
+- render "header_title"
+
+%h3.page-title
+ Edit Milestone
+
+= render "form"
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index f91bee0b610..66c6cc9e279 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,18 +1,16 @@
- page_title "Milestones"
+- if show_new_nav? && can?(current_user, :admin_milestones, @group)
+ - content_for :breadcrumbs_extra do
+ = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
+
= render "groups/head_issues"
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
- if can?(current_user, :admin_milestones, @group)
- = link_to new_group_milestone_path(@group), class: "btn btn-new" do
- New milestone
-
-.row-content-block
- Only milestones from
- %strong= @group.name
- group are listed here.
+ = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
.milestones
%ul.content-list
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 7c7573862d0..eca7fb9ddb1 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -1,43 +1,8 @@
+- breadcrumb_title "Milestones"
- page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group))
%h3.page-title
New Milestone
-%p.light
- This will create milestone in every selected project
-%hr
-
-= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
- .row
- - if @milestone.errors.any?
- #error_explanation
- .alert.alert-danger
- %ul
- - @milestone.errors.full_messages.each do |msg|
- %li
- = msg
-
- .col-md-6
- .form-group
- = f.label :title, "Title", class: "control-label"
- .col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true
- .form-group.milestone-description
- = f.label :description, "Description", class: "control-label"
- .col-sm-10
- = render layout: 'projects/md_preview', locals: { url: '' } do
- = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
- .clearfix
- .error-alert
- .form-group
- = f.label :projects, "Projects", class: "control-label"
- .col-sm-10
- = f.collection_select :project_ids, @group.projects.non_archived, :id, :name,
- { selected: @group.projects.non_archived.pluck(:id) }, required: true, multiple: true, class: 'select2'
-
- = render "shared/milestones/form_dates", f: f
-
- .form-actions
- = f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel"
+= render "form"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 33e68bc766e..54b1b7a734a 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,4 +1,4 @@
= render "header_title"
= render 'shared/milestones/top', milestone: @milestone, group: @group
-= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
+= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true if @milestone.is_legacy_group_milestone?
= render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 102
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 000c7af2326..e9daac95ca1 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,3 +1,6 @@
+- @breadcrumb_link = dashboard_groups_path
+- breadcrumb_title "Groups"
+- @hide_top_links = true
- page_title 'New Group'
- header_title "Groups", dashboard_groups_path
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 62ad47972b9..7a2e688a114 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -20,8 +20,8 @@
%span.label.label-warning archived
%span.badge
= storage_counter(project.statistics.storage_size)
- = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
- = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
+ = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank?
.nothing-here-block This group has no projects yet
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
new file mode 100644
index 00000000000..bf36baf48ab
--- /dev/null
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -0,0 +1,4 @@
+- page_title "Pipelines"
+= render "groups/settings_head"
+
+= render 'ci/variables/index'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 80a8ba4a755..e07f61c94e4 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Group"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
diff --git a/app/views/groups/variables/show.html.haml b/app/views/groups/variables/show.html.haml
new file mode 100644
index 00000000000..df533952b76
--- /dev/null
+++ b/app/views/groups/variables/show.html.haml
@@ -0,0 +1 @@
+= render 'ci/variables/show'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 331d1181220..56e628a2b74 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -27,10 +27,11 @@
%td.shortcut
.key f
%td Focus Filter
- %tr
- %td.shortcut
- .key p b
- %td Show/hide the Performance Bar
+ - if performance_bar_enabled?
+ %tr
+ %td.shortcut
+ .key p b
+ %td Show/hide the Performance Bar
%tr
%td.shortcut
.key ?
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 615dd56afbd..48edbb8c16f 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -525,7 +525,7 @@
%h4
%code .file-holder
- - blob = Snippet.new(content: "Wow\nSuch\nFile")
+ - blob = Snippet.new(content: "Wow\nSuch\nFile").blob
.example
.file-holder
.js-file-title.file-title
diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml
index 57e8c3ca1e1..fde671e25a9 100644
--- a/app/views/import/base/create.js.haml
+++ b/app/views/import/base/create.js.haml
@@ -4,7 +4,7 @@
job.attr("id", "project_#{@project.id}")
target_field = job.find(".import-target")
target_field.empty()
- target_field.append('#{link_to @project.path_with_namespace, namespace_project_path(@project.namespace, @project)}')
+ target_field.append('#{link_to @project.path_with_namespace, project_path(@project)}')
$("table.import-jobs tbody").prepend(job)
job.addClass("active").find(".import-actions").html("<i class='fa fa-spinner fa-spin'></i> started")
- else
diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml
index 882fdf1317d..ad6213b4efd 100644
--- a/app/views/invites/show.html.haml
+++ b/app/views/invites/show.html.haml
@@ -12,7 +12,7 @@
- project = @member.source
project
%strong
- = link_to project.name_with_namespace, namespace_project_url(project.namespace, project)
+ = link_to project.name_with_namespace, project_url(project)
- when Group
- group = @member.source
group
diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder
index 2ed78bb3b65..0c113c08526 100644
--- a/app/views/issues/_issue.atom.builder
+++ b/app/views/issues/_issue.atom.builder
@@ -1,6 +1,6 @@
xml.entry do
- xml.id namespace_project_issue_url(issue.project.namespace, issue.project, issue)
- xml.link href: namespace_project_issue_url(issue.project.namespace, issue.project, issue)
+ xml.id project_issue_url(issue.project, issue)
+ xml.link href: project_issue_url(issue.project, issue)
xml.title truncate(issue.title, length: 80)
xml.updated issue.updated_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml
index bcd2f03e83c..e2dbdcbb939 100644
--- a/app/views/layouts/_broadcast.html.haml
+++ b/app/views/layouts/_broadcast.html.haml
@@ -1,2 +1,2 @@
-- BroadcastMessage.current.each do |message|
+- BroadcastMessage.current&.each do |message|
= broadcast_message(message)
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index abb6dc2e9f3..6ad22958df3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,7 +30,7 @@
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
- = stylesheet_link_tag 'peek' if peek_enabled?
+ = stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- if show_new_nav?
= stylesheet_link_tag "new_nav", media: "all"
@@ -44,7 +44,7 @@
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
- = webpack_bundle_tag 'peek' if peek_enabled?
+ = webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 6caaba240bb..4bb0dfc73fd 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -5,10 +5,10 @@
:javascript
gl.GfmAutoComplete = gl.GfmAutoComplete || {};
gl.GfmAutoComplete.dataSources = {
- members: "#{members_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}",
- issues: "#{issues_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- mergeRequests: "#{merge_requests_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- labels: "#{labels_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- milestones: "#{milestones_namespace_project_autocomplete_sources_path(project.namespace, project)}",
- commands: "#{commands_namespace_project_autocomplete_sources_path(project.namespace, project, type: noteable_type, type_id: params[:id])}"
+ members: "#{members_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}",
+ issues: "#{issues_project_autocomplete_sources_path(project)}",
+ mergeRequests: "#{merge_requests_project_autocomplete_sources_path(project)}",
+ labels: "#{labels_project_autocomplete_sources_path(project)}",
+ milestones: "#{milestones_project_autocomplete_sources_path(project)}",
+ commands: "#{commands_project_autocomplete_sources_path(project, type: noteable_type, type_id: params[:id])}"
};
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 1a9f5401a78..873220cc73d 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -10,12 +10,17 @@
- if content_for?(:sub_nav)
= yield :sub_nav
.content-wrapper{ class: "#{(layout_nav_class unless show_new_nav?)}" }
+ - if show_new_nav?
+ .mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
+ - if show_new_nav?
+ - if content_for?(:new_global_flash)
+ = yield :new_global_flash
+ - unless @hide_breadcrumbs
+ = render "layouts/nav/breadcrumbs"
= render "layouts/flash"
= yield :flash_message
- - if show_new_nav?
- = render "layouts/nav/breadcrumbs"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index b689991bb6d..59f16b47bf7 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -5,7 +5,7 @@
- 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?
- - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) }
+ - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) }
.search.search-form{ class: "#{'has-location-badge' if label.present?}" }
= form_tag search_path, method: :get, class: 'navbar-form' do |f|
.search-input-container
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index d879df8fc82..38b95d11fd4 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,9 +1,8 @@
!!! 5
%html{ lang: I18n.locale, class: "#{page_class}" }
= render "layouts/head"
- %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
+ %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form
- = render 'peek/bar'
- if show_new_nav?
= render "layouts/header/new"
- else
@@ -11,3 +10,5 @@
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
+
+ = render 'peek/bar'
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 8cbc3f6105f..bc3293fd100 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -74,9 +74,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- - if can_toggle_new_nav?
- %li
- = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
+ %li
+ = link_to "Turn on new navigation", profile_preferences_path(anchor: "new-navigation")
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
@@ -92,8 +91,3 @@
= yield :header_content
= render 'shared/outdated_browser'
-
-- if @project && !@project.empty_repo?
- - if ref = @ref || @project.repository.root_ref
- :javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
diff --git a/app/views/layouts/header/_new.html.haml b/app/views/layouts/header/_new.html.haml
index 5859f689dd1..60940dba475 100644
--- a/app/views/layouts/header/_new.html.haml
+++ b/app/views/layouts/header/_new.html.haml
@@ -6,8 +6,8 @@
%h1.title
= link_to root_path, title: 'Dashboard' do
= brand_header_logo
- %span.hidden-xs
- GitLab
+ %span.logo-text.hidden-xs
+ = render 'shared/logo_type.svg'
- if current_user
= render "layouts/nav/new_dashboard"
@@ -69,7 +69,7 @@
%li
= link_to "Settings", profile_path
%li
- = link_to "Turn off new nav", profile_preferences_path(anchor: "new-navigation")
+ = link_to "Turn off new navigation", profile_preferences_path(anchor: "new-navigation")
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
@@ -81,11 +81,6 @@
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
- = icon('times', class: 'js-navbar-toggle-left', style: 'display: none;')
+ = icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
-
-- if @project && !@project.empty_repo?
- - if ref = @ref || @project.repository.root_ref
- :javascript
- var findFileURL = "#{namespace_project_find_file_path(@project.namespace, @project, ref)}";
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 4d41579168c..9da739b0974 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -30,13 +30,13 @@
%li.dropdown-bold-header This project
- if create_project_issue
%li
- = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project)
+ = link_to 'New issue', new_project_issue_path(@project)
- if merge_project
%li
- = link_to 'New merge request', namespace_project_new_merge_request_path(merge_project.namespace, merge_project)
+ = link_to 'New merge request', project_new_merge_request_path(merge_project)
- if create_project_snippet
%li.header-new-project-snippet
- = link_to 'New snippet', new_namespace_project_snippet_path(@project.namespace, @project)
+ = link_to 'New snippet', new_project_snippet_path(@project)
%li.divider
%li.dropdown-bold-header GitLab
- if current_user.can_create_project?
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index 5f1641f4300..4db84771f4e 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -1,19 +1,27 @@
-- breadcrumb_title = @breadcrumb_title || controller.controller_name.humanize
+- breadcrumb_link = breadcrumb_title_link
- hide_top_links = @hide_top_links || false
%nav.breadcrumbs{ role: "navigation" }
- .breadcrumbs-container{ class: container_class }
+ .breadcrumbs-container{ class: [container_class, @content_class] }
+ - if defined?(@new_sidebar)
+ = button_tag class: 'toggle-mobile-nav', type: 'button' do
+ %span.sr-only Open sidebar
+ = icon ('bars')
.breadcrumbs-links.js-title-container
- unless hide_top_links
.title
= link_to "GitLab", root_path
\/
+ - if content_for?(:header_title_before)
+ = yield :header_title_before
+ \/
= header_title
%h2.breadcrumbs-sub-title
%ul.list-unstyled
- - if content_for?(:sub_title_before)
- = yield :sub_title_before
- %li= link_to breadcrumb_title, request.path
+ - if @breadcrumbs_extra_links
+ - @breadcrumbs_extra_links.each do |extra|
+ %li= link_to extra[:text], extra[:link]
+ %li= link_to @breadcrumb_title, breadcrumb_link
- if content_for?(:breadcrumbs_extra)
.breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
= yield :header_content
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index ac222ad8c82..be7d27df2a0 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -42,18 +42,18 @@
.key
= icon('arrow-up', 'aria-label' => 'hidden')
I
+ %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:issues))
%span
Issues
- .badge= number_with_delimiter(assigned_issuables_count(:issues))
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
.shortcut-mappings
.key
= icon('arrow-up', 'aria-label' => 'hidden')
M
+ %span.badge.pull-right= number_with_delimiter(assigned_issuables_count(:merge_requests))
%span
Merge Requests
- .badge= number_with_delimiter(assigned_issuables_count(:merge_requests))
= nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do
.shortcut-mappings
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index 40c1ca7b53e..95443de40c2 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -1,10 +1,14 @@
.nav-sidebar
- = link_to admin_root_path, title: 'Admin Overview', class: 'context-header' do
- .avatar-container.s40.settings-avatar
- = icon('wrench')
- .project-title Admin Area
+ .context-header
+ = link_to admin_root_path, title: 'Admin Overview' do
+ .avatar-container.s40.settings-avatar
+ = icon('wrench')
+ .project-title Admin Area
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
- = nav_link(controller: %w(dashboard admin projects users groups builds runners cohorts), html_options: {class: 'home'}) do
+ = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
Overview
@@ -13,7 +17,7 @@
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: 'Overview' do
%span
- Overview
+ Dashboard
= nav_link(controller: [:admin, :projects]) do
= link_to admin_projects_path, title: 'Projects' do
%span
@@ -26,7 +30,7 @@
= link_to admin_groups_path, title: 'Groups' do
%span
Groups
- = nav_link path: 'builds#index' do
+ = nav_link path: 'jobs#index' do
= link_to admin_jobs_path, title: 'Jobs' do
%span
Jobs
diff --git a/app/views/layouts/nav/_new_dashboard.html.haml b/app/views/layouts/nav/_new_dashboard.html.haml
index 7109baa4dad..cfdfcbebc9f 100644
--- a/app/views/layouts/nav/_new_dashboard.html.haml
+++ b/app/views/layouts/nav/_new_dashboard.html.haml
@@ -3,7 +3,7 @@
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
Projects
- = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = nav_link(controller: ['dashboard/groups', 'explore/groups']) do
= link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do
Groups
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index b7ac04cc3e5..a7897c09e79 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,20 +1,24 @@
.nav-sidebar
- = link_to group_path(@group), title: 'Group', class: 'context-header' do
- .avatar-container.s40.group-avatar
- = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
- .group-title
- = @group.name
+ .context-header
+ = link_to group_path(@group), title: @group.name do
+ .avatar-container.s40.group-avatar
+ = image_tag group_icon(@group), class: "avatar s40 avatar-tile"
+ .group-title
+ = @group.name
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Home' do
+ = link_to group_path(@group), title: 'About group' do
%span
- Group
+ About
%ul.sidebar-sub-level-items
= nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Group Home' do
+ = link_to group_path(@group), title: 'Group details' do
%span
- Home
+ Details
= nav_link(path: 'groups#activity') do
= link_to activity_group_path(@group), title: 'Activity' do
@@ -55,7 +59,22 @@
%span
Members
- if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit]) do
+ = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
= link_to edit_group_path(@group), title: 'Settings' do
%span
Settings
+ %ul.sidebar-sub-level-items
+ = nav_link(path: 'groups#edit') do
+ = link_to edit_group_path(@group), title: 'General' do
+ %span
+ General
+
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects' do
+ %span
+ Projects
+
+ = nav_link(controller: :ci_cd) do
+ = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
+ %span
+ Pipelines
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
index 033ea149cfb..6bbd569583e 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -1,8 +1,12 @@
.nav-sidebar
- = link_to profile_path, title: 'Profile Settings', class: 'context-header' do
- .avatar-container.s40.settings-avatar
- = icon('user')
- .project-title User Settings
+ .context-header
+ = link_to profile_path, title: 'Profile Settings' do
+ .avatar-container.s40.settings-avatar
+ = icon('user')
+ .project-title User Settings
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile Settings' do
@@ -43,6 +47,10 @@
= link_to profile_keys_path, title: 'SSH Keys' do
%span
SSH Keys
+ = nav_link(controller: :gpg_keys) do
+ = link_to profile_gpg_keys_path, title: 'GPG Keys' do
+ %span
+ GPG Keys
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
%span
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index eae9da5da14..00395b222e4 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,20 +1,24 @@
.nav-sidebar
- can_edit = can?(current_user, :admin_project, @project)
- = link_to project_path(@project), title: 'Project', class: 'context-header' do
- .avatar-container.s40.project-avatar
- = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
- .project-title
- = @project.name
+ .context-header
+ = link_to project_path(@project), title: @project.name do
+ .avatar-container.s40.project-avatar
+ = project_icon(@project, alt: @project.name, class: 'avatar s40 avatar-tile')
+ .project-title
+ = @project.name
+ = button_tag class: 'close-nav-button', type: 'button' do
+ %span.sr-only Close sidebar
+ = icon ('times')
%ul.sidebar-top-level-items
= nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
- = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
+ = link_to project_path(@project), title: 'About project', class: 'shortcuts-project' do
%span
- Project
+ About
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show') do
- = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do
- %span= _('Home')
+ = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
+ %span= _('Details')
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
@@ -27,93 +31,93 @@
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
- = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
%ul.sidebar-sub-level-items
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project) do
+ = link_to project_tree_path(@project) do
#{ _('Files') }
= nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ = link_to project_commits_path(@project, current_ref) do
#{ _('Commits') }
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = link_to project_branches_path(@project) do
#{ _('Branches') }
= nav_link(controller: [:tags, :releases]) do
- = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = link_to project_tags_path(@project) do
#{ _('Tags') }
= nav_link(path: 'graphs#show') do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to project_graph_path(@project, current_ref) do
#{ _('Contributors') }
= nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ = link_to project_network_path(@project, current_ref) do
#{ s_('ProjectNetworkGraph|Graph') }
= nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
#{ _('Compare') }
= nav_link(path: 'graphs#charts') do
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to charts_project_graph_path(@project, current_ref) do
#{ _('Charts') }
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span
Registry
- if project_nav_tab? :issues
- = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
- Issues
- - if @project.default_issues_tracker?
+ - if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ Issues
%ul.sidebar-sub-level-items
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= nav_link(controller: :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+ = link_to project_issues_path(@project), title: 'Issues' do
%span
List
= nav_link(controller: :boards) do
- = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
+ = link_to project_boards_path(@project), title: 'Board' do
%span
Board
- if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
%span
Merge Requests
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
Milestones
- if project_nav_tab? :merge_requests
- = nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ = nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
- Merge Requests
%span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ Merge Requests
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
@@ -148,7 +152,7 @@
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do
- = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
%span
Charts
@@ -160,12 +164,12 @@
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
%span
Snippets
- if project_nav_tab? :settings
- = nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
+ = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
@@ -177,8 +181,8 @@
= link_to edit_project_path(@project), title: 'General' do
%span
General
- = nav_link(controller: :members) do
- = link_to project_settings_members_path(@project), title: 'Members' do
+ = nav_link(controller: :project_members) do
+ = link_to project_project_members_path(@project), title: 'Members' do
%span
Members
- if can_edit
@@ -187,23 +191,23 @@
%span
Integrations
= nav_link(controller: :repository) do
- = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
%span
Repository
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
- = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do
+ = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
%span
Pipelines
- if Gitlab.config.pages.enabled
= nav_link(controller: :pages) do
- = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+ = link_to project_pages_path(@project), title: 'Pages' do
%span
Pages
- else
= nav_link(path: %w[members#show]) do
- = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
+ = link_to project_settings_members_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
@@ -216,18 +220,18 @@
-# Shortcut to Repository > Graph (formerly, Network)
- if project_nav_tab? :network
%li.hidden
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
Graph
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- unless @project.empty_repo?
%li.hidden
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
Charts
-# Shortcut to Issues > New Issue
%li.hidden
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
Create a new issue
-# Shortcut to Pipelines > Jobs
@@ -244,4 +248,4 @@
-# Shortcut to issue boards
%li.hidden
- = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index ae1e1361f0f..26d9640e98a 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -29,7 +29,7 @@
= link_to profile_emails_path, title: 'Emails' do
%span
Emails
- - unless current_user.ldap_user?
+ - if current_user.allow_password_authentication?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
%span
@@ -43,6 +43,10 @@
= link_to profile_keys_path, title: 'SSH Keys' do
%span
SSH Keys
+ = nav_link(controller: :gpg_keys) do
+ = link_to profile_gpg_keys_path, title: 'GPG Keys' do
+ %span
+ GPG Keys
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
%span
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index b095adcfe7e..924cd2e9681 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -12,29 +12,29 @@
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network)) do
- = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
Repository
- if project_nav_tab? :container_registry
= nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ = link_to project_container_registry_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
%span
Registry
- if project_nav_tab? :issues
- = nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
- - if @project.default_issues_tracker?
+ - if @project.issues_enabled?
%span.badge.count.issue_counter= number_with_delimiter(issuables_count_for_state(:issues, :opened, finder: IssuesFinder.new(current_user, project_id: @project.id)))
- if project_nav_tab? :merge_requests
- controllers = [:merge_requests, 'projects/merge_requests/conflicts']
- - controllers.push(:merge_requests, :labels, :milestones) unless @project.default_issues_tracker?
+ - controllers.push(:merge_requests, :labels, :milestones) unless @project.issues_enabled?
= nav_link(controller: controllers) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
%span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(issuables_count_for_state(:merge_requests, :opened, finder: MergeRequestsFinder.new(current_user, project_id: @project.id)))
@@ -53,20 +53,21 @@
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
- = link_to namespace_project_snippets_path(@project.namespace, @project), title: 'Snippets', class: 'shortcuts-snippets' do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
%span
Snippets
+ - if project_nav_tab? :project_members
+ = nav_link(controller: :project_members) do
+ = link_to project_project_members_path(@project), title: 'Members', class: 'shortcuts-members' do
+ %span
+ Members
+
- if project_nav_tab? :settings
= nav_link(path: %w[projects#edit members#show integrations#show services#edit repository#show ci_cd#show pages#show]) do
= link_to edit_project_path(@project), title: 'Settings', class: 'shortcuts-tree' do
%span
Settings
- - else
- = nav_link(path: %w[members#show]) do
- = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Settings', class: 'shortcuts-tree' do
- %span
- Settings
-# Shortcut to Project > Activity
%li.hidden
@@ -77,18 +78,18 @@
-# Shortcut to Repository > Graph (formerly, Network)
- if project_nav_tab? :network
%li.hidden
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
+ = link_to project_network_path(@project, current_ref), title: 'Network', class: 'shortcuts-network' do
Graph
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
- unless @project.empty_repo?
%li.hidden
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ = link_to charts_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
Charts
-# Shortcut to Issues > New Issue
%li.hidden
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'shortcuts-new-issue' do
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
Create a new issue
-# Shortcut to Pipelines > Jobs
@@ -105,4 +106,4 @@
-# Shortcut to issue boards
%li.hidden
- = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
+ = link_to 'Issue Boards', project_boards_path(@project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 4458c3c2c23..99adb83cd1f 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -11,7 +11,7 @@
- project = @target_project || @project
- if current_user
:javascript
- window.uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
+ window.uploads_path = "#{project_uploads_path(project)}";
- content_for :header_content do
.js-dropdown-menu-projects
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index bc12e38675f..b35d4b7502d 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
Issue was closed by #{@updated_by.name}
-Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
+Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index d0c96b83976..c4e06cb3cb1 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml
index 40f7d61fe19..472c31e9a5e 100644
--- a/app/views/notify/issue_moved_email.html.haml
+++ b/app/views/notify/issue_moved_email.html.haml
@@ -2,5 +2,5 @@
Issue was moved to another project.
%p
New issue:
- = link_to namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) do
+ = link_to project_issue_url(@new_project, @new_issue) do
= @new_issue.title
diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb
index b3bd43c2055..66ede43635b 100644
--- a/app/views/notify/issue_moved_email.text.erb
+++ b/app/views/notify/issue_moved_email.text.erb
@@ -1,4 +1,4 @@
Issue was moved to another project.
New issue location:
-<%= namespace_project_issue_url(@new_project.namespace, @new_project, @new_issue) %>
+<%= project_issue_url(@new_project, @new_issue) %>
diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb
index e6ab3fcde77..4200881f7e8 100644
--- a/app/views/notify/issue_status_changed_email.text.erb
+++ b/app/views/notify/issue_status_changed_email.text.erb
@@ -1,4 +1,4 @@
Issue was <%= @issue_status %> by <%= @updated_by.name %>
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 4c9719ba732..ae2a2933865 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index 46c1c9dee0b..661c23bcbe2 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,6 +1,6 @@
Merge Request #{@merge_request.to_reference} was merged
-Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
+Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
diff --git a/app/views/notify/new_gpg_key_email.html.haml b/app/views/notify/new_gpg_key_email.html.haml
new file mode 100644
index 00000000000..4b9350c4e88
--- /dev/null
+++ b/app/views/notify/new_gpg_key_email.html.haml
@@ -0,0 +1,10 @@
+%p
+ Hi #{@user.name}!
+%p
+ A new GPG key was added to your account:
+%p
+ Fingerprint:
+ %code= @gpg_key.fingerprint
+%p
+ If this key was added in error, you can remove it under
+ = link_to "GPG Keys", profile_gpg_keys_url
diff --git a/app/views/notify/new_gpg_key_email.text.erb b/app/views/notify/new_gpg_key_email.text.erb
new file mode 100644
index 00000000000..80b5a1fd7ff
--- /dev/null
+++ b/app/views/notify/new_gpg_key_email.text.erb
@@ -0,0 +1,7 @@
+Hi <%= @user.name %>!
+
+A new GPG key was added to your account:
+
+Fingerprint: <%= @gpg_key.fingerprint %>
+
+If this key was added in error, you can remove it at <%= profile_gpg_keys_url %>
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 13f1ac08e94..3c716f77296 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,6 +1,6 @@
New Issue was created.
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
diff --git a/app/views/notify/new_mention_in_issue_email.text.erb b/app/views/notify/new_mention_in_issue_email.text.erb
index f19ac3adfc7..23213106c5b 100644
--- a/app/views/notify/new_mention_in_issue_email.text.erb
+++ b/app/views/notify/new_mention_in_issue_email.text.erb
@@ -1,6 +1,6 @@
You have been mentioned in an issue.
-Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
+Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
diff --git a/app/views/notify/new_mention_in_merge_request_email.text.erb b/app/views/notify/new_mention_in_merge_request_email.text.erb
index 5bf0282e097..6fcebb22fc4 100644
--- a/app/views/notify/new_mention_in_merge_request_email.text.erb
+++ b/app/views/notify/new_mention_in_merge_request_email.text.erb
@@ -1,6 +1,6 @@
You have been mentioned in Merge Request <%= @merge_request.to_reference %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 3c8f178ac77..7d98400e6fe 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,6 +1,6 @@
New Merge Request <%= @merge_request.to_reference %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml
index 3def26342a1..f0ba7827cef 100644
--- a/app/views/notify/project_was_exported_email.html.haml
+++ b/app/views/notify/project_was_exported_email.html.haml
@@ -2,7 +2,7 @@
Project #{@project.name} was exported successfully.
%p
The project export can be downloaded from:
- = link_to download_export_namespace_project_url(@project.namespace, @project), rel: 'nofollow', download: '' do
+ = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do
= @project.name_with_namespace + " export"
%p
The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_exported_email.text.erb b/app/views/notify/project_was_exported_email.text.erb
index 42c4d176876..cd3a1f7934f 100644
--- a/app/views/notify/project_was_exported_email.text.erb
+++ b/app/views/notify/project_was_exported_email.text.erb
@@ -1,6 +1,6 @@
Project <%= @project.name %> was exported successfully.
The project export can be downloaded from:
-<%= download_export_namespace_project_url(@project.namespace, @project) %>
+<%= download_export_project_url(@project) %>
The download link will expire in 24 hours.
diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml
index 87b3ff7f0b3..c476a39b661 100644
--- a/app/views/notify/project_was_moved_email.html.haml
+++ b/app/views/notify/project_was_moved_email.html.haml
@@ -2,7 +2,7 @@
Project #{@old_path_with_namespace} was moved to another location
%p
The project is now located under
- = link_to namespace_project_url(@project.namespace, @project) do
+ = link_to project_url(@project) do
= @project.name_with_namespace
%p
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb
index b2c5f71e465..7c45163e0e8 100644
--- a/app/views/notify/project_was_moved_email.text.erb
+++ b/app/views/notify/project_was_moved_email.text.erb
@@ -1,7 +1,7 @@
Project <%= @old_path_with_namespace %> was moved to another location
The project is now located under
-<%= namespace_project_url(@project.namespace, @project) %>
+<%= project_url(@project) %>
To update the remote url in your local repository run (for ssh):
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 546376aeed8..5c5520f4cb8 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -3,7 +3,7 @@
%h3
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
- at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
+ at #{link_to(@message.project_name_with_namespace, project_url(@message.project))}
- if @message.compare
- if @message.reverse_compare?
@@ -17,7 +17,7 @@
%ul
- @message.commits.each do |commit|
%li
- %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))
+ %strong= link_to(commit.short_id, project_commit_url(@message.project, commit))
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)}
diff --git a/app/views/notify/resolved_all_discussions_email.text.erb b/app/views/notify/resolved_all_discussions_email.text.erb
index b0d380af8fc..2881f3e699e 100644
--- a/app/views/notify/resolved_all_discussions_email.text.erb
+++ b/app/views/notify/resolved_all_discussions_email.text.erb
@@ -1,3 +1,3 @@
All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= @resolved_by.name %>
-<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %>
+<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
diff --git a/app/views/peek/views/_rblineprof.html.haml b/app/views/peek/views/_rblineprof.html.haml
new file mode 100644
index 00000000000..6c037930ca9
--- /dev/null
+++ b/app/views/peek/views/_rblineprof.html.haml
@@ -0,0 +1,7 @@
+Profile:
+
+= link_to 'all', url_for(lineprofiler: 'true'), class: 'js-toggle-modal-peek-line-profile'
+\/
+= link_to 'app & lib', url_for(lineprofiler: 'app'), class: 'js-toggle-modal-peek-line-profile'
+\/
+= link_to 'views', url_for(lineprofiler: 'views'), class: 'js-toggle-modal-peek-line-profile'
diff --git a/app/views/peek/views/_sql.html.haml b/app/views/peek/views/_sql.html.haml
index 16fc010f66f..dd8b524064f 100644
--- a/app/views/peek/views/_sql.html.haml
+++ b/app/views/peek/views/_sql.html.haml
@@ -1,13 +1,13 @@
%strong
- %a#peek-show-queries{ href: '#' }
+ %a.js-toggle-modal-peek-sql
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-pg-queries.modal{ tabindex: -1 }
- .modal-dialog
- #modal-peek-pg-queries-content.modal-content
+ .modal-dialog.modal-full
+ .modal-content
.modal-header
- %a.close{ href: "#", "data-dismiss" => "modal" } ×
+ %button.close.btn.btn-link.btn-sm{ type: 'button', data: { dismiss: 'modal' } } X
%h4
SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 1ec1e7c70e4..fe1cf802971 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -10,7 +10,7 @@
%td
%strong
- if can?(current_user, :admin_project, project)
- = link_to service.title, edit_namespace_project_service_path(project.namespace, project, service)
+ = link_to service.title, edit_project_service_path(project, service)
- else
= service.title
%td
diff --git a/app/views/profiles/gpg_keys/_email_with_badge.html.haml b/app/views/profiles/gpg_keys/_email_with_badge.html.haml
new file mode 100644
index 00000000000..5f7844584e1
--- /dev/null
+++ b/app/views/profiles/gpg_keys/_email_with_badge.html.haml
@@ -0,0 +1,8 @@
+- css_classes = %w(label label-verification-status)
+- css_classes << (verified ? 'verified': 'unverified')
+- text = verified ? 'Verified' : 'Unverified'
+
+.gpg-email-badge
+ .gpg-email-badge-email= email
+ %div{ class: css_classes }
+ = text
diff --git a/app/views/profiles/gpg_keys/_form.html.haml b/app/views/profiles/gpg_keys/_form.html.haml
new file mode 100644
index 00000000000..3fcf563d970
--- /dev/null
+++ b/app/views/profiles/gpg_keys/_form.html.haml
@@ -0,0 +1,10 @@
+%div
+ = form_for [:profile, @gpg_key], html: { class: 'js-requires-input' } do |f|
+ = form_errors(@gpg_key)
+
+ .form-group
+ = f.label :key, class: 'label-light'
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the GPG key. Paste the public part which begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----'."
+
+ .prepend-top-default
+ = f.submit 'Add key', class: "btn btn-create"
diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml
new file mode 100644
index 00000000000..b04981f90e3
--- /dev/null
+++ b/app/views/profiles/gpg_keys/_key.html.haml
@@ -0,0 +1,18 @@
+%li.key-list-item
+ .pull-left.append-right-10
+ = icon 'key', class: "settings-list-icon hidden-xs"
+ .key-list-item-info
+ - key.emails_with_verified_status.map do |email, verified|
+ = render partial: 'email_with_badge', locals: { email: email, verified: verified }
+
+ .description
+ %code= key.fingerprint
+ .pull-right
+ %span.key-created-at
+ created #{time_ago_with_tooltip(key.created_at)}
+ = link_to profile_gpg_key_path(key), data: { confirm: 'Are you sure? Removing this GPG key does not affect already signed commits.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only Remove
+ = icon('trash')
+ = link_to revoke_profile_gpg_key_path(key), data: { confirm: 'Are you sure? All commits that were signed with this GPG key will be unverified.' }, method: :put, class: "btn btn-danger prepend-left-10" do
+ %span.sr-only Revoke
+ Revoke
diff --git a/app/views/profiles/gpg_keys/_key_table.html.haml b/app/views/profiles/gpg_keys/_key_table.html.haml
new file mode 100644
index 00000000000..cabb92c5a24
--- /dev/null
+++ b/app/views/profiles/gpg_keys/_key_table.html.haml
@@ -0,0 +1,11 @@
+- is_admin = local_assigns.fetch(:admin, false)
+
+- if @gpg_keys.any?
+ %ul.well-list
+ = render partial: 'profiles/gpg_keys/key', collection: @gpg_keys, locals: { is_admin: is_admin }
+- else
+ %p.settings-message.text-center
+ - if is_admin
+ There are no GPG keys associated with this account.
+ - else
+ There are no GPG keys with access to your account.
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
new file mode 100644
index 00000000000..8331daeeb75
--- /dev/null
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -0,0 +1,21 @@
+- page_title "GPG Keys"
+= render 'profiles/head'
+
+.row.prepend-top-default
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = page_title
+ %p
+ GPG keys allow you to verify signed commits.
+ .col-lg-9
+ %h5.prepend-top-0
+ Add a GPG key
+ %p.profile-settings-content
+ Before you can add a GPG key you need to
+ = link_to 'generate it.', help_page_path('workflow/gpg_signed_commits/index.md')
+ = render 'form'
+ %hr
+ %h5
+ Your GPG keys (#{@gpg_keys.count})
+ .append-bottom-default
+ = render 'key_table'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index a089aeb2447..9aed498a8a0 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -16,25 +16,28 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id
= scheme.name
- - if can_toggle_new_nav?
- .col-sm-12
- %hr
- .col-lg-3.profile-settings-sidebar#new-navigation
- %h4.prepend-top-0
- New Navigation
+ .col-sm-12
+ %hr
+ .col-lg-4.profile-settings-sidebar#new-navigation
+ %h4.prepend-top-0
+ New Navigation
+ %p
+ This setting allows you to turn on or off the new upcoming navigation concept.
+ .col-lg-8.syntax-theme
+ .nav-wip
%p
- This setting allows you to turn on or off the new upcoming navigation concept.
- = succeed '.' do
- = link_to 'Learn more', '', target: '_blank'
- .col-lg-9.syntax-theme
- = label_tag do
- .preview= image_tag "old_nav.png"
- %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
- Old
- = label_tag do
- .preview= image_tag "new_nav.png"
- %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
- New
+ The new navigation is currently a work-in-progress concept and is currently only usable on wide-screens. There are a number of improvements that we are working on in order to further refine our navigation.
+ %p
+ %a{ href: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/32794', target: 'blank' } Learn more
+ about the improvements that are coming soon!
+ = label_tag do
+ .preview= image_tag "old_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
+ Old
+ = label_tag do
+ .preview= image_tag "new_nav.png"
+ %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
+ New
.col-sm-12
%hr
.col-lg-4.profile-settings-sidebar
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index bac75a49075..a8ae0b92334 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Profile"
- @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 67792de3870..037cb30efb9 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,6 +1,10 @@
- page_title 'Two-Factor Authentication', 'Account'
-- header_title "Two-Factor Authentication", profile_two_factor_auth_path
+- if show_new_nav?
+ - add_to_breadcrumbs("Account", profile_account_path)
+- else
+ - header_title "Two-Factor Authentication", profile_two_factor_auth_path
- @content_class = "limit-container-width" unless fluid_layout
+
= render 'profiles/head'
- if inject_u2f_api?
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index 10f581d751b..ecc966ed453 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,7 +1,7 @@
%div{ class: container_class }
.nav-block.activity-filter-block.activities
.controls
- = link_to namespace_project_path(@project.namespace, @project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
+ = link_to project_path(@project, rss_url_options), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
diff --git a/app/views/projects/_deletion_failed.html.haml b/app/views/projects/_deletion_failed.html.haml
new file mode 100644
index 00000000000..4f3698f91e6
--- /dev/null
+++ b/app/views/projects/_deletion_failed.html.haml
@@ -0,0 +1,6 @@
+- project = local_assigns.fetch(:project)
+- return unless project.delete_error.present?
+
+.project-deletion-failed-message.alert.alert-warning
+ This project was scheduled for deletion, but failed with the following message:
+ = project.delete_error
diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml
index cb4d2bbacf5..da1b2d7f9b6 100644
--- a/app/views/projects/_find_file_link.html.haml
+++ b/app/views/projects/_find_file_link.html.haml
@@ -1,3 +1,3 @@
-= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
+= link_to project_find_file_path(@project, @ref), class: 'btn shortcuts-find-file', rel: 'nofollow' do
= icon('search')
%span= _('Find file')
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
new file mode 100644
index 00000000000..f47d84ef755
--- /dev/null
+++ b/app/views/projects/_flash_messages.html.haml
@@ -0,0 +1,8 @@
+- project = local_assigns.fetch(:project)
+- flash_message_container = show_new_nav? ? :new_global_flash : :flash_message
+
+= content_for flash_message_container do
+ = render partial: 'deletion_failed', locals: { project: project }
+ - if current_user && can?(current_user, :download_code, project)
+ = render 'shared/no_ssh'
+ = render 'shared/no_password'
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f1ef50d2de2..1a71bfca2e2 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -5,7 +5,7 @@
.event-last-push-text
%span You pushed to
%strong
- = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name'
+ = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- if event.project != @project
%span at
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 2bab22e125d..a56c3503c77 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -14,5 +14,5 @@
Add a homepage to your wiki that contains information about your project
%p
We recommend you
- = link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
+ = link_to "add a homepage", project_wiki_path(@project, :home)
to your project's wiki and GitLab will show it here instead of this message.
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index ef8d8051cbf..9e2688e492e 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,5 +1,8 @@
- @no_container = true
+- if show_new_nav?
+ - add_to_breadcrumbs("Project", project_path(@project))
+
- page_title "Activity"
= render "projects/head"
diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml
index e2966ec33c2..03be6f15313 100644
--- a/app/views/projects/artifacts/_tree_directory.html.haml
+++ b/app/views/projects/artifacts/_tree_directory.html.haml
@@ -1,4 +1,4 @@
-- path_to_directory = browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: directory.path)
+- path_to_directory = browse_project_job_artifacts_path(@project, @build, path: directory.path)
%tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index ea0b43b85cf..8edb9be049a 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -1,4 +1,4 @@
-- path_to_file = file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path: file.path)
+- path_to_file = file_project_job_artifacts_path(@project, @build, path: file.path)
%tr.tree-item{ 'data-link' => path_to_file }
- blob = file.blob
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 961c805dc7c..a33743c2f57 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -5,18 +5,18 @@
.tree-holder
.nav-block
- .tree-controls
- = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build),
- rel: 'nofollow', download: '', class: 'btn btn-default download' do
- = icon('download')
- Download artifacts archive
-
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
- path_breadcrumbs do |title, path|
%li
- = link_to truncate(title, length: 40), browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path)
+
+ .tree-controls
+ = link_to download_project_job_artifacts_path(@project, @build),
+ rel: 'nofollow', download: '', class: 'btn btn-default download' do
+ = icon('download')
+ Download artifacts archive
.tree-content-holder
%table.table.tree-table
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index b25c7c95196..18e86ac5a92 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -7,15 +7,15 @@
.nav-block
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to 'Artifacts', browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build)
- path_breadcrumbs do |title, path|
- title = truncate(title, length: 40)
%li
- if path == @path
- = link_to file_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path) do
+ = link_to file_project_job_artifacts_path(@project, @build, path) do
%strong= title
- else
- = link_to title, browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build, path)
+ = link_to title, browse_project_job_artifacts_path(@project, @build, path)
%article.file-holder
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 3627f72f5e1..c7359d873d9 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -21,10 +21,10 @@
.commit
= author_avatar(commit, size: 36)
.commit-row-title
- %strong
- = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark"
+ %span.item-title.str-truncated-100
+ = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
.pull-right
- = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
&nbsp;
.light
= commit_author_link(commit, avatar: false)
diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml
index 2c944b516a4..1c148de9678 100644
--- a/app/views/projects/blob/_breadcrumb.html.haml
+++ b/app/views/projects/blob/_breadcrumb.html.haml
@@ -6,16 +6,16 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
- path_breadcrumbs do |title, path|
- title = truncate(title, length: 40)
%li
- if path == @path
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, path)) do
+ = link_to project_blob_path(@project, tree_join(@ref, path)) do
%strong= title
- else
- = link_to title, namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
+ = link_to title, project_tree_path(@project, tree_join(@ref, path))
.tree-controls
= render 'projects/find_file_link'
@@ -24,14 +24,14 @@
-# only show normal/blame view links for text files
- if blob.readable_text?
- if blame
- = link_to 'Normal view', namespace_project_blob_path(@project.namespace, @project, @id),
+ = link_to 'Normal view', project_blob_path(@project, @id),
class: 'btn'
- else
- = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id),
+ = link_to 'Blame', project_blame_path(@project, @id),
class: 'btn js-blob-blame-link' unless blob.empty?
- = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id),
+ = link_to 'History', project_commits_path(@project, @id),
class: 'btn'
- = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
+ = link_to 'Permalink', project_blob_path(@project,
tree_join(@commit.sha, @path)), class: 'btn js-data-file-blob-permalink-url'
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index 40978583e8b..b2959ef6d31 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,7 +5,7 @@
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title= _('Create New Directory')
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
+ = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do
.form-group
= label_tag :dir_name, _('Directory name'), class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index c8ca0406213..6a4a657fa8c 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -6,7 +6,7 @@
%h3.page-title Delete #{@blob.name}
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
+ = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do
= render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
.form-group
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 4af62461151..992fe7f717f 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Repository"
- @no_container = true
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
@@ -9,7 +10,7 @@
- if @conflict
.alert.alert-danger
Someone edited the file the same time you did. Please check out
- = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
+ = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs.
.editor-title-row
%h3.page-title.blob-edit-page-title
@@ -22,13 +23,13 @@
Write
%li
- = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do
+ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
= editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+ = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
- = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
+ = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id)
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 2afb909572a..a4263774dfd 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Repository"
- page_title "New File", @path.presence, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
@@ -7,10 +8,10 @@
New file
= render 'template_selectors'
.file-editor
- = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
+ = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
- cancel_path: namespace_project_tree_path(@project.namespace, @project, @id)
+ cancel_path: project_tree_path(@project, @id)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 41f75a491a5..7dd834e84b5 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Repository"
- @no_container = true
- page_title @blob.path, @ref
@@ -16,4 +17,4 @@
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/blob/viewers/_changelog.html.haml b/app/views/projects/blob/viewers/_changelog.html.haml
index 53921e63b5f..46e3e7f798a 100644
--- a/app/views/projects/blob/viewers/_changelog.html.haml
+++ b/app/views/projects/blob/viewers/_changelog.html.haml
@@ -1,4 +1,4 @@
= icon('history fw')
= succeed '.' do
To find the state of this project's repository at the time of any of these versions, check out
- = link_to "the tags", namespace_project_tags_path(viewer.project.namespace, viewer.project)
+ = link_to "the tags", project_tags_path(viewer.project)
diff --git a/app/views/projects/blob/viewers/_image.html.haml b/app/views/projects/blob/viewers/_image.html.haml
index 640d59b3174..5fd22a59217 100644
--- a/app/views/projects/blob/viewers/_image.html.haml
+++ b/app/views/projects/blob/viewers/_image.html.haml
@@ -1,2 +1,2 @@
.file-content.image_file
- %img{ src: blob_raw_url, alt: viewer.blob.name }
+ = image_tag(blob_raw_url, alt: viewer.blob.name)
diff --git a/app/views/projects/blob/viewers/_readme.html.haml b/app/views/projects/blob/viewers/_readme.html.haml
index 334b33faf48..d8492abc638 100644
--- a/app/views/projects/blob/viewers/_readme.html.haml
+++ b/app/views/projects/blob/viewers/_readme.html.haml
@@ -1,4 +1,4 @@
= icon('info-circle fw')
= succeed '.' do
To learn more about this project, read
- = link_to "the wiki", namespace_project_wikis_path(viewer.project.namespace, viewer.project)
+ = link_to "the wiki", get_project_wiki_path(viewer.project)
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index 3622720a8b7..2076e46fde8 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -3,8 +3,7 @@
- page_title "Boards"
- if show_new_nav?
- - content_for :sub_title_before do
- %li= link_to "Issues", namespace_project_issues_path(@project.namespace, @project)
+ - add_to_breadcrumbs("Issues", project_issues_path(@project))
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
@@ -34,7 +33,7 @@
":key" => "_uid" }
= render "projects/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
- "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
+ "new-issue-path" => new_project_issue_path(@project),
"milestone-path" => milestones_filter_dropdown_path,
"label-path" => labels_filter_path,
":issue-link-base" => "issueLinkBase",
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml
index c314573bdea..8d957613be1 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml
@@ -22,7 +22,7 @@
- dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
= dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/projects/boards/components/sidebar/_due_date.html.haml
index 1a3b88e28c5..f44a9d49a54 100644
--- a/app/views/projects/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/projects/boards/components/sidebar/_due_date.html.haml
@@ -23,7 +23,7 @@
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/projects/boards/components/sidebar/_labels.html.haml
index bee0f3dd065..7d0c35fe183 100644
--- a/app/views/projects/boards/components/sidebar/_labels.html.haml
+++ b/app/views/projects/boards/components/sidebar/_labels.html.haml
@@ -19,8 +19,8 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
- data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: namespace_project_labels_path(@project.namespace, @project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
%span.dropdown-toggle-text
Label
= icon('chevron-down')
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml
index 4e46351bf8a..002e9994ee0 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml
@@ -16,10 +16,10 @@
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown
- %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
+ %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml
index a08c7f2af09..aaddd7e249f 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml
@@ -1,5 +1,5 @@
- if current_user
- .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" }
+ .block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" }
%span.issuable-header-text.hide-collapsed.pull-left
Notifications
%button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 869633e016d..19712a8f1be 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -6,7 +6,7 @@
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
%li{ class: "js-branch-#{branch.name}" }
%div
- = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated ref-name' do
+ = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork')
= branch.name
&nbsp;
@@ -25,7 +25,7 @@
Merge request
- if branch.name != @repository.root_ref
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do
Compare
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name]
@@ -42,7 +42,7 @@
title: "Delete protected branch",
data: { toggle: "modal",
target: "#modal-delete-branch",
- delete_path: namespace_project_branch_path(@project.namespace, @project, branch.name),
+ delete_path: project_branch_path(@project, branch.name),
branch_name: branch.name } }
= icon("trash-o")
- else
@@ -51,7 +51,7 @@
title: "Only a project master or owner can delete a protected branch" }
= icon("trash-o")
- else
- = link_to namespace_project_branch_path(@project.namespace, @project, branch.name),
+ = link_to project_branch_path(@project, branch.name),
class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: "Delete branch",
method: :delete,
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index ad8f9da0621..18fbb81c167 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,9 +1,9 @@
.branch-commit
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(project, commit.id), class: "commit-sha"
&middot;
%span.str-truncated
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 4bade77a077..945a5c11d6d 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -2,11 +2,15 @@
- page_title "Branches"
= render "projects/commits/head"
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
+
%div{ class: container_class }
.top-area.adjust
- .nav-text
- Protected branches can be managed in
- = link_to 'project settings', namespace_project_protected_branches_path(@project.namespace, @project)
+ - if can?(current_user, :admin_project, @project)
+ .nav-text
+ Protected branches can be managed in
+ = link_to 'project settings', project_protected_branches_path(@project)
.nav-controls
= form_tag(filter_branches_path, method: :get) do
@@ -25,9 +29,9 @@
= link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value)
- if can? current_user, :push_code, @project
- = link_to namespace_project_merged_branches_path(@project.namespace, @project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
+ = link_to project_merged_branches_path(@project), class: 'btn btn-inverted btn-remove has-tooltip', title: "Delete all branches that are merged into '#{@project.repository.root_ref}'", method: :delete, data: { confirm: "Deleting the merged branches cannot be undone. Are you sure?", container: 'body' } do
Delete merged branches
- = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
+ = link_to new_project_branch_path(@project), class: 'btn btn-create' do
New branch
- if @branches.any?
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 5a0eba3551f..03eefcc2b4d 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -27,7 +27,7 @@
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index a73ddd5eb33..883922dbf04 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -10,19 +10,19 @@
%li.dropdown-header
#{ _('Source code') }
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download zip')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.gz')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar.bz2')
%li
- = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
+ = link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span= _('Download tar')
@@ -37,7 +37,7 @@
%li.dropdown-header Previous Artifacts
- artifacts.each do |job|
%li
- = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
+ = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
%i.fa.fa-download
%span
#{ s_('DownloadArtifacts|Download') } '#{job.name}'
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index aa1a533b5cb..b04d6a1fa5e 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -10,19 +10,19 @@
- if can_create_issue
%li
- = link_to new_namespace_project_issue_path(@project.namespace, @project) do
+ = link_to new_project_issue_path(@project) do
= icon('exclamation-circle fw')
#{ _('New issue') }
- if merge_project
%li
- = link_to namespace_project_new_merge_request_path(merge_project.namespace, merge_project) do
+ = link_to project_new_merge_request_path(merge_project) do
= icon('tasks fw')
#{ _('New merge request') }
- if can_create_snippet
%li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project) do
+ = link_to new_project_snippet_path(@project) do
= icon('file-text-o fw')
#{ _('New snippet') }
@@ -31,28 +31,28 @@
- if can?(current_user, :push_code, @project)
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+ = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
%li
- = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') }
%li
- = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') }
- elsif current_user && current_user.already_forked?(@project)
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+ = link_to project_new_blob_path(@project, @project.default_branch || 'master') do
= icon('file fw')
#{ _('New file') }
- elsif can?(current_user, :fork_project, @project)
%li
- - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
+ - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 42f8c75f57b..f45cc7f0f45 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -5,14 +5,14 @@
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- elsif !current_user.can_create_project?
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
+ = link_to new_project_fork_path(@project), title: _('You have reached your project limit'), class: 'btn has-tooltip disabled' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), class: 'btn' do
+ = link_to new_project_fork_path(@project), class: 'btn' do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
%span.arrow
- = link_to namespace_project_forks_path(@project.namespace, @project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
+ = link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
= @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 58413e2fc52..c82ae35a685 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,8 +1,8 @@
- if current_user
- = link_to toggle_star_namespace_project_path(@project.namespace, @project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
+ = link_to toggle_star_project_path(@project), { class: 'btn star-btn toggle-star', method: :post, remote: true } do
- if current_user.starred?(@project)
= icon('star')
- %span.starred= _('Unstar')
+ %span.starred= _('Unstar')
- else
= icon('star-o')
%span= s_('StarProject|Star')
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index d9f28d66b66..c1842527480 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -14,7 +14,7 @@
%td.branch-commit
- if can?(current_user, :read_build, job)
- = link_to namespace_project_job_url(job.project.namespace, job.project, job) do
+ = link_to project_job_url(job.project, job) do
%span.build-link ##{job.id}
- else
%span.build-link ##{job.id}
@@ -30,7 +30,7 @@
= custom_icon("icon_commit")
- if commit_sha
- = link_to job.short_sha, namespace_project_commit_path(job.project.namespace, job.project, job.sha), class: "commit-sha"
+ = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha"
- if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
@@ -63,7 +63,7 @@
- if admin
%td
- if job.project
- = link_to job.project.name_with_namespace, admin_namespace_project_path(job.project.namespace, job.project)
+ = link_to job.project.name_with_namespace, admin_project_path(job.project)
%td
- if job.try(:runner)
= runner_link(job.runner)
@@ -95,16 +95,16 @@
%td
.pull-right
- if can?(current_user, :read_build, job) && job.artifacts?
- = link_to download_namespace_project_job_artifacts_path(job.project.namespace, job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
+ = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do
= icon('download')
- if can?(current_user, :update_build, job)
- if job.active?
- = link_to cancel_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
+ = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- elsif allow_retry
- if job.playable? && !admin && can?(current_user, :update_build, job)
- = link_to play_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= custom_icon('icon_play')
- elsif job.retryable?
- = link_to retry_namespace_project_job_path(job.project.namespace, job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
= icon('repeat')
diff --git a/app/views/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml
new file mode 100644
index 00000000000..22674b671c9
--- /dev/null
+++ b/app/views/projects/commit/_ajax_signature.html.haml
@@ -0,0 +1,3 @@
+- if commit.has_signature?
+ %button{ class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } }
+ %i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 2267f123e38..d0a380516f9 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -22,7 +22,7 @@
= label_tag 'start_branch', branch_label, class: 'control-label'
.col-sm-10
= hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch'
- = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } })
+ = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: project_branches_path(@project), submit_form_on_click: false } })
- if can?(current_user, :push_code, @project)
= render 'shared/new_merge_request_checkbox'
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 8aed88da38b..7338468967f 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -1,10 +1,10 @@
%ul.nav-links.no-top.no-bottom.commit-ci-menu
= nav_link(path: 'commit#show') do
- = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ = link_to project_commit_path(@project, @commit.id) do
Changes
%span.badge= @diffs.size
- if can?(current_user, :read_pipeline, @project)
= nav_link(path: 'commit#pipelines') do
- = link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
+ = link_to pipelines_project_commit_path(@project, @commit.id) do
Pipelines
- %span.badge= @commit.pipelines.size
+ %span.badge.js-pipelines-mr-count= @commit.pipelines.size
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 7fe44816bae..419fbe99af8 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,5 +1,6 @@
.page-content-header
.header-main-content
+ = render partial: 'signature', object: @commit.signature
%strong
#{ s_('CommitBoxTitle|Commit') }
%span.commit-sha= @commit.short_id
@@ -21,7 +22,7 @@
%span.btn.disabled.btn-grouped.hidden-xs.append-right-10
= icon('comment')
= @notes_count
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
+ = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do
#{ _('Browse files') }
.dropdown.inline
%a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
@@ -29,22 +30,22 @@
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
%li.visible-xs-block.visible-sm-block
- = link_to namespace_project_tree_path(@project.namespace, @project, @commit) do
- _('Browse Files')
+ = link_to project_tree_path(@project, @commit) do
+ #{ _('Browse Files') }
- unless @commit.has_been_reverted?(current_user)
%li.clearfix
- = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
%li.clearfix
- = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id), has_tooltip: false)
+ = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can_collaborate_with_project?
%li.clearfix
- = link_to s_("CreateTag|Tag"), new_namespace_project_tag_path(@project.namespace, @project, ref: @commit)
+ = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider
%li.dropdown-header
#{ _('Download') }
- unless @commit.parents.length > 1
- %li= link_to s_("DownloadCommit|Email Patches"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch)
- %li= link_to s_("DownloadCommit|Plain Diff"), namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff)
+ %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
+ %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
.commit-box
%h3.commit-title
@@ -59,7 +60,7 @@
= custom_icon("icon_commit")
%span.cgray= n_('parent', 'parents', @commit.parents.count)
- @commit.parents.each do |parent|
- = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "commit-sha"
+ = link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
%span.commit-info.branches
%i.fa.fa-spinner.fa-spin
@@ -67,10 +68,10 @@
- last_pipeline = @commit.last_pipeline
.well-segment.pipeline-info
.status-icon-container{ class: "ci-status-icon-#{@commit.status}" }
- = link_to namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id) do
+ = link_to project_pipeline_path(@project, last_pipeline.id) do
= ci_icon_for_status(last_pipeline.status)
#{ _('Pipeline') }
- = link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
+ = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id)
= ci_label_for_status(last_pipeline.status)
- if last_pipeline.stages_count.nonzero?
#{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) }
@@ -80,4 +81,4 @@
= time_interval_in_words last_pipeline.duration
:javascript
- $(".commit-info.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
+ $(".commit-info.branches").load("#{branches_project_commit_path(@project, @commit.id)}");
diff --git a/app/views/projects/commit/_invalid_signature_badge.html.haml b/app/views/projects/commit/_invalid_signature_badge.html.haml
new file mode 100644
index 00000000000..3a73aae9d95
--- /dev/null
+++ b/app/views/projects/commit/_invalid_signature_badge.html.haml
@@ -0,0 +1,9 @@
+- title = capture do
+ .gpg-popover-icon.invalid
+ = render 'shared/icons/icon_status_notfound_borderless.svg'
+ %div
+ This commit was signed with an <strong>unverified</strong> signature.
+
+- locals = { signature: signature, title: title, label: 'Unverified', css_classes: ['invalid'] }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/_signature.html.haml b/app/views/projects/commit/_signature.html.haml
new file mode 100644
index 00000000000..60fa52557ef
--- /dev/null
+++ b/app/views/projects/commit/_signature.html.haml
@@ -0,0 +1,5 @@
+- if signature
+ - if signature.valid_signature?
+ = render partial: 'projects/commit/valid_signature_badge', locals: { signature: signature }
+ - else
+ = render partial: 'projects/commit/invalid_signature_badge', locals: { signature: signature }
diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml
new file mode 100644
index 00000000000..66f00eb5507
--- /dev/null
+++ b/app/views/projects/commit/_signature_badge.html.haml
@@ -0,0 +1,18 @@
+- css_classes = commit_signature_badge_classes(css_classes)
+
+- title = capture do
+ .gpg-popover-status
+ = title
+
+- content = capture do
+ .clearfix
+ = content
+
+ GPG Key ID:
+ %span.monospace= signature.gpg_key_primary_keyid
+
+
+ = link_to('Learn more about signing commits', help_page_path('workflow/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
+
+%button{ class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } }
+ = label
diff --git a/app/views/projects/commit/_valid_signature_badge.html.haml b/app/views/projects/commit/_valid_signature_badge.html.haml
new file mode 100644
index 00000000000..db1a41bbf64
--- /dev/null
+++ b/app/views/projects/commit/_valid_signature_badge.html.haml
@@ -0,0 +1,32 @@
+- title = capture do
+ .gpg-popover-icon.valid
+ = render 'shared/icons/icon_status_success_borderless.svg'
+ %div
+ This commit was signed with a <strong>verified</strong> signature.
+
+- content = capture do
+ - gpg_key = signature.gpg_key
+ - user = gpg_key&.user
+ - user_name = signature.gpg_key_user_name
+ - user_email = signature.gpg_key_user_email
+
+ - if user
+ = link_to user_path(user), class: 'gpg-popover-user-link' do
+ %div
+ = user_avatar_without_link(user: user, size: 32)
+
+ %div
+ %strong= gpg_key.user.name
+ %div @#{gpg_key.user.username}
+ - else
+ = mail_to user_email do
+ %div
+ = user_avatar_without_link(user_name: user_name, user_email: user_email, size: 32)
+
+ %div
+ %strong= user_name
+ %div= user_email
+
+- locals = { signature: signature, title: title, content: content, label: 'Verified', css_classes: ['valid'] }
+
+= render partial: 'projects/commit/signature_badge', locals: locals
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index ac93eac41ac..c66ea873dba 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -2,4 +2,4 @@
= render 'commit_box'
= render 'ci_menu'
-= render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id)
+= render 'projects/commit/pipelines_list', endpoint: pipelines_project_commit_path(@project, @commit.id)
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index b778e8af121..07c83c0a590 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,6 +1,7 @@
- @no_container = true
- container_class = !fluid_layout && diff_view == :inline ? 'container-limited' : ''
- limited_container_width = fluid_layout ? '' : 'limit-container-width'
+- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
= render "projects/commits/head"
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
index 1657fb46163..d806acdda13 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -1,6 +1,6 @@
xml.entry do
- xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id)
- xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
+ xml.id project_commit_url(@project, id: commit.id)
+ xml.link href: project_commit_url(@project, id: commit.id)
xml.title truncate(commit.title, length: 80)
xml.updated commit.committed_date.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 8a4ef5a45b3..12b73ecdf13 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,14 +9,14 @@
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
- %li.commit.flex-list.js-toggle-container{ id: "commit-#{commit.short_id}" }
+ %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.hidden-xs
= author_avatar(commit, size: 36)
.commit-detail
.commit-content
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message item-title"
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
@@ -36,9 +36,15 @@
#{ commit_text.html_safe }
- .commit-actions.flex-row.hidden-xs
+ .commit-actions.hidden-xs
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha btn btn-transparent"
+
+ - if request.xhr?
+ = render partial: 'projects/commit/signature', object: commit.signature
+ - else
+ = render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
+
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index cf8dffc8957..d14897428d0 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -7,9 +7,9 @@
%span.commits-count= n_("%d commit", "%d commits", commits.count) % commits.count
%li.commits-row{ data: { day: day } }
- %ul.content-list.commit-list
+ %ul.content-list.commit-list.flex-list
= render partial: 'projects/commits/commit', collection: commits, locals: { project: project, ref: ref }
- if hidden > 0
%li.alert.alert-warning
- = n_('%d additional commit has been omitted to prevent performance issues.', '%d additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
+ = n_('%s additional commit has been omitted to prevent performance issues.', '%s additional commits have been omitted to prevent performance issues.', hidden) % number_with_delimiter(hidden)
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index ebeaab863bc..e1549baef89 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -4,33 +4,33 @@
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_files_path(@project) do
+ = link_to project_tree_path(@project) do
#{ _('Files') }
= nav_link(controller: [:commit, :commits]) do
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ = link_to project_commits_path(@project, current_ref) do
#{ _('Commits') }
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = link_to project_branches_path(@project) do
#{ _('Branches') }
= nav_link(controller: [:tags, :releases]) do
- = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = link_to project_tags_path(@project) do
#{ _('Tags') }
= nav_link(path: 'graphs#show') do
- = link_to namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to project_graph_path(@project, current_ref) do
#{ _('Contributors') }
= nav_link(controller: %w(network)) do
- = link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
+ = link_to project_network_path(@project, current_ref) do
#{ s_('ProjectNetworkGraph|Graph') }
= nav_link(controller: :compare) do
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
+ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
#{ _('Compare') }
= nav_link(path: 'graphs#charts') do
- = link_to charts_namespace_project_graph_path(@project.namespace, @project, current_ref) do
+ = link_to charts_project_graph_path(@project, current_ref) do
#{ _('Charts') }
diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml
index 5fb89935467..48cefbe45f2 100644
--- a/app/views/projects/commits/_inline_commit.html.haml
+++ b/app/views/projects/commits/_inline_commit.html.haml
@@ -1,8 +1,8 @@
%li.commit.inline-commit
.commit-row-title
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-sha"
+ = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha"
&nbsp;
%span.str-truncated
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message"
.pull-right
#{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 9cf792e1721..a9b77631474 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name}:#{@ref} commits"
-xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html"
-xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
+xml.link href: project_commits_url(@project, @ref, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link href: project_commits_url(@project, @ref), rel: "alternate", type: "text/html"
+xml.id project_commits_url(@project, @ref)
xml.updated @commits.first.committed_date.xmlschema if @commits.any?
xml << render(@commits) if @commits.any?
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 7ed7e441344..bd2d900997e 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,8 +1,12 @@
- @no_container = true
+- breadcrumb_title _("Commits")
- page_title _("Commits"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
= content_for :sub_nav do
= render "head"
@@ -19,16 +23,16 @@
.tree-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control
- = link_to _("View open merge request"), namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn'
+ = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
- elsif create_mr_button?(@repository.root_ref, @ref)
.control
= link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
.control
- = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'commits-search-form') do
+ = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form', data: { 'signatures-path' => namespace_project_signatures_path }) do
= search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
.control
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
+ = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
= icon("rss")
%div{ id: dom_id(@project) }
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index adb724c1b8d..94b7db5eb25 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,4 +1,4 @@
-= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
+= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
.compare-switch-container
@@ -7,7 +7,7 @@
.input-group.inline-input-group
%span.input-group-addon from
= hidden_field_tag :from, params[:from]
- = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
.dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render 'shared/ref_dropdown'
.compare-ellipsis.inline ...
@@ -15,12 +15,12 @@
.input-group.inline-input-group
%span.input-group-addon to
= hidden_field_tag :to, params[:to]
- = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
.dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render 'shared/ref_dropdown'
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?
- = link_to "View open merge request", namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'prepend-left-10 btn'
+ = link_to "View open merge request", project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn'
- elsif create_mr_button?
= link_to "Create merge request", create_mr_path, class: 'prepend-left-10 btn'
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 2cf14859f30..05de21e8dbf 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
- page_title "Compare"
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
%div{ class: container_class }
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index a1bca2cf83a..8bc863f77b3 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,5 +1,8 @@
- @no_container = true
+- breadcrumb_title "Compare"
- page_title "#{params[:from]}...#{params[:to]}"
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
%div{ class: container_class }
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 7000b289f75..c704635ead3 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
- page_title "Cycle Analytics"
+- if show_new_nav?
+ - add_to_breadcrumbs("Project", project_path(@project))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics')
@@ -9,8 +11,8 @@
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
.landing.content-block{ "v-if" => "!isOverviewDialogDismissed" }
- %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box' }
- = icon("times", "@click" => "dismissOverviewDialog()")
+ %button.dismiss-button{ type: 'button', 'aria-label': 'Dismiss Cycle Analytics introduction box', "@click" => "dismissOverviewDialog()" }
+ = icon("times")
.svg-container
= custom_icon('icon_cycle_analytics_splash')
.inner-content
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index cb98ce04430..45985a5ecef 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -12,4 +12,4 @@
Create a new deploy key for this project
= render @deploy_keys.form_partial_path
%hr
- #js-deploy-keys{ data: { endpoint: namespace_project_deploy_keys_path } }
+ #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project) } }
diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml
index 37219f8d7ae..cd910b82b57 100644
--- a/app/views/projects/deploy_keys/edit.html.haml
+++ b/app/views/projects/deploy_keys/edit.html.haml
@@ -7,4 +7,4 @@
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
= f.submit 'Save changes', class: 'btn-save btn'
- = link_to 'Cancel', namespace_project_settings_repository_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_settings_repository_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 4502c397d29..4c22166c256 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -6,12 +6,12 @@
= link_to deployment.ref, project_ref_path(@project, deployment.ref), class: "ref-name"
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-sha"
+ = link_to deployment.short_sha, project_commit_path(@project, deployment.sha), class: "commit-sha"
%p.commit-title.flex-truncate-parent
%span.flex-truncate-child
- if commit_title = deployment.commit_title
= author_avatar(deployment.commit, size: 20)
- = link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
+ = link_to_gfm commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 402c18c447e..da34a83d8e0 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -3,8 +3,8 @@
Too many changes to show.
.pull-right
- if current_controller?(:commit)
- = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
- = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-sm"
+ = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-sm"
+ = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-sm"
- elsif current_controller?('projects/merge_requests/diffs') && @merge_request&.persisted?
= link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-sm"
= link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
index 19d08181223..aa004a739d7 100644
--- a/app/views/projects/diffs/viewers/_image.html.haml
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -8,15 +8,15 @@
.image
%span.wrap
.frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
- %img{ src: blob_raw_path, alt: diff_file.file_path }
+ = image_tag(blob_raw_path, alt: diff_file.file_path)
%p.image-info= number_to_human_size(blob.size)
- else
.image
.two-up.view
%span.wrap
.frame.deleted
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ %a{ href: project_blob_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path)
%p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size)
|
@@ -27,8 +27,8 @@
%span.meta-height
%span.wrap
.frame.added
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) }
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %a{ href: project_blob_path(@project, tree_join(diff_file.content_sha, diff_file.new_path)) }
+ = image_tag(blob_raw_path, alt: diff_file.new_path)
%p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size)
|
@@ -41,10 +41,10 @@
.swipe.view.hide
.swipe-frame
.frame.deleted
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path)
.swipe-wrap
.frame.added
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ = image_tag(blob_raw_path, alt: diff_file.new_path)
%span.swipe-bar
%span.top-handle
%span.bottom-handle
@@ -52,9 +52,9 @@
.onion-skin.view.hide
.onion-skin-frame
.frame.deleted
- %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ = image_tag(old_blob_raw_path, alt: diff_file.old_path)
.frame.added
- %img{ src: blob_raw_path, alt: diff_file.new_path }
+ = image_tag(blob_raw_path, alt: diff_file.new_path)
.controls
.transparent
.drag-track
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 78057facde7..087cb804449 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -134,7 +134,7 @@
.help-block The maximum file size allowed is 200KB.
- if @project.avatar?
%hr
- = link_to 'Remove avatar', namespace_project_avatar_path(@project.namespace, @project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
+ = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= f.submit 'Save changes', class: "btn btn-save"
.row.prepend-top-default
@@ -148,7 +148,7 @@
Runs a number of housekeeping tasks within the current repository,
such as compressing file revisions and removing unreachable objects.
.col-lg-8
- = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
+ = link_to 'Housekeeping', housekeeping_project_path(@project),
method: :post, class: "btn btn-default"
%hr
.row.prepend-top-default
@@ -164,12 +164,12 @@
.col-lg-8
- if @project.export_project_path
- = link_to 'Download export', download_export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Download export', download_export_project_path(@project),
rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Generate new export', generate_new_export_project_path(@project),
method: :post, class: "btn btn-default"
- else
- = link_to 'Export project', export_namespace_project_path(@project.namespace, @project),
+ = link_to 'Export project', export_project_path(@project),
method: :post, class: "btn btn-default"
.bs-callout.bs-callout-info
@@ -207,13 +207,13 @@
- if @project.archived?
%p
%strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive project', unarchive_namespace_project_path(@project.namespace, @project),
+ = link_to 'Unarchive project', unarchive_project_path(@project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
method: :post, class: "btn btn-success"
- else
%p
%strong Archived projects cannot be committed to!
- = link_to 'Archive project', archive_namespace_project_path(@project.namespace, @project),
+ = link_to 'Archive project', archive_project_path(@project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
method: :post, class: "btn btn-warning"
%hr
@@ -252,7 +252,7 @@
%p.append-bottom-0
Please select the group you want to transfer this project to in the dropdown to the right.
.col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group
= label_tag :new_namespace_id, nil, class: 'label-light' do
%span Select a new namespace
@@ -276,7 +276,7 @@
= succeed "." do
= link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
.col-lg-8
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_namespace_project_path(@project.namespace, @project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
= button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
@@ -289,7 +289,7 @@
%p.append-bottom-0
Removing the project will delete its repository and all related resources including issues, merge requests etc.
.col-lg-8
- = form_tag(namespace_project_path(@project.namespace, @project), method: :delete) do
+ = form_tag(project_path(@project), method: :delete) do
%p
%strong Removed projects cannot be restored!
= button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 50e0bad3ccf..d17709380d5 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,9 +1,6 @@
- @no_container = true
-= content_for :flash_message do
- - if current_user && can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- = render 'shared/no_password'
+= render partial: 'flash_messages', locals: { project: @project }
= render "projects/head"
= render "home_panel"
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index 6d040f5cfe6..1605f3a3351 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -19,4 +19,4 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save'
- = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_environments_path(@project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/_stop.html.haml b/app/views/projects/environments/_stop.html.haml
index 14a2d627203..c35f9af2873 100644
--- a/app/views/projects/environments/_stop.html.haml
+++ b/app/views/projects/environments/_stop.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :create_deployment, environment) && environment.stop_action?
.inline
- = link_to stop_namespace_project_environment_path(@project.namespace, @project, environment), method: :post,
+ = link_to stop_project_environment_path(@project, environment), method: :post,
class: 'btn stop-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to stop this environment?' } do
= icon('stop', class: 'stop-env-icon')
diff --git a/app/views/projects/environments/_terminal_button.html.haml b/app/views/projects/environments/_terminal_button.html.haml
index 97de9c95de7..a6201bdbc42 100644
--- a/app/views/projects/environments/_terminal_button.html.haml
+++ b/app/views/projects/environments/_terminal_button.html.haml
@@ -1,3 +1,3 @@
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
- = link_to terminal_namespace_project_environment_path(@project.namespace, @project, environment), class: 'btn terminal-button' do
+ = link_to terminal_project_environment_path(@project, environment), class: 'btn terminal-button' do
= icon('terminal')
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 80d2b6f5d95..d0f723af5bf 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -2,6 +2,9 @@
- page_title "Environments"
= render "projects/pipelines/head"
+- if show_new_nav?
+ - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
+
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments")
@@ -12,6 +15,6 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"project-environments-path" => project_environments_path(@project),
"project-stopped-environments-path" => project_environments_path(@project, scope: :stopped),
- "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project),
+ "new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments"),
"css-class" => container_class } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index c5722cf5997..e9e1ad9ef30 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -10,12 +10,12 @@
.top-area
.row
.col-sm-6
- %h3.page-title
+ %h3
Environment:
= link_to @environment.name, environment_path(@environment)
- #prometheus-graphs{ data: { "settings-path": edit_namespace_project_service_path(@project.namespace, @project, 'prometheus'),
+ #prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
- "additional-metrics": additional_metrics_namespace_project_environment_path(@project.namespace, @project, @environment, format: :json),
- "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: namespace_project_environment_deployments_path(@project.namespace, @project, @environment, format: :json) } }
+ "additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
+ "has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 24638c77cbb..88f43a1e7e4 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Environments"
- page_title 'New Environment'
= render "projects/pipelines/head"
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 31e2bb11ce8..0ce0f5465fc 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -12,9 +12,9 @@
= render 'projects/environments/external_url', environment: @environment
= render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment)
- = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
+ = link_to 'Edit', edit_project_environment_path(@project, @environment), class: 'btn'
- if can?(current_user, :stop_environment, @environment)
- = link_to 'Stop', stop_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
+ = link_to 'Stop', stop_project_environment_path(@project, @environment), data: { confirm: 'Are you sure you want to stop this environment?' }, class: 'btn btn-danger', method: :post
.environments-container
- if @deployments.blank?
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 4c4aa0baff3..464135b5ac7 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -22,4 +22,4 @@
= render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }
- #terminal{ data: { project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws" } }
+ #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } }
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 8a409541fe5..e3bf48ee47f 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -7,7 +7,7 @@
= render 'shared/ref_switcher', destination: 'find_file', path: @path
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
%li.file-finder
%input#file_find.form-control.file-finder-input{ type: "text", placeholder: _('Find by path'), autocomplete: 'off' }
@@ -20,8 +20,8 @@
:javascript
var projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
- url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}",
- treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}",
- blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}"
+ url: "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}",
+ treeUrl: "#{escape_javascript(project_tree_path(@project, @ref))}",
+ blobUrlTemplate: "#{escape_javascript(project_blob_path(@project, @id || @commit.id))}"
});
new ShortcutsFindFile(projectFindFile);
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
index 524b77783ef..d365bcd4ecc 100644
--- a/app/views/projects/forks/error.html.haml
+++ b/app/views/projects/forks/error.html.haml
@@ -20,6 +20,6 @@
= error
%p
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do
+ = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
%i.fa.fa-code-fork
Try to fork again
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index f4aa523b32d..111cbcda266 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -34,7 +34,7 @@
= custom_icon('icon_fork')
%span Fork
- else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
+ = link_to new_project_fork_path(@project), title: "Fork project", class: 'btn btn-new' do
= custom_icon('icon_fork')
%span Fork
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index 5242bc72b71..0f36e1a7353 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -30,7 +30,7 @@
= namespace.human_name
- else
.fork-thumbnail
- = link_to namespace_project_forks_path(@project.namespace, @project, namespace_key: namespace.id), method: "POST" do
+ = link_to project_forks_path(@project, namespace_key: namespace.id), method: "POST" do
- if /no_((\w*)_)*avatar/.match(avatar)
.no-avatar
= icon 'question'
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index b23bbadbdb4..b98dc09534f 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -20,14 +20,14 @@
- if generic_commit_status.ref
.icon-container
= generic_commit_status.tags.any? ? icon('tag') : icon('code-fork')
- = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref)
+ = link_to generic_commit_status.ref, project_commits_path(generic_commit_status.project, generic_commit_status.ref)
- else
.light none
.icon-container.commit-icon
= custom_icon("icon_commit")
- if commit_sha
- = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
+ = link_to generic_commit_status.short_sha, project_commit_path(generic_commit_status.project, generic_commit_status.sha), class: "commit-sha"
- if retried
= icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.')
@@ -53,7 +53,7 @@
- if admin
%td
- if generic_commit_status.project
- = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project)
+ = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project)
%td
- if generic_commit_status.try(:runner)
= runner_link(generic_commit_status.runner)
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index 464ac34d961..249b9d82ad9 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
- page_title "Charts"
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 680f8ae6c8f..4256a8c4d7e 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -3,6 +3,10 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
+
= render 'projects/commits/head'
%div{ class: container_class }
@@ -35,7 +39,7 @@
:javascript
$.ajax({
type: "GET",
- url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}",
+ url: "#{project_graph_path(@project, current_ref, format: :json)}",
dataType: "json",
success: function (data) {
var graph = new ContributorsStatGraph();
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
index 6962b223451..05b06cfc8b2 100644
--- a/app/views/projects/hook_logs/_index.html.haml
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -28,7 +28,7 @@
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
- = link_to 'View details', namespace_project_hook_hook_log_path(project.namespace, project, hook, hook_log)
+ = link_to 'View details', project_hook_hook_log_path(project, hook, hook_log)
= paginate hook_logs, theme: 'gitlab'
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index 2eabe92f8eb..ab5a7b117d7 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -6,6 +6,6 @@
Request details
.col-lg-9
- = link_to 'Resend Request', retry_namespace_project_hook_hook_log_path(@project.namespace, @project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
+ = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index fd382c1d63f..c8c17d2d828 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -9,12 +9,12 @@
#{link_to 'Webhooks', help_page_path('user/project/integrations/webhooks')} can be
used for binding events when something is happening within the project.
.col-lg-9.append-bottom-default
- = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
- = link_to 'Test hook', test_namespace_project_hook_path(@project.namespace, @project, @hook), class: 'btn btn-default'
- = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook
+ = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
index 25a87411cac..778ff91362d 100644
--- a/app/views/projects/imports/new.html.haml
+++ b/app/views/projects/imports/new.html.haml
@@ -12,7 +12,7 @@
:preserve
#{h(sanitize_repo_path(@project, @project.import_error))}
-= form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f|
+= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
= render "shared/import_form", f: f
.form-actions
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 7a188cb6445..e9f21594a71 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -5,29 +5,29 @@
%ul{ class: (container_class) }
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= nav_link(controller: :issues) do
- = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
+ = link_to project_issues_path(@project), title: 'Issues' do
%span
List
= nav_link(controller: :boards) do
- = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do
+ = link_to project_boards_path(@project), title: 'Board' do
%span
Board
- if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
%span
Merge Requests
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
Milestones
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 6a0d96f50cd..7dc35be57a6 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -24,7 +24,7 @@
- if issue.milestone
%span.issuable-milestone.hidden-xs
&nbsp;
- = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do
+ = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title) do
= icon('clock-o')
= issue.milestone.title
- if issue.due_date
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
index 35b7d1b920c..264032a3a31 100644
--- a/app/views/projects/issues/_issue_by_email.html.haml
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -30,5 +30,5 @@
Anyone who gets ahold of it can create issues as if they were you.
You should
- = link_to 'reset it', new_issue_address_namespace_project_path(@project.namespace, @project), class: 'incoming-email-token-reset'
+ = link_to 'reset it', new_issue_address_project_path(@project), class: 'incoming-email-token-reset'
if that ever happens.
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index bda52fe461c..6a567487514 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -18,7 +18,7 @@
- unless @issue.project.id == merge_request.target_project.id
in
- project = merge_request.target_project
- = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+ = link_to project.name_with_namespace, project_path(project)
- if merge_request.merged?
%span.merge-request-status.prepend-left-10.merged
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 698959ec74f..756faf4625e 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -2,10 +2,9 @@
= icon('rss')
- if @can_bulk_update
= button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
-= link_to "New issue", new_namespace_project_issue_path(@project.namespace,
- @project,
- issue: { assignee_id: issues_finder.assignee.try(:id),
- milestone_id: issues_finder.milestones.first.try(:id) }),
- class: "btn btn-new",
- title: "New issue",
- id: "new_issue_link"
+= link_to "New issue", new_project_issue_path(@project,
+ issue: { assignee_id: issues_finder.assignee.try(:id),
+ milestone_id: issues_finder.milestones.first.try(:id) }),
+ class: "btn btn-new",
+ title: "New issue",
+ id: "new_issue_link"
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index dba092c8844..e1b4a49850a 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :push_code, @project)
- .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue), create_mr_path: create_merge_request_namespace_project_issue_path(@project.namespace, @project, @issue), create_branch_path: namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
+ .create-mr-dropdown-wrap{ data: { can_create_path: can_create_branch_project_issue_path(@project, @issue), create_mr_path: create_merge_request_project_issue_path(@project, @issue), create_branch_path: project_branches_path(@project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid) } }
.btn-group.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 8c9f6f3b4df..1df38db9fd4 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -11,4 +11,4 @@
= render_pipeline_status(pipeline)
%span.related-branch-info
%strong
- = link_to branch, namespace_project_compare_path(@project.namespace, @project, from: @project.default_branch, to: branch), class: "ref-name"
+ = link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 61346884346..4029926f373 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name} issues"
xml.link href: url_for(params), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id namespace_project_issues_url(@project.namespace, @project)
+xml.link href: project_issues_url(@project), rel: "alternate", type: "text/html"
+xml.id project_issues_url(@project)
xml.updated @issues.first.updated_at.xmlschema if @issues.reorder(nil).any?
xml << render(partial: 'issues/issue', collection: @issues) if @issues.reorder(nil).any?
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 89ac5ff7128..aacb057840d 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -33,4 +33,4 @@
- if new_issue_email
= render 'issue_by_email', email: new_issue_email
- else
- = render 'shared/empty_states/issues', button_path: new_namespace_project_issue_path(@project.namespace, @project)
+ = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project)
diff --git a/app/views/projects/issues/new.html.haml b/app/views/projects/issues/new.html.haml
index e8aae0f47e2..60fe442014f 100644
--- a/app/views/projects/issues/new.html.haml
+++ b/app/views/projects/issues/new.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Issues"
- page_title "New Issue"
%h3.page-title
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index d909b0bfbbd..a57844f974e 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -30,27 +30,26 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- if can_update_issue
- %li
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'issuable-edit'
- %li
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- %li
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li= link_to 'Edit', edit_project_issue_path(@project, @issue)
+ - unless current_user == @issue.author
+ %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 #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam
- %li
- = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
+ %li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can_update_issue || can_report_spam
%li.divider
- %li
- = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
+
+ = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
+
- if can_report_spam
- = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
+ = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details
@@ -65,10 +64,10 @@
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
- #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ #merge-requests{ data: { url: referenced_merge_requests_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript.
- #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ #related-branches{ data: { url: related_branches_project_issue_url(@project, @issue) } }
// This element is filled in using JavaScript.
.content-block.emoji-block
diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index ad72ab5b199..83a2af1dc74 100644
--- a/app/views/projects/jobs/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -1,18 +1,18 @@
- show_controls = local_assigns.fetch(:show_controls, true)
- pipeline = @build.pipeline
-.content-block.build-header.top-area
+.content-block.build-header.top-area.page-content-header
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false, title: @build.status_title
%strong
Job
- = link_to "##{@build.id}", namespace_project_job_path(@project.namespace, @project, @build), class: 'js-build-id'
+ = link_to "##{@build.id}", project_job_path(@project, @build), class: 'js-build-id'
in pipeline
%strong
= link_to "##{pipeline.id}", pipeline_path(pipeline)
for
%strong
- = link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: 'commit-sha'
+ = link_to pipeline.short_sha, project_commit_path(@project, pipeline.sha), class: 'commit-sha'
from
%strong
= link_to @build.ref, project_ref_path(@project, @build.ref), class: 'ref-name'
@@ -24,8 +24,8 @@
- if show_controls
.nav-controls
- if can?(current_user, :create_issue, @project) && @build.failed?
- = link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
+ = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_namespace_project_job_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
+ = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 93e8a4e385c..f2db71e8838 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -11,7 +11,7 @@
#js-details-block-vue
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
- .block{ class: ("block-first" if !@build.coverage) }
+ .block
.title
Job artifacts
- if @build.artifacts_expired?
@@ -26,18 +26,18 @@
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
- = link_to keep_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
+ = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
- = link_to download_namespace_project_job_artifacts_path(@project.namespace, @project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
+ = link_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'btn btn-sm btn-default' do
Download
- if @build.artifacts_metadata?
- = link_to browse_namespace_project_job_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default' do
+ = link_to browse_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default' do
Browse
- if @build.trigger_request
- .build-widget
+ .build-widget.block
%h4.title
Trigger
@@ -55,10 +55,10 @@
.js-build-variable.trigger-build-variable= key
.js-build-value.trigger-build-value= value
- .block
+ %div{ class: (@build.pipeline.stages_count > 1 ? "block" : "block-last") }
%p
Commit
- = link_to @build.pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, @build.pipeline.sha), class: 'commit-sha link-commit'
+ = link_to @build.pipeline.short_sha, project_commit_path(@project, @build.pipeline.sha), class: 'commit-sha link-commit'
= clipboard_button(text: @build.pipeline.short_sha, title: "Copy commit SHA to clipboard")
- if @build.merge_request
in
@@ -69,13 +69,13 @@
- if @build.pipeline.stages_count > 1
.dropdown.build-dropdown
- .title
+ %div
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
- = link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit'
+ = link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
from
- = link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit'
+ = link_to "#{@build.pipeline.ref}", project_branch_path(@project, @build.pipeline.ref), class: 'link-commit'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.stage-selection More
= icon('chevron-down')
@@ -88,7 +88,7 @@
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- = link_to namespace_project_job_path(@project.namespace, @project, build) do
+ = link_to project_job_path(@project, build) do
= icon('arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index a33e3978ee1..d78891546f7 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -2,6 +2,9 @@
- page_title "Jobs"
= render "projects/pipelines/head"
+- if show_new_nav?
+ - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
+
%div{ class: container_class }
.top-area
- build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
@@ -10,7 +13,7 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel running', cancel_all_namespace_project_jobs_path(@project.namespace, @project),
+ = link_to 'Cancel running', cancel_all_project_jobs_path(@project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index c73bae0a2c9..fa086413fbe 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -21,7 +21,7 @@
%br
Go to
- = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
+ = link_to project_runners_path(@build.project) do
Runners page
- if @build.starts_environment?
@@ -54,23 +54,24 @@
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- .build-trace-container#build-trace
- .top-bar.sticky
+ .build-trace-container.prepend-top-default
+ .top-bar.js-top-bar
.js-truncated-info.truncated-info.hidden<
Showing last
%span.js-truncated-info-size.truncated-info-size><
KiB of log -
- %a.js-raw-link.raw-link{ href: raw_namespace_project_job_path(@project.namespace, @project, @build) }>< Complete Raw
+ %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
+
.controllers
- if @build.has_trace?
- = link_to raw_namespace_project_job_path(@project.namespace, @project, @build),
+ = link_to raw_project_job_path(@project, @build),
title: 'Show complete raw',
data: { placement: 'top', container: 'body' },
class: 'js-raw-link-controller has-tooltip controllers-buttons' do
= icon('file-text-o')
- if can?(current_user, :update_build, @project) && @build.erasable?
- = link_to erase_namespace_project_job_path(@project.namespace, @project, @build),
+ = link_to erase_project_job_path(@project, @build),
method: :post,
data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
title: 'Erase job log',
@@ -82,15 +83,17 @@
.has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
%button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
= custom_icon('scroll_down')
- .bash.sticky.js-scroll-container
- %code.js-build-output
+
+ %pre.build-trace#build-trace
+ %code.bash.js-build-output
.build-loader-animation.js-build-refresh
+
= render "sidebar"
.js-build-options{ data: javascript_build_options }
-#js-job-details-vue{ data: { endpoint: namespace_project_job_path(@project.namespace, @project, @build, format: :json) } }
+#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 7f0059cdcda..84b0b65d1c0 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -6,4 +6,4 @@
%h3.page-title
Edit Label
%hr
- = render 'shared/labels/form', url: namespace_project_label_path(@project.namespace.becomes(Namespace), @project, @label), back_path: namespace_project_labels_path(@project.namespace, @project)
+ = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index fc72c4fb635..4b9da02c6b8 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,26 +1,34 @@
- @no_container = true
- page_title "Labels"
- hide_class = ''
+- can_admin_label = can?(current_user, :admin_label, @project)
+
+- if show_new_nav? && can?(current_user, :admin_label, @project)
+ - content_for :breadcrumbs_extra do
+ = link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new"
+
= render "shared/mr_head"
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
.top-area.adjust
.nav-text
- Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
+ Labels can be applied to issues and merge requests.
+ - if can_admin_label
+ Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- .nav-controls
- - if can?(current_user, :admin_label, @project)
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+ - if can_admin_label
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ = link_to new_project_label_path(@project), class: "btn btn-new" do
New label
.labels
- - if can?(current_user, :admin_label, @project)
+ - if can_admin_label
-# Only show it in the first page
- hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
.prioritized-labels{ class: ('hide' if hide) }
%h5 Prioritized Labels
- %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
+ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_project_labels_path(@project) }
#js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
= render 'shared/empty_states/priority_labels'
- if @prioritized_labels.present?
@@ -28,7 +36,7 @@
- if @labels.present?
.other-labels
- - if can?(current_user, :admin_label, @project)
+ - if can_admin_label
%h5{ class: ('hide' if hide) } Other Labels
%ul.content-list.manage-labels-list.js-other-labels
= render partial: 'shared/label', subject: @project, collection: @labels, as: :label
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 8f6c085a361..562b6fb8d8c 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Labels"
- page_title "New Label"
= render "shared/mr_head"
@@ -6,4 +7,4 @@
%h3.page-title
New Label
%hr
- = render 'shared/labels/form', url: namespace_project_labels_path(@project.namespace.becomes(Namespace), @project), back_path: namespace_project_labels_path(@project.namespace, @project)
+ = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index aac74a25b75..243dcfdc187 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -13,4 +13,4 @@
and try again.
%hr
.clearfix
- = link_to 'Go back', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg pull-right'
+ = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg pull-right'
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 04bd4e8b683..3bdb5d0adc4 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -2,7 +2,7 @@
This service will be installed on the Mattermost instance at
%strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
%hr
-= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
+= form_for(:mattermost, method: :post, url: project_mattermost_path(@project), html: { class: 'js-requires-input'} ) do |f|
%h4 Team
%p
= @teams.one? ? 'The team' : 'Select the team'
@@ -42,5 +42,5 @@
%hr
.clearfix
.pull-right
- = link_to 'Cancel', edit_namespace_project_service_path(@project.namespace, @project, @service), class: 'btn btn-lg'
+ = link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg'
= f.submit 'Install', class: 'btn btn-save btn-lg'
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
index b7f73fe5339..1e505222887 100644
--- a/app/views/projects/merge_requests/_head.html.haml
+++ b/app/views/projects/merge_requests/_head.html.haml
@@ -4,18 +4,18 @@
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: :merge_requests) do
- = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
+ = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
%span
List
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
- = link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
- = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
Milestones
diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
index 766cb272bec..917ec7fdbda 100644
--- a/app/views/projects/merge_requests/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
@@ -1,3 +1,6 @@
+- content_for :page_specific_javascripts do
+ = webpack_bundle_tag('how_to_merge')
+
#modal_merge_info.modal
.modal-dialog
.modal-content
@@ -50,14 +53,3 @@
= succeed '.' do
You can also checkout merge requests locally by
= link_to 'following these guidelines', help_page_path('user/project/merge_requests/index.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer'
-
-:javascript
- $(function(){
- var modal = $('#modal_merge_info').modal({modal: true, show:false});
- $('.how_to_merge_link').bind("click", function(){
- modal.show();
- });
- $('.modal-header .close').bind("click", function(){
- modal.hide();
- })
- })
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 3599f2271b5..0a1ebcb8124 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -23,7 +23,7 @@
- if merge_request.milestone
%span.issuable-milestone.hidden-xs
&nbsp;
- = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do
+ = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title) do
= icon('clock-o')
= merge_request.milestone.title
- if merge_request.target_project.default_branch != merge_request.target_branch
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index d9428b8562e..a2e819fb3a7 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -1,3 +1,5 @@
+- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
+
- if @merge_request.closed_without_fork?
.alert.alert-danger
%p The source project of this merge request has been removed.
@@ -15,21 +17,24 @@
.issuable-meta
= issuable_meta(@merge_request, @project, "Merge request")
- - if can?(current_user, :update_merge_request, @merge_request)
- .issuable-actions
- .clearfix.issue-btn-group.dropdown
- %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
- Options
- = icon('caret-down')
- .dropdown-menu.dropdown-menu-align-right.hidden-lg
- %ul
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
+ Options
+ = icon('caret-down')
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can_update_merge_request
+ %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
+ - unless current_user == @merge_request.author
+ %li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
+ - if can_update_merge_request
%li{ class: merge_request_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- %li
- = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
- Edit
+
+ - if can_update_merge_request
+ = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
+
+ = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
diff --git a/app/views/projects/merge_requests/_pipelines.html.haml b/app/views/projects/merge_requests/_pipelines.html.haml
index 2f1dbe87619..473b7b919c8 100644
--- a/app/views/projects/merge_requests/_pipelines.html.haml
+++ b/app/views/projects/merge_requests/_pipelines.html.haml
@@ -1,4 +1,4 @@
-- endpoint_path = local_assigns[:endpoint] || pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, format: :json)
+- endpoint_path = local_assigns[:endpoint] || pipelines_project_merge_request_path(@project, @merge_request, format: :json)
- disable_initialization = local_assigns.fetch(:disable_initialization, false)
= render 'projects/commit/pipelines_list', endpoint: endpoint_path, disable_initialization: disable_initialization
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index f016b9c13b3..454bc359b6b 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -10,8 +10,8 @@
= render 'shared/issuable/sidebar', issuable: @merge_request
-#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json),
- resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } }
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+ resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
.loading{ "v-if" => "isLoading" }
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
index e675e1830d0..13026b7566a 100644
--- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml
@@ -13,4 +13,4 @@
%button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" }
%span {{commitButtonText}}
.col-xs-6.text-right
- = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel"
+ = link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "btn btn-cancel"
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index f016b9c13b3..454bc359b6b 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -10,8 +10,8 @@
= render 'shared/issuable/sidebar', issuable: @merge_request
-#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json),
- resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } }
+#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
+ resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
.loading{ "v-if" => "isLoading" }
%i.fa.fa-spinner.fa-spin
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 7cda326afef..8958b2cf5e1 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -1,9 +1,9 @@
%h3.page-title
New Merge Request
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: namespace_project_new_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
- .merge-request-branches.row
+ .merge-request-branches.js-merge-request-new-compare.row{ 'data-target-project-url': project_new_merge_request_update_branches_path(@source_project), 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-md-6
.panel.panel-default.panel-new-merge-request
.panel-heading
@@ -66,10 +66,3 @@
- if @merge_request.errors.any?
= form_errors(@merge_request)
= f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn"
-
-:javascript
- new Compare({
- targetProjectUrl: "#{namespace_project_new_merge_request_update_branches_path(@source_project.namespace, @source_project)}",
- sourceBranchUrl: "#{namespace_project_new_merge_request_branch_from_path(@source_project.namespace, @source_project)}",
- targetBranchUrl: "#{namespace_project_new_merge_request_branch_to_path(@source_project.namespace, @source_project)}"
- });
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index c72dd1d8e29..4b5fa28078a 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -17,7 +17,7 @@
= f.hidden_field :target_project_id
= f.hidden_field :target_branch
-.mr-compare.merge-request
+.mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" }
- if @commits.empty?
.commits-empty
%h4
@@ -50,8 +50,3 @@
.mr-loading-status
= spinner
-
-:javascript
- var merge_request = new MergeRequest({
- action: "#{j params[:tab].presence || 'new'}",
- });
diff --git a/app/views/projects/merge_requests/creations/new.html.haml b/app/views/projects/merge_requests/creations/new.html.haml
index 2e798ce780a..3220512d60d 100644
--- a/app/views/projects/merge_requests/creations/new.html.haml
+++ b/app/views/projects/merge_requests/creations/new.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title "Merge Requests"
- page_title "New Merge Request"
- if @merge_request.can_be_created && !params[:change_branches]
diff --git a/app/views/projects/merge_requests/diffs/_versions.html.haml b/app/views/projects/merge_requests/diffs/_versions.html.haml
index 0999b95c9c9..9f7152b9824 100644
--- a/app/views/projects/merge_requests/diffs/_versions.html.haml
+++ b/app/views/projects/merge_requests/diffs/_versions.html.haml
@@ -77,7 +77,7 @@
= icon('info-circle')
Selected versions have different base commits.
Changes will include
- = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
+ = link_to project_compare_path(@project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
= succeed '.' do
@@ -94,4 +94,4 @@
of the diff.
.pull-right
- = link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
+ = link_to 'Show latest version', diffs_project_merge_request_path(@project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 6fe44ba3c3d..c020e7db380 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,10 +1,10 @@
- @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
-- new_merge_request_path = namespace_project_new_merge_request_path(merge_project.namespace, merge_project) if merge_project
+- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests"
-- unless @project.default_issues_tracker?
+- unless @project.issues_enabled?
= content_for :sub_nav do
= render "projects/merge_requests/head"
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index dbbf1bde088..ea6cd16c7ad 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -3,10 +3,10 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('common_vue')
- = page_specific_javascript_bundle_tag('diff_notes')
+ = webpack_bundle_tag('common_vue')
+ = webpack_bundle_tag('diff_notes')
-.merge-request{ 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
+.merge-request{ 'data-mr-action': "#{j params[:tab].presence || 'show'}", 'data-url' => merge_request_path(@merge_request, format: :json), 'data-project-path' => project_path(@merge_request.project) }
= render "projects/merge_requests/mr_title"
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
@@ -15,13 +15,13 @@
- if @merge_request.source_branch_exists?
= render "projects/merge_requests/how_to_merge"
+ -# haml-lint:disable InlineJavaScript
:javascript
window.gl.mrWidgetData = #{serialize_issuable(@merge_request)}
#js-vue-mr-widget.mr-widget
- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'vue_merge_request_widget'
.content-block.content-block-small.emoji-list-container
@@ -35,21 +35,21 @@
.nav-links.scrolling-tabs
%ul.merge-request-tabs
%li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
+ = link_to project_merge_request_path(@project, @merge_request), data: { target: 'div#notes', action: 'show', toggle: 'tab' } do
Discussion
%span.badge= @merge_request.related_notes.user.count
- if @merge_request.source_project
%li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ = link_to commits_project_merge_request_path(@project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
Commits
%span.badge= @commits_count
- if @pipelines.any?
%li.pipelines-tab
- = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ = link_to pipelines_project_merge_request_path(@project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
Pipelines
- %span.badge= @pipelines.size
+ %span.badge.js-pipelines-mr-count= @pipelines.size
%li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ = link_to diffs_project_merge_request_path(@project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
Changes
%span.badge= @merge_request.diff_size
#resolve-count-app.line-resolve-all-container.prepend-top-10{ "v-cloak" => true }
@@ -76,7 +76,7 @@
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- if @pipelines.any?
- = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ = render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
#diffs.diffs.tab-pane
-# This tab is always loaded via AJAX
@@ -88,10 +88,3 @@
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked?
= render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
-
-:javascript
- $(function () {
- window.mergeRequest = new MergeRequest({
- action: "#{j params[:tab].presence || 'show'}",
- });
- });
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 9a95b2a82ff..2e74b1b83cb 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -19,7 +19,7 @@
.form-actions
- if @milestone.new_record?
= f.submit 'Create milestone', class: "btn-create btn"
- = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel"
- else
= f.submit 'Save changes', class: "btn-save btn"
- = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel"
+ = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel"
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 77b566db6b6..bc82b45f902 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -1,5 +1,5 @@
= render 'shared/milestones/milestone',
- milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
- issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
- merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
+ milestone_path: project_milestone_path(milestone.project, milestone),
+ issues_path: project_issues_path(milestone.project, milestone_title: milestone.title),
+ merge_requests_path: project_merge_requests_path(milestone.project, milestone_title: milestone.title),
milestone: milestone
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index e1096bd1d67..e0b29b0c2e1 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,15 +1,20 @@
- @no_container = true
- page_title 'Milestones'
+
+- if show_new_nav? && can?(current_user, :admin_milestone, @project)
+ - content_for :breadcrumbs_extra do
+ = link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone'
+
= render "shared/mr_head"
%div{ class: container_class }
.top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
- .nav-controls
+ .nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) }
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone' do
+ = link_to new_project_milestone_path(@project), class: "btn btn-new #{("visible-xs" if show_new_nav?)}", title: 'New milestone' do
New milestone
.milestones
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 586eb909afa..84ffbc0a926 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,4 +1,5 @@
- @no_container = true
+- breadcrumb_title "Milestones"
- page_title "New Milestone"
= render "shared/mr_head"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 4b692aba11c..0bf0e11c107 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -23,14 +23,14 @@
.milestone-buttons
- if can?(current_user, :admin_milestone, @project)
- if @milestone.active?
- = link_to 'Close milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
+ = link_to 'Close milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-nr btn-grouped"
- else
- = link_to 'Reopen milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
+ = link_to 'Reopen milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped btn-nr" do
+ = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped btn-nr" do
Edit
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
+ = link_to project_milestone_path(@project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-danger" do
Delete
%a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" }
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index ed6077f6c6b..ab948df4a3f 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,12 +1,15 @@
+- breadcrumb_title "Graph"
- page_title "Graph", @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('network')
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
= render "projects/commits/head"
= render "head"
%div{ class: container_class }
.project-network
.controls
- = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
+ = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success' do
= icon('search')
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 7b8be58554a..87cc23fc649 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,3 +1,6 @@
+- @breadcrumb_link = dashboard_projects_path
+- breadcrumb_title "Projects"
+- @hide_top_links = true
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
@@ -9,8 +12,9 @@
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
New project
- %p
- Create or Import your project from popular Git services
+ - if import_sources_enabled?
+ %p
+ Create or Import your project from popular Git services
.col-lg-9
= form_for @project, html: { class: 'new_project' } do |f|
.row
@@ -68,7 +72,7 @@
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
- = icon('bug', text: 'Fogbugz')
+ = icon('bug', text: 'FogBugz')
%div
- if gitea_import_enabled?
= link_to new_import_gitea_url, class: 'btn import_gitea' do
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
index 1cf286ddc40..ba5845877e5 100644
--- a/app/views/projects/no_repo.html.haml
+++ b/app/views/projects/no_repo.html.haml
@@ -9,12 +9,12 @@
%hr
.no-repo-actions
- = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do
+ = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
#{ _('Create empty bare repository') }
%strong.prepend-left-10.append-right-10 or
- = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do
+ = link_to new_project_import_path(@project), class: 'btn' do
#{ _('Import repository') }
- if can? current_user, :remove_project, @project
diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml
index 42d9ef5ccba..7d6c30b7f8d 100644
--- a/app/views/projects/pages/_destroy.haml
+++ b/app/views/projects/pages/_destroy.haml
@@ -7,6 +7,6 @@
%p
Removing the pages will prevent from exposing them to outside world.
.form-actions
- = link_to 'Remove pages', namespace_project_pages_path(@project.namespace, @project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
+ = link_to 'Remove pages', project_pages_path(@project), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove"
- else
.nothing-here-block Only the project owner can remove pages
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 4f2dd1a1398..a85cda407af 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -6,8 +6,8 @@
- @domains.each do |domain|
%li
.pull-right
- = link_to 'Details', namespace_project_pages_domain_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped"
- = link_to 'Remove', namespace_project_pages_domain_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+ = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
.clearfix
%span= link_to domain.domain, domain.url
%p
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index b22a54d75c8..098b0ef56ef 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -5,7 +5,7 @@
Pages
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
- = link_to new_namespace_project_pages_domain_path(@project.namespace, @project), class: 'btn btn-new pull-right', title: 'New Domain' do
+ = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do
%i.fa.fa-plus
New Domain
diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml
index fc7fa5c1876..857ae00d0ab 100644
--- a/app/views/projects/pipeline_schedules/_form.html.haml
+++ b/app/views/projects/pipeline_schedules/_form.html.haml
@@ -24,6 +24,14 @@
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.col-md-9
+ %label.label-light
+ #{ s_('PipelineSchedules|Variables') }
+ %ul.js-pipeline-variable-list.pipeline-variable-list
+ - @schedule.variables.each do |variable|
+ = render 'variable_row', id: variable.id, key: variable.key, value: variable.value
+ = render 'variable_row'
+ .form-group
+ .col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
%div
= f.check_box :active, required: false, value: @schedule.active?
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 966d6cd8495..7343d6e039c 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -4,12 +4,12 @@
= pipeline_schedule.description
%td.branch-name-cell
= icon('code-fork')
- - if pipeline_schedule.ref
+ - if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td
- if pipeline_schedule.last_pipeline
.status-icon-container{ class: "ci-status-icon-#{pipeline_schedule.last_pipeline.status}" }
- = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline_schedule.last_pipeline.id) do
+ = link_to project_pipeline_path(@project, pipeline_schedule.last_pipeline.id) do
= ci_icon_for_status(pipeline_schedule.last_pipeline.status)
%span ##{pipeline_schedule.last_pipeline.id}
- else
@@ -26,7 +26,7 @@
= pipeline_schedule.owner&.name
%td
.pull-right.btn-group
- - if can?(current_user, :update_pipeline_schedule, @project) && !pipeline_schedule.owned_by?(current_user)
+ - if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
diff --git a/app/views/projects/pipeline_schedules/_variable_row.html.haml b/app/views/projects/pipeline_schedules/_variable_row.html.haml
new file mode 100644
index 00000000000..564cb5d1ca9
--- /dev/null
+++ b/app/views/projects/pipeline_schedules/_variable_row.html.haml
@@ -0,0 +1,17 @@
+- id = local_assigns.fetch(:id, nil)
+- key = local_assigns.fetch(:key, "")
+- value = local_assigns.fetch(:value, "")
+%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
+ .pipeline-variable-row-body
+ %input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
+ %input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
+ %input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
+ name: "schedule[variables_attributes][][key]",
+ value: key,
+ placeholder: s_('PipelineSchedules|Input variable key') }
+ %textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
+ name: "schedule[variables_attributes][][value]",
+ placeholder: s_('PipelineSchedules|Input variable value') }
+ = value
+ %button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
+ %i.fa.fa-minus-circle{ 'aria-hidden': "true" }
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index c296152e54f..8426b29bb14 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -1,9 +1,18 @@
+- breadcrumb_title "Schedules"
+
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'schedules_index'
- @no_container = true
- page_title _("Pipeline Schedules")
+
+- if show_new_nav? && can?(current_user, :create_pipeline_schedule, @project)
+ - content_for :breadcrumbs_extra do
+ = link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create'
+
+ - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
+
= render "projects/pipelines/head"
%div{ class: container_class }
@@ -12,9 +21,10 @@
- schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) }
= render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
- .nav-controls
- = link_to new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create' do
- %span= _('New schedule')
+ - if can?(current_user, :create_pipeline_schedule, @project)
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
+ = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
+ %span= _('New schedule')
- if @schedules.present?
%ul.content-list
diff --git a/app/views/projects/pipeline_schedules/new.html.haml b/app/views/projects/pipeline_schedules/new.html.haml
index 87390d4dd02..c7237cb96d8 100644
--- a/app/views/projects/pipeline_schedules/new.html.haml
+++ b/app/views/projects/pipeline_schedules/new.html.haml
@@ -1,5 +1,10 @@
+- breadcrumb_title "Schedules"
+- @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project)
- page_title _("New Pipeline Schedule")
+- if show_new_nav?
+ - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
+
%h3.page-title
= _("Schedule a new pipeline")
%hr
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index d2f0cb0806f..ee2f236cec4 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -29,6 +29,6 @@
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do
- = link_to charts_namespace_project_pipelines_path(@project.namespace, @project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
%span
Charts
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 673c3370b62..f5149306734 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -26,10 +26,10 @@
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
- = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha js-details-short"
+ = link_to @commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha js-details-short"
= link_to("#", class: "js-details-expand hidden-xs hidden-sm") do
%span.text-expander
\...
%span.js-details-content.hide
- = link_to @pipeline.sha, namespace_project_commit_path(@project.namespace, @project, @pipeline.sha), class: "commit-sha commit-hash-full"
+ = link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 85550e8fd32..ad61f033a1c 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -3,15 +3,15 @@
.tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom
%li.js-pipeline-tab-link
- = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
+ = link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
Pipeline
%li.js-builds-tab-link
- = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
+ = link_to builds_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
Jobs
%span.badge.js-builds-counter= pipeline.statuses.count
- if failed_builds.present?
%li.js-failures-tab-link
- = link_to failures_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
+ = link_to failures_project_pipeline_path(@project, @pipeline), data: {target: 'div#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
Failed Jobs
%span.badge.js-failures-counter= failed_builds.count
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 78002e8cd64..fd3ad69d85d 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
- page_title _("Charts"), _("Pipelines")
+- if show_new_nav?
+ - add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 38237d2d97d..c1729850cf4 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -2,10 +2,10 @@
- page_title "Pipelines"
= render "projects/pipelines/head"
-#pipelines-list-vue{ data: { endpoint: namespace_project_pipelines_path(@project.namespace, @project, format: :json),
+#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
"css-class" => container_class,
"help-page-path" => help_page_path('ci/quick_start/README'),
- "new-pipeline-path" => new_namespace_project_pipeline_path(@project.namespace, @project),
+ "new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"all-path" => project_pipelines_path(@project),
"pending-path" => project_pipelines_path(@project, scope: :pending),
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 71a8e490c3e..c966df62856 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -1,10 +1,11 @@
+- breadcrumb_title "Pipelines"
- page_title "New Pipeline"
%h3.page-title
New Pipeline
%hr
-= form_for @pipeline, as: :pipeline, url: namespace_project_pipelines_path(@project.namespace, @project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
+= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f|
= form_errors(@pipeline)
.form-group
= f.label :ref, 'Create for', class: 'control-label'
@@ -17,7 +18,7 @@
.help-block Existing branch name, tag
.form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_pipelines_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-cancel'
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index b39453a50fb..63f85fc69a2 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -8,7 +8,7 @@
= render "projects/pipelines/with_tabs", pipeline: @pipeline
-.js-pipeline-details-vue{ data: { endpoint: namespace_project_pipeline_path(@project.namespace, @project, @pipeline, format: :json) } }
+.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json) } }
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_vue')
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 580129ca809..255d7ef38e0 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -3,7 +3,7 @@
%h4.prepend-top-0
Pipelines
.col-lg-8
- = form_for @project, url: namespace_project_pipelines_settings_path(@project.namespace.becomes(Namespace), @project) do |f|
+ = form_for @project, url: project_pipelines_settings_path(@project) do |f|
%fieldset.builds-feature
- unless @repository.gitlab_ci_yml
.form-group
@@ -47,6 +47,14 @@
%hr
.form-group
+ = f.label :ci_config_path, 'Custom CI config path', class: 'label-light'
+ = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
+ %p.help-block
+ The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>
+ = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
+
+ %hr
+ .form-group
.checkbox
= f.label :public_builds do
= f.check_box :public_builds
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
deleted file mode 100644
index c7996077bc7..00000000000
--- a/app/views/projects/project_members/_group_members.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-.panel.panel-default
- .panel-heading
- Group members with access to
- %strong= @group.name
- %span.badge= members.size
- - if can?(current_user, :admin_group_member, @group)
- .controls
- = link_to 'Manage group members',
- group_group_members_path(@group),
- class: 'btn'
- %ul.content-list
- = render partial: 'shared/members/member',
- collection: members.limit(20),
- as: :member,
- locals: { show_controls: false }
- - if members.size > 20
- %li
- and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 8bf2246662a..bf5b11ea30c 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,6 +1,6 @@
.row
.col-sm-12
- = form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
+ = form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
= label_tag :user_ids, "Select members to invite", class: "label-light"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
@@ -18,4 +18,4 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
= f.submit "Add to project", class: "btn btn-create"
- = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default", title: "Import members from another project"
+ = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml
index 643569db646..c10ef648a8f 100644
--- a/app/views/projects/project_members/_new_shared_group.html.haml
+++ b/app/views/projects/project_members/_new_shared_group.html.haml
@@ -1,6 +1,6 @@
.row
.col-sm-12
- = form_tag namespace_project_group_links_path(@project.namespace, @project), class: 'js-requires-input', method: :post do
+ = form_tag project_group_links_path(@project), class: 'js-requires-input', method: :post do
.form-group
= label_tag :link_group_id, "Select a group to share with", class: "label-light"
= groups_select_tag(:link_group_id, data: { skip_groups: @skip_groups }, class: "input-clamp", required: true)
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
deleted file mode 100644
index 7902ddb1ae9..00000000000
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- @project_group_links.each do |group_links|
- - shared_group = group_links.group
- - shared_group_members = shared_group.members
- - shared_group_users_count = shared_group_members.size
- .panel.panel-default
- .panel-heading
- Shared with
- %strong= shared_group.name
- group, members with
- %strong= group_links.human_access
- role (#{shared_group_users_count})
- - if can?(current_user, :admin_group, shared_group)
- .panel-head-actions
- = link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
- %i.fa.fa-pencil-square-o
- Edit group members
- %ul.content-list
- = render partial: 'shared/members/member',
- collection: shared_group_members.order(access_level: :desc).limit(20),
- as: :member,
- locals: { show_controls: false, show_roles: false }
- - if shared_group_users_count > 20
- %li
- and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)}
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 7b1a26043e1..e71d58ec26d 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -5,11 +5,11 @@
%strong
#{@project.name}
%span.badge= @project_members.total_count
- = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ = form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
= render 'shared/members/sort_dropdown'
- %ul.content-list
+ %ul.content-list.members-list
= render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 42ce4f8001b..f6ca8d5a921 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -5,11 +5,11 @@
%p.light
Only project members will be imported. Group members will be skipped.
%hr
-= form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
+= form_tag apply_import_project_project_members_path(@project), method: 'post', class: 'form-horizontal' do
.form-group
= label_tag :source_project_id, "Project", class: 'control-label'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
= button_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", namespace_project_settings_members_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/_index.html.haml b/app/views/projects/project_members/index.html.haml
index fa99610c0be..9f7c5a315eb 100644
--- a/app/views/projects/project_members/_index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,6 +1,11 @@
+- page_title "Members"
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Settings", edit_project_path(@project))
+
.row.prepend-top-default
- .col-lg-4.settings-sidebar
- %h4.prepend-top-0
+ .col-lg-12
+ %h4
Project members
- if can?(current_user, :admin_project_member, @project)
%p
@@ -13,7 +18,6 @@
%i Masters
or
%i Owners
- .col-lg-8
.light
- if can?(current_user, :admin_project_member, @project)
%ul.nav-links.project-member-tabs{ role: 'tablist' }
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index cf0db943865..5377d745371 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,28 +1,4 @@
-.panel.panel-default.protected-branches-list
- - if @protected_branches.empty?
- .panel-heading
- %h3.panel-title
- Protected branch (#{@protected_branches.size})
- %p.settings-message.text-center
- There are currently no protected branches, protect a branch with the form above.
- - else
- - can_admin_project = can?(current_user, :admin_project, @project)
+- can_admin_project = can?(current_user, :admin_project, @project)
- %table.table.table-bordered
- %colgroup
- %col{ width: "25%" }
- %col{ width: "30%" }
- %col{ width: "25%" }
- %col{ width: "20%" }
- %thead
- %tr
- %th Protected branch (#{@protected_branches.size})
- %th Last commit
- %th Allowed to merge
- %th Allowed to push
- - if can_admin_project
- %th
- %tbody
- = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project}
-
- = paginate @protected_branches, theme: 'gitlab'
+= render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do
+ = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project}
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index 99bc2516366..98d56a3e5c5 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -1,41 +1,14 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
- .panel.panel-default
- .panel-heading
- %h3.panel-title
- Protect a branch
- .panel-body
- .form-horizontal
- = form_errors(@protected_branch)
- .form-group
- = f.label :name, class: 'col-md-2 text-right' do
- Branch:
- .col-md-10
- = render partial: "projects/protected_branches/dropdown", locals: { f: f }
- .help-block
- = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
- such as
- %code *-stable
- or
- %code production/*
- are supported
- .form-group
- %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' }
- Allowed to merge:
- .col-md-10
- .merge_access_levels-container
- = dropdown_tag('Select',
- options: { toggle_class: 'js-allowed-to-merge wide',
- dropdown_class: 'dropdown-menu-selectable capitalize-header',
- data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
- .form-group
- %label.col-md-2.text-right{ for: 'push_access_levels_attributes' }
- Allowed to push:
- .col-md-10
- .push_access_levels-container
- = dropdown_tag('Select',
- options: { toggle_class: 'js-allowed-to-push wide',
- dropdown_class: 'dropdown-menu-selectable capitalize-header',
- data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
+- content_for :merge_access_levels do
+ .merge_access_levels-container
+ = dropdown_tag('Select',
+ options: { toggle_class: 'js-allowed-to-merge wide',
+ dropdown_class: 'dropdown-menu-selectable capitalize-header',
+ data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
+- content_for :push_access_levels do
+ .push_access_levels-container
+ = dropdown_tag('Select',
+ options: { toggle_class: 'js-allowed-to-push wide',
+ dropdown_class: 'dropdown-menu-selectable capitalize-header',
+ data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
- .panel-footer
- = f.submit 'Protect', class: 'btn-create btn', disabled: true
+= render 'projects/protected_branches/shared/create_protected_branch'
diff --git a/app/views/projects/protected_branches/_index.html.haml b/app/views/projects/protected_branches/_index.html.haml
index 5d2422bdf54..2f30fe33a97 100644
--- a/app/views/projects/protected_branches/_index.html.haml
+++ b/app/views/projects/protected_branches/_index.html.haml
@@ -1,26 +1,10 @@
-- expanded = Rails.env.test?
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_branches')
-%section.settings
- .settings-header
- %h4
- Protected Branches
- %button.btn.js-settings-toggle
- = expanded ? 'Collapse' : 'Expand'
- %p
- Keep stable branches secure and force developers to use merge requests.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
- %p
- By default, protected branches are designed to:
- %ul
- %li prevent their creation, if not already created, from everybody except Masters
- %li prevent pushes from everybody except Masters
- %li prevent <strong>anyone</strong> from force pushing to the branch
- %li prevent <strong>anyone</strong> from deleting the branch
- %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
+- content_for :create_protected_branch do
+ = render 'projects/protected_branches/create_protected_branch'
- - if can? current_user, :admin_project, @project
- = render 'projects/protected_branches/create_protected_branch'
+- content_for :branches_list do
+ = render "projects/protected_branches/branches_list"
- = render "projects/protected_branches/branches_list"
+= render 'projects/protected_branches/shared/index'
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 0f80de94392..b12ae995ece 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,22 +1,2 @@
-%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
- %td
- %span.ref-name= protected_branch.name
-
- - if @project.root_ref?(protected_branch.name)
- %span.label.label-info.prepend-left-5 default
- %td
- - if protected_branch.wildcard?
- - matching_branches = protected_branch.matching(repository.branches)
- = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
- - else
- - if commit = protected_branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
- = time_ago_with_tooltip(commit.committed_date)
- - else
- (branch was removed from repository)
-
+= render layout: 'projects/protected_branches/shared/protected_branch', locals: { protected_branch: protected_branch } do
= render partial: 'projects/protected_branches/update_protected_branch', locals: { protected_branch: protected_branch }
-
- - if can_admin_project
- %td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
new file mode 100644
index 00000000000..2a0704bc7af
--- /dev/null
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -0,0 +1,30 @@
+.panel.panel-default.protected-branches-list.js-protected-branches-list
+ - if @protected_branches.empty?
+ .panel-heading
+ %h3.panel-title
+ Protected branch (#{@protected_branches.size})
+ %p.settings-message.text-center
+ There are currently no protected branches, protect a branch with the form above.
+ - else
+ %table.table.table-bordered
+ %colgroup
+ %col{ width: "20%" }
+ %col{ width: "20%" }
+ %col{ width: "20%" }
+ %col{ width: "20%" }
+ - if can_admin_project
+ %col
+ %thead
+ %tr
+ %th Protected branch (#{@protected_branches.size})
+ %th Last commit
+ %th Allowed to merge
+ %th Allowed to push
+ - if can_admin_project
+ %th
+ %tbody
+ %tr
+ %td.flash-container{ colspan: 5 }
+ = yield
+
+ = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
new file mode 100644
index 00000000000..9f0c4f3b3a8
--- /dev/null
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -0,0 +1,33 @@
+= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title
+ Protect a branch
+ .panel-body
+ .form-horizontal
+ = form_errors(@protected_branch)
+ .form-group
+ = f.label :name, class: 'col-md-2 text-right' do
+ Branch:
+ .col-md-10
+ = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f }
+ .help-block
+ = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
+ such as
+ %code *-stable
+ or
+ %code production/*
+ are supported
+ .form-group
+ %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' }
+ Allowed to merge:
+ .col-md-10
+ = yield :merge_access_levels
+ .form-group
+ %label.col-md-2.text-right{ for: 'push_access_levels_attributes' }
+ Allowed to push:
+ .col-md-10
+ = yield :push_access_levels
+
+ .panel-footer
+ = f.submit 'Protect', class: 'btn-create btn', disabled: true
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/shared/_dropdown.html.haml
index 6e9c473494e..6e9c473494e 100644
--- a/app/views/projects/protected_branches/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/shared/_dropdown.html.haml
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
new file mode 100644
index 00000000000..6a47cbdf724
--- /dev/null
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -0,0 +1,24 @@
+- expanded = Rails.env.test?
+
+%section.settings
+ .settings-header
+ %h4
+ Protected Branches
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Keep stable branches secure and force developers to use merge requests.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ %p
+ By default, protected branches are designed to:
+ %ul
+ %li prevent their creation, if not already created, from everybody except Masters
+ %li prevent pushes from everybody except Masters
+ %li prevent <strong>anyone</strong> from force pushing to the branch
+ %li prevent <strong>anyone</strong> from deleting the branch
+ %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
+
+ - if can? current_user, :admin_project, @project
+ = content_for :create_protected_branch
+
+ = content_for :branches_list
diff --git a/app/views/projects/protected_branches/_matching_branch.html.haml b/app/views/projects/protected_branches/shared/_matching_branch.html.haml
index 27896272733..98793d632e6 100644
--- a/app/views/projects/protected_branches/_matching_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_matching_branch.html.haml
@@ -6,5 +6,5 @@
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_branch.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
new file mode 100644
index 00000000000..10b81e42572
--- /dev/null
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -0,0 +1,24 @@
+- can_admin_project = can?(current_user, :admin_project, @project)
+
+%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch) } }
+ %td
+ %span.ref-name= protected_branch.name
+
+ - if @project.root_ref?(protected_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if protected_branch.wildcard?
+ - matching_branches = protected_branch.matching(repository.branches)
+ = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+ - else
+ - if commit = protected_branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = time_ago_with_tooltip(commit.committed_date)
+ - else
+ (branch was removed from repository)
+
+ = yield
+
+ - if can_admin_project
+ %td
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
index a806a0756ec..1012ceefe93 100644
--- a/app/views/projects/protected_branches/show.html.haml
+++ b/app/views/projects/protected_branches/show.html.haml
@@ -19,7 +19,7 @@
%th Last commit
%tbody
- @matching_refs.each do |matching_branch|
- = render partial: "matching_branch", object: matching_branch
+ = render partial: "projects/protected_branches/shared/matching_branch", object: matching_branch
- else
%p.settings-message.text-center
Couldn't find any matching branches.
diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml
index dd5b346d922..ea91e8af70e 100644
--- a/app/views/projects/protected_tags/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml
@@ -1,32 +1,8 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f|
- .panel.panel-default
- .panel-heading
- %h3.panel-title
- Protect a tag
- .panel-body
- .form-horizontal
- = form_errors(@protected_tag)
- .form-group
- = f.label :name, class: 'col-md-2 text-right' do
- Tag:
- .col-md-10.protected-tags-dropdown
- = render partial: "projects/protected_tags/dropdown", locals: { f: f }
- .help-block
- = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags')
- such as
- %code v*
- or
- %code *-release
- are supported
- .form-group
- %label.col-md-2.text-right{ for: 'create_access_levels_attributes' }
- Allowed to create:
- .col-md-10
- .create_access_levels-container
- = dropdown_tag('Select',
- options: { toggle_class: 'js-allowed-to-create wide',
- dropdown_class: 'dropdown-menu-selectable',
- data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }})
+- content_for :create_access_levels do
+ .create_access_levels-container
+ = dropdown_tag('Select',
+ options: { toggle_class: 'js-allowed-to-create wide',
+ dropdown_class: 'dropdown-menu-selectable',
+ data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }})
- .panel-footer
- = f.submit 'Protect', class: 'btn-create btn', disabled: true
+= render 'projects/protected_tags/shared/create_protected_tag'
diff --git a/app/views/projects/protected_tags/_index.html.haml b/app/views/projects/protected_tags/_index.html.haml
index 8250f692a69..955220562a6 100644
--- a/app/views/projects/protected_tags/_index.html.haml
+++ b/app/views/projects/protected_tags/_index.html.haml
@@ -1,26 +1,10 @@
-- expanded = Rails.env.test?
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('protected_tags')
-%section.settings
- .settings-header
- %h4
- Protected Tags
- %button.btn.js-settings-toggle
- = expanded ? 'Collapse' : 'Expand'
- %p
- Limit access to creating and updating tags.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
- %p
- By default, protected tags are designed to:
- %ul
- %li Prevent tag creation by everybody except Masters
- %li Prevent <strong>anyone</strong> from updating the tag
- %li Prevent <strong>anyone</strong> from deleting the tag
+- content_for :create_protected_tag do
+ = render 'projects/protected_tags/create_protected_tag'
- %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}.
+- content_for :tag_list do
+ = render "projects/protected_tags/tags_list"
- - if can? current_user, :admin_project, @project
- = render 'projects/protected_tags/create_protected_tag'
-
- = render "projects/protected_tags/tags_list"
+= render 'projects/protected_tags/shared/index'
diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml
index f11ce0483a9..da1f97c8d6a 100644
--- a/app/views/projects/protected_tags/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/_protected_tag.html.haml
@@ -1,22 +1,2 @@
-%tr.js-protected-tag-edit-form{ data: { url: namespace_project_protected_tag_path(@project.namespace, @project, protected_tag) } }
- %td
- %span.ref-name= protected_tag.name
-
- - if @project.root_ref?(protected_tag.name)
- %span.label.label-info.prepend-left-5 default
- %td
- - if protected_tag.wildcard?
- - matching_tags = protected_tag.matching(repository.tags)
- = link_to pluralize(matching_tags.count, "matching tag"), namespace_project_protected_tag_path(@project.namespace, @project, protected_tag)
- - else
- - if commit = protected_tag.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
- = time_ago_with_tooltip(commit.committed_date)
- - else
- (tag was removed from repository)
-
+= render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do
= render partial: 'projects/protected_tags/update_protected_tag', locals: { protected_tag: protected_tag }
-
- - if can_admin_project
- %td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/protected_tags/_tags_list.html.haml b/app/views/projects/protected_tags/_tags_list.html.haml
index d432a5c9113..a6b18cc9f8f 100644
--- a/app/views/projects/protected_tags/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/_tags_list.html.haml
@@ -1,30 +1,4 @@
-.panel.panel-default.protected-tags-list
- - if @protected_tags.empty?
- .panel-heading
- %h3.panel-title
- Protected tag (#{@protected_tags.size})
- %p.settings-message.text-center
- There are currently no protected tags, protect a tag with the form above.
- - else
- - can_admin_project = can?(current_user, :admin_project, @project)
+- can_admin_project = can?(current_user, :admin_project, @project)
- %table.table.table-bordered
- %colgroup
- %col{ width: "25%" }
- %col{ width: "25%" }
- %col{ width: "50%" }
- - if can_admin_project
- %col
- %thead
- %tr
- %th Protected tag (#{@protected_tags.size})
- %th Last commit
- %th Allowed to create
- - if can_admin_project
- %th
- %tbody
- %tr
- %td.flash-container{ colspan: 4 }
- = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project}
-
- = paginate @protected_tags, theme: 'gitlab'
+= render layout: 'projects/protected_tags/shared/tags_list' do
+ = render partial: 'projects/protected_tags/protected_tag', collection: @protected_tags, locals: { can_admin_project: can_admin_project}
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
new file mode 100644
index 00000000000..5a53c704fcb
--- /dev/null
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -0,0 +1,29 @@
+= form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title
+ Protect a tag
+ .panel-body
+ .form-horizontal
+ = form_errors(@protected_tag)
+ .form-group
+ = f.label :name, class: 'col-md-2 text-right' do
+ Tag:
+ .col-md-10.protected-tags-dropdown
+ = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f }
+ .help-block
+ = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags')
+ such as
+ %code v*
+ or
+ %code *-release
+ are supported
+ .form-group
+ %label.col-md-2.text-right{ for: 'create_access_levels_attributes' }
+ Allowed to create:
+ .col-md-10
+ .create_access_levels-container
+ = yield :create_access_levels
+
+ .panel-footer
+ = f.submit 'Protect', class: 'btn-create btn', disabled: true
diff --git a/app/views/projects/protected_tags/_dropdown.html.haml b/app/views/projects/protected_tags/shared/_dropdown.html.haml
index 9b6923210f7..9b6923210f7 100644
--- a/app/views/projects/protected_tags/_dropdown.html.haml
+++ b/app/views/projects/protected_tags/shared/_dropdown.html.haml
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
new file mode 100644
index 00000000000..c07bd454ff6
--- /dev/null
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -0,0 +1,24 @@
+- expanded = Rails.env.test?
+
+%section.settings
+ .settings-header
+ %h4
+ Protected Tags
+ %button.btn.js-settings-toggle
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ Limit access to creating and updating tags.
+ .settings-content.no-animate{ class: ('expanded' if expanded) }
+ %p
+ By default, protected tags are designed to:
+ %ul
+ %li Prevent tag creation by everybody except Masters
+ %li Prevent <strong>anyone</strong> from updating the tag
+ %li Prevent <strong>anyone</strong> from deleting the tag
+
+ %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}.
+
+ - if can? current_user, :admin_project, @project
+ = yield :create_protected_tag
+
+ = yield :tag_list
diff --git a/app/views/projects/protected_tags/_matching_tag.html.haml b/app/views/projects/protected_tags/shared/_matching_tag.html.haml
index f17353df122..05f102d1ca3 100644
--- a/app/views/projects/protected_tags/_matching_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_matching_tag.html.haml
@@ -6,5 +6,5 @@
%span.label.label-info.prepend-left-5 default
%td
- commit = @project.commit(matching_tag.name)
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit-sha')
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
= time_ago_with_tooltip(commit.committed_date)
diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
new file mode 100644
index 00000000000..c778f7b9781
--- /dev/null
+++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
@@ -0,0 +1,22 @@
+%tr.js-protected-tag-edit-form{ data: { url: project_protected_tag_path(@project, protected_tag) } }
+ %td
+ %span.ref-name= protected_tag.name
+
+ - if @project.root_ref?(protected_tag.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if protected_tag.wildcard?
+ - matching_tags = protected_tag.matching(repository.tags)
+ = link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag)
+ - else
+ - if commit = protected_tag.commit
+ = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha')
+ = time_ago_with_tooltip(commit.committed_date)
+ - else
+ (tag was removed from repository)
+
+ = yield
+
+ - if can? current_user, :admin_project, @project
+ %td
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_tag], data: { confirm: 'Tag will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
new file mode 100644
index 00000000000..3f42ae58438
--- /dev/null
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -0,0 +1,30 @@
+.panel.panel-default.protected-tags-list.js-protected-tags-list
+ - if @protected_tags.empty?
+ .panel-heading
+ %h3.panel-title
+ Protected tag (#{@protected_tags.size})
+ %p.settings-message.text-center
+ There are currently no protected tags, protect a tag with the form above.
+ - else
+ - can_admin_project = can?(current_user, :admin_project, @project)
+
+ %table.table.table-bordered
+ %colgroup
+ %col{ width: "25%" }
+ %col{ width: "25%" }
+ %col{ width: "50%" }
+ - if can_admin_project
+ %col
+ %thead
+ %tr
+ %th Protected tag (#{@protected_tags.size})
+ %th Last commit
+ %th Allowed to create
+ - if can_admin_project
+ %th
+ %tbody
+ %tr
+ %td.flash-container{ colspan: 4 }
+ = yield
+
+ = paginate @protected_tags, theme: 'gitlab'
diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml
index 16fc02fe9f4..86629f1753b 100644
--- a/app/views/projects/protected_tags/show.html.haml
+++ b/app/views/projects/protected_tags/show.html.haml
@@ -19,7 +19,7 @@
%th Last commit
%tbody
- @matching_refs.each do |matching_tag|
- = render partial: "matching_tag", object: matching_tag
+ = render partial: "projects/protected_tags/shared/matching_tag", object: matching_tag
- else
%p.settings-message.text-center
Couldn't find any matching tags.
diff --git a/app/views/projects/registry/repositories/_image.html.haml b/app/views/projects/registry/repositories/_image.html.haml
index dcdc432b654..a0535edafc3 100644
--- a/app/views/projects/registry/repositories/_image.html.haml
+++ b/app/views/projects/registry/repositories/_image.html.haml
@@ -8,7 +8,7 @@
- if can?(current_user, :update_container_image, @project)
.controls.hidden-xs.pull-right
- = link_to namespace_project_container_registry_path(@project.namespace, @project, image),
+ = link_to project_container_registry_path(@project, image),
class: 'btn btn-remove has-tooltip',
title: 'Remove repository',
data: { confirm: 'Are you sure?' },
@@ -30,4 +30,3 @@
= render partial: 'tag', collection: image.tags
- else
.nothing-here-block No tags in Container Registry for this container image.
-
diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml
index 378a23f07e6..0b082a2137f 100644
--- a/app/views/projects/registry/repositories/_tag.html.haml
+++ b/app/views/projects/registry/repositories/_tag.html.haml
@@ -25,7 +25,7 @@
- if can?(current_user, :update_container_image, @project)
%td.content
.controls.hidden-xs.pull-right
- = link_to namespace_project_registry_repository_tag_path(@project.namespace, @project, tag.repository, tag.name),
+ = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name),
method: :delete,
class: 'btn btn-remove has-tooltip',
title: 'Remove tag',
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 93ee9382a6e..0a5a38a3694 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -10,11 +10,11 @@
%strong= @tag.name
- = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
+ = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
= render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'shared/notes/hints'
.error-alert
.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
- = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
+ = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/remove_fork.js.haml b/app/views/projects/remove_fork.js.haml
index 17b9fecfeb1..6d083c5c516 100644
--- a/app/views/projects/remove_fork.js.haml
+++ b/app/views/projects/remove_fork.js.haml
@@ -1,2 +1,2 @@
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml
index d9c39fb87b7..170f9e259df 100644
--- a/app/views/projects/repositories/_feed.html.haml
+++ b/app/views/projects/repositories/_feed.html.haml
@@ -1,7 +1,7 @@
- commit = update
%tr
%td
- = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do
+ = link_to project_commits_path(@project, commit.head.name) do
%strong
= commit.head.name
- if @project.root_ref?(commit.head.name)
@@ -9,7 +9,7 @@
%td
%div
- = link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
+ = link_to project_commits_path(@project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author)
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 674f87e8220..abc97bcdff5 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -9,7 +9,7 @@
= icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
%small
- = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
+ = link_to edit_project_runner_path(@project, runner) do
%i.fa.fa-edit.btn
- else
%span.commit-sha
@@ -21,7 +21,7 @@
= link_to 'Remove Runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
- runner_project = @project.runner_projects.find_by(runner_id: runner)
- = link_to 'Disable for this project', namespace_project_runner_project_path(@project.namespace, @project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
+ = link_to 'Disable for this project', project_runner_project_path(@project, runner_project), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- elsif runner.specific?
= form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f|
= f.hidden_field :runner_id, value: runner.id
diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml
index 0671dd66e78..a4e820628f3 100644
--- a/app/views/projects/runners/_shared_runners.html.haml
+++ b/app/views/projects/runners/_shared_runners.html.haml
@@ -9,10 +9,10 @@
on GitLab.com).
%hr
- if @project.shared_runners_enabled?
- = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do
+ = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-warning', method: :post do
Disable shared Runners
- else
- = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-success', method: :post do
+ = link_to toggle_shared_runners_project_runners_path(@project), class: 'btn btn-success', method: :post do
Enable shared Runners
&nbsp; for this project
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index f8835454140..28ccbf7eb15 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -1,21 +1,8 @@
%h3 Specific Runners
-.bs-callout.help-callout
- %h4 How to setup a specific Runner for a new project
-
- %ol
- %li
- Install a Runner compatible with GitLab CI
- (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
- %li
- Specify the following URL during the Runner setup:
- %code= root_url(only_path: false)
- %li
- Use the following registration token during setup:
- %code= @project.runners_token
- %li
- Start the Runner!
-
+= render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: @project.runners_token,
+ type: 'specific' }
- if @project_runners.any?
%h4.underlined-title Runners activated for this project
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 6dffc026392..b842fd57cf3 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -9,20 +9,21 @@
%p= @service.description
.col-lg-9
- = form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_namespace_project_service_path } }) do |form|
+ = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- .footer-block.row-content-block
- %button.btn.btn-save{ type: 'submit' }
- = icon('spinner spin', class: 'hidden js-btn-spinner')
- %span.js-btn-label
- Save changes
- &nbsp;
- - if @service.valid? && @service.activated?
- - unless @service.can_test?
- - disabled_class = 'disabled'
- - disabled_title = @service.disabled_title
+ - if @service.editable?
+ .footer-block.row-content-block
+ %button.btn.btn-save{ type: 'submit' }
+ = icon('spinner spin', class: 'hidden js-btn-spinner')
+ %span.js-btn-label
+ Save changes
+ &nbsp;
+ - if @service.valid? && @service.activated?
+ - unless @service.can_test?
+ - disabled_class = 'disabled'
+ - disabled_title = @service.disabled_title
- = link_to 'Cancel', namespace_project_settings_integrations_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 997b702da33..915c6b22162 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -21,7 +21,7 @@
%td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") }
= boolean_to_icon service.activated?
%td
- = link_to edit_namespace_project_service_path(@project.namespace, @project, service.to_param) do
+ = link_to edit_project_service_path(@project, service.to_param) do
%strong= service.title
%td.hidden-xs
= service.description
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index 0f1a76a104a..8056217bb1e 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -1,3 +1,8 @@
+- breadcrumb_title "Integrations"
- page_title @service.title, "Services"
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Settings", edit_project_path(@project))
+
= render "projects/settings/head"
= render 'form'
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index fcc91be11cd..44c0b7a90dc 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -2,6 +2,6 @@
- unless @service.activated?
.row
.col-sm-9.col-sm-offset-3
- = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
+ = link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15)
Add to Mattermost
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index c4ac384ca1a..d8e11500964 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -8,10 +8,10 @@
%p
Metrics are automatically configured and monitored
based on a library of metrics from popular exporters.
- = link_to 'More information', '#'
+ = link_to 'More information', help_page_path('user/project/integrations/prometheus')
.col-lg-9
- .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{namespace_project_prometheus_active_metrics_path(@project.namespace, @project, :json)}" } }
+ .panel.panel-default.js-panel-monitored-metrics{ data: { "active-metrics" => "#{project_prometheus_active_metrics_path(@project, :json)}" } }
.panel-heading
%h3.panel-title
Monitored
@@ -41,5 +41,5 @@
%code
$CI_ENVIRONMENT_SLUG
to exporter&rsquo;s queries.
- = link_to 'More information', '#'
+ = link_to 'More information', help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
%ul.list-unstyled.metrics-list.js-missing-var-metrics-list
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index 00bd563999f..15ba09b10ba 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -9,26 +9,22 @@
= link_to edit_project_path(@project), title: 'General' do
%span
General
- = nav_link(controller: :members) do
- = link_to project_settings_members_path(@project), title: 'Members' do
- %span
- Members
- if can_edit
= nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
Integrations
= nav_link(controller: :repository) do
- = link_to namespace_project_settings_repository_path(@project.namespace, @project), title: 'Repository' do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
%span
Repository
- if @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
- = link_to namespace_project_settings_ci_cd_path(@project.namespace, @project), title: 'Pipelines' do
+ = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
%span
Pipelines
- if Gitlab.config.pages.enabled
= nav_link(controller: :pages) do
- = link_to namespace_project_pages_path(@project.namespace, @project), title: 'Pages' do
+ = link_to project_pages_path(@project), title: 'Pages' do
%span
Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 00ccc3ec41e..0c4130857da 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -1,8 +1,12 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title "Pipelines"
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Settings", edit_project_path(@project))
+
= render "projects/settings/head"
= render 'projects/runners/index'
-= render 'projects/variables/index'
+= render 'ci/variables/index'
= render 'projects/triggers/index'
= render 'projects/pipelines_settings/show'
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index a6640592dba..d5792e95f5a 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -3,14 +3,14 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- - %w(push_events tag_push_events issues_events confidential_issues_events note_events merge_requests_events job_events pipeline_events wiki_page_events).each do |trigger|
- - if hook.send(trigger)
- %span.label.label-gray.deploy-project-label= trigger.titleize
+ - ProjectHook::TRIGGERS.each_value do |event|
+ - if hook.public_send(event)
+ %span.label.label-gray.deploy-project-label= event.to_s.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
- SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"}
- = link_to "Edit", edit_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
- = link_to "Test", test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm"
- = link_to namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent" do
+ SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
+ = link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-small'
+ = link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index 1d1d0849289..149da96d3f6 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,5 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title 'Integrations'
+- if show_new_nav?
+ - add_to_breadcrumbs("Settings", edit_project_path(@project))
= render "projects/settings/head"
= render 'projects/hooks/index'
= render 'projects/services/index'
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 40ea02abce9..cb37f3c7580 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -1,11 +1,19 @@
- page_title "Repository"
- @content_class = "limit-container-width" unless fluid_layout
+
+- if show_new_nav?
+ - add_to_breadcrumbs("Settings", edit_project_path(@project))
+
= render "projects/settings/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('deploy_keys')
+-# Protected branches & tags use a lot of nested partials.
+-# The shared parts of the views can be found in the `shared` directory.
+-# Those are used throughout the actual views. These `shared` views are then
+-# reused in EE.
= render "projects/protected_branches/index"
= render "projects/protected_tags/index"
= render @deploy_keys
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index ed34f5c0520..39f8cb9a0e0 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -1,7 +1,7 @@
xml.title "#{@project.name} activity"
-xml.link href: namespace_project_url(@project.namespace, @project, rss_url_options), rel: "self", type: "application/atom+xml"
-xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
-xml.id namespace_project_url(@project.namespace, @project)
+xml.link href: project_url(@project, rss_url_options), rel: "self", type: "application/atom+xml"
+xml.link href: project_url(@project), rel: "alternate", type: "text/html"
+xml.id project_url(@project)
xml.updated @events[0].updated_at.xmlschema if @events[0]
xml << render(@events) if @events.any?
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 152e50a79bb..a9b39cedb1d 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,12 +1,11 @@
- @no_container = true
+- breadcrumb_title "Project"
+- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_path(@project.namespace, @project, rss_url_options), title: "#{@project.name} activity")
+ = auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
-= content_for :flash_message do
- - if current_user && can?(current_user, :download_code, @project)
- = render 'shared/no_ssh'
- = render 'shared/no_password'
+= render partial: 'flash_messages', locals: { project: @project }
= render "projects/head"
= render "projects/last_push"
@@ -16,16 +15,16 @@
%nav.project-stats{ class: container_class }
%ul.nav
%li
- = link_to project_files_path(@project) do
+ = link_to project_tree_path(@project) do
#{_('Files')} (#{storage_counter(@project.statistics.total_repository_size)})
%li
- = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
+ = link_to project_commits_path(@project, current_ref) do
#{n_('Commit', 'Commits', @project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)})
%li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = link_to project_branches_path(@project) do
#{n_('Branch', 'Branches', @repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = link_to project_tags_path(@project) do
#{n_('Tag', 'Tags', @repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 34ee4ff1937..f09871c7fcc 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -2,16 +2,16 @@
.hidden-xs
- if can?(current_user, :update_project_snippet, @snippet)
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do
+ = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
Edit
- if can?(current_user, :update_project_snippet, @snippet)
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
+ = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do
Delete
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
+ = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam_by?(current_user)
- = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -21,16 +21,16 @@
%ul
- if can?(current_user, :create_project_snippet, @project)
%li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New snippet" do
+ = link_to new_project_snippet_path(@project), title: "New snippet" do
New snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_project_snippet, @snippet)
%li
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ = link_to edit_project_snippet_path(@project, @snippet) do
Edit
- if @snippet.submittable_as_spam_by?(current_user)
%li
- = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
+ = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 24b92094b7d..d41cc8e0425 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
Edit Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
+= render "shared/snippets/form", url: project_snippet_path(@project, @snippet)
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 84e05cd6d88..ccc5fe80755 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,19 +1,16 @@
- page_title "Snippets"
+- if show_new_nav? && can?(current_user, :create_project_snippet, @project)
+ - content_for :breadcrumbs_extra do
+ = link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet"
+
- if current_user
.top-area
- include_private = @project.team.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
- .nav-controls.hidden-xs
+ .nav-controls{ class: ("visible-xs" if show_new_nav?) }
- if can?(current_user, :create_project_snippet, @project)
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do
- New snippet
-
-- if can?(current_user, :create_project_snippet, @project)
- .visible-xs
- &nbsp;
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do
- New snippet
+ = link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet"
= render 'snippets/snippets'
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index cfed3a79bc5..d3e6b456f48 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
+= render "shared/snippets/form", url: project_snippets_path(@project, @snippet)
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 44cb734d7b9..468ab922542 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -2,7 +2,7 @@
- release = @releases.find { |release| release.tag == tag.name }
%li.flex-row
.row-main-content.str-truncated
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'item-title ref-name' do
+ = link_to project_tag_path(@project, tag.name), class: 'item-title ref-name' do
= icon('tag')
= tag.name
@@ -29,9 +29,9 @@
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
+ = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do
= icon("pencil")
- if can?(current_user, :admin_project, @project)
- = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
+ = link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 56656ea3d86..00000e0667c 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -3,6 +3,9 @@
- page_title "Tags"
= render "projects/commits/head"
+- if show_new_nav?
+ - add_to_breadcrumbs("Repository", project_tree_path(@project))
+
.flex-list{ class: container_class }
.top-area.adjust
.nav-text.row-main-content
@@ -24,7 +27,7 @@
%li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :push_code, @project)
- = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do
+ = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do
New tag
.tags
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 52af295bddd..f1bbaf40387 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -39,7 +39,7 @@
.help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
- = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
+ = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
:javascript
window.gl = window.gl || { };
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 2b81ce4b9fa..d02cd70f4c3 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -19,17 +19,17 @@
.nav-controls.controls-flex
- if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
+ = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do
= icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
+ = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do
= icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
+ = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do
= icon('history')
.btn-container.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :admin_project, @project)
.btn-container.controls-item-full
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
- if @tag.message.present?
diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml
index 17b9fecfeb1..6d083c5c516 100644
--- a/app/views/projects/transfer.js.haml
+++ b/app/views/projects/transfer.js.haml
@@ -1,2 +1,2 @@
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml
index 425b460eb09..fd8175e1e01 100644
--- a/app/views/projects/tree/_blob_item.html.haml
+++ b/app/views/projects/tree/_blob_item.html.haml
@@ -2,7 +2,7 @@
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
+ = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do
%span.str-truncated= file_name
%td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right
diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml
index f9147815427..4579a912f39 100644
--- a/app/views/projects/tree/_readme.html.haml
+++ b/app/views/projects/tree/_readme.html.haml
@@ -2,8 +2,8 @@
%article.file-holder.readme-holder{ class: ("limited-width-container" unless fluid_layout) }
.js-file-title.file-title
= blob_icon readme.mode, readme.name
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do
+ = link_to project_blob_path(@project, tree_join(@ref, readme.path)) do
%strong
= readme.name
- = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
+ = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: project_blob_path(@project, tree_join(@ref, readme.path), viewer: :rich, format: :json)
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 84da16b6bb1..f3d4706809f 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
%span.str-truncated
- = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
+ = link_to_gfm commit.full_title, project_commit_path(@project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 7854e1305db..6560bd5ab3f 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -10,7 +10,7 @@
- if @path.present?
%tr.tree-item
%td.tree-item-file-name
- = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10'
+ = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10'
%td
%td.hidden-xs
@@ -20,7 +20,7 @@
= render "projects/tree/readme", readme: tree.readme
- if can_edit_tree?
- = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
:javascript
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 00da76349da..858418ff8df 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -4,11 +4,11 @@
%ul.breadcrumb.repo-breadcrumb
%li
- = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = link_to project_tree_path(@project, @ref) do
= @project.path
- path_breadcrumbs do |title, path|
%li
- = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, tree_join(@ref, path))
+ = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- if current_user
%li
@@ -23,7 +23,7 @@
%ul.dropdown-menu
- if can_edit_tree?
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
+ = link_to project_new_blob_path(@project, @id) do
= icon('pencil fw')
#{ _('New file') }
%li
@@ -36,10 +36,10 @@
#{ _('New directory') }
- elsif can?(current_user, :fork_project, @project)
%li
- - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
+ - continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('pencil fw')
@@ -48,7 +48,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to upload a file again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('file fw')
@@ -57,7 +57,7 @@
- continue_params = { to: request.fullpath,
notice: edit_in_new_fork_notice + " Try to create a new directory again.",
notice_now: edit_in_new_fork_notice_now }
- - fork_path = namespace_project_forks_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
continue: continue_params)
= link_to fork_path, method: :post do
= icon('folder fw')
@@ -65,17 +65,17 @@
%li.divider
%li
- = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = link_to new_project_branch_path(@project) do
= icon('code-fork fw')
#{ _('New branch') }
%li
- = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = link_to new_project_tag_path(@project) do
= icon('tags fw')
#{ _('New tag') }
.tree-controls
= render 'projects/find_file_link'
- = link_to s_('Commits|History'), namespace_project_commits_path(@project.namespace, @project, @id), class: 'btn'
+ = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml
index 15c9536133c..0c9c8750f2c 100644
--- a/app/views/projects/tree/_tree_item.html.haml
+++ b/app/views/projects/tree/_tree_item.html.haml
@@ -2,7 +2,7 @@
%td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(tree_item)
- = link_to namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)), title: path do
+ = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do
%span.str-truncated= path
%td.hidden-xs.tree-commit
%td.tree-time-ago.text-right
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 96a08f9f8be..c8587245f88 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,11 +1,12 @@
- @no_container = true
+- breadcrumb_title _("Repository")
+- @content_class = "limit-container-width" unless fluid_layout
- page_title @path.presence || _("Files"), @ref
= content_for :meta_tags do
- = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
+ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render "projects/commits/head"
-= render 'projects/last_push'
-
-%div{ class: container_class }
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+ = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 9b5f63ae81a..6249c32b7cc 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -33,10 +33,10 @@
- take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
- revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
- if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
- = link_to 'Take ownership', take_ownership_namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
+ = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
- if can?(current_user, :admin_trigger, trigger)
- = link_to edit_namespace_project_trigger_path(@project.namespace, @project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
+ = link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
%i.fa.fa-pencil
- if can?(current_user, :manage_trigger, trigger)
- = link_to namespace_project_trigger_path(@project.namespace, @project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
+ = link_to project_trigger_path(@project, trigger), data: { confirm: revoke_trigger_confirmation }, method: :delete, title: "Revoke", class: "btn btn-default btn-warning btn-sm btn-trigger-revoke" do
%i.fa.fa-trash
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index dcf1f767bf7..2c05ebe52ae 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -1,6 +1,6 @@
- if @project.valid?
:plain
- location.href = "#{edit_namespace_project_path(@project.namespace, @project)}";
+ location.href = "#{edit_project_path(@project)}";
- else
:plain
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
diff --git a/app/views/projects/variables/show.html.haml b/app/views/projects/variables/show.html.haml
index 297a53ca98c..df533952b76 100644
--- a/app/views/projects/variables/show.html.haml
+++ b/app/views/projects/variables/show.html.haml
@@ -1,9 +1 @@
-- page_title "Variables"
-
-.row.prepend-top-default.append-bottom-default
- .col-lg-3
- = render "content"
- .col-lg-9
- %h5.prepend-top-0
- Update variable
- = render "form", btn_text: "Save variable"
+= render 'ci/variables/show'
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index c10b3004bc3..fc6b7a33943 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -12,7 +12,7 @@
.form-group
.col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-12
- = render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do
+ = render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'shared/notes/hints'
@@ -36,8 +36,8 @@
- if @page && @page.persisted?
= f.submit 'Save changes', class: "btn-save btn"
.pull-right
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped"
+ = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel btn-grouped"
- else
= f.submit 'Create page', class: "btn-create btn"
.pull-right
- = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
+ = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel"
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 6a578dbf640..3bbd8042c3a 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -2,8 +2,8 @@
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
- if can?(current_user, :create_wiki, @project) && @page.latest?
- = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn js-wiki-edit" do
+ = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
Edit
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 1e553940593..13dd8461433 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -9,7 +9,7 @@
.form-group
= label_tag :new_wiki_path do
%span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
%span.new-wiki-page-slug-tip
= icon('lightbulb-o')
Tip: You can specify the full path for the new file.
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index 6298cf6c8da..7c2f562d422 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -1,5 +1,5 @@
%li
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ = link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
%small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index c2f9e65015d..62873d3aa66 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -3,7 +3,7 @@
%a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
- - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project)
+ - git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do
= icon('cloud-download')
@@ -15,7 +15,7 @@
= render @sidebar_wiki_entries, context: 'sidebar'
.block
- = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do
+ = link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
More Pages
= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_sidebar_wiki_page.html.haml b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
index 0a61d90177b..2423ac6abce 100644
--- a/app/views/projects/wikis/_sidebar_wiki_page.html.haml
+++ b/app/views/projects/wikis/_sidebar_wiki_page.html.haml
@@ -1,3 +1,3 @@
%li{ class: active_when(params[:id] == wiki_page.slug) }
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do
+ = link_to project_wiki_path(@project, wiki_page) do
= wiki_page.title.capitalize
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index fbe192a40ec..df0ec14eb3b 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -8,7 +8,7 @@
.nav-text
%h2.wiki-page-title
- if @page.persisted?
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ = link_to @page.title.capitalize, project_wiki_path(@project, @page)
- else
= @page.title.capitalize
%span.light
@@ -23,10 +23,10 @@
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
New page
- if @page.persisted?
- = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn" do
Page history
- if can?(current_user, :admin_wiki, @project)
- = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
+ = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete
= render 'form'
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 0e47e2a5fa3..306feeff259 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -6,7 +6,7 @@
.nav-text
%h2.wiki-page-title
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ = link_to @page.title.capitalize, project_wiki_path(@project, @page)
%span.light
&middot;
History
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 5fba2b1a5ae..dece1fad0bb 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -9,7 +9,7 @@
Wiki Pages
.nav-controls
- = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do
+ = link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
Clone repository
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index f003ff6b63f..9dadd685ea2 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,4 +1,5 @@
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
+- breadcrumb_title "Wiki"
- page_title @page.title.capitalize, "Wiki"
.wiki-page-header.has-sidebar-toggle
@@ -22,7 +23,7 @@
- if @page.historical?
.warning_message
This is an old version of this page.
- You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
+ You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", project_wiki_history_path(@project, @page)}.
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 7f1f807e2e7..de473c23d66 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -3,7 +3,7 @@
.file-holder
.js-file-title.file-title
- ref = @search_results.repository_ref
- - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
+ - blob_link = project_blob_path(@project, tree_join(ref, file_name))
= link_to blob_link do
%i.fa.fa-file
%strong
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 026f404ce07..aef825691e0 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -11,7 +11,7 @@
%small.pull-right.cgray
- if snippet_title.project_id?
- = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project)
+ = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project)
.snippet-info
= snippet_title.to_reference
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index d87f9df2677..16a0e432d62 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -2,7 +2,7 @@
.blob-result
.file-holder
.js-file-title.file-title
- = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.basename) do
+ = link_to project_wiki_path(@project, wiki_blob.basename) do
%i.fa.fa-file
%strong
= wiki_blob.basename
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 215dbb3909e..499697f2777 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,3 +1,5 @@
+- @hide_top_links = true
+- breadcrumb_title "Search"
- page_title @search_term
.prepend-top-default
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index de0281e97c6..2f776a17f45 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -23,7 +23,7 @@
- if can_subscribe_to_label_in_different_levels?(label)
%a.js-unsubscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path } }
%span Unsubscribe
- %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
%span Subscribe at project level
%a.js-subscribe-button.label-subscribe-button{ role: 'button', href: '#', class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
%span Subscribe at group level
@@ -56,7 +56,7 @@
= icon('chevron-down')
%ul.dropdown-menu
%li
- %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label) } }
+ %a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_project_label_path(@project, label) } }
Project level
%a.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { url: toggle_subscription_group_label_path(label.group, label) } }
Group level
@@ -66,7 +66,7 @@
= icon('spinner spin', class: 'label-subscribe-button-loading')
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
- = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
+ = link_to promote_project_label_path(label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
%span.sr-only Promote to Group
= icon('level-up')
- if can?(current_user, :admin_label, label)
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index 7b599dff0e3..7f58298c60f 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -2,7 +2,7 @@
- if can?(current_user, :admin_label, @project)
.draggable-handler
= icon('bars')
- .js-toggle-priority.toggle-priority{ data: { url: remove_priority_namespace_project_label_path(@project.namespace, @project, label),
+ .js-toggle-priority.toggle-priority{ data: { url: remove_priority_project_label_path(@project, label),
dom_id: dom_id(label), type: label.type } }
%button.add-priority.btn.has-tooltip{ title: 'Prioritize', type: 'button', :'data-placement' => 'top' }
= icon('star-o')
diff --git a/app/views/shared/_logo_type.svg b/app/views/shared/_logo_type.svg
new file mode 100644
index 00000000000..cb07e2634a9
--- /dev/null
+++ b/app/views/shared/_logo_type.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 617 169"><path d="M315.26 2.97h-21.8l.1 162.5h88.3v-20.1h-66.5l-.1-142.4M465.89 136.95c-5.5 5.7-14.6 11.4-27 11.4-16.6 0-23.3-8.2-23.3-18.9 0-16.1 11.2-23.8 35-23.8 4.5 0 11.7.5 15.4 1.2v30.1h-.1m-22.6-98.5c-17.6 0-33.8 6.2-46.4 16.7l7.7 13.4c8.9-5.2 19.8-10.4 35.5-10.4 17.9 0 25.8 9.2 25.8 24.6v7.9c-3.5-.7-10.7-1.2-15.1-1.2-38.2 0-57.6 13.4-57.6 41.4 0 25.1 15.4 37.7 38.7 37.7 15.7 0 30.8-7.2 36-18.9l4 15.9h15.4v-83.2c-.1-26.3-11.5-43.9-44-43.9M557.63 149.1c-8.2 0-15.4-1-20.8-3.5V70.5c7.4-6.2 16.6-10.7 28.3-10.7 21.1 0 29.2 14.9 29.2 39 0 34.2-13.1 50.3-36.7 50.3m9.2-110.6c-19.5 0-30 13.3-30 13.3v-21l-.1-27.8h-21.3l.1 158.5c10.7 4.5 25.3 6.9 41.2 6.9 40.7 0 60.3-26 60.3-70.9-.1-35.5-18.2-59-50.2-59M77.9 20.6c19.3 0 31.8 6.4 39.9 12.9l9.4-16.3C114.5 6 97.3 0 78.9 0 32.5 0 0 28.3 0 85.4c0 59.8 35.1 83.1 75.2 83.1 20.1 0 37.2-4.7 48.4-9.4l-.5-63.9V75.1H63.6v20.1h38l.5 48.5c-5 2.5-13.6 4.5-25.3 4.5-32.2 0-53.8-20.3-53.8-63-.1-43.5 22.2-64.6 54.9-64.6M231.43 2.95h-21.3l.1 27.3v94.3c0 26.3 11.4 43.9 43.9 43.9 4.5 0 8.9-.4 13.1-1.2v-19.1c-3.1.5-6.4.7-9.9.7-17.9 0-25.8-9.2-25.8-24.6v-65h35.7v-17.8h-35.7l-.1-38.5M155.96 165.47h21.3v-124h-21.3v124M155.96 24.37h21.3V3.07h-21.3v21.3"/></svg>
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index aa93572bf94..dff847159d3 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -6,7 +6,7 @@
- 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_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } }
+ %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_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
= custom_icon(icon_status)
= icon('caret-down')
diff --git a/app/views/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
index 4211ec6351d..e7355ae2eea 100644
--- a/app/views/shared/_mr_head.html.haml
+++ b/app/views/shared/_mr_head.html.haml
@@ -1,4 +1,4 @@
-- if @project.default_issues_tracker?
+- if @project.issues_enabled?
= render "projects/issues/head"
- else
= render "projects/merge_requests/head"
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index 9ed844cf5e7..5f3cdaefd54 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,19 +1,6 @@
- if @projects.any?
.project-item-select-holder
- = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }, with_feature_enabled: local_assigns[:with_feature_enabled]
+ = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%a.btn.btn-new.new-project-item-select-button
= local_assigns[:label]
= icon('caret-down')
-
- :javascript
- $('.new-project-item-select-button').on('click', function() {
- $('.project-item-select').select2('open');
- });
-
- var relativePath = '#{local_assigns[:path]}';
-
- $('.project-item-select').on('click', function() {
- window.location = $(this).val() + '/' + relativePath;
- });
-
- new ProjectSelect()
diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml
index ab7a2db002e..c5e4d6e2871 100644
--- a/app/views/shared/_personal_access_tokens_table.html.haml
+++ b/app/views/shared/_personal_access_tokens_table.html.haml
@@ -39,22 +39,3 @@
- else
.settings-message.text-center
This user has no active #{type} Tokens.
-
-%hr
-
-%h5 Inactive #{type} Tokens (#{inactive_tokens.length})
-- if inactive_tokens.present?
- .table-responsive
- %table.table.inactive-tokens
- %thead
- %tr
- %th Name
- %th Created
- %tbody
- - inactive_tokens.each do |token|
- %tr
- %td= token.name
- %td= token.created_at.to_date.to_s(:medium)
-- else
- .settings-message.text-center
- This user has no inactive #{type} Tokens.
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index d52bb6b4dd7..4498c8f8349 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,12 +1,12 @@
- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
+= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project), field_name: 'ref', submit_form_on_click: true }, { toggle_class: "js-project-refs-dropdown" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
= dropdown_title _("Switch branch/tag")
= dropdown_filter _("Search branches and tags")
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index b200e5fc528..7ca14ac93cc 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -7,10 +7,11 @@
= markdown @service.help
.service-settings
- .form-group
- = form.label :active, "Active", class: "control-label"
- .col-sm-10
- = form.check_box :active
+ - if @service.show_active_box?
+ .form-group
+ = form.label :active, "Active", class: "control-label"
+ .col-sm-10
+ = form.check_box :active
- if @service.supported_events.present?
.form-group
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 046b127f73c..b0c0ab523c7 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -16,7 +16,8 @@
Also, issues are searchable and filterable.
- if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
- = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
+ - else
+ = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else
.text-center
%h4 There are no issues to show.
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
index 5e2f4cf109d..bfda522f2f6 100644
--- a/app/views/shared/empty_states/_labels.html.haml
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -7,5 +7,5 @@
%h4 Labels can be applied to issues and merge requests to categorize them.
%p You can also star a label to make it a priority label.
- if can?(current_user, :admin_label, @project)
- = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
- = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
+ = link_to 'New label', new_project_label_path(@project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
+ = link_to 'Generate a default set of labels', generate_project_labels_path(@project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
diff --git a/app/views/shared/icons/_add_new_group.svg b/app/views/shared/icons/_add_new_group.svg
new file mode 100644
index 00000000000..ecd52c5e99f
--- /dev/null
+++ b/app/views/shared/icons/_add_new_group.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#E1DBF2" fill-rule="nonzero" d="M59.65 32.65H60l-2-2.42-2 2.4-2-2.4-2 2.4-2-2.4-2 2.4-2-2.4-2 2.42h.77C45.57 34.6 46 36.75 46 39c0 2.84-.7 5.5-1.92 7.86 1.97 2.28 4.83 3.64 7.92 3.64 5.8 0 10.5-4.74 10.5-10.6 0-2.8-1.08-5.36-2.85-7.25zM43.18 29.6c2.4-2.1 5.52-3.3 8.82-3.3 7.46 0 13.5 6.1 13.5 13.6S59.46 53.5 52 53.5c-3.68 0-7.1-1.5-9.6-4.04C39.3 53.44 34.44 56 29 56c-9.4 0-17-7.6-17-17s7.6-17 17-17c3.22 0 6.23.9 8.8 2.45 2.13 1.3 3.97 3.05 5.38 5.16zM17 34c-.65 1.54-1 3.23-1 5 0 7.18 5.82 13 13 13s13-5.82 13-13c0-1.77-.35-3.46-1-5h-9c-.53 0-1.04-.2-1.4-.6L29 31.84l-1.6 1.58c-.36.4-.87.6-1.4.6h-9zm21.38-4c-2.4-2.5-5.76-4-9.38-4-3.62 0-6.98 1.5-9.38 4h5.55l2.42-2.4c.74-.8 2-.8 2.8 0l2.4 2.4h5.54z"/>
+ <path fill="#6B4FBB" d="M47.6 42.32c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zm8.8 0c-.66 0-1.2-.54-1.2-1.2 0-.68.54-1.22 1.2-1.22.66 0 1.2.54 1.2 1.2 0 .68-.54 1.22-1.2 1.22zM25 44h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-1c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_add_new_project.svg b/app/views/shared/icons/_add_new_project.svg
new file mode 100644
index 00000000000..3c1e15453df
--- /dev/null
+++ b/app/views/shared/icons/_add_new_project.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#FEE1D3" fill-rule="nonzero" d="M30 24a4 4 0 0 0-4 4v22a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V28a4 4 0 0 0-4-4H30zm0-4h18a8 8 0 0 1 8 8v22a8 8 0 0 1-8 8H30a8 8 0 0 1-8-8V28a8 8 0 0 1 8-8z"/><path fill="#FC6D26" d="M33 30h8a2 2 0 1 1 0 4h-8a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4zm0 7h12a2 2 0 1 1 0 4H33a2 2 0 1 1 0-4z"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_add_new_user.svg b/app/views/shared/icons/_add_new_user.svg
new file mode 100644
index 00000000000..0ad40498d7b
--- /dev/null
+++ b/app/views/shared/icons/_add_new_user.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#E1DBF2" d="M44 31l-2.5-3-2.5 3-2.5-3-2.5 3-2.5-3-2.5 3h-2.72c2.65-4.2 7.36-7 12.72-7s10.07 2.8 12.72 7H49l-2.5-3-2.5 3z"/>
+ <path fill="#E1DBF2" fill-rule="nonzero" d="M39 57c-9.4 0-17-7.6-17-17s7.6-17 17-17 17 7.6 17 17-7.6 17-17 17zm0-4c7.18 0 13-5.82 13-13s-5.82-13-13-13-13 5.82-13 13 5.82 13 13 13z"/>
+ <path fill="#6B4FBB" d="M35 45h8c0 2.2-1.8 4-4 4s-4-1.8-4-4zm-1.5-2c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_configure_server.svg b/app/views/shared/icons/_configure_server.svg
new file mode 100644
index 00000000000..b1137b7ec94
--- /dev/null
+++ b/app/views/shared/icons/_configure_server.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82">
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#000" fill-opacity=".03" d="M2.12 42C2.04 43 2 44 2 45c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 61.03 58.42 76 39 76S3.65 61.03 2.12 42z"/>
+ <path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/>
+ <path fill="#FEE1D3" fill-rule="nonzero" d="M24.92 35.15c-1.72-1.4-1.98-3.9-.6-5.63l1.26-1.55c1.4-1.72 3.9-2 5.63-.6l.7.56c.7-.4 1.4-.73 2.1-1V26c0-2.2 1.8-4 4-4h2c2.2 0 4 1.8 4 4v.92c.8.28 1.5.62 2.1 1l.7-.55c1.7-1.4 4.3-1.12 5.7.6l1.3 1.55c1.4 1.72 1.2 4.23-.6 5.63l-.7.6c.3.74.4 1.5.5 2.3l.9.2c2.2.5 3.5 2.64 3 4.8L56.4 45c-.5 2.15-2.64 3.5-4.8 3l-.88-.2c-.44.63-.92 1.24-1.46 1.8l.4.82c.9 1.98.1 4.38-1.9 5.35l-1.8.87c-2 .97-4.37.15-5.34-1.84l-.46-.85c-.34.03-.74.05-1.13.05-.4 0-.8-.02-1.2-.05l-.4.85c-.95 2-3.34 2.8-5.33 1.84l-1.8-.87c-1.97-.97-2.8-3.37-1.83-5.35l.4-.8c-.54-.58-1.02-1.2-1.46-1.83l-.8.2c-2.2.5-4.3-.9-4.8-3l-.4-2c-.5-2.2.85-4.3 3-4.8l.9-.2c.1-.8.3-1.6.5-2.3l-.7-.6zm4.95.77c-.53 1.2-.83 2.47-.87 3.8-.02.9-.66 1.68-1.55 1.9l-2.32.53.45 1.94 2.3-.6c.9-.2 1.8.2 2.23 1 .7 1.1 1.5 2.2 2.5 3 .7.6.9 1.6.5 2.4l-1 2.1 1.8.9 1.1-2.1c.4-.8 1.3-1.3 2.2-1.1.7.1 1.3.2 2 .2s1.3-.1 2-.2c.9-.2 1.8.3 2.2 1.1l1 2.1 1.8-.9-1.2-2c-.4-.8-.2-1.8.5-2.4 1-.85 1.84-1.88 2.45-3.05.4-.82 1.33-1.24 2.2-1.04l2.33.54.45-1.95-2.32-.54c-.9-.2-1.52-.97-1.54-1.88-.03-1.4-.33-2.6-.86-3.8-.4-.9-.2-1.8.5-2.4l1.9-1.5-1.3-1.6-1.8 1.5c-.8.5-1.8.6-2.5 0-1.1-.8-2.3-1.4-3.5-1.7-.9-.2-1.5-1-1.5-1.9V26h-2v2.38c0 .9-.6 1.7-1.5 1.93-1.3.4-2.5 1-3.5 1.7-.8.6-1.8.6-2.5 0l-1.9-1.5-1.26 1.6 1.8 1.5c.7.6.94 1.6.6 2.4z"/>
+ <path fill="#FC6D26" fill-rule="nonzero" d="M39 46c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_globe.svg b/app/views/shared/icons/_globe.svg
new file mode 100644
index 00000000000..c2daae5f317
--- /dev/null
+++ b/app/views/shared/icons/_globe.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#E1DBF2" d="M30.24 27.823A14.98 14.98 0 0 0 24 40c0 2.549.636 4.949 1.757 7.051-.297-2.684.644-4.026 2.823-4.026 3.707 0 2.462 5.365 4.473 5.761 2.01.396 4.175.396 4.267 3.29.04 1.257-.265 2.157-.917 2.7a15.095 15.095 0 0 0 8.555-1.006c.035-1.91.303-4.941 2.21-5.61 2.373-.833-.55-1.431.734-3.368 1.17-1.762-3.297-5.2 0-4.832 3.477.388 5.044-.816 6.024-1.456a14.903 14.903 0 0 0-1.373-4.94c-.873.4-2.19.465-3.702-.538-.757-.502-1.084-3.944-2.107-3.944-3.823 0-4.065 3.17-5.994 3.944-1.076.431-4.193 3.773-5.614 3.596-1.126-.14-1.071-4.417-2.45-5.166-1.359-.738-2.174-1.948-2.447-3.633zM39 59c-10.493 0-19-8.507-19-19s8.507-19 19-19 19 8.507 19 19-8.507 19-19 19z"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_icon_clone.svg b/app/views/shared/icons/_icon_clone.svg
new file mode 100644
index 00000000000..ccc897aa98f
--- /dev/null
+++ b/app/views/shared/icons/_icon_clone.svg
@@ -0,0 +1,3 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14">
+<path d="M13 12.75v-8.5q0-0.102-0.074-0.176t-0.176-0.074h-8.5q-0.102 0-0.176 0.074t-0.074 0.176v8.5q0 0.102 0.074 0.176t0.176 0.074h8.5q0.102 0 0.176-0.074t0.074-0.176zM14 4.25v8.5q0 0.516-0.367 0.883t-0.883 0.367h-8.5q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h8.5q0.516 0 0.883 0.367t0.367 0.883zM11 1.25v1.25h-1v-1.25q0-0.102-0.074-0.176t-0.176-0.074h-8.5q-0.102 0-0.176 0.074t-0.074 0.176v8.5q0 0.102 0.074 0.176t0.176 0.074h1.25v1h-1.25q-0.516 0-0.883-0.367t-0.367-0.883v-8.5q0-0.516 0.367-0.883t0.883-0.367h8.5q0.516 0 0.883 0.367t0.367 0.883z"></path>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_notfound_borderless.svg b/app/views/shared/icons/_icon_status_notfound_borderless.svg
new file mode 100644
index 00000000000..e58bd264ef8
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_notfound_borderless.svg
@@ -0,0 +1 @@
+<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><path d="M12.822 11.29c.816-.581 1.421-1.348 1.683-2.322.603-2.243-.973-4.553-3.53-4.553-1.15 0-2.085.41-2.775 1.089-.42.413-.672.835-.8 1.167a1.179 1.179 0 0 0 2.2.847c.016-.043.1-.184.252-.334.264-.259.613-.412 1.123-.412.938 0 1.47.78 1.254 1.584-.105.39-.37.726-.773 1.012a3.25 3.25 0 0 1-.945.47 1.179 1.179 0 0 0-.874 1.138v2.234a1.179 1.179 0 1 0 2.358 0V11.78a5.9 5.9 0 0 0 .827-.492z" fill-rule="nonzero"/><ellipse cx="10.825" cy="16.711" rx="1.275" ry="1.322"/></svg>
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index 7cfdfb6e6ee..0d507cc7a6e 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -1,9 +1,9 @@
- type = local_assigns.fetch(:type)
-%aside.issues-bulk-update.js-right-sidebar.right-sidebar.affix-top{ data: { "offset-top" => "50", "spy" => "affix" }, "aria-live" => "polite" }
+%aside.issues-bulk-update.js-right-sidebar.right-sidebar{ "aria-live" => "polite", data: { 'signed-in': current_user.present? } }
.issuable-sidebar.hidden
= form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do
- .block
+ .block.issuable-sidebar-header
.filter-item.inline.update-issues-btn.pull-left
= button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true
= button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide pull-right"
@@ -31,7 +31,7 @@
.title
Milestone
.filter-item
- = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true, default_label: "Milestone" } })
+ = dropdown_tag("Select milestone", options: { title: "Assign milestone", toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), use_id: true, default_label: "Milestone" } })
.block
.title
Labels
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
new file mode 100644
index 00000000000..8a1268a1c6d
--- /dev/null
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -0,0 +1,14 @@
+- is_current_user = issuable_author_is_current_user(issuable)
+- 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_url(issuable), method: button_method,
+ class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
+ = link_to "Reopen #{display_issuable_type}", reopen_issuable_url(issuable), method: button_method,
+ class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{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
+- else
+ = link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
+ class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
new file mode 100644
index 00000000000..6756a7f17fd
--- /dev/null
+++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml
@@ -0,0 +1,49 @@
+- display_issuable_type = issuable_display_type(issuable)
+- button_action = issuable.closed? ? 'reopen' : 'close'
+- display_button_action = button_action.capitalize
+- button_responsive_class = 'hidden-xs hidden-sm'
+- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button issuable-close-button"
+- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
+- button_method = issuable_close_reopen_button_method(issuable)
+
+.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
+ = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_url(issuable),
+ method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
+
+ = button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
+ data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
+ = icon('caret-down', class: 'toggle-icon icon')
+
+ %ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } }
+ %li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
+ data: { text: "Close #{display_issuable_type}", url: close_issuable_url(issuable),
+ button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title
+ Close
+ = display_issuable_type
+
+ %li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
+ data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_url(issuable),
+ button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title
+ Reopen
+ = display_issuable_type
+
+ %li.divider.droplab-item-ignore
+
+ %li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
+ button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
+ .description
+ %strong.title Report abuse
+ %p.text
+ Report
+ = display_issuable_type.pluralize
+ that are abusive, inappropriate or spam.
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 9a8529c6cbb..e8feff32d26 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -20,7 +20,7 @@
%a.dropdown-toggle-page{ href: "#" }
Create new label
%li
- = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do
+ = link_to project_labels_path(@project), :"data-is-link" => true do
- if show_create && @project && can?(current_user, :admin_label, @project)
Manage labels
- else
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 6750921338a..955b8866c2c 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -11,10 +11,10 @@
%ul.dropdown-footer-list
- if can? current_user, :admin_milestone, project
%li
- = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do
+ = link_to new_project_milestone_path(project), title: "New Milestone" do
Create new
%li
- = link_to namespace_project_milestones_path(project.namespace, project) do
+ = link_to project_milestones_path(project) do
- if can? current_user, :admin_milestone, project
Manage milestones
- else
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index ae890567225..6f0b7600698 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -19,7 +19,7 @@
content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do
.js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } }
- .filtered-search-box-input-container
+ .filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
@@ -98,7 +98,7 @@
- if type == :boards
- if can?(current_user, :admin_list, @project)
.dropdown.prepend-left-10#js-add-list
- %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } }
+ %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) } }
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 745f1ee62da..ecbaa901792 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -37,13 +37,13 @@
= link_to 'Edit', '#', class: 'edit-link pull-right'
.value.hide-collapsed
- if issuable.milestone
- = link_to issuable.milestone.title, namespace_project_milestone_path(@project.namespace, @project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
+ = link_to issuable.milestone.title, project_milestone_path(@project, issuable.milestone), class: "bold has-tooltip", title: milestone_remaining_days(issuable.milestone), data: { container: "body", html: 1 }
- else
%span.no-value None
.selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
- = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
+ = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }})
- if issuable.has_attribute?(:time_estimate)
#issuable-time-tracker.block
// Fallback while content is loading
@@ -106,7 +106,7 @@
- selected_labels.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (project_labels_path(@project, :json) if @project) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index 1d5a61cffce..bcdad3c153a 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -14,7 +14,7 @@
%span{ class: ('text-warning' if group_link.expires_soon?) }
Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
.controls.member-controls
- = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
+ = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
= hidden_field_tag "group_link[group_access]", group_link.group_access
.member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
@@ -36,7 +36,7 @@
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
%i.clear-icon.js-clear-input
- if can_admin_member
- = link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
+ = link_to project_group_link_path(@project, group_link),
method: :delete,
data: { confirm: "Are you sure you want to remove #{group.name}?" },
class: 'btn btn-remove prepend-left-10' do
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index a5aa768b1b2..951b4dd7b36 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,5 +1,6 @@
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
+- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
@@ -8,45 +9,53 @@
%span.list-item-name
- if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
- %strong
- = link_to user.name, user_path(user)
- %span.cgray= user.to_reference
+ .user-info
+ = link_to user.name, user_path(user), class: 'member'
+ %span.cgray= user.to_reference
- - if user == current_user
- %span.label.label-success.prepend-left-5 It's you
+ - if user == current_user
+ %span.label.label-success.prepend-left-5 It's you
- - if user.blocked?
- %label.label.label-danger
- %strong Blocked
+ - if user.blocked?
+ %label.label.label-danger
+ %strong Blocked
- - if source.instance_of?(Group) && source != @group
- &middot;
- = link_to source.full_name, source, class: "member-group-link"
+ - if source.instance_of?(Group) && source != @group
+ &middot;
+ = link_to source.full_name, source, class: "member-group-link"
- .hidden-xs.cgray
- - if member.request?
- Requested
- = time_ago_with_tooltip(member.requested_at)
- - else
- Joined #{time_ago_with_tooltip(member.created_at)}
- - if member.expires?
- ·
- %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
- Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
+ .cgray
+ - if member.request?
+ Requested
+ = time_ago_with_tooltip(member.requested_at)
+ - else
+ Joined #{time_ago_with_tooltip(member.created_at)}
+ - if member.expires?
+ ·
+ %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
+ Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: ''
- %strong= member.invite_email
- .cgray
- Invited
- - if member.created_by
- by
- = link_to member.created_by.name, user_path(member.created_by)
- = time_ago_with_tooltip(member.created_at)
+ .user-info
+ .member= member.invite_email
+ .cgray
+ Invited
+ - if member.created_by
+ by
+ = link_to member.created_by.name, user_path(member.created_by)
+ = time_ago_with_tooltip(member.created_at)
- if show_roles
- current_resource = @project || @group
.controls.member-controls
- if show_controls && member.source == current_resource
+
+ - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
+ = link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
+ method: :post,
+ class: 'btn btn-default prepend-left-10 hidden-xs',
+ title: 'Resend invite'
+
- if user != current_user && can_admin_member
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
@@ -75,13 +84,17 @@
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
method: :post,
- class: 'btn btn-default prepend-left-10'
+ class: 'btn btn-default prepend-left-10 visible-xs-block'
- elsif member.request? && can_admin_member
- = link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
+ = link_to polymorphic_path([:approve_access_request, member]),
method: :post,
class: 'btn btn-success prepend-left-10',
- title: 'Grant access'
+ title: 'Grant access' do
+ %span{ class: ('visible-xs-block' unless force_mobile_view) }
+ Grant access
+ - unless force_mobile_view
+ = icon('check inverse', class: 'hidden-xs')
- if can?(current_user, action_member_permission(:destroy, member), member)
- if current_user == user
@@ -96,8 +109,9 @@
data: { confirm: remove_member_message(member) },
class: 'btn btn-remove prepend-left-10',
title: remove_member_title(member) do
- %span.visible-xs-block
+ %span{ class: ('visible-xs-block' unless force_mobile_view) }
Delete
- = icon('trash', class: 'hidden-xs')
+ - unless force_mobile_view
+ = icon('trash', class: 'hidden-xs')
- else
%span.member-access-text= member.human_access
diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml
index 92f6e7428ae..09b9944082f 100644
--- a/app/views/shared/members/_requests.html.haml
+++ b/app/views/shared/members/_requests.html.haml
@@ -1,8 +1,10 @@
+- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
+
- if requesters.any?
- .panel.panel-default.prepend-top-default
+ .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
.panel-heading
Users requesting access to
%strong= membership_source.name
%span.badge= requesters.size
- %ul.content-list
- = render partial: 'shared/members/member', collection: requesters, as: :member
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index a7c67ac9980..3739f4c221d 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -1,14 +1,13 @@
-# @project is present when viewing Project's milestone
- project = @project || issuable.project
- namespace = @project_namespace || project.namespace.becomes(Namespace)
+- labels = issuable.labels
- assignees = issuable.assignees
-- issuable_type = issuable.class.table_name
- base_url_args = [namespace, project]
-- issuable_type_args = base_url_args + [issuable_type]
+- issuable_type_args = base_url_args + [issuable.class.table_name]
- issuable_url_args = base_url_args + [issuable]
-- can_update = can?(current_user, :"update_#{issuable.to_ability_name}", issuable)
-%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row #{'is-disabled' unless can_update}", 'data-iid' => issuable.iid, 'data-id' => issuable.id, 'data-url' => polymorphic_path(issuable_url_args) }
+%li.issuable-row
%span
- if show_project_name
%strong #{project.name} &middot;
@@ -18,10 +17,10 @@
= confidential_icon(issuable)
= link_to issuable.title, issuable_url_args, title: issuable.title
.issuable-detail
- = link_to [project.namespace.becomes(Namespace), project, issuable] do
+ = link_to [namespace, project, issuable] do
%span.issuable-number= issuable.to_reference
- - issuable.labels.each do |label|
+ - labels.each do |label|
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 680e1f3a4ea..6f6a036b13f 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -1,10 +1,15 @@
- dashboard = local_assigns[:dashboard]
-- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
+- custom_dom_id = dom_id(milestone.try(:milestones) ? milestone.milestones.first : milestone)
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
.col-sm-6
%strong= link_to truncate(milestone.title, length: 100), milestone_path
+ - if milestone.is_group_milestone?
+ %span - Group Milestone
+ - else
+ %span - Project Milestone
+
.col-sm-6
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
@@ -13,31 +18,37 @@
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
- - if milestone.is_a?(GlobalMilestone)
+ - if milestone.is_a?(GlobalMilestone) || milestone.is_group_milestone?
.row
.col-sm-6
- .expiration= render('shared/milestone_expired', milestone: milestone)
- .projects
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = dashboard ? milestone.project.name_with_namespace : milestone.project.name
+ - if milestone.is_legacy_group_milestone?
+ .expiration= render('shared/milestone_expired', milestone: milestone)
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = dashboard ? milestone.project.name_with_namespace : milestone.project.name
- if @group
- .col-sm-6
+ .col-sm-6.milestone-actions
- if can?(current_user, :admin_milestones, @group)
+ - if milestone.is_group_milestone?
+ = link_to edit_group_milestone_path(@group, milestone.id), class: "btn btn-xs btn-grouped" do
+ Edit
+ \
- if milestone.closed?
- = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen"
- else
- = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
+ = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-xs btn-grouped btn-close"
- if @project
.row
- .col-sm-6= render('shared/milestone_expired', milestone: milestone)
+ .col-sm-6
+ = render('shared/milestone_expired', milestone: milestone)
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
+ = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit
\
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
- = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
+ = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped"
+ = link_to project_milestone_path(milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove btn-grouped" do
Delete
diff --git a/app/views/shared/milestones/_participants_tab.html.haml b/app/views/shared/milestones/_participants_tab.html.haml
index 549d2e2f61e..1615871385e 100644
--- a/app/views/shared/milestones/_participants_tab.html.haml
+++ b/app/views/shared/milestones/_participants_tab.html.haml
@@ -4,5 +4,5 @@
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, length: 40)
- %br
- %small.cgray= user.username
+ %div
+ %small.cgray= user.username
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 9bb87640319..895fb8247b5 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -21,7 +21,7 @@
.title
Start date
- if @project && can?(current_user, :admin_milestone, @project)
- = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+ = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
.value
%span.value-content
- if milestone.start_date
@@ -51,7 +51,7 @@
.title.hide-collapsed
Due date
- if @project && can?(current_user, :admin_milestone, @project)
- = link_to 'Edit', edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: 'edit-link pull-right'
+ = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'edit-link pull-right'
.value.hide-collapsed
%span.value-content
- if milestone.due_date
@@ -73,7 +73,7 @@
Issues
%span.badge= milestone.issues_visible_to_user(current_user).count
- if project && can?(current_user, :create_issue, project)
- = link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
+ = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
New issue
.value.hide-collapsed.bold
%span.milestone-stat
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 4de8a6cb15f..b95a4ea674d 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,8 +1,10 @@
+- issues_accessible = milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
+
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.js-milestone-tabs
- - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
+ - if issues_accessible
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
@@ -25,13 +27,14 @@
Labels
%span.badge= milestone.labels.count
+- issues = milestone.sorted_issues(current_user)
- show_project_name = local_assigns.fetch(:show_project_name, false)
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content
- - if milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
- .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_namespace_project_milestone_path(@project.namespace, @project, @milestone) if @project && current_user) } }
- = render 'shared/milestones/issues_tab', issues: milestone.sorted_issues(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
+ - if issues_accessible
+ .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
+ = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
-# loaded async
= render "shared/milestones/tab_loading"
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 2562f085338..b93837e3087 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -22,39 +22,55 @@
- if group
.pull-right
- if can?(current_user, :admin_milestones, group)
+ - if milestone.is_group_milestone?
+ = link_to edit_group_milestone_path(group, milestone.iid), class: "btn btn btn-grouped" do
+ Edit
- if milestone.active?
- = link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
- = link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.detail-page-description.milestone-detail
%h2.title
= markdown_field(milestone, :title)
+ - if @milestone.is_group_milestone? && @milestone.description.present?
+ %div
+ .description
+ .wiki
+ = markdown_field(@milestone, :description)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
-.table-holder
- %table.table
- %thead
- %tr
- %th Project
- %th Open issues
- %th State
- %th Due date
- - milestone.milestones.each do |ms|
- %tr
- %td
- - project_name = group ? ms.project.name : ms.project.name_with_namespace
- = link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
- %td
- = ms.issues_visible_to_user(current_user).opened.count
- %td
- - if ms.closed?
- Closed
- - else
- Open
- %td
- = ms.expires_at
+- if @milestone.is_legacy_group_milestone? || @milestone.is_dashboard_milestone?
+ .table-holder
+ %table.table
+ %thead
+ %tr
+ %th Project
+ %th Open issues
+ %th State
+ %th Due date
+ - milestone.milestones.each do |ms|
+ %tr
+ %td
+ - project_name = group ? ms.project.name : ms.project.name_with_namespace
+ = link_to project_name, project_milestone_path(ms.project, ms)
+ %td
+ = ms.issues_visible_to_user(current_user).opened.count
+ %td
+ - if ms.closed?
+ Closed
+ - else
+ Open
+ %td
+ = ms.expires_at
+- elsif @milestone.is_group_milestone?
+ %br
+ View
+ = link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
+ or
+ = link_to 'Merge Requests', merge_requests_group_path(@group, milestone_title: milestone.title)
+ in this milestone
diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml
index 29cf5825292..1dfe380db16 100644
--- a/app/views/shared/notes/_comment_button.html.haml
+++ b/app/views/shared/notes/_comment_button.html.haml
@@ -1,6 +1,6 @@
- noteable_name = @note.noteable.human_class_name
-.pull-left.btn-group.append-right-10.comment-type-dropdown.js-comment-type-dropdown
+.pull-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
%input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
- if @note.can_be_discussion_note?
@@ -9,8 +9,8 @@
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
- %a{ href: '#' }
- = icon('check')
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
.description
%strong Comment
%p
@@ -19,8 +19,8 @@
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
- %a{ href: '#' }
- = icon('check')
+ %button.btn.btn-transparent
+ = icon('check', class: 'icon')
.description
%strong Start discussion
%p
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 1e34b7c1e76..7174855e176 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -60,6 +60,6 @@
= link_to note.attachment.url, target: '_blank' do
= icon('paperclip')
= note.attachment_identifier
- = link_to delete_attachment_namespace_project_note_path(note.project.namespace, note.project, note),
+ = link_to delete_attachment_project_note_path(note.project, note),
title: 'Delete this attachment', method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: 'danger js-note-attachment-delete' do
= icon('trash-o', class: 'cred')
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 8c3d6351ac2..4bdbc26a4c3 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -31,7 +31,7 @@
- if show_last_commit_as_description
.description.prepend-top-5
- = link_to_gfm project.commit.title, namespace_project_commit_path(project.namespace, project, project.commit),
+ = link_to_gfm project.commit.title, project_commit_path(project, project.commit),
class: "commit-row-message"
- elsif project.description.present?
.description.prepend-top-5
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 8549cb91b03..43322978749 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -36,6 +36,6 @@
= f.submit 'Save changes', class: "btn-save btn"
- if @snippet.project_id
- = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 5d2d2317f22..7388f20a9fd 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -30,7 +30,7 @@
- if link_project && snippet.project_id?
%span.hidden-xs
in
- = link_to namespace_project_path(snippet.project.namespace, snippet.project) do
+ = link_to project_path(snippet.project) do
= snippet.project.name_with_namespace
.pull-right.snippet-updated-at
diff --git a/app/views/shared/web_hooks/_test_button.html.haml b/app/views/shared/web_hooks/_test_button.html.haml
new file mode 100644
index 00000000000..cf1d5e061c6
--- /dev/null
+++ b/app/views/shared/web_hooks/_test_button.html.haml
@@ -0,0 +1,12 @@
+- triggers = local_assigns.fetch(:triggers)
+- button_class = local_assigns.fetch(:button_class, '')
+- hook = local_assigns.fetch(:hook)
+
+.hook-test-button.dropdown.inline
+ %button.btn{ 'data-toggle' => 'dropdown', class: button_class }
+ Test
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' }
+ - triggers.each_value do |event|
+ %li
+ = link_to_test_hook(hook, event)
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index ca8afb4bb6a..f01915107e3 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,3 +1,5 @@
+- @hide_top_links = true
+- breadcrumb_title "Snippets"
- page_title "New Snippet"
%h3.page-title
New Snippet
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 8818590362d..706f13dd004 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -1,3 +1,4 @@
+- @hide_top_links = true
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index d1e88274878..805a346a85e 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -12,7 +12,7 @@
- if event.push?
#{event.action_name} #{event.ref_type}
%strong
- - commits_path = namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
+ - commits_path = project_commits_path(event.project, event.ref_name)
= link_to_if event.project.repository.branch_exists?(event.ref_name), event.ref_name, commits_path
- else
= event_action_name(event)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index f246bd7a586..919ba5d15d3 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,3 +1,5 @@
+- @hide_top_links = true
+- @hide_breadcrumbs = true
- page_title @user.name
- page_description @user.bio
- content_for :page_specific_javascripts do
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index e85e221d353..45ce49bb5c0 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -2,18 +2,34 @@ class BackgroundMigrationWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
- # Schedules a number of jobs in bulk
+ # Enqueues a number of jobs in bulk.
#
# The `jobs` argument should be an Array of Arrays, each sub-array must be in
# the form:
#
# [migration-class, [arg1, arg2, ...]]
- def self.perform_bulk(*jobs)
+ def self.perform_bulk(jobs)
Sidekiq::Client.push_bulk('class' => self,
'queue' => sidekiq_options['queue'],
'args' => jobs)
end
+ # Schedules multiple jobs in bulk, with a delay.
+ #
+ def self.perform_bulk_in(delay, jobs)
+ now = Time.now.to_i
+ schedule = now + delay.to_i
+
+ if schedule <= now
+ raise ArgumentError, 'The schedule time must be in the future!'
+ end
+
+ Sidekiq::Client.push_bulk('class' => self,
+ 'queue' => sidekiq_options['queue'],
+ 'args' => jobs,
+ 'at' => schedule)
+ end
+
# Performs the background migration.
#
# See Gitlab::BackgroundMigration.perform for more information.
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
new file mode 100644
index 00000000000..4f47717ff69
--- /dev/null
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -0,0 +1,16 @@
+class CreateGpgSignatureWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform(commit_sha, project_id)
+ project = Project.find_by(id: project_id)
+
+ return unless project
+
+ commit = project.commit(commit_sha)
+
+ return unless commit
+
+ commit.signature
+ end
+end
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
index 08e281e7350..e383202260d 100644
--- a/app/workers/expire_job_cache_worker.rb
+++ b/app/workers/expire_job_cache_worker.rb
@@ -18,18 +18,10 @@ class ExpireJobCacheWorker
private
def project_pipeline_path(project, pipeline)
- Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
- project.namespace,
- project,
- pipeline,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
end
def project_job_path(project, job)
- Gitlab::Routing.url_helpers.namespace_project_build_path(
- project.namespace,
- project,
- job.id,
- format: :json)
+ Gitlab::Routing.url_helpers.project_build_path(project, job.id, format: :json)
end
end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 92e622285de..7c02d6cf892 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -23,42 +23,24 @@ class ExpirePipelineCacheWorker
private
def project_pipelines_path(project)
- Gitlab::Routing.url_helpers.namespace_project_pipelines_path(
- project.namespace,
- project,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipelines_path(project, format: :json)
end
def project_pipeline_path(project, pipeline)
- Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
- project.namespace,
- project,
- pipeline,
- format: :json)
+ Gitlab::Routing.url_helpers.project_pipeline_path(project, pipeline, format: :json)
end
def commit_pipelines_path(project, commit)
- Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
- project.namespace,
- project,
- commit.id,
- format: :json)
+ Gitlab::Routing.url_helpers.pipelines_project_commit_path(project, commit.id, format: :json)
end
def new_merge_request_pipelines_path(project)
- Gitlab::Routing.url_helpers.namespace_project_new_merge_request_path(
- project.namespace,
- project,
- format: :json)
+ Gitlab::Routing.url_helpers.project_new_merge_request_path(project, format: :json)
end
def each_pipelines_merge_request_path(project, pipeline)
pipeline.all_merge_requests.each do |merge_request|
- path = Gitlab::Routing.url_helpers.pipelines_namespace_project_merge_request_path(
- project.namespace,
- project,
- merge_request,
- format: :json)
+ path = Gitlab::Routing.url_helpers.pipelines_project_merge_request_path(project, merge_request, format: :json)
yield(path)
end
diff --git a/app/workers/invalid_gpg_signature_update_worker.rb b/app/workers/invalid_gpg_signature_update_worker.rb
new file mode 100644
index 00000000000..db6b1ea8e8d
--- /dev/null
+++ b/app/workers/invalid_gpg_signature_update_worker.rb
@@ -0,0 +1,12 @@
+class InvalidGpgSignatureUpdateWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform(gpg_key_id)
+ gpg_key = GpgKey.find_by(id: gpg_key_id)
+
+ return unless gpg_key
+
+ Gitlab::Gpg::InvalidGpgSignatureUpdater.new(gpg_key).run
+ end
+end
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 7b485b3363c..d7087f20dfc 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -6,15 +6,12 @@ class PipelineScheduleWorker
Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
.preload(:owner, :project).find_each do |schedule|
begin
- unless schedule.runnable_by_owner?
- schedule.deactivate!
- next
- end
-
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
+ pipeline = Ci::CreatePipelineService.new(schedule.project,
+ schedule.owner,
+ ref: schedule.ref)
.execute(:schedule, save_on_errors: false, schedule: schedule)
+
+ schedule.deactivate! unless pipeline.persisted?
rescue => e
Rails.logger.error "#{schedule.id}: Failed to create a scheduled pipeline: #{e.message}"
ensure
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index b462327490e..a9188b78460 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -3,14 +3,11 @@ class ProjectDestroyWorker
include DedicatedSidekiqQueue
def perform(project_id, user_id, params)
- begin
- project = Project.unscoped.find(project_id)
- rescue ActiveRecord::RecordNotFound
- return
- end
-
+ project = Project.find(project_id)
user = User.find(user_id)
::Projects::DestroyService.new(project, user, params.symbolize_keys).execute
+ rescue ActiveRecord::RecordNotFound => error
+ logger.error("Failed to delete project (#{project_id}): #{error.message}")
end
end
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
index fdfdeab7b41..4883d848c53 100644
--- a/app/workers/project_service_worker.rb
+++ b/app/workers/project_service_worker.rb
@@ -2,6 +2,8 @@ class ProjectServiceWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ sidekiq_options dead: false
+
def perform(hook_id, data)
data = data.with_indifferent_access
Service.find(hook_id).execute(data)
diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb
index ad5ddf02a12..713c0228040 100644
--- a/app/workers/web_hook_worker.rb
+++ b/app/workers/web_hook_worker.rb
@@ -2,7 +2,7 @@ class WebHookWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
- sidekiq_options retry: 4
+ sidekiq_options retry: 4, dead: false
def perform(hook_id, data, hook_name)
hook = WebHook.find(hook_id)
diff --git a/changelogs/unreleased/10085-stop-encoding-user-name.yml b/changelogs/unreleased/10085-stop-encoding-user-name.yml
new file mode 100644
index 00000000000..8fab474e047
--- /dev/null
+++ b/changelogs/unreleased/10085-stop-encoding-user-name.yml
@@ -0,0 +1,4 @@
+---
+title: "Insert user name directly without encoding"
+merge_request: 10085
+author: Nathan Neulinger <nneul@neulinger.org>
diff --git a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml b/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
deleted file mode 100644
index 2c915e62357..00000000000
--- a/changelogs/unreleased/12151-add-since-and-until-params-to-issuables.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added "created_after" and "created_before" params to issuables
-merge_request: 12151
-author: Kyle Bishop @kybishop
diff --git a/changelogs/unreleased/12200-add-french-translation.yml b/changelogs/unreleased/12200-add-french-translation.yml
deleted file mode 100644
index f31d982e0b9..00000000000
--- a/changelogs/unreleased/12200-add-french-translation.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Adding French translations"
-merge_request: 12200
-author : Erwan "Dremor" Georget
diff --git a/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md
new file mode 100644
index 00000000000..87e95240bba
--- /dev/null
+++ b/changelogs/unreleased/12892-reset-css-text-align-to-initial-for-rtl.md
@@ -0,0 +1,4 @@
+---
+title: "reset text-align to initial to let elements with dir="auto" align texts to right in RTL languages ( default is left )"
+merge_request: 12892
+author: goshhob
diff --git a/changelogs/unreleased/13336-multiple-broadcast-messages.yml b/changelogs/unreleased/13336-multiple-broadcast-messages.yml
deleted file mode 100644
index 7dc73e1c6ea..00000000000
--- a/changelogs/unreleased/13336-multiple-broadcast-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display all current broadcast messages, not just the last one
-merge_request: 11113
-author: rickettm
diff --git a/changelogs/unreleased/19629-remove-inactive-tokens-list.yml b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml
new file mode 100644
index 00000000000..414e3d49e29
--- /dev/null
+++ b/changelogs/unreleased/19629-remove-inactive-tokens-list.yml
@@ -0,0 +1,4 @@
+---
+title: Remove Inactive Personal Access Tokens list from Access Tokens page
+merge_request: 12866
+author:
diff --git a/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml b/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml
new file mode 100644
index 00000000000..c4c3fc7ceb2
--- /dev/null
+++ b/changelogs/unreleased/20817-please-add-coordinator-url-to-admin-area-runner-page.yml
@@ -0,0 +1,4 @@
+---
+title: Add coordinator url to admin area runner page
+merge_request: 11603
+author:
diff --git a/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml b/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml
new file mode 100644
index 00000000000..837a34bd067
--- /dev/null
+++ b/changelogs/unreleased/22600-related-resources-uris-using-grape-source-helpers.yml
@@ -0,0 +1,4 @@
+---
+title: Declare related resources into V4 API entities
+merge_request:
+author:
diff --git a/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml
new file mode 100644
index 00000000000..807cd097178
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-event-filters-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replaces dashboard/event_filters.feature spinach with rspec
+merge_request: 12651
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
deleted file mode 100644
index 07c201de96e..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-mr-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/merge_requests' spinach with rspec
-merge_request: 12440
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
deleted file mode 100644
index a5f78202c93..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-new-project-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/new-project.feature' spinach with rspec
-merge_request: 12550
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-spinach.yml
new file mode 100644
index 00000000000..b3197c4cfa6
--- /dev/null
+++ b/changelogs/unreleased/23036-replace-dashboard-spinach.yml
@@ -0,0 +1,4 @@
+---
+title: Replaces dashboard/dashboard.feature spinach with rspec
+merge_request: 12876
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml b/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
deleted file mode 100644
index 65df9a836a5..00000000000
--- a/changelogs/unreleased/23036-replace-dashboard-todo-spinach.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'dashboard/todos' spinach with rspec
-merge_request: 12453
-author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/23998-blame-age-map.yml b/changelogs/unreleased/23998-blame-age-map.yml
deleted file mode 100644
index 26a38f0939c..00000000000
--- a/changelogs/unreleased/23998-blame-age-map.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add blame view age mapping
-merge_request: 7198
-author: Jeff Stubler
diff --git a/changelogs/unreleased/25102-files-view-button.yml b/changelogs/unreleased/25102-files-view-button.yml
deleted file mode 100644
index 4ba815d9464..00000000000
--- a/changelogs/unreleased/25102-files-view-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix mobile view of files view buttons
-merge_request:
-author:
diff --git a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml b/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
deleted file mode 100644
index 9fa824b161d..00000000000
--- a/changelogs/unreleased/25164-disable-fork-on-project-limit.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Disable fork button on project limit
-merge_request: 12145
-author: Ivan Chernov
diff --git a/changelogs/unreleased/26125-match-username-on-search.yml b/changelogs/unreleased/26125-match-username-on-search.yml
deleted file mode 100644
index 74e918bec16..00000000000
--- a/changelogs/unreleased/26125-match-username-on-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Inserts exact matches of name, username and email to the top of the search
- list
-merge_request: 12525
-author:
diff --git a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml b/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
deleted file mode 100644
index 667454ae95d..00000000000
--- a/changelogs/unreleased/26212-upload-user-avatar-trough-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Accept image for avatar in user API
-merge_request: 12143
-author: Ivan Chernov
diff --git a/changelogs/unreleased/26372-duplicate-issue-slash-command.yml b/changelogs/unreleased/26372-duplicate-issue-slash-command.yml
new file mode 100644
index 00000000000..3108344e0bf
--- /dev/null
+++ b/changelogs/unreleased/26372-duplicate-issue-slash-command.yml
@@ -0,0 +1,4 @@
+---
+title: Added /duplicate quick action to close a duplicate issue
+merge_request: 12845
+author: Ryan Scott
diff --git a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml b/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
deleted file mode 100644
index 497239db808..00000000000
--- a/changelogs/unreleased/27070-rename-slash-commands-to-quick-actions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename "Slash commands" to "Quick actions" and deprecate "chat commands" in favor
- of "slash commands"
-merge_request:
-author:
diff --git a/changelogs/unreleased/27586-center-dropdown.yml b/changelogs/unreleased/27586-center-dropdown.yml
deleted file mode 100644
index 4935f7504f7..00000000000
--- a/changelogs/unreleased/27586-center-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Center dropdown for mini graph
-merge_request:
-author:
diff --git a/changelogs/unreleased/27645-html-email-brackets-bug.yml b/changelogs/unreleased/27645-html-email-brackets-bug.yml
deleted file mode 100644
index e8004d03884..00000000000
--- a/changelogs/unreleased/27645-html-email-brackets-bug.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix an email parsing bug where brackets would be inserted in emails from some Outlook clients
-merge_request: 9045
-author: jneen
diff --git a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml b/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
deleted file mode 100644
index 92b5b59f46f..00000000000
--- a/changelogs/unreleased/27697-make-arrow-icons-consistent-in-dropdown.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use fa-chevron-down on dropdown arrows for consistency
-merge_request: 9659
-author: TM Lee
diff --git a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml b/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
deleted file mode 100644
index 97ebabaff1c..00000000000
--- a/changelogs/unreleased/28139-use-color-input-broadcast-messages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use color inputs for broadcast messages
-merge_request:
-author:
diff --git a/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml b/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml
new file mode 100644
index 00000000000..b8f30b52b18
--- /dev/null
+++ b/changelogs/unreleased/28202_decrease_abc_threshold_step2.yml
@@ -0,0 +1,4 @@
+---
+title: Decrease ABC threshold to 56.96
+merge_request: 11227
+author: Maxim Rydkin
diff --git a/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml b/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
deleted file mode 100644
index 720a79b8e1c..00000000000
--- a/changelogs/unreleased/28717-support-additional-prometheus-metrics.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Additional Prometheus metrics support
-merge_request: 11712
-author:
diff --git a/changelogs/unreleased/29289-project-destroy-clean-up-after-failure.yml b/changelogs/unreleased/29289-project-destroy-clean-up-after-failure.yml
new file mode 100644
index 00000000000..488b37ac37f
--- /dev/null
+++ b/changelogs/unreleased/29289-project-destroy-clean-up-after-failure.yml
@@ -0,0 +1,4 @@
+---
+title: Handle errors while a project is being deleted asynchronously.
+merge_request: 11088
+author:
diff --git a/changelogs/unreleased/2971-multiproject-grah-ce-port.yml b/changelogs/unreleased/2971-multiproject-grah-ce-port.yml
new file mode 100644
index 00000000000..37584cac6ab
--- /dev/null
+++ b/changelogs/unreleased/2971-multiproject-grah-ce-port.yml
@@ -0,0 +1,4 @@
+---
+title: Fix vertical alignment in firefox and safari for pipeline mini graph
+merge_request:
+author:
diff --git a/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml b/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml
new file mode 100644
index 00000000000..8850422fc88
--- /dev/null
+++ b/changelogs/unreleased/29901-refactor-initialization-dropzone_input-js.yml
@@ -0,0 +1,4 @@
+---
+title: refactor initializations in dropzone_input.js
+merge_request: 12768
+author: Brandon Everett
diff --git a/changelogs/unreleased/30213-project-transfer-move-rollback.yml b/changelogs/unreleased/30213-project-transfer-move-rollback.yml
deleted file mode 100644
index 3eb1e399c54..00000000000
--- a/changelogs/unreleased/30213-project-transfer-move-rollback.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rollback project repo move if there is an error in Projects::TransferService
-merge_request: 11877
-author:
diff --git a/changelogs/unreleased/30634-protected-pipeline.yml b/changelogs/unreleased/30634-protected-pipeline.yml
new file mode 100644
index 00000000000..e46538e5b46
--- /dev/null
+++ b/changelogs/unreleased/30634-protected-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow running the pipeline if ref is protected and user cannot merge the
+ branch or create the tag
+merge_request: 11910
+author:
diff --git a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml b/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
deleted file mode 100644
index 83ce3fb4d0a..00000000000
--- a/changelogs/unreleased/30708-stop-using-deleted-at-to-filter-namespaces.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Removes deleted_at and pending_delete occurrences in Project related queries
-merge_request: 12091
-author:
diff --git a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml b/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
deleted file mode 100644
index 3058404b3f8..00000000000
--- a/changelogs/unreleased/30725-reset-user-limits-when-unchecking-external-user.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Ensures default user limits when external user is unchecked
-merge_request: 12218
-author:
diff --git a/changelogs/unreleased/31129-jira-project-key-elim.yml b/changelogs/unreleased/31129-jira-project-key-elim.yml
new file mode 100644
index 00000000000..bfa0e99f250
--- /dev/null
+++ b/changelogs/unreleased/31129-jira-project-key-elim.yml
@@ -0,0 +1,4 @@
+---
+title: Remove project_key from the Jira configuration
+merge_request: 12050
+author:
diff --git a/changelogs/unreleased/31397-job-detail-real-time.yml b/changelogs/unreleased/31397-job-detail-real-time.yml
deleted file mode 100644
index 90487a1e75a..00000000000
--- a/changelogs/unreleased/31397-job-detail-real-time.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds realtime feature to job show view header and sidebar info. Updates UX.
-merge_request:
-author:
diff --git a/changelogs/unreleased/31415-responsive-pipelines-table-2.yml b/changelogs/unreleased/31415-responsive-pipelines-table-2.yml
deleted file mode 100644
index 59402b85871..00000000000
--- a/changelogs/unreleased/31415-responsive-pipelines-table-2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Create responsive mobile view for pipelines table
-merge_request:
-author:
diff --git a/changelogs/unreleased/31533-usage-data-projects-stats.yml b/changelogs/unreleased/31533-usage-data-projects-stats.yml
new file mode 100644
index 00000000000..11bb6118337
--- /dev/null
+++ b/changelogs/unreleased/31533-usage-data-projects-stats.yml
@@ -0,0 +1,4 @@
+---
+title: Add Slack and JIRA services counts to Usage Data
+merge_request:
+author:
diff --git a/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml b/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml
new file mode 100644
index 00000000000..69900f0b314
--- /dev/null
+++ b/changelogs/unreleased/31571-don-t-let-webhooks-jobs-go-to-the-dead-jobs-queue.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent web hook and project service background jobs from going to the dead
+ jobs queue
+merge_request:
+author:
diff --git a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml b/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
deleted file mode 100644
index 99e64b9b467..00000000000
--- a/changelogs/unreleased/32048-shared-runners-admin-buttons-have-odd-spacing.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix spacing on runner buttons.
-merge_request: !12535
-author:
diff --git a/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml b/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml
deleted file mode 100644
index 7fc9e0a4f0e..00000000000
--- a/changelogs/unreleased/32054-rails-should-use-timestamptz-database-type-for-postgresql.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
-merge_request: 11229
-author: @blackst0ne
diff --git a/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml b/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
deleted file mode 100644
index d6534ed4e1a..00000000000
--- a/changelogs/unreleased/32301-filter-archive-project-on-param-present.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Filter archived project in API v3 only if param present
-merge_request: 12245
-author: Ivan Chernov
diff --git a/changelogs/unreleased/32470-pag-links.yml b/changelogs/unreleased/32470-pag-links.yml
deleted file mode 100644
index d0fd284f3ee..00000000000
--- a/changelogs/unreleased/32470-pag-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: more visual contrast in pagination widget
-merge_request:
-author:
diff --git a/changelogs/unreleased/32517-disable-hover-state.yml b/changelogs/unreleased/32517-disable-hover-state.yml
deleted file mode 100644
index 31b02778963..00000000000
--- a/changelogs/unreleased/32517-disable-hover-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes hover style for nodes that are either links or buttons in the pipeline
- graph
-merge_request:
-author:
diff --git a/changelogs/unreleased/32834-task-note-only.yml b/changelogs/unreleased/32834-task-note-only.yml
deleted file mode 100644
index c9ea61ec4ec..00000000000
--- a/changelogs/unreleased/32834-task-note-only.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Prevent description change notes when toggling tasks
-merge_request: 12057
-author: Jared Deckard <jared.deckard@gmail.com>
diff --git a/changelogs/unreleased/32885-unintentionally-removing-branch-when-merging-merge-request.yml b/changelogs/unreleased/32885-unintentionally-removing-branch-when-merging-merge-request.yml
deleted file mode 100644
index 313aeab91b5..00000000000
--- a/changelogs/unreleased/32885-unintentionally-removing-branch-when-merging-merge-request.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Set default for Remove source branch to false.
-merge_request: !12576
-author:
diff --git a/changelogs/unreleased/33003-avatar-in-project-api.yml b/changelogs/unreleased/33003-avatar-in-project-api.yml
deleted file mode 100644
index 41d796ebb32..00000000000
--- a/changelogs/unreleased/33003-avatar-in-project-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Accept image for avatar in project API
-merge_request: 11988
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml b/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
deleted file mode 100644
index d3172c405c3..00000000000
--- a/changelogs/unreleased/33082-use-update_pipeline_schedule-for-edit-and-take_ownership-in-pipelineschedulescontroller.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Use authorize_update_pipeline_schedule in PipelineSchedulesController
-merge_request: 11846
-author:
diff --git a/changelogs/unreleased/33097-issue-tracker.yml b/changelogs/unreleased/33097-issue-tracker.yml
new file mode 100644
index 00000000000..0b13f7165db
--- /dev/null
+++ b/changelogs/unreleased/33097-issue-tracker.yml
@@ -0,0 +1,4 @@
+---
+title: Associate Issues tab only with internal issues tracker
+merge_request:
+author:
diff --git a/changelogs/unreleased/33130-remove-group-modal.yml b/changelogs/unreleased/33130-remove-group-modal.yml
deleted file mode 100644
index 4672d41ded5..00000000000
--- a/changelogs/unreleased/33130-remove-group-modal.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: "Remove group modal like remove project modal (requires typing + confirmation)"
-merge_request: 12569
-author: Diego Souza
diff --git a/changelogs/unreleased/33132-change-icon-color.yml b/changelogs/unreleased/33132-change-icon-color.yml
deleted file mode 100644
index c0e148f985b..00000000000
--- a/changelogs/unreleased/33132-change-icon-color.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Render CI statuses with warnings in orange
-merge_request:
-author:
diff --git a/changelogs/unreleased/33208-singup-active-state-underline.yml b/changelogs/unreleased/33208-singup-active-state-underline.yml
deleted file mode 100644
index cddb43214ea..00000000000
--- a/changelogs/unreleased/33208-singup-active-state-underline.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixes "sign in / Register" active state underline misalignment
-merge_request: 11890
-author: Frank Sierra
diff --git a/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml b/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml
deleted file mode 100644
index 4a7b02fec94..00000000000
--- a/changelogs/unreleased/33381-display-issue-state-in-mr-widget-issue-links.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display issue state in issue links section of merge request widget
-merge_request: 12021
-author:
diff --git a/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml b/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
deleted file mode 100644
index a7d8ac9054b..00000000000
--- a/changelogs/unreleased/33441-supplement_simplified_chinese_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Simplified Chinese translation of Project Page & Repository Page
-merge_request: 11994
-author: Huang Tao
diff --git a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml b/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
deleted file mode 100644
index e383bab23d6..00000000000
--- a/changelogs/unreleased/33442-supplement_traditional_chinese_in_hong_kong_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Traditional Chinese in Hong Kong translation of Project Page & Repository Page
-merge_request: 11995
-author: Huang Tao
diff --git a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml b/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
deleted file mode 100644
index d6b1b2524c6..00000000000
--- a/changelogs/unreleased/33443-supplement_traditional_chinese_in_taiwan_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Traditional Chinese in Taiwan translation of Project Page & Repository Page
-merge_request: 12514
-author: Huang Tao
diff --git a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml b/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
deleted file mode 100644
index 385f18e2560..00000000000
--- a/changelogs/unreleased/33445-document-delete-merge-branches-won-t-touch-protected-branches-docs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Document the Delete Merged Branches functionality
-merge_request:
-author:
diff --git a/changelogs/unreleased/33461-display-user-id.yml b/changelogs/unreleased/33461-display-user-id.yml
deleted file mode 100644
index cba94625b07..00000000000
--- a/changelogs/unreleased/33461-display-user-id.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Display own user id in account settings page
-merge_request: 12141
-author: Riccardo Padovani
diff --git a/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml b/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
deleted file mode 100644
index 590472c0990..00000000000
--- a/changelogs/unreleased/33538-update-ci-dockerfile-now-that-chrome-headless-no-longer-in-beta.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update QA Dockerfile to lock Chrome browser version
-merge_request: 12071
-author:
diff --git a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml b/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
deleted file mode 100644
index 4f2ba2e1de3..00000000000
--- a/changelogs/unreleased/33561-supplement_bulgarian_translation_of_i18n.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Supplement Bulgarian translation of Project Page & Repository Page
-merge_request: 12083
-author: Lyubomir Vasilev
diff --git a/changelogs/unreleased/33601-add-csrf-token-verification-to-api.yml b/changelogs/unreleased/33601-add-csrf-token-verification-to-api.yml
new file mode 100644
index 00000000000..88cfb99a73e
--- /dev/null
+++ b/changelogs/unreleased/33601-add-csrf-token-verification-to-api.yml
@@ -0,0 +1,4 @@
+---
+title: Add CSRF token verification to API
+merge_request: 12154
+author: Vitaliy @blackst0ne Klachkov
diff --git a/changelogs/unreleased/33741-clarify-k8s-service-keys.yml b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml
new file mode 100644
index 00000000000..91142a0d580
--- /dev/null
+++ b/changelogs/unreleased/33741-clarify-k8s-service-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Clarifies and rearranges the input variables on the kubernetes integration
+ page and adjusts the docs slightly to meet the same order
+merge_request: !12188
+author:
diff --git a/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml
new file mode 100644
index 00000000000..3a45ad88270
--- /dev/null
+++ b/changelogs/unreleased/33770-respect-blockquote-line-breaks.yml
@@ -0,0 +1,4 @@
+---
+title: Respect blockquote line breaks in markdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/33837-remove-trash-on-registry-image.yml b/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
deleted file mode 100644
index 2d337f5e6e4..00000000000
--- a/changelogs/unreleased/33837-remove-trash-on-registry-image.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove registry image delete button if user cant delete it
-merge_request: 12317
-author: Ivan Chernov
diff --git a/changelogs/unreleased/33846-no-runner-for-admin.yml b/changelogs/unreleased/33846-no-runner-for-admin.yml
deleted file mode 100644
index a2d46802c61..00000000000
--- a/changelogs/unreleased/33846-no-runner-for-admin.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add explicit message when no runners on admin
-merge_request: 12266
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml b/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
deleted file mode 100644
index 4bacfca7551..00000000000
--- a/changelogs/unreleased/34052-store-mr-ref-fetched-in-database.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Store merge request ref_fetched status in the database
-merge_request: 12424
-author:
diff --git a/changelogs/unreleased/34075-pipelines-count-mt.yml b/changelogs/unreleased/34075-pipelines-count-mt.yml
new file mode 100644
index 00000000000..3846e7b06a4
--- /dev/null
+++ b/changelogs/unreleased/34075-pipelines-count-mt.yml
@@ -0,0 +1,5 @@
+---
+title: Update Pipeline's badge count in Merge Request and Commits view to match real-time
+ content
+merge_request:
+author:
diff --git a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml b/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
deleted file mode 100644
index 69d5d34b072..00000000000
--- a/changelogs/unreleased/34078-allow-to-enable-feature-flags-with-more-granularity.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow the feature flags to be enabled/disabled with more granularity
-merge_request: 12357
-author:
diff --git a/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml b/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml
deleted file mode 100644
index c0ea75adfa0..00000000000
--- a/changelogs/unreleased/34097-issue-board-remove-from-board-button-when-viewing-an-issue-gives-js-error-and-fails.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove "Remove from board" button from backlog and closed list
-merge_request: 12430
-author:
diff --git a/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml b/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml
new file mode 100644
index 00000000000..1911705dd2b
--- /dev/null
+++ b/changelogs/unreleased/34110-memory-usage-notice-doesn-t-link-anywhere.yml
@@ -0,0 +1,4 @@
+---
+title: Added link to the MR widget that directs to the monitoring dashboard
+merge_request:
+author:
diff --git a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml b/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
deleted file mode 100644
index 8f8b5a96c2b..00000000000
--- a/changelogs/unreleased/34116-milestone-filtering-on-group-issues.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Change milestone endpoint for groups
-merge_request: 12374
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml b/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
deleted file mode 100644
index a3ade8db214..00000000000
--- a/changelogs/unreleased/34141-allow-unauthenticated-access-to-the-users-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Allow unauthenticated access to the /api/v4/users API
-merge_request: 12445
-author:
diff --git a/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml b/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
deleted file mode 100644
index 4fa385c3c27..00000000000
--- a/changelogs/unreleased/34207-remove-bin-ci-upgrade-rb.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove bin/ci/upgrade.rb as not working all
-merge_request: 12414
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml b/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
deleted file mode 100644
index af743f3e506..00000000000
--- a/changelogs/unreleased/34286-add-esperanto-translations-for-cycle-analytics-and-project-and-repository-pages.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add Esperanto translations for Cycle Analytics, Project, and Repository pages
-merge_request: 12442
-author: Huang Tao
diff --git a/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml b/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
deleted file mode 100644
index 42e906d24c6..00000000000
--- a/changelogs/unreleased/34289-drop-gfm-on-milestone-issuable-title.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Drop GFM support for issuable title on milestone for consistency and performance
-merge_request:
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml b/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
deleted file mode 100644
index 07fe79e90ee..00000000000
--- a/changelogs/unreleased/34309-drop-gfm-mr-ms.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Drop GFM support for the title of Milestone/MergeRequest in template
-merge_request: 12451
-author: Takuya Noguchi
diff --git a/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml b/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
new file mode 100644
index 00000000000..d188a558d38
--- /dev/null
+++ b/changelogs/unreleased/34361-lazy-load-images-on-the-frontend.yml
@@ -0,0 +1,4 @@
+---
+title: Lazy load images for better Frontend performance
+merge_request: 12503
+author:
diff --git a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml b/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
deleted file mode 100644
index 4911315d018..00000000000
--- a/changelogs/unreleased/34403-issue-dropdown-persists-when-adding-issue-number-to-issue-description.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Closes any open Autocomplete of the markdown editor when the form is closed
-merge_request: 12521
-author:
diff --git a/changelogs/unreleased/34534-update-vue-resource.yml b/changelogs/unreleased/34534-update-vue-resource.yml
new file mode 100644
index 00000000000..2d0af0c9bfe
--- /dev/null
+++ b/changelogs/unreleased/34534-update-vue-resource.yml
@@ -0,0 +1,4 @@
+---
+title: Updates vue resource and code according to breaking changes
+merge_request:
+author:
diff --git a/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml b/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml
new file mode 100644
index 00000000000..e843bbac239
--- /dev/null
+++ b/changelogs/unreleased/34549-extract-devise-mappings-into-helper.yml
@@ -0,0 +1,4 @@
+---
+title: Extract "@request.env[devise.mapping] = Devise.mappings[:user]" to a test helper
+merge_request: 12742
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/34563-usage-ping-github.yml b/changelogs/unreleased/34563-usage-ping-github.yml
new file mode 100644
index 00000000000..3ab982beea3
--- /dev/null
+++ b/changelogs/unreleased/34563-usage-ping-github.yml
@@ -0,0 +1,4 @@
+---
+title: Add GitHub imported projects count to usage data
+merge_request:
+author:
diff --git a/changelogs/unreleased/34729-blob.yml b/changelogs/unreleased/34729-blob.yml
new file mode 100644
index 00000000000..15a469d3af0
--- /dev/null
+++ b/changelogs/unreleased/34729-blob.yml
@@ -0,0 +1,4 @@
+---
+title: Fix crash on /help/ui
+merge_request:
+author:
diff --git a/changelogs/unreleased/34810-vue-pagination.yml b/changelogs/unreleased/34810-vue-pagination.yml
new file mode 100644
index 00000000000..5cd03518a98
--- /dev/null
+++ b/changelogs/unreleased/34810-vue-pagination.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent disabled pagination button to be clicked
+merge_request:
+author:
diff --git a/changelogs/unreleased/34831-remove-coffee-rails-gem.yml b/changelogs/unreleased/34831-remove-coffee-rails-gem.yml
new file mode 100644
index 00000000000..b555f112b8d
--- /dev/null
+++ b/changelogs/unreleased/34831-remove-coffee-rails-gem.yml
@@ -0,0 +1,4 @@
+---
+title: Remove coffee-rails gem
+merge_request:
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml
new file mode 100644
index 00000000000..e6cd834aed2
--- /dev/null
+++ b/changelogs/unreleased/34858-bump-scss-lint-to-0-54-0.yml
@@ -0,0 +1,4 @@
+---
+title: Bump scss-lint to 0.54.0
+merge_request: 12733
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34867-remove-net-ssh-gem.yml b/changelogs/unreleased/34867-remove-net-ssh-gem.yml
new file mode 100644
index 00000000000..f5648d62467
--- /dev/null
+++ b/changelogs/unreleased/34867-remove-net-ssh-gem.yml
@@ -0,0 +1,4 @@
+---
+title: Remove net-ssh gem
+merge_request:
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml b/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml
new file mode 100644
index 00000000000..d996ae2826a
--- /dev/null
+++ b/changelogs/unreleased/34927-protect-manual-actions-on-tags.yml
@@ -0,0 +1,4 @@
+---
+title: Protect manual actions against protected tag too
+merge_request: 12908
+author:
diff --git a/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml
new file mode 100644
index 00000000000..25cc8b5e45f
--- /dev/null
+++ b/changelogs/unreleased/34978-remove-public-ci-favicon-ico.yml
@@ -0,0 +1,4 @@
+---
+title: Remove public/ci/favicon.ico
+merge_request: 12803
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml b/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml
new file mode 100644
index 00000000000..9d9558347ba
--- /dev/null
+++ b/changelogs/unreleased/35155-upgrade-fog-core-to-1-44-3-and-its-providers-to-the-latest.yml
@@ -0,0 +1,4 @@
+---
+title: Bump fog-core to 1.44.3 and fog providers' plugins to latest
+merge_request: 12897
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml b/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml
new file mode 100644
index 00000000000..4fd60a79782
--- /dev/null
+++ b/changelogs/unreleased/35163-url-in-commit-message-can-be-broken-in-blame.yml
@@ -0,0 +1,4 @@
+---
+title: Use only CSS to truncate commit message in blame
+merge_request: 12900
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35164-cycle-analytics-firefox.yml b/changelogs/unreleased/35164-cycle-analytics-firefox.yml
new file mode 100644
index 00000000000..0b7115136ca
--- /dev/null
+++ b/changelogs/unreleased/35164-cycle-analytics-firefox.yml
@@ -0,0 +1,4 @@
+---
+title: allow closing Cycle Analytics intro box in firefox
+merge_request:
+author:
diff --git a/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml b/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml
new file mode 100644
index 00000000000..4afe603720d
--- /dev/null
+++ b/changelogs/unreleased/35181-cannot-create-label-from-board-page.yml
@@ -0,0 +1,4 @@
+---
+title: Fix label creation from new list for subgroup projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/35191-prioritized-labels-for-non-member.yml b/changelogs/unreleased/35191-prioritized-labels-for-non-member.yml
new file mode 100644
index 00000000000..fbe55d4c2b0
--- /dev/null
+++ b/changelogs/unreleased/35191-prioritized-labels-for-non-member.yml
@@ -0,0 +1,4 @@
+---
+title: Remove help message about prioritized labels for non-members
+merge_request: 12912
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml b/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml
new file mode 100644
index 00000000000..45b6c57579b
--- /dev/null
+++ b/changelogs/unreleased/35204-doc-api-ci-lint-typo.yml
@@ -0,0 +1,4 @@
+---
+title: Add link to doc/api/ci/lint.md
+merge_request: 12914
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35225-transient-poll.yml b/changelogs/unreleased/35225-transient-poll.yml
new file mode 100644
index 00000000000..59e2e738c7b
--- /dev/null
+++ b/changelogs/unreleased/35225-transient-poll.yml
@@ -0,0 +1,4 @@
+---
+title: fix transient js error in rspec tests
+merge_request:
+author:
diff --git a/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml
new file mode 100644
index 00000000000..9b2a66da1c3
--- /dev/null
+++ b/changelogs/unreleased/35253-desc-protected-branches-for-non-member.yml
@@ -0,0 +1,4 @@
+---
+title: Hide description about protected branches to non-member
+merge_request: 12945
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml b/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml
new file mode 100644
index 00000000000..73808030f4c
--- /dev/null
+++ b/changelogs/unreleased/35338-deploy-keys-should-not-show-pending-delete-projects.yml
@@ -0,0 +1,4 @@
+---
+title: Pending delete projects should not show in deploy keys.
+merge_request: 13088
+author:
diff --git a/changelogs/unreleased/35391-fix-star-i18n-in-js.yml b/changelogs/unreleased/35391-fix-star-i18n-in-js.yml
new file mode 100644
index 00000000000..a6fd4dc89fd
--- /dev/null
+++ b/changelogs/unreleased/35391-fix-star-i18n-in-js.yml
@@ -0,0 +1,4 @@
+---
+title: Fix translations for Star/Unstar in JS file
+merge_request:
+author:
diff --git a/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml b/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml
new file mode 100644
index 00000000000..fa906accbb8
--- /dev/null
+++ b/changelogs/unreleased/35453-pending-delete-projects-error-in-admin-dashboard-fix.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes 500 error caused by pending delete projects in admin dashboard
+merge_request: 13067
+author:
diff --git a/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml b/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml
new file mode 100644
index 00000000000..da4b730f0ca
--- /dev/null
+++ b/changelogs/unreleased/35478-allow-admin-to-read-user-list.yml
@@ -0,0 +1,4 @@
+---
+title: Allow admin to read_users_list even if it's restricted
+merge_request: 13066
+author:
diff --git a/changelogs/unreleased/35539-can-t-create-a-merge-request-containing-a-binary-file-with-non-utf-8-characters.yml b/changelogs/unreleased/35539-can-t-create-a-merge-request-containing-a-binary-file-with-non-utf-8-characters.yml
new file mode 100644
index 00000000000..8d92aacc9ef
--- /dev/null
+++ b/changelogs/unreleased/35539-can-t-create-a-merge-request-containing-a-binary-file-with-non-utf-8-characters.yml
@@ -0,0 +1,5 @@
+---
+title: Fix creating merge request diffs when diff contains bytes that are invalid
+ in UTF-8
+merge_request:
+author:
diff --git a/changelogs/unreleased/5971-webhook-testing.yml b/changelogs/unreleased/5971-webhook-testing.yml
new file mode 100644
index 00000000000..58233091977
--- /dev/null
+++ b/changelogs/unreleased/5971-webhook-testing.yml
@@ -0,0 +1,4 @@
+---
+title: Allow testing any events for project hooks and system hooks
+merge_request: 11728
+author: Alexander Randa (@randaalex)
diff --git a/changelogs/unreleased/adam-external-issue-references-spike.yml b/changelogs/unreleased/adam-external-issue-references-spike.yml
deleted file mode 100644
index aeec6688425..00000000000
--- a/changelogs/unreleased/adam-external-issue-references-spike.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve support for external issue references
-merge_request: 12485
-author:
diff --git a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml b/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
deleted file mode 100644
index 4948d415bed..00000000000
--- a/changelogs/unreleased/add-ci_variables-environment_scope-mysql.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Rename duplicated variables with the same key for projects. Add environment_scope
- column to variables and add unique constraint to make sure that no variables could
- be created with the same key within a project
-merge_request: 12363
-author:
diff --git a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml b/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
deleted file mode 100644
index f2591042e98..00000000000
--- a/changelogs/unreleased/add-group-members-counting-and-plan-related-data-on-namespaces-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add group members counting and plan related data on namespaces API
-merge_request:
-author:
diff --git a/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml b/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml
new file mode 100644
index 00000000000..b5cf521561a
--- /dev/null
+++ b/changelogs/unreleased/add-instrumentation-to-link-to-gfm.yml
@@ -0,0 +1,4 @@
+---
+title: Add instrumentation to MarkupHelper#link_to_gfm
+merge_request: 13069
+author:
diff --git a/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml b/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml
new file mode 100644
index 00000000000..7d47c60e262
--- /dev/null
+++ b/changelogs/unreleased/artifacts-download-dropdown-menu-is-too-narrow.yml
@@ -0,0 +1,4 @@
+---
+title: Increase width of dropdown menus automatically
+merge_request: 12809
+author: Thomas Wucher
diff --git a/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml b/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml
new file mode 100644
index 00000000000..42e1c9e8f83
--- /dev/null
+++ b/changelogs/unreleased/bump-omniauth-ldap-gem-version.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent LDAP login callback from being called with a GET request
+merge_request: 13059
+author:
diff --git a/changelogs/unreleased/bvl-free-unused-names.yml b/changelogs/unreleased/bvl-free-unused-names.yml
new file mode 100644
index 00000000000..53acb95e5bb
--- /dev/null
+++ b/changelogs/unreleased/bvl-free-unused-names.yml
@@ -0,0 +1,5 @@
+---
+title: Free up some top level words, reject top level groups named like files in the
+ public folder
+merge_request: 12932
+author:
diff --git a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml b/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
deleted file mode 100644
index f37f2fa94ae..00000000000
--- a/changelogs/unreleased/bvl-rename-all-reserved-paths.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Rename all reserved paths that could have been created
-merge_request: 11713
-author:
diff --git a/changelogs/unreleased/commit-comments-limited-width.yml b/changelogs/unreleased/commit-comments-limited-width.yml
deleted file mode 100644
index 97f50105495..00000000000
--- a/changelogs/unreleased/commit-comments-limited-width.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit commit & snippets comments width
-merge_request:
-author:
diff --git a/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml b/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml
deleted file mode 100644
index 357a623e0e8..00000000000
--- a/changelogs/unreleased/dashboard-milestone-tabs-loading-async.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed dashboard milestone tabs not loading
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-blob-binaryness-change.yml b/changelogs/unreleased/dm-blob-binaryness-change.yml
deleted file mode 100644
index f3e3af26f12..00000000000
--- a/changelogs/unreleased/dm-blob-binaryness-change.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Detect if file that appears to be text in the first 1024 bytes is actually
- binary afer loading all data
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-commit-row-browse-button.yml b/changelogs/unreleased/dm-commit-row-browse-button.yml
deleted file mode 100644
index 4240a7de5de..00000000000
--- a/changelogs/unreleased/dm-commit-row-browse-button.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix inconsistent display of the "Browse files" button in the commit list
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-diff-viewers.yml b/changelogs/unreleased/dm-diff-viewers.yml
deleted file mode 100644
index e5b1352c8f1..00000000000
--- a/changelogs/unreleased/dm-diff-viewers.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Implement diff viewers
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-empty-state-new-merge-request.yml b/changelogs/unreleased/dm-empty-state-new-merge-request.yml
deleted file mode 100644
index 5fad7a0f883..00000000000
--- a/changelogs/unreleased/dm-empty-state-new-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 'New merge request' button for users who don't have push access to canonical
- project
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-group-page-name.yml b/changelogs/unreleased/dm-group-page-name.yml
deleted file mode 100644
index 233879364e3..00000000000
--- a/changelogs/unreleased/dm-group-page-name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Show group name instead of path on group page
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml b/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml
deleted file mode 100644
index 7fd252e9b8b..00000000000
--- a/changelogs/unreleased/dm-mail-room-check-without-omnibus.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Don't check if MailRoom is running on Omnibus
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-page-image-size.yml b/changelogs/unreleased/dm-page-image-size.yml
deleted file mode 100644
index b18c00470fc..00000000000
--- a/changelogs/unreleased/dm-page-image-size.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit OpenGraph image size to 64x64
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-target-branch-slash-command-desc.yml b/changelogs/unreleased/dm-target-branch-slash-command-desc.yml
deleted file mode 100644
index 768ddf0416e..00000000000
--- a/changelogs/unreleased/dm-target-branch-slash-command-desc.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update /target_branch slash command description to be more consistent
-merge_request:
-author:
diff --git a/changelogs/unreleased/dm-unnecessary-top-padding.yml b/changelogs/unreleased/dm-unnecessary-top-padding.yml
deleted file mode 100644
index 4557c06f8e7..00000000000
--- a/changelogs/unreleased/dm-unnecessary-top-padding.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unnecessary top padding on group MR index
-merge_request:
-author:
diff --git a/changelogs/unreleased/doc-gitaly-network.yml b/changelogs/unreleased/doc-gitaly-network.yml
deleted file mode 100644
index 5376d8d5096..00000000000
--- a/changelogs/unreleased/doc-gitaly-network.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add option to run Gitaly on a remote server
-merge_request: 12381
-author:
diff --git a/changelogs/unreleased/dt-printing-to-api.yml b/changelogs/unreleased/dt-printing-to-api.yml
deleted file mode 100644
index 5253b57f21a..00000000000
--- a/changelogs/unreleased/dt-printing-to-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Added printing_merge_requst_link_enabled to the API
-merge_request:
-author: David Turner <dturner@twosigma.com>
diff --git a/changelogs/unreleased/dz-fix-calendar-today.yml b/changelogs/unreleased/dz-fix-calendar-today.yml
new file mode 100644
index 00000000000..5320d8b26b5
--- /dev/null
+++ b/changelogs/unreleased/dz-fix-calendar-today.yml
@@ -0,0 +1,4 @@
+---
+title: Fix today day highlight in calendar
+merge_request: 13048
+author:
diff --git a/changelogs/unreleased/enable-scss-lint-bang-format.yml b/changelogs/unreleased/enable-scss-lint-bang-format.yml
new file mode 100644
index 00000000000..0b73760198e
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-bang-format.yml
@@ -0,0 +1,4 @@
+---
+title: Enable BangFormat in scss-lint [ci skip]
+merge_request: 12815
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml b/changelogs/unreleased/enable-scss-lint-declaration-order.yml
index 616241dd941..7ac2f55592e 100644
--- a/changelogs/unreleased/dm-relative-submodule-url-trailing-whitespace.yml
+++ b/changelogs/unreleased/enable-scss-lint-declaration-order.yml
@@ -1,4 +1,4 @@
---
-title: Strip trailing whitespace in relative submodule URL
-merge_request:
-author:
+title: Enable DeclarationOrder in scss-lint
+merge_request: 12805
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/enable-scss-lint-import-path.yml b/changelogs/unreleased/enable-scss-lint-import-path.yml
new file mode 100644
index 00000000000..d158cf5b5f3
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-import-path.yml
@@ -0,0 +1,4 @@
+---
+title: Enable ImportPath in scss-lint
+merge_request: 12749
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/enable-scss-lint-property-spelling.yml b/changelogs/unreleased/enable-scss-lint-property-spelling.yml
new file mode 100644
index 00000000000..c5a5a4dddb6
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-property-spelling.yml
@@ -0,0 +1,4 @@
+---
+title: Enable PropertySpelling in scss-lint
+merge_request: 12752
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/enable-scss-lint-space-after-comma.yml b/changelogs/unreleased/enable-scss-lint-space-after-comma.yml
new file mode 100644
index 00000000000..210f34fbb87
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-space-after-comma.yml
@@ -0,0 +1,4 @@
+---
+title: Enable SpaceAfterComma in scss-lint
+merge_request: 12734
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml
new file mode 100644
index 00000000000..59d5df56525
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-unnecessary-parent-reference.yml
@@ -0,0 +1,4 @@
+---
+title: Enable UnnecessaryParentReference in scss-lint
+merge_request: 12738
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/enable-webpack-code-splitting.yml b/changelogs/unreleased/enable-webpack-code-splitting.yml
deleted file mode 100644
index d61c3b97d11..00000000000
--- a/changelogs/unreleased/enable-webpack-code-splitting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable support for webpack code-splitting by dynamically setting publicPath
- at runtime
-merge_request: 12032
-author:
diff --git a/changelogs/unreleased/feature-add-support-for-services-configuration.yml b/changelogs/unreleased/feature-add-support-for-services-configuration.yml
deleted file mode 100644
index 88a3eacd774..00000000000
--- a/changelogs/unreleased/feature-add-support-for-services-configuration.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add support for image and services configuration in .gitlab-ci.yml
-merge_request: 8578
-author:
diff --git a/changelogs/unreleased/feature-backup-custom-path.yml b/changelogs/unreleased/feature-backup-custom-path.yml
new file mode 100644
index 00000000000..1c5f25b3ee5
--- /dev/null
+++ b/changelogs/unreleased/feature-backup-custom-path.yml
@@ -0,0 +1,4 @@
+---
+title: Support custom directory in gitlab:backup:create task
+merge_request: 12984
+author: Markus Koller
diff --git a/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml
new file mode 100644
index 00000000000..bdafc5929c0
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-auto-retry-failed-ci-job.yml
@@ -0,0 +1,4 @@
+---
+title: Allow to configure automatic retry of a failed CI/CD job
+merge_request: 12909
+author:
diff --git a/changelogs/unreleased/feature-gpg-signed-commits.yml b/changelogs/unreleased/feature-gpg-signed-commits.yml
new file mode 100644
index 00000000000..99bc5a309ef
--- /dev/null
+++ b/changelogs/unreleased/feature-gpg-signed-commits.yml
@@ -0,0 +1,4 @@
+---
+title: GPG signed commits integration
+merge_request: 9546
+author: Alexis Reigel
diff --git a/changelogs/unreleased/feature-unify-email-layouts.yml b/changelogs/unreleased/feature-unify-email-layouts.yml
deleted file mode 100644
index 7a2e3f20b6b..00000000000
--- a/changelogs/unreleased/feature-unify-email-layouts.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Update the devise mail templates to match the design of the pipeline emails
-merge_request: 10483
-author: Alexis Reigel
diff --git a/changelogs/unreleased/fix-2801.yml b/changelogs/unreleased/fix-2801.yml
deleted file mode 100644
index 4f0aa06b6ba..00000000000
--- a/changelogs/unreleased/fix-2801.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Expires full_path cache after a repository is renamed/transferred
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-33991.yml b/changelogs/unreleased/fix-33991.yml
deleted file mode 100644
index 39732611b6e..00000000000
--- a/changelogs/unreleased/fix-33991.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Users can subscribe to group labels on the group labels page
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-assigned-issuable-lists.yml b/changelogs/unreleased/fix-assigned-issuable-lists.yml
deleted file mode 100644
index fc2cd18ddb6..00000000000
--- a/changelogs/unreleased/fix-assigned-issuable-lists.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add issuable-list class to shared mr/issue lists to fix new responsive layout
- design
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml b/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml
new file mode 100644
index 00000000000..7a68e91c6d3
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-build-merge-request-link-to-fork-project.yml
@@ -0,0 +1,4 @@
+---
+title: Fix job merge request link to a forked source project
+merge_request: 12965
+author:
diff --git a/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml b/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
deleted file mode 100644
index f59c6ecd90c..00000000000
--- a/changelogs/unreleased/fix-gb-fix-skipped-pipeline-with-allowed-to-fail-jobs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix CI/CD status in case there are only allowed to failed jobs in the pipeline
-merge_request: 11166
-author:
diff --git a/changelogs/unreleased/fix-gb-handle-max-pages-artifacts-size-correctly.yml b/changelogs/unreleased/fix-gb-handle-max-pages-artifacts-size-correctly.yml
new file mode 100644
index 00000000000..3d9592bbf2a
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-handle-max-pages-artifacts-size-correctly.yml
@@ -0,0 +1,4 @@
+---
+title: Handle maximum pages artifacts size correctly
+merge_request: 13072
+author:
diff --git a/changelogs/unreleased/fix-overflow-slash-commands.yml b/changelogs/unreleased/fix-overflow-slash-commands.yml
deleted file mode 100644
index 98ec399e8cb..00000000000
--- a/changelogs/unreleased/fix-overflow-slash-commands.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed overflow on mobile screens for the slash commands
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml b/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
deleted file mode 100644
index 856990a6126..00000000000
--- a/changelogs/unreleased/fix-sidebar-showing-mobile-merge-requests.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed sidebar not collapsing on merge requests in mobile screens
-merge_request:
-author:
diff --git a/changelogs/unreleased/fix-u2f-for-opera.yml b/changelogs/unreleased/fix-u2f-for-opera.yml
deleted file mode 100644
index 0eafb8eff9a..00000000000
--- a/changelogs/unreleased/fix-u2f-for-opera.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix FIDO U2F for Opera browser
-merge_request: 12082
-author: Jakub Kramarz and Jonas Kalderstam
diff --git a/changelogs/unreleased/fix_docs_commits_multiple_files.yml b/changelogs/unreleased/fix_docs_commits_multiple_files.yml
deleted file mode 100644
index 36567354b28..00000000000
--- a/changelogs/unreleased/fix_docs_commits_multiple_files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Documentation bugfix of invalid JSON payload example of Create a commit with
- multiple files and actions
-merge_request: 12117
-author: @blackst0ne
diff --git a/changelogs/unreleased/fixes-for-internal-auth-disabled.yml b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml
new file mode 100644
index 00000000000..188d2770455
--- /dev/null
+++ b/changelogs/unreleased/fixes-for-internal-auth-disabled.yml
@@ -0,0 +1,4 @@
+---
+title: Fixes needed when GitLab sign-in is not enabled
+merge_request: 12491
+author: Robin Bobbitt
diff --git a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml b/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
deleted file mode 100644
index ec2f4f9c3d8..00000000000
--- a/changelogs/unreleased/hb-fix-abuse-report-on-stale-user-profile.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix errors caused by attempts to report already blocked or deleted users
-merge_request: 12502
-author: Horacio Bertorello
diff --git a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml b/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
deleted file mode 100644
index 3b465d84126..00000000000
--- a/changelogs/unreleased/hb-hide-archived-labels-from-group-issue-tracker.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Hide archived project labels from group issue tracker
-merge_request: 12547
-author: Horacio Bertorello
diff --git a/changelogs/unreleased/help-landing-page-customizations.yml b/changelogs/unreleased/help-landing-page-customizations.yml
deleted file mode 100644
index 58cab751ded..00000000000
--- a/changelogs/unreleased/help-landing-page-customizations.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Help landing page customizations
-merge_request: 11878
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml b/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml
deleted file mode 100644
index 05d52fcad0f..00000000000
--- a/changelogs/unreleased/issuable-sidebar-edit-button-field-focus.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed dropdown filter input not focusing after transition
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_20900.yml b/changelogs/unreleased/issue_20900.yml
deleted file mode 100644
index e8cef6d2bce..00000000000
--- a/changelogs/unreleased/issue_20900.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove issues/merge requests drag n drop and sorting from milestone view
-merge_request:
-author:
diff --git a/changelogs/unreleased/issue_33205.yml b/changelogs/unreleased/issue_33205.yml
deleted file mode 100644
index 54b442048d8..00000000000
--- a/changelogs/unreleased/issue_33205.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix API bug accepting wrong parameter to create merge request
-merge_request:
-author:
diff --git a/changelogs/unreleased/issueable-list-cleanup.yml b/changelogs/unreleased/issueable-list-cleanup.yml
deleted file mode 100644
index d3d67d04574..00000000000
--- a/changelogs/unreleased/issueable-list-cleanup.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Clean up UI of issuable lists and make more responsive
-merge_request:
-author:
diff --git a/changelogs/unreleased/karma-headless-chrome.yml b/changelogs/unreleased/karma-headless-chrome.yml
deleted file mode 100644
index af3e9b3b0f9..00000000000
--- a/changelogs/unreleased/karma-headless-chrome.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace PhantomJS with headless Chrome for karma test suite
-merge_request: 12036
-author:
diff --git a/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml b/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml
new file mode 100644
index 00000000000..80e6c50d5b3
--- /dev/null
+++ b/changelogs/unreleased/mk-add-ldap-ssl-certificate-verification.yml
@@ -0,0 +1,4 @@
+---
+title: Add LDAP SSL certificate verification option
+merge_request:
+author:
diff --git a/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml b/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml
new file mode 100644
index 00000000000..37a5fa66d13
--- /dev/null
+++ b/changelogs/unreleased/mk-add-lower-path-index-to-redirect-routes.yml
@@ -0,0 +1,4 @@
+---
+title: Improve redirect route query performance
+merge_request: 13062
+author:
diff --git a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml b/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
deleted file mode 100644
index 8a0e9ca855c..00000000000
--- a/changelogs/unreleased/monitoring-dashboard-fix-y-label.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
-merge_request:
-author:
diff --git a/changelogs/unreleased/moved-submodules.yml b/changelogs/unreleased/moved-submodules.yml
deleted file mode 100644
index eee858717ed..00000000000
--- a/changelogs/unreleased/moved-submodules.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: 'Handle renamed submodules in repository browser'
-merge_request: 10798
-author: David Turner
diff --git a/changelogs/unreleased/mr-branch-link-use-tree.yml b/changelogs/unreleased/mr-branch-link-use-tree.yml
new file mode 100644
index 00000000000..f4c4d9f5082
--- /dev/null
+++ b/changelogs/unreleased/mr-branch-link-use-tree.yml
@@ -0,0 +1,4 @@
+---
+title: MR branch link now links to tree instead of commits
+merge_request:
+author:
diff --git a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml b/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
deleted file mode 100644
index 14b5493a246..00000000000
--- a/changelogs/unreleased/mr-widget-memory-usage-tech-debt-fix.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Changed utilities imports from ~ to relative paths
-merge_request:
-author:
diff --git a/changelogs/unreleased/new-navigation-custom-logo.yml b/changelogs/unreleased/new-navigation-custom-logo.yml
new file mode 100644
index 00000000000..22e6c5dc7e5
--- /dev/null
+++ b/changelogs/unreleased/new-navigation-custom-logo.yml
@@ -0,0 +1,4 @@
+---
+title: Fix sizing of custom header logo in new navigation
+merge_request:
+author:
diff --git a/changelogs/unreleased/pass-before-script-as-is.yml b/changelogs/unreleased/pass-before-script-as-is.yml
new file mode 100644
index 00000000000..ac6513dcff6
--- /dev/null
+++ b/changelogs/unreleased/pass-before-script-as-is.yml
@@ -0,0 +1,4 @@
+---
+title: Pass before_script and script as-is preserving arrays
+merge_request:
+author:
diff --git a/changelogs/unreleased/pat-alert-when-signin-disabled.yml b/changelogs/unreleased/pat-alert-when-signin-disabled.yml
deleted file mode 100644
index dca3670aeb7..00000000000
--- a/changelogs/unreleased/pat-alert-when-signin-disabled.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Provide hint to create a personal access token for Git over HTTP
-merge_request: 12105
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/pat-msg-on-auth-failure.yml b/changelogs/unreleased/pat-msg-on-auth-failure.yml
deleted file mode 100644
index c1b1528bb7a..00000000000
--- a/changelogs/unreleased/pat-msg-on-auth-failure.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Instruct user to use personal access token for Git over HTTP
-merge_request: 11986
-author: Robin Bobbitt
diff --git a/changelogs/unreleased/pawel-ensure_temp_files_are_deleted_in_fs_metrics-35457.yml b/changelogs/unreleased/pawel-ensure_temp_files_are_deleted_in_fs_metrics-35457.yml
new file mode 100644
index 00000000000..1186dc59dc7
--- /dev/null
+++ b/changelogs/unreleased/pawel-ensure_temp_files_are_deleted_in_fs_metrics-35457.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure filesystem metrics test files are deleted
+merge_request:
+author:
diff --git a/changelogs/unreleased/pawel-prometheus_client_pid_reuse_error.yml b/changelogs/unreleased/pawel-prometheus_client_pid_reuse_error.yml
new file mode 100644
index 00000000000..dfff4c23308
--- /dev/null
+++ b/changelogs/unreleased/pawel-prometheus_client_pid_reuse_error.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Prometheus client PID reuse bug
+merge_request: 13130
+author:
diff --git a/changelogs/unreleased/polish-sidebar-toggle.yml b/changelogs/unreleased/polish-sidebar-toggle.yml
deleted file mode 100644
index 41ec567fc52..00000000000
--- a/changelogs/unreleased/polish-sidebar-toggle.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Remove unused space in sidebar todo toggle when not signed in
-merge_request:
-author:
diff --git a/changelogs/unreleased/post-upload-pack-opt-out.yml b/changelogs/unreleased/post-upload-pack-opt-out.yml
new file mode 100644
index 00000000000..302a99795a0
--- /dev/null
+++ b/changelogs/unreleased/post-upload-pack-opt-out.yml
@@ -0,0 +1,4 @@
+---
+title: Enable gitaly_post_upload_pack by default
+merge_request: 13078
+author:
diff --git a/changelogs/unreleased/project-readme-limited-width.yml b/changelogs/unreleased/project-readme-limited-width.yml
deleted file mode 100644
index 17d87a5691e..00000000000
--- a/changelogs/unreleased/project-readme-limited-width.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Limit the width of the projects README text
-merge_request:
-author:
diff --git a/changelogs/unreleased/rc-fix-branches-api-endpoint.yml b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml
new file mode 100644
index 00000000000..a8f49298258
--- /dev/null
+++ b/changelogs/unreleased/rc-fix-branches-api-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the /projects/:id/repository/branches endpoint to handle dots in the branch
+ name when the project full patch contains a `/`
+merge_request: 13115
+author:
diff --git a/changelogs/unreleased/remove-nprogress-gleaning.yml b/changelogs/unreleased/remove-nprogress-gleaning.yml
new file mode 100644
index 00000000000..78e4dc82dd4
--- /dev/null
+++ b/changelogs/unreleased/remove-nprogress-gleaning.yml
@@ -0,0 +1,4 @@
+---
+title: Remove CSS for nprogress removed
+merge_request: 12737
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/replace_spinach_spec_browse_files.yml b/changelogs/unreleased/replace_spinach_spec_browse_files.yml
new file mode 100644
index 00000000000..7380d39fa9f
--- /dev/null
+++ b/changelogs/unreleased/replace_spinach_spec_browse_files.yml
@@ -0,0 +1,4 @@
+---
+title: Replace 'browse_files.feature' spinach test with an rspec analog
+merge_request: 12251
+author: @blackst0ne
diff --git a/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml b/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
deleted file mode 100644
index 38227ebfa7a..00000000000
--- a/changelogs/unreleased/replace_spinach_spec_profile_notifications-feature.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'profile/notifications.feature' spinach test with an rspec analog
-merge_request: 12345
-author: @blackst0ne
diff --git a/changelogs/unreleased/replase_spinach_spec_create-feature.yml b/changelogs/unreleased/replase_spinach_spec_create-feature.yml
deleted file mode 100644
index 0613d195d56..00000000000
--- a/changelogs/unreleased/replase_spinach_spec_create-feature.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Replace 'create.feature' spinach test with an rspec analog
-merge_request: 12343
-author: @blackst0ne
diff --git a/changelogs/unreleased/request-store-wrap.yml b/changelogs/unreleased/request-store-wrap.yml
new file mode 100644
index 00000000000..8017054b77b
--- /dev/null
+++ b/changelogs/unreleased/request-store-wrap.yml
@@ -0,0 +1,4 @@
+---
+title: Add RequestCache which makes caching with RequestStore easier
+merge_request: 12920
+author:
diff --git a/changelogs/unreleased/sh-allow-force-repo-create.yml b/changelogs/unreleased/sh-allow-force-repo-create.yml
deleted file mode 100644
index 2a65ba807bb..00000000000
--- a/changelogs/unreleased/sh-allow-force-repo-create.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make Project#ensure_repository force create a repo
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-bump-oauth2-gem.yml b/changelogs/unreleased/sh-bump-oauth2-gem.yml
deleted file mode 100644
index b894a64968b..00000000000
--- a/changelogs/unreleased/sh-bump-oauth2-gem.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Bump Faraday and dependent OAuth2 gem version to support no_proxy variable
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml b/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
deleted file mode 100644
index 9309f961345..00000000000
--- a/changelogs/unreleased/sh-fix-project-destroy-in-namespace.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Defer project destroys within a namespace in Groups::DestroyService#async_execute
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-optimize-project-commit-api.yml b/changelogs/unreleased/sh-optimize-project-commit-api.yml
deleted file mode 100644
index e6a8a80593c..00000000000
--- a/changelogs/unreleased/sh-optimize-project-commit-api.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
-merge_request:
-author:
diff --git a/changelogs/unreleased/sh-structured-logging.yml b/changelogs/unreleased/sh-structured-logging.yml
new file mode 100644
index 00000000000..d89eb93f689
--- /dev/null
+++ b/changelogs/unreleased/sh-structured-logging.yml
@@ -0,0 +1,4 @@
+---
+title: Add structured logging for Rails processes
+merge_request:
+author:
diff --git a/changelogs/unreleased/speed-up-graphs.yml b/changelogs/unreleased/speed-up-graphs.yml
deleted file mode 100644
index 7cb155af6fd..00000000000
--- a/changelogs/unreleased/speed-up-graphs.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Speed up used languages calculation on charts page
-merge_request:
-author:
diff --git a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml b/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
deleted file mode 100644
index 6bf03d9a382..00000000000
--- a/changelogs/unreleased/speed-up-issue-counting-for-a-project.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache open issue and merge request counts for project tabs to speed up project
- pages
-merge_request: 12457
-author:
diff --git a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml b/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
deleted file mode 100644
index 7e66ea4ca8b..00000000000
--- a/changelogs/unreleased/stop-notification-recipient-service-modifying-participants.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure participants for issues, merge requests, etc. are calculated correctly
- when sending notifications
-merge_request:
-author:
diff --git a/changelogs/unreleased/tc-issue-api-assignee.yml b/changelogs/unreleased/tc-issue-api-assignee.yml
new file mode 100644
index 00000000000..8d6360d5baf
--- /dev/null
+++ b/changelogs/unreleased/tc-issue-api-assignee.yml
@@ -0,0 +1,4 @@
+---
+title: Add author_id & assignee_id param to /issues API
+merge_request: 13004
+author:
diff --git a/changelogs/unreleased/tc-link-to-commit-on-help-page.yml b/changelogs/unreleased/tc-link-to-commit-on-help-page.yml
deleted file mode 100644
index 3d11ba43d1f..00000000000
--- a/changelogs/unreleased/tc-link-to-commit-on-help-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Make the revision on the `/help` page clickable
-merge_request: 12016
-author:
diff --git a/changelogs/unreleased/tc-no-delete-protected-merged-branches.yml b/changelogs/unreleased/tc-no-delete-protected-merged-branches.yml
deleted file mode 100644
index 663a0349bac..00000000000
--- a/changelogs/unreleased/tc-no-delete-protected-merged-branches.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Do not delete protected branches when deleting all merged branches
-merge_request: 12624
-author:
diff --git a/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml b/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
deleted file mode 100644
index 7bcbd6468c7..00000000000
--- a/changelogs/unreleased/tc-refactor-projects-finder-init-collection.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add User#full_private_access? to check if user has access to all private groups & projects
-merge_request: 12373
-author:
diff --git a/changelogs/unreleased/toggle-new-project-import-description.yml b/changelogs/unreleased/toggle-new-project-import-description.yml
new file mode 100644
index 00000000000..8f0d09e0540
--- /dev/null
+++ b/changelogs/unreleased/toggle-new-project-import-description.yml
@@ -0,0 +1,4 @@
+---
+title: Toggle import description with import_sources_enabled
+merge_request: 12691
+author: Brianna Kicia \ No newline at end of file
diff --git a/changelogs/unreleased/update_bootsnap_1-1-1.yml b/changelogs/unreleased/update_bootsnap_1-1-1.yml
deleted file mode 100644
index 9ecfe4b60c8..00000000000
--- a/changelogs/unreleased/update_bootsnap_1-1-1.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Bump bootsnap to 1.1.1
-merge_request: 12425
-author: @blackst0ne
diff --git a/changelogs/unreleased/zj-commit-status-sortable-name.yml b/changelogs/unreleased/zj-commit-status-sortable-name.yml
deleted file mode 100644
index 1be9ac6380f..00000000000
--- a/changelogs/unreleased/zj-commit-status-sortable-name.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Handle nameless legacy jobs
-merge_request:
-author:
diff --git a/changelogs/unreleased/zj-faster-charts-page.yml b/changelogs/unreleased/zj-faster-charts-page.yml
deleted file mode 100644
index 9afcf111328..00000000000
--- a/changelogs/unreleased/zj-faster-charts-page.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Improve performance of the pipeline charts page
-merge_request: 12378
-author:
diff --git a/changelogs/unreleased/zj-pipeline-badge-improvements.yml b/changelogs/unreleased/zj-pipeline-badge-improvements.yml
new file mode 100644
index 00000000000..735192ede2d
--- /dev/null
+++ b/changelogs/unreleased/zj-pipeline-badge-improvements.yml
@@ -0,0 +1,4 @@
+---
+title: Update build badges to be pipeline badges and display passing instead of success
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-review-apps-usage-data.yml b/changelogs/unreleased/zj-review-apps-usage-data.yml
deleted file mode 100644
index 7d224d0fc32..00000000000
--- a/changelogs/unreleased/zj-review-apps-usage-data.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add review apps to usage metrics
-merge_request: 12185
-author:
diff --git a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml b/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
deleted file mode 100644
index 0ace7b99657..00000000000
--- a/changelogs/unreleased/zj-usage-ping-only-gl-pipelines.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Split pipelines as internal and external in the usage data
-merge_request: 12277
-author:
diff --git a/config/README.md b/config/README.md
index 0a5ea2424e0..2778d0d4f02 100644
--- a/config/README.md
+++ b/config/README.md
@@ -19,4 +19,132 @@ an ERB file and then loads the resulting YML as its configuration.
This file is called `resque.yml` for historical reasons. We are **NOT**
using Resque at the moment. It is used to specify Redis configuration
-values instead.
+values when a single database instance of Redis is desired.
+
+# Advanced Redis configuration files
+
+In more advanced configurations of Redis key-value storage, it is desirable
+to separate the keys by lifecycle and intended use to ease provisioning and
+management of scalable Redis clusters.
+
+These settings provide routing and other configuration data (such as sentinel,
+persistence policies, and other Redis customization) for connections
+to Redis single instances, Redis sentinel, and Redis clusters.
+
+If desired, the routing URL provided by these settings can be used with:
+1. Unix Socket
+ 1. named socket for each Redis instance desired.
+ 2. `database number` for each Redis instance desired.
+2. TCP Socket
+ 1. `host name` or IP for each Redis instance desired
+ 2. TCP port number for each Redis instance desired
+ 3. `database number` for each Redis instance desired
+
+## Example URL attribute formats for GitLab Redis `.yml` configuration files
+* Unix Socket, default Redis database (0)
+ * `url: unix:/path/to/redis.sock`
+ * `url: unix:/path/to/redis.sock?db=`
+* Unix Socket, Redis database 44
+ * `url: unix:/path/to/redis.sock?db=44`
+ * `url: unix:/path/to/redis.sock?extra=foo&db=44`
+* TCP Socket for Redis on localhost, port 6379, database 33
+ * `url: redis://:mynewpassword@localhost:6379/33`
+* TCP Socket for Redis on remote host `myserver`, port 6379, database 33
+ * `url: redis://:mynewpassword@myserver:6379/33`
+
+## redis.cache.yml
+
+If configured, `redis.cache.yml` overrides the
+`resque.yml` settings to configure the Redis database instance
+used for `Rails.cache` and other volatile non-persistent data which enhances
+the performance of GitLab.
+Settings here can be overridden by the environment variable
+`GITLAB_REDIS_CACHE_CONFIG_FILE` which provides
+an alternate location for configuration settings.
+
+The order of precedence for the URL used to connect to the Redis instance
+used for `cache` is:
+1. URL from a configuration file pointed to by the
+`GITLAB_REDIS_CACHE_CONFIG_FILE` environment variable
+2. URL from `redis.cache.yml`
+3. URL from a configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. URL from `resque.yml`
+5. `redis://localhost:6380`
+
+The order of precedence for all other configuration settings for `cache`
+are selected from only the first of the following files found (if a setting
+is not provided in an earlier file, the remainder of the files are not
+searched):
+1. the configuration file pointed to by the
+`GITLAB_REDIS_CACHE_CONFIG_FILE` environment variable
+2. the configuration file `redis.cache.yml`
+3. the configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. the configuration file `resque.yml`
+
+## redis.queues.yml
+
+If configured, `redis.queues.yml` overrides the
+`resque.yml` settings to configure the Redis database instance
+used for clients of `::Gitlab::Redis::Queues`.
+These queues are intended to be the foundation
+of reliable inter-process communication between modules, whether on the same
+host node, or within a cluster. The primary clients of the queues are
+SideKiq, Mailroom, CI Runner, Workhorse, and push services. Settings here can
+be overridden by the environment variable
+`GITLAB_REDIS_QUEUES_CONFIG_FILE` which provides an alternate location for
+configuration settings.
+
+The order of precedence for the URL used to connect to the Redis instance
+used for `queues` is:
+1. URL from a configuration file pointed to by the
+`GITLAB_REDIS_QUEUES_CONFIG_FILE` environment variable
+2. URL from `redis.queues.yml`
+3. URL from a configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. URL from `resque.yml`
+5. `redis://localhost:6381`
+
+The order of precedence for all other configuration settings for `queues`
+are selected from only the first of the following files found (if a setting
+is not provided in an earlier file, the remainder of the files are not
+searched):
+1. the configuration file pointed to by the
+`GITLAB_REDIS_QUEUES_CONFIG_FILE` environment variable
+2. the configuration file `redis.queues.yml`
+3. the configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. the configuration file `resque.yml`
+
+## redis.shared_state.yml
+
+If configured, `redis.shared_state.yml` overrides the
+`resque.yml` settings to configure the Redis database instance
+used for clients of `::Gitlab::Redis::SharedState` such as session state,
+and rate limiting.
+Settings here can be overridden by the environment variable
+`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` which provides
+an alternate location for configuration settings.
+
+The order of precedence for the URL used to connect to the Redis instance
+used for `shared_state` is:
+1. URL from a configuration file pointed to by the
+`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` environment variable
+2. URL from `redis.shared_state.yml`
+3. URL from a configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. URL from `resque.yml`
+5. `redis://localhost:6382`
+
+The order of precedence for all other configuration settings for `shared_state`
+are selected from only the first of the following files found (if a setting
+is not provided in an earlier file, the remainder of the files are not
+searched):
+1. the configuration file pointed to by the
+`GITLAB_REDIS_SHARED_STATE_CONFIG_FILE` environment variable
+2. the configuration file `redis.shared_state.yml`
+3. the configuration file pointed to by the
+`GITLAB_REDIS_CONFIG_FILE` environment variable
+4. the configuration file `resque.yml`
+
diff --git a/config/application.rb b/config/application.rb
index 3f39170a123..1c13cc81270 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -6,7 +6,9 @@ Bundler.require(:default, Rails.env)
module Gitlab
class Application < Rails::Application
- require_dependency Rails.root.join('lib/gitlab/redis')
+ require_dependency Rails.root.join('lib/gitlab/redis/cache')
+ require_dependency Rails.root.join('lib/gitlab/redis/queues')
+ require_dependency Rails.root.join('lib/gitlab/redis/shared_state')
require_dependency Rails.root.join('lib/gitlab/request_context')
# Settings in config/environments/* take precedence over those specified here.
@@ -26,7 +28,8 @@ module Gitlab
#{config.root}/app/models/members
#{config.root}/app/models/project_services
#{config.root}/app/workers/concerns
- #{config.root}/app/services/concerns))
+ #{config.root}/app/services/concerns
+ #{config.root}/app/finders/concerns))
config.generators.templates.push("#{config.root}/generator_templates")
@@ -105,7 +108,7 @@ module Gitlab
config.assets.precompile << "katex.css"
config.assets.precompile << "katex.js"
config.assets.precompile << "xterm/xterm.css"
- config.assets.precompile << "peek.css"
+ config.assets.precompile << "performance_bar.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css"
@@ -141,15 +144,15 @@ module Gitlab
end
end
- # Use Redis caching across all environments
- redis_config_hash = Gitlab::Redis.params
- redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
- redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
+ # Use caching across all environments
+ caching_config_hash = Gitlab::Redis::Cache.params
+ caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE
+ caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
if Sidekiq.server? # threaded context
- redis_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
- redis_config_hash[:pool_timeout] = 1
+ caching_config_hash[:pool_size] = Sidekiq.options[:concurrency] + 5
+ caching_config_hash[:pool_timeout] = 1
end
- config.cache_store = :redis_store, redis_config_hash
+ config.cache_store = :redis_store, caching_config_hash
config.active_record.raise_in_transactional_callbacks = true
@@ -162,5 +165,23 @@ module Gitlab
config.generators do |g|
g.factory_girl false
end
+
+ config.after_initialize do
+ Rails.application.reload_routes!
+
+ project_url_helpers = Module.new do
+ extend ActiveSupport::Concern
+
+ Gitlab::Application.routes.named_routes.helper_names.each do |name|
+ next unless name.include?('namespace_project')
+
+ define_method(name.sub('namespace_project', 'project')) do |project, *args|
+ send(name, project&.namespace, project, *args)
+ end
+ end
+ end
+
+ Gitlab::Routing.add_helpers(project_url_helpers)
+ end
end
end
diff --git a/config/boot.rb b/config/boot.rb
index 02baeab29ab..f2830ae3166 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -4,14 +4,3 @@ require 'rubygems'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-
-begin
- require 'bootsnap/setup'
-rescue SystemCallError => exception
- $stderr.puts "WARNING: Bootsnap failed to setup: #{exception.message}"
-end
-
-# set default directory for multiproces metrics gathering
-if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test'
- ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
-end
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
index db1b712d3bc..eb71d3f5fe1 100644
--- a/config/database.yml.mysql
+++ b/config/database.yml.mysql
@@ -42,3 +42,4 @@ test: &test
password:
# host: localhost
# socket: /tmp/mysql.sock
+ prepared_statements: false
diff --git a/config/database.yml.postgresql b/config/database.yml.postgresql
index c517a4c0cb8..4b30982fe82 100644
--- a/config/database.yml.postgresql
+++ b/config/database.yml.postgresql
@@ -46,3 +46,4 @@ test: &test
username: postgres
password:
# host: localhost
+ prepared_statements: false
diff --git a/config/environments/test.rb b/config/environments/test.rb
index c3b788c038e..278144b8943 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -43,4 +43,9 @@ Rails.application.configure do
config.cache_store = :null_store
config.active_job.queue_adapter = :test
+
+ if ENV['CI'] && !ENV['RAILS_ENABLE_TEST_LOG']
+ config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
+ config.log_level = :fatal
+ end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 43a8c0078ca..e9bf2df490f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -228,7 +228,8 @@ production: &base
# ==========================
## LDAP settings
- # You can inspect a sample of the LDAP users with login access by running:
+ # You can test connections and inspect a sample of the LDAP users with login
+ # access by running:
# bundle exec rake gitlab:ldap:check RAILS_ENV=production
ldap:
enabled: false
@@ -251,13 +252,45 @@ production: &base
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'
+ # Example: 'ldap.mydomain.com'
host: '_your_ldap_server'
- port: 389
- uid: 'sAMAccountName'
- method: 'plain' # "tls" or "ssl" or "plain"
+ # This port is an example, it is sometimes different but it is always an integer and not a string
+ port: 389 # usually 636 for SSL
+ uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
+
+ # Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ # Encryption method. The "method" key is deprecated in favor of
+ # "encryption".
+ #
+ # Examples: "start_tls" or "simple_tls" or "plain"
+ #
+ # Deprecated values: "tls" was replaced with "start_tls" and "ssl" was
+ # replaced with "simple_tls".
+ #
+ encryption: 'plain'
+
+ # Enables SSL certificate verification if encryption method is
+ # "start_tls" or "simple_tls". (Defaults to false for backward-
+ # compatibility)
+ verify_certificates: false
+
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_cert: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
# A value of 0 means there is no timeout.
@@ -286,17 +319,20 @@ production: &base
# Base where we can search for users
#
- # Ex. ou=People,dc=gitlab,dc=example
+ # Ex. 'ou=People,dc=gitlab,dc=example' or 'DC=mydomain,DC=com'
#
base: ''
# Filter LDAP users
#
- # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Format: RFC 4515 https://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
+ # Example for getting only specific users:
+ # '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+ #
user_filter: ''
# LDAP attributes that GitLab will use to create an account for the LDAP user.
@@ -383,13 +419,13 @@ production: &base
# service_validate_url: '/cas/p3/serviceValidate',
# logout_url: '/cas/logout'} }
# - { name: 'authentiq',
- # # for client credentials (client ID and secret), go to https://www.authentiq.com/
+ # # for client credentials (client ID and secret), go to https://www.authentiq.com/developers
# app_id: 'YOUR_CLIENT_ID',
# app_secret: 'YOUR_CLIENT_SECRET',
# args: {
# scope: 'aq:name email~rs address aq:push'
- # # redirect_uri parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
- # # redirect_uri: 'YOUR_REDIRECT_URI'
+ # # callback_url parameter is optional except when 'gitlab.host' in this file is set to 'localhost'
+ # # callback_url: 'YOUR_CALLBACK_URL'
# }
# }
# - { name: 'github',
@@ -450,10 +486,6 @@ production: &base
# Gitaly settings
gitaly:
- # This setting controls whether GitLab uses Gitaly (new component
- # introduced in 9.0). Eventually Gitaly use will become mandatory and
- # this option will disappear.
- enabled: true
# Default Gitaly authentication token. Can be overriden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly.
@@ -544,6 +576,15 @@ production: &base
# host: localhost
# port: 3808
+ ## Monitoring
+ # Built in monitoring settings
+ monitoring:
+ # Time between sampling of unicorn socket metrics, in seconds
+ # unicorn_sampler_interval: 10
+ # IP whitelist to access monitoring endpoints
+ ip_whitelist:
+ - 127.0.0.0/8
+
#
# 5. Extra customization
# ==========================
@@ -615,6 +656,52 @@ test:
title: "JIRA"
url: https://sample_company.atlassian.net
project_key: PROJECT
+
+ omniauth:
+ enabled: true
+ allow_single_sign_on: true
+ external_providers: []
+
+ providers:
+ - { name: 'cas3',
+ label: 'cas3',
+ args: { url: 'https://sso.example.com',
+ disable_ssl_verification: false,
+ login_url: '/cas/login',
+ service_validate_url: '/cas/p3/serviceValidate',
+ logout_url: '/cas/logout'} }
+ - { name: 'github',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.com/",
+ verify_ssl: false,
+ args: { scope: 'user:email' } }
+ - { name: 'bitbucket',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'gitlab',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ - { name: 'google_oauth2',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ - { name: 'facebook',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'twitter',
+ app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ - { name: 'auth0',
+ args: {
+ client_id: 'YOUR_AUTH0_CLIENT_ID',
+ client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ namespace: 'YOUR_AUTH0_DOMAIN' } }
+ - { name: 'authentiq',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET',
+ args: { scope: 'aq:name email~rs address aq:push' } }
ldap:
enabled: false
servers:
@@ -623,7 +710,7 @@ test:
host: 127.0.0.1
port: 3890
uid: 'uid'
- method: 'plain' # "tls" or "ssl" or "plain"
+ encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
base: 'dc=example,dc=com'
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8ddf8e4d2e4..02d3161f769 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -145,6 +145,24 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['attributes'] = {} if server['attributes'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+
+ # For backwards compatibility
+ server['encryption'] ||= server['method']
+ server['encryption'] = 'simple_tls' if server['encryption'] == 'ssl'
+ server['encryption'] = 'start_tls' if server['encryption'] == 'tls'
+
+ # Certificates are not verified for backwards compatibility.
+ # This default should be flipped to true in 9.5.
+ if server['verify_certificates'].nil?
+ server['verify_certificates'] = false
+
+ message = <<-MSG.strip_heredoc
+ LDAP SSL certificate verification is disabled for backwards-compatibility.
+ Please add the "verify_certificates" option to gitlab.yml for each LDAP
+ server. Certificate verification will be enabled by default in GitLab 9.5.
+ MSG
+ Rails.logger.warn(message)
+ end
end
end
@@ -223,7 +241,7 @@ rescue ArgumentError # no user configured
end
Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil?
-Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
+Settings.gitlab['password_authentication_enabled'] ||= true if Settings.gitlab['password_authentication_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.__send__(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)' if Settings.gitlab['issue_closing_pattern'].nil?
@@ -441,10 +459,6 @@ Settings.backup['pg_schema'] = nil
Settings.backup['path'] = Settings.absolute(Settings.backup['path'] || "tmp/backups/")
Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
-# Convert upload connection settings to use symbol keys, to make Fog happy
-if Settings.backup['upload']['connection']
- Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
-end
Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
Settings.backup['upload']['encryption'] ||= nil
Settings.backup['upload']['storage_class'] ||= nil
@@ -483,7 +497,6 @@ Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour
# Gitaly
#
Settings['gitaly'] ||= Settingslogic.new({})
-Settings.gitaly['enabled'] = true if Settings.gitaly['enabled'].nil?
#
# Webpack settings
@@ -495,6 +508,13 @@ Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808
#
+# Monitoring settings
+#
+Settings['monitoring'] ||= Settingslogic.new({})
+Settings.monitoring['ip_whitelist'] ||= ['127.0.0.1/8']
+Settings.monitoring['unicorn_sampler_interval'] ||= 10
+
+#
# Testing settings
#
if Rails.env.test?
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
new file mode 100644
index 00000000000..a2f8421f5d7
--- /dev/null
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -0,0 +1,12 @@
+require 'prometheus/client'
+
+Prometheus::Client.configure do |config|
+ config.logger = Rails.logger
+
+ config.initial_mmap_file_size = 4 * 1024
+ config.multiprocess_files_dir = ENV['prometheus_multiproc_dir']
+
+ if Rails.env.development? || Rails.env.test?
+ config.multiprocess_files_dir ||= Rails.root.join('tmp/prometheus_multiproc_dir')
+ end
+end
diff --git a/config/initializers/7_redis.rb b/config/initializers/7_redis.rb
index ae2ca258df1..af4967521b8 100644
--- a/config/initializers/7_redis.rb
+++ b/config/initializers/7_redis.rb
@@ -1,3 +1,8 @@
-# Make sure we initialize a Redis connection pool before Sidekiq starts
-# multi-threaded execution.
-Gitlab::Redis.with { nil }
+# Make sure we initialize a Redis connection pool before multi-threaded
+# execution starts by
+# 1. Sidekiq
+# 2. Rails.cache
+# 3. HTTP clients
+Gitlab::Redis::Cache.with { nil }
+Gitlab::Redis::Queues.with { nil }
+Gitlab::Redis::SharedState.with { nil }
diff --git a/config/initializers/8_gitaly.rb b/config/initializers/8_gitaly.rb
index 31c7c91d78f..f4f116e67f7 100644
--- a/config/initializers/8_gitaly.rb
+++ b/config/initializers/8_gitaly.rb
@@ -1,8 +1,6 @@
require 'uri'
-if Gitlab.config.gitaly.enabled || Rails.env.test?
- Gitlab.config.repositories.storages.keys.each do |storage|
- # Force validation of each address
- Gitlab::GitalyClient.address(storage)
- end
+Gitlab.config.repositories.storages.keys.each do |storage|
+ # Force validation of each address
+ Gitlab::GitalyClient.address(storage)
end
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index a0a63ddf8f0..2aeb94d47cd 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -114,11 +114,21 @@ def instrument_classes(instrumentation)
# This is a Rails scope so we have to instrument it manually.
instrumentation.instrument_method(Project, :visible_to_user)
+ # Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/34509
+ instrumentation.instrument_method(MarkupHelper, :link_to_gfm)
+
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
end
# rubocop:enable Metrics/AbcSize
+Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_sampler_interval).start
+
+Gitlab::Application.configure do |config|
+ # 0 should be Sentry to catch errors in this middleware
+ config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
+end
+
if Gitlab::Metrics.enabled?
require 'pathname'
require 'influxdb'
@@ -167,6 +177,10 @@ if Gitlab::Metrics.enabled?
loc && loc[0].start_with?(models) && method.source =~ regex
end
end
+
+ # Ability is in app/models, is not an ActiveRecord model, but should still
+ # be instrumented.
+ Gitlab::Metrics::Instrumentation.instrument_methods(Ability)
end
Gitlab::Metrics::Instrumentation.configure do |config|
@@ -175,7 +189,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable
- Gitlab::Metrics::Sampler.new.start
+ Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections
def connect(*args)
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index a5636765774..8e2e639fc41 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -87,9 +87,7 @@ Doorkeeper.configure do
# "password" => Resource Owner Password Credentials Grant Flow
# "client_credentials" => Client Credentials Grant Flow
#
- # If not specified, Doorkeeper enables all the four grant flows.
- #
- grant_flows %w(authorization_code password client_credentials)
+ grant_flows %w(authorization_code implicit password client_credentials)
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index 4ff9019c43c..c58f425b19b 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -29,7 +29,7 @@ Doorkeeper::OpenidConnect.configure do
o.claim(:email) { |user| user.public_email }
o.claim(:email_verified) { |user| true if user.public_email? }
o.claim(:website) { |user| user.full_website_url if user.website_url? }
- o.claim(:profile) { |user| Rails.application.routes.url_helpers.user_url user }
+ o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user }
o.claim(:picture) { |user| user.avatar_url(only_path: false) }
end
end
diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb
index 0fee832788d..bfab8c77a4b 100644
--- a/config/initializers/flipper.rb
+++ b/config/initializers/flipper.rb
@@ -1,4 +1,8 @@
require 'flipper/middleware/memoizer'
-Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
- lambda { Feature.flipper }
+unless Rails.env.test?
+ Rails.application.config.middleware.use Flipper::Middleware::Memoizer,
+ lambda { Feature.flipper }
+
+ Feature.register_feature_groups
+end
diff --git a/config/initializers/gettext_rails_i18n_patch.rb b/config/initializers/gettext_rails_i18n_patch.rb
index 69118f464ca..377e5104f9d 100644
--- a/config/initializers/gettext_rails_i18n_patch.rb
+++ b/config/initializers/gettext_rails_i18n_patch.rb
@@ -33,7 +33,6 @@ module GettextI18nRailsJs
[
".js",
".jsx",
- ".coffee",
".vue"
].include? ::File.extname(file)
end
diff --git a/config/initializers/grape_route_helpers_fix.rb b/config/initializers/grape_route_helpers_fix.rb
new file mode 100644
index 00000000000..d3cf9e453d0
--- /dev/null
+++ b/config/initializers/grape_route_helpers_fix.rb
@@ -0,0 +1,35 @@
+if defined?(GrapeRouteHelpers)
+ module GrapeRouteHelpers
+ class DecoratedRoute
+ # GrapeRouteHelpers gem tries to parse the versions
+ # from a string, not supporting Grape `version` array definition.
+ #
+ # Without the following fix, we get this on route helpers generation:
+ #
+ # => undefined method `scan' for ["v3", "v4"]
+ #
+ # 2.0.0 implementation of this method:
+ #
+ # ```
+ # def route_versions
+ # version_pattern = /[^\[",\]\s]+/
+ # if route_version
+ # route_version.scan(version_pattern)
+ # else
+ # [nil]
+ # end
+ # end
+ # ```
+ def route_versions
+ return [nil] if route_version.nil? || route_version.empty?
+
+ if route_version.is_a?(String)
+ version_pattern = /[^\[",\]\s]+/
+ route_version.scan(version_pattern)
+ else
+ route_version
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
new file mode 100644
index 00000000000..c9a515dfcd5
--- /dev/null
+++ b/config/initializers/lograge.rb
@@ -0,0 +1,21 @@
+# Only use Lograge for Rails
+unless Sidekiq.server?
+ filename = File.join(Rails.root, 'log', "#{Rails.env}_json.log")
+
+ Rails.application.configure do
+ config.lograge.enabled = true
+ # Store the lograge JSON files in a separate file
+ config.lograge.keep_original_rails_log = true
+ # Don't use the Logstash formatter since this requires logstash-event, an
+ # unmaintained gem that monkey patches `Time`
+ config.lograge.formatter = Lograge::Formatters::Json.new
+ config.lograge.logger = ActiveSupport::Logger.new(filename)
+ # Add request parameters to log output
+ config.lograge.custom_options = lambda do |event|
+ {
+ time: event.time.utc.iso8601(3),
+ params: event.payload[:params].except(%w(controller action format))
+ }
+ end
+ end
+end
diff --git a/config/initializers/mysql_set_length_for_binary_indexes.rb b/config/initializers/mysql_set_length_for_binary_indexes.rb
new file mode 100644
index 00000000000..de0bc5322aa
--- /dev/null
+++ b/config/initializers/mysql_set_length_for_binary_indexes.rb
@@ -0,0 +1,21 @@
+# This patches ActiveRecord so indexes for binary columns created using the
+# MySQL adapter apply a length of 20. Otherwise MySQL can't create an index on
+# binary columns.
+
+module MysqlSetLengthForBinaryIndex
+ def add_index(table_name, column_names, options = {})
+ Array(column_names).each do |column_name|
+ column = ActiveRecord::Base.connection.columns(table_name).find { |c| c.name == column_name }
+
+ if column&.type == :binary
+ options[:length] = 20
+ end
+ end
+
+ super(table_name, column_names, options)
+ end
+end
+
+if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:prepend, MysqlSetLengthForBinaryIndex)
+end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index f7fa6d1c2de..a36e59c941a 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -16,7 +16,7 @@ OmniAuth.config.allowed_request_methods = [:post]
# In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env|
- OmniAuth::RequestForgeryProtection.call(env)
+ Gitlab::RequestForgeryProtection.call(env)
end
if Gitlab.config.omniauth.enabled
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index 65432caac2a..a54d53cbbe2 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -1,4 +1,4 @@
-Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis.params) }
+Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
Peek.into Peek::Views::Host
Peek.into Peek::Views::PerformanceBar
@@ -26,7 +26,3 @@ class PEEK_DB_CLIENT
end
PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker
-
-class Peek::Views::PerformanceBar::ProcessUtilization
- prepend ::Gitlab::PerformanceBar::PeekPerformanceBarWithRackBody
-end
diff --git a/config/initializers/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb
index 03ac55be0b6..d9d3034150f 100644
--- a/config/initializers/relative_naming_ci_namespace.rb
+++ b/config/initializers/relative_naming_ci_namespace.rb
@@ -4,10 +4,10 @@
# - [project.namespace, project, build]
#
# instead of:
-# - namespace_project_job_path(project.namespace, project, build)
+# - project_job_path(project, build)
#
# Without that, Ci:: namespace is used for resolving routes:
-# - namespace_project_ci_build_path(project.namespace, project, build)
+# - project_ci_build_path(project, build)
module Ci
def self.use_relative_model_naming?
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 8919f7640fe..e8213ac8ba4 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -19,12 +19,12 @@ cookie_key = if Rails.env.development?
if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else
- redis_config = Gitlab::Redis.params
- redis_config[:namespace] = Gitlab::Redis::SESSION_NAMESPACE
+ sessions_config = Gitlab::Redis::SharedState.params
+ sessions_config[:namespace] = Gitlab::Redis::SharedState::SESSION_NAMESPACE
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
- servers: redis_config,
+ servers: sessions_config,
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index ecd73956488..a1cc9655319 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,12 +1,12 @@
-# Custom Redis configuration
-redis_config_hash = Gitlab::Redis.params
-redis_config_hash[:namespace] = Gitlab::Redis::SIDEKIQ_NAMESPACE
+# Custom Queues configuration
+queues_config_hash = Gitlab::Redis::Queues.params
+queues_config_hash[:namespace] = Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE
# Default is to retry 25 times with exponential backoff. That's too much.
Sidekiq.default_worker_options = { retry: 3 }
Sidekiq.configure_server do |config|
- config.redis = redis_config_hash
+ config.redis = queues_config_hash
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
@@ -54,7 +54,7 @@ Sidekiq.configure_server do |config|
end
Sidekiq.configure_client do |config|
- config.redis = redis_config_hash
+ config.redis = queues_config_hash
config.client_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ClientMiddleware
@@ -74,5 +74,5 @@ begin
end
end
end
-rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
+rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EADDRNOTAVAIL, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED
end
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 88d93d4bc6b..c3a5be8d38c 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -21,7 +21,7 @@
:delivery_method: sidekiq
:delivery_options:
:redis_url: <%= config[:redis_url].to_json %>
- :namespace: <%= Gitlab::Redis::SIDEKIQ_NAMESPACE %>
+ :namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %>
:queue: email_receiver
:worker: EmailReceiverWorker
<% if config[:sentinels] %>
@@ -36,7 +36,7 @@
:arbitration_method: redis
:arbitration_options:
:redis_url: <%= config[:redis_url].to_json %>
- :namespace: <%= Gitlab::Redis::MAILROOM_NAMESPACE %>
+ :namespace: <%= Gitlab::Redis::Queues::MAILROOM_NAMESPACE %>
<% if config[:sentinels] %>
:sentinels:
<% config[:sentinels].each do |sentinel| %>
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index daecde49570..61d39e7bfcf 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -1,32 +1,103 @@
-- group: Kubernetes
- priority: 1
+- group: HA Proxy
+ priority: 10
metrics:
- - title: "Memory usage"
- y_label: "Values"
+ - title: "Throughput"
+ y_label: "Requests / Sec"
required_metrics:
- - container_memory_usage_bytes
+ - haproxy_frontend_http_requests_total
weight: 1
queries:
- - query_range: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
- label: Container memory
- unit: MiB
- - title: "Current memory usage"
+ - query_range: 'sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m]))'
+ label: Total
+ unit: req / sec
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
required_metrics:
- - container_memory_usage_bytes
+ - haproxy_frontend_http_responses_total
weight: 1
queries:
- - query: 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20'
- display_empty: false
- unit: MiB
- - title: "CPU usage"
+ - query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
+ label: HTTP Errors
+ unit: "%"
+- group: AWS Elastic Load Balancer
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
required_metrics:
- - container_cpu_usage_seconds_total
+ - aws_elb_request_count_sum
+ weight: 1
+ queries:
+ - query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
+ label: Total
+ unit: req / sec
+ - title: "Latency"
+ y_label: "Latency (ms)"
+ required_metrics:
+ - aws_elb_latency_average
+ weight: 1
+ queries:
+ - query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
+ label: Average
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
+ required_metrics:
+ - aws_elb_request_count_sum
+ - aws_elb_httpcode_backend_5_xx_sum
+ weight: 1
+ queries:
+ - query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
+ label: HTTP Errors
+ unit: "%"
+- group: NGINX
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - nginx_requests_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
+ label: Total
+ unit: req / sec
+ - title: "Latency"
+ y_label: "Latency (ms)"
+ required_metrics:
+ - nginx_upstream_response_msecs_avg
+ weight: 1
+ queries:
+ - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000'
+ label: Upstream
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "Error Rate (%)"
+ required_metrics:
+ - nginx_responses_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) / sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m]))'
+ label: HTTP Errors
+ unit: "%"
+- group: Kubernetes
+ priority: 5
+ metrics:
+ - title: "Memory Usage"
+ y_label: "Memory Usage (MB)"
+ required_metrics:
+ - container_memory_usage_bytes
weight: 1
queries:
- - query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
- - title: "Current CPU usage"
+ - query_range: '(sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024'
+ label: Average
+ unit: MB
+ - title: "CPU Utilization"
+ y_label: "CPU Utilization (%)"
required_metrics:
- container_cpu_usage_seconds_total
weight: 1
queries:
- - query: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100'
+ - query_range: 'sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100'
+ label: Average
+ unit: "%"
diff --git a/config/redis.cache.yml.example b/config/redis.cache.yml.example
new file mode 100644
index 00000000000..27478f0a93e
--- /dev/null
+++ b/config/redis.cache.yml.example
@@ -0,0 +1,38 @@
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
+development:
+ url: redis://localhost:6379/10
+ #
+ # url: redis://localhost:6380
+ # sentinels:
+ # -
+ # host: localhost
+ # port: 26380 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26380 # point to sentinel, not to redis port
+test:
+ url: redis://localhost:6379/10
+ #
+ # url: redis://localhost:6380
+production:
+ # Redis (single instance)
+ url: unix:/var/run/redis/redis.cache.sock
+ ##
+ # Redis + Sentinel (for HA)
+ #
+ # Please read instructions carefully before using it as you may lose data:
+ # http://redis.io/topics/sentinel
+ #
+ # You must specify a list of a few sentinels that will handle client connection
+ # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html
+ ##
+ # url: redis://master:6380
+ # sentinels:
+ # -
+ # host: slave1
+ # port: 26380 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26380 # point to sentinel, not to redis port
diff --git a/config/redis.queues.yml.example b/config/redis.queues.yml.example
new file mode 100644
index 00000000000..dab1f26b096
--- /dev/null
+++ b/config/redis.queues.yml.example
@@ -0,0 +1,38 @@
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
+development:
+ url: redis://localhost:6379/11
+ #
+ # url: redis://localhost:6381
+ # sentinels:
+ # -
+ # host: localhost
+ # port: 26381 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26381 # point to sentinel, not to redis port
+test:
+ url: redis://localhost:6379/11
+ #
+ # url: redis://localhost:6381
+production:
+ # Redis (single instance)
+ url: unix:/var/run/redis/redis.queues.sock
+ ##
+ # Redis + Sentinel (for HA)
+ #
+ # Please read instructions carefully before using it as you may lose data:
+ # http://redis.io/topics/sentinel
+ #
+ # You must specify a list of a few sentinels that will handle client connection
+ # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html
+ ##
+ # url: redis://master:6381
+ # sentinels:
+ # -
+ # host: slave1
+ # port: 26381 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26381 # point to sentinel, not to redis port
diff --git a/config/redis.shared_state.yml.example b/config/redis.shared_state.yml.example
new file mode 100644
index 00000000000..9371e3619b7
--- /dev/null
+++ b/config/redis.shared_state.yml.example
@@ -0,0 +1,38 @@
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
+development:
+ url: redis://localhost:6379/12
+ #
+ # url: redis://localhost:6382
+ # sentinels:
+ # -
+ # host: localhost
+ # port: 26382 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26382 # point to sentinel, not to redis port
+test:
+ url: redis://localhost:6379/12
+ #
+ # url: redis://localhost:6382
+production:
+ # Redis (single instance)
+ url: unix:/var/run/redis/redis.shared_state.sock
+ ##
+ # Redis + Sentinel (for HA)
+ #
+ # Please read instructions carefully before using it as you may lose data:
+ # http://redis.io/topics/sentinel
+ #
+ # You must specify a list of a few sentinels that will handle client connection
+ # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html
+ ##
+ # url: redis://master:6382
+ # sentinels:
+ # -
+ # host: slave1
+ # port: 26382 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26382 # point to sentinel, not to redis port
diff --git a/config/routes/api.rb b/config/routes/api.rb
index 69c8efc151c..ce7a7c88900 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,2 +1,2 @@
API::API.logger Rails.logger
-mount API::API => '/api'
+mount API::API => '/'
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 11cdff55ed8..23052a6c6dc 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -12,7 +12,7 @@ scope(path: 'groups/*group_id',
end
resource :avatar, only: [:destroy]
- resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] do
+ resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do
member do
get :merge_requests
get :participants
@@ -23,6 +23,14 @@ scope(path: 'groups/*group_id',
resources :labels, except: [:show] do
post :toggle_subscription, on: :member
end
+
+ scope path: '-' do
+ namespace :settings do
+ resource :ci_cd, only: [:show], controller: 'ci_cd'
+ end
+
+ resources :variables, only: [:index, :show, :update, :create, :destroy]
+ end
end
scope(path: 'groups/*id',
diff --git a/config/routes/legacy_builds.rb b/config/routes/legacy_builds.rb
new file mode 100644
index 00000000000..5ab2b953ce1
--- /dev/null
+++ b/config/routes/legacy_builds.rb
@@ -0,0 +1,22 @@
+resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
+ collection do
+ resources :artifacts, only: [], controller: 'build_artifacts' do
+ collection do
+ get :latest_succeeded,
+ path: '*ref_name_and_path',
+ format: false
+ end
+ end
+ end
+
+ member do
+ get :raw
+ end
+
+ resource :artifacts, only: [], controller: 'build_artifacts' do
+ get :download
+ get :browse, path: 'browse(/*path)', format: false
+ get :file, path: 'file/*path', format: false
+ get :raw, path: 'raw/*path', format: false
+ end
+end
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index 3dc890e5785..3e4e6111ab8 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -23,6 +23,11 @@ resource :profile, only: [:show, :update] do
end
resource :preferences, only: [:show, :update]
resources :keys, only: [:index, :show, :create, :destroy]
+ resources :gpg_keys, only: [:index, :create, :destroy] do
+ member do
+ put :revoke
+ end
+ end
resources :emails, only: [:index, :create, :destroy]
resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 0d0a8dff25e..06928c7b9ce 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -1,5 +1,4 @@
require 'constraints/project_url_constrainer'
-require 'gitlab/routes/legacy_builds'
resources :projects, only: [:index, :new, :create]
@@ -253,7 +252,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- Gitlab::Routes::LegacyBuilds.new(self).draw
+ draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
member do
@@ -273,7 +272,7 @@ constraints(ProjectUrlConstrainer.new) do
namespace :registry do
resources :repository, only: [] do
resources :tags, only: [:destroy],
- constraints: { id: Gitlab::Regex.container_registry_reference_regex }
+ constraints: { id: Gitlab::Regex.container_registry_tag_regex }
end
end
@@ -380,14 +379,16 @@ constraints(ProjectUrlConstrainer.new) do
collection do
scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
constraints format: /svg/ do
- get :build
+ # Keep around until 10.0, see gitlab-org/gitlab-ce#35307
+ get :build, to: "badges#pipeline"
+ get :pipeline
get :coverage
end
end
end
end
namespace :settings do
- resource :members, only: [:show]
+ get :members, to: redirect('/%{namespace_id}/%{project_id}/project_members')
resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 11911636fa7..edcf3ddf57b 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -76,6 +76,8 @@ scope format: false do
get '/tree/*id', to: 'tree#show', as: :tree
get '/raw/*id', to: 'raw#show', as: :raw
get '/blame/*id', to: 'blame#show', as: :blame
+
+ get '/commits/*id/signatures', to: 'commits#signatures', as: :signatures
get '/commits/*id', to: 'commits#show', as: :commits
post '/create_dir/*id', to: 'tree#create_dir', as: :create_dir
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index a49e244af1a..e9c9aa8b2f9 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -1,21 +1,21 @@
scope path: :uploads do
# Note attachments and User/Group/Project avatars
- get "system/:model/:mounted_as/:id/:filename",
+ get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
# show uploads for models, snippets (notes) available for now
- get ':model/:id/:secret/:filename',
+ get 'system/:model/:id/:secret/:filename',
to: 'uploads#show',
constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ }
# show temporary uploads
- get 'temp/:secret/:filename',
+ get 'system/temp/:secret/:filename',
to: 'uploads#show',
constraints: { filename: /[^\/]+/ }
# Appearance
- get "system/:model/:mounted_as/:id/:filename",
+ get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ }
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 1d9e69a2408..7496bfa4fbb 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -29,6 +29,8 @@
- [email_receiver, 2]
- [emails_on_push, 2]
- [mailers, 2]
+ - [invalid_gpg_signature_update, 2]
+ - [create_gpg_signature, 2]
- [upload_checksum, 1]
- [use_key, 1]
- [repository_fork, 1]
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 40a16a32359..cc10da2bd88 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -121,6 +121,10 @@ after_fork do |server, worker|
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
+ # reset prometheus client, this will cause any opened metrics files to be closed
+ defined?(::Prometheus::Client.reinitialize_on_pid_change) &&
+ Prometheus::Client.reinitialize_on_pid_change
+
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
diff --git a/config/webpack.config.js b/config/webpack.config.js
index cbb0a899638..f08daa2fddb 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -42,6 +42,7 @@ var config = {
group: './group.js',
groups: './groups/index.js',
groups_list: './groups_list.js',
+ how_to_merge: './how_to_merge.js',
issue_show: './issue_show/index.js',
integrations: './integrations',
job_details: './jobs/job_details_bundle.js',
@@ -56,7 +57,7 @@ var config = {
pipelines_details: './pipelines/pipeline_details_bundle.js',
profile: './profile/profile_bundle.js',
prometheus_metrics: './prometheus_metrics',
- protected_branches: './protected_branches/protected_branches_bundle.js',
+ protected_branches: './protected_branches',
protected_tags: './protected_tags',
sidebar: './sidebar/sidebar_bundle.js',
schedule_form: './pipeline_schedules/pipeline_schedule_form_bundle.js',
@@ -66,11 +67,11 @@ var config = {
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'],
- users: './users/users_bundle.js',
+ users: './users/index.js',
raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
- peek: './peek.js',
+ performance_bar: './performance_bar.js',
webpack_runtime: './webpack.js',
},
diff --git a/db/migrate/20160804142904_add_ci_config_file_to_project.rb b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
new file mode 100644
index 00000000000..341ae555c1b
--- /dev/null
+++ b/db/migrate/20160804142904_add_ci_config_file_to_project.rb
@@ -0,0 +1,11 @@
+class AddCiConfigFileToProject < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :ci_config_path, :string
+ end
+
+ def down
+ remove_column :projects, :ci_config_path
+ end
+end
diff --git a/db/migrate/20170222111732_create_gpg_keys.rb b/db/migrate/20170222111732_create_gpg_keys.rb
new file mode 100644
index 00000000000..541228e8735
--- /dev/null
+++ b/db/migrate/20170222111732_create_gpg_keys.rb
@@ -0,0 +1,19 @@
+class CreateGpgKeys < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :gpg_keys do |t|
+ t.timestamps_with_timezone null: false
+
+ t.references :user, index: true, foreign_key: { on_delete: :cascade }
+
+ t.binary :primary_keyid
+ t.binary :fingerprint
+
+ t.text :key
+
+ t.index :primary_keyid, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
+ t.index :fingerprint, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
+ end
+ end
+end
diff --git a/db/migrate/20170428064307_add_column_delete_error_to_projects.rb b/db/migrate/20170428064307_add_column_delete_error_to_projects.rb
new file mode 100644
index 00000000000..09f9d9b5b7a
--- /dev/null
+++ b/db/migrate/20170428064307_add_column_delete_error_to_projects.rb
@@ -0,0 +1,7 @@
+class AddColumnDeleteErrorToProjects < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :delete_error, :text
+ end
+end
diff --git a/db/migrate/20170525130346_create_group_variables_table.rb b/db/migrate/20170525130346_create_group_variables_table.rb
new file mode 100644
index 00000000000..eaa38dbc40d
--- /dev/null
+++ b/db/migrate/20170525130346_create_group_variables_table.rb
@@ -0,0 +1,23 @@
+class CreateGroupVariablesTable < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ create_table :ci_group_variables do |t|
+ t.string :key, null: false
+ t.text :value
+ t.text :encrypted_value
+ t.string :encrypted_value_salt
+ t.string :encrypted_value_iv
+ t.integer :group_id, null: false
+ t.boolean :protected, default: false, null: false
+
+ t.timestamps_with_timezone null: false
+ end
+
+ add_index :ci_group_variables, [:group_id, :key], unique: true
+ end
+
+ def down
+ drop_table :ci_group_variables
+ end
+end
diff --git a/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb b/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb
new file mode 100644
index 00000000000..0146235c5ba
--- /dev/null
+++ b/db/migrate/20170525130758_add_foreign_key_to_group_variables.rb
@@ -0,0 +1,15 @@
+class AddForeignKeyToGroupVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :ci_group_variables, :namespaces, column: :group_id
+ end
+
+ def down
+ remove_foreign_key :ci_group_variables, column: :group_id
+ end
+end
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
new file mode 100644
index 00000000000..3eaafac321d
--- /dev/null
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -0,0 +1,187 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ CONCURRENCY = 4
+
+ disable_ddl_transaction!
+
+ # The tables/columns for which to remove orphans and add foreign keys. Order
+ # matters as some tables/columns should be processed before others.
+ TABLES = [
+ [:boards, :projects, :project_id],
+ [:lists, :labels, :label_id],
+ [:lists, :boards, :board_id],
+ [:services, :projects, :project_id],
+ [:forked_project_links, :projects, :forked_to_project_id],
+ [:merge_requests, :projects, :target_project_id],
+ [:labels, :projects, :project_id],
+ [:issues, :projects, :project_id],
+ [:events, :projects, :project_id],
+ [:milestones, :projects, :project_id],
+ [:notes, :projects, :project_id],
+ [:snippets, :projects, :project_id],
+ [:web_hooks, :projects, :project_id],
+ [:protected_branch_merge_access_levels, :protected_branches, :protected_branch_id],
+ [:protected_branch_push_access_levels, :protected_branches, :protected_branch_id],
+ [:protected_branches, :projects, :project_id],
+ [:protected_tags, :projects, :project_id],
+ [:deploy_keys_projects, :projects, :project_id],
+ [:users_star_projects, :projects, :project_id],
+ [:releases, :projects, :project_id],
+ [:project_group_links, :projects, :project_id],
+ [:pages_domains, :projects, :project_id],
+ [:todos, :projects, :project_id],
+ [:project_import_data, :projects, :project_id],
+ [:project_features, :projects, :project_id],
+ [:ci_builds, :projects, :project_id],
+ [:ci_pipelines, :projects, :project_id],
+ [:ci_runner_projects, :projects, :project_id],
+ [:ci_triggers, :projects, :project_id],
+ [:environments, :projects, :project_id],
+ [:deployments, :projects, :project_id]
+ ]
+
+ def up
+ # These existing foreign keys don't have an "ON DELETE CASCADE" clause.
+ remove_foreign_key_without_error(:boards, :project_id)
+ remove_foreign_key_without_error(:lists, :label_id)
+ remove_foreign_key_without_error(:lists, :board_id)
+ remove_foreign_key_without_error(:protected_branch_merge_access_levels,
+ :protected_branch_id)
+
+ remove_foreign_key_without_error(:protected_branch_push_access_levels,
+ :protected_branch_id)
+
+ remove_orphaned_rows
+ add_foreign_keys
+
+ # These columns are not indexed yet, meaning a cascading delete would take
+ # forever.
+ add_concurrent_index(:project_group_links, :project_id)
+ add_concurrent_index(:pages_domains, :project_id)
+ end
+
+ def down
+ TABLES.each do |(source, _, column)|
+ remove_foreign_key_without_error(source, column)
+ end
+
+ add_concurrent_foreign_key(:boards, :projects, column: :project_id)
+ add_concurrent_foreign_key(:lists, :labels, column: :label_id)
+ add_concurrent_foreign_key(:lists, :boards, column: :board_id)
+
+ add_concurrent_foreign_key(:protected_branch_merge_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+
+ add_concurrent_foreign_key(:protected_branch_push_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+
+ remove_index_without_error(:project_group_links, :project_id)
+ remove_index_without_error(:pages_domains, :project_id)
+ end
+
+ def add_foreign_keys
+ TABLES.each do |(source, target, column)|
+ add_concurrent_foreign_key(source, target, column: column)
+ end
+ end
+
+ # Removes orphans from various tables concurrently.
+ def remove_orphaned_rows
+ Gitlab::Database.with_connection_pool(CONCURRENCY) do |pool|
+ queues = queues_for_rows(TABLES)
+
+ threads = queues.map do |queue|
+ Thread.new do
+ pool.with_connection do |connection|
+ Thread.current[:foreign_key_connection] = connection
+
+ # Disables statement timeouts for the current connection. This is
+ # necessary as removing of orphaned data might otherwise exceed the
+ # statement timeout.
+ disable_statement_timeout
+
+ remove_orphans(*queue.pop) until queue.empty?
+
+ steal_from_queues(queues - [queue])
+ end
+ end
+ end
+
+ threads.each(&:join)
+ end
+ end
+
+ def steal_from_queues(queues)
+ loop do
+ stolen = false
+
+ queues.each do |queue|
+ # Stealing is racy so it's possible a pop might be called on an
+ # already-empty queue.
+ begin
+ remove_orphans(*queue.pop(true))
+ stolen = true
+ rescue ThreadError
+ end
+ end
+
+ break unless stolen
+ end
+ end
+
+ def remove_orphans(source, target, column)
+ quoted_source = quote_table_name(source)
+ quoted_target = quote_table_name(target)
+ quoted_column = quote_column_name(column)
+
+ execute <<-EOF.strip_heredoc
+ DELETE FROM #{quoted_source}
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM #{quoted_target}
+ WHERE #{quoted_target}.id = #{quoted_source}.#{quoted_column}
+ )
+ AND #{quoted_source}.#{quoted_column} IS NOT NULL
+ EOF
+ end
+
+ def remove_foreign_key_without_error(table, column)
+ remove_foreign_key(table, column: column)
+ rescue ArgumentError
+ end
+
+ def remove_index_without_error(table, column)
+ remove_concurrent_index(table, column)
+ rescue ArgumentError
+ end
+
+ def connection
+ # Rails memoizes connection objects, but this causes them to be shared
+ # amongst threads; we don't want that.
+ Thread.current[:foreign_key_connection] || ActiveRecord::Base.connection
+ end
+
+ def queues_for_rows(rows)
+ queues = Array.new(CONCURRENCY) { Queue.new }
+ slice_size = rows.length / CONCURRENCY
+
+ # Divide all the tuples as evenly as possible amongst the queues.
+ rows.each_slice(slice_size).each_with_index do |slice, index|
+ bucket = index % CONCURRENCY
+
+ slice.each do |row|
+ queues[bucket] << row
+ end
+ end
+
+ queues
+ end
+end
diff --git a/db/migrate/20170613154149_create_gpg_signatures.rb b/db/migrate/20170613154149_create_gpg_signatures.rb
new file mode 100644
index 00000000000..f6b5e7ebb7b
--- /dev/null
+++ b/db/migrate/20170613154149_create_gpg_signatures.rb
@@ -0,0 +1,23 @@
+class CreateGpgSignatures < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :gpg_signatures do |t|
+ t.timestamps_with_timezone null: false
+
+ t.references :project, index: true, foreign_key: { on_delete: :cascade }
+ t.references :gpg_key, index: true, foreign_key: { on_delete: :nullify }
+
+ t.boolean :valid_signature
+
+ t.binary :commit_sha
+ t.binary :gpg_key_primary_keyid
+
+ t.text :gpg_key_user_name
+ t.text :gpg_key_user_email
+
+ t.index :commit_sha, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
+ t.index :gpg_key_primary_keyid, length: Gitlab::Database.mysql? ? 20 : nil
+ end
+ end
+end
diff --git a/db/migrate/20170616133147_create_merge_request_diff_commits.rb b/db/migrate/20170616133147_create_merge_request_diff_commits.rb
new file mode 100644
index 00000000000..616464cb470
--- /dev/null
+++ b/db/migrate/20170616133147_create_merge_request_diff_commits.rb
@@ -0,0 +1,20 @@
+class CreateMergeRequestDiffCommits < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :merge_request_diff_commits, id: false do |t|
+ t.datetime_with_timezone :authored_date
+ t.datetime_with_timezone :committed_date
+ t.belongs_to :merge_request_diff, null: false, foreign_key: { on_delete: :cascade }
+ t.integer :relative_order, null: false
+ t.binary :sha, null: false, limit: 20
+ t.text :author_name
+ t.text :author_email
+ t.text :committer_name
+ t.text :committer_email
+ t.text :message
+
+ t.index [:merge_request_diff_id, :relative_order], name: 'index_merge_request_diff_commits_on_mr_diff_id_and_order', unique: true
+ end
+ end
+end
diff --git a/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb b/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..92833765a82
--- /dev/null
+++ b/db/migrate/20170620064728_create_ci_pipeline_schedule_variables.rb
@@ -0,0 +1,25 @@
+class CreateCiPipelineScheduleVariables < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ create_table :ci_pipeline_schedule_variables do |t|
+ t.string :key, null: false
+ t.text :value
+ t.text :encrypted_value
+ t.string :encrypted_value_salt
+ t.string :encrypted_value_iv
+ t.integer :pipeline_schedule_id, null: false
+
+ t.timestamps_with_timezone null: true
+ end
+
+ add_index :ci_pipeline_schedule_variables,
+ [:pipeline_schedule_id, :key],
+ name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key",
+ unique: true
+ end
+
+ def down
+ drop_table :ci_pipeline_schedule_variables
+ end
+end
diff --git a/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb b/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..7bbf66e0ac3
--- /dev/null
+++ b/db/migrate/20170620065449_add_foreign_key_to_ci_pipeline_schedule_variables.rb
@@ -0,0 +1,15 @@
+class AddForeignKeyToCiPipelineScheduleVariables < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(:ci_pipeline_schedule_variables, :ci_pipeline_schedules, column: :pipeline_schedule_id)
+ end
+
+ def down
+ remove_foreign_key(:ci_pipeline_schedule_variables, column: :pipeline_schedule_id)
+ end
+end
diff --git a/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb
new file mode 100644
index 00000000000..46497775527
--- /dev/null
+++ b/db/migrate/20170622130029_correct_protected_branches_foreign_keys.rb
@@ -0,0 +1,40 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CorrectProtectedBranchesForeignKeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_foreign_key_without_error(:protected_branch_push_access_levels,
+ column: :protected_branch_id)
+
+ execute <<-EOF
+ DELETE FROM protected_branch_push_access_levels
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM protected_branches
+ WHERE protected_branch_push_access_levels.protected_branch_id = protected_branches.id
+ )
+ AND protected_branch_id IS NOT NULL
+ EOF
+
+ add_concurrent_foreign_key(:protected_branch_push_access_levels,
+ :protected_branches,
+ column: :protected_branch_id)
+ end
+
+ def down
+ # Previously there was a foreign key without a CASCADING DELETE, so we'll
+ # just leave the foreign key in place.
+ end
+
+ def remove_foreign_key_without_error(*args)
+ remove_foreign_key(*args)
+ rescue ArgumentError
+ end
+end
diff --git a/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb
new file mode 100644
index 00000000000..9f524fac8a7
--- /dev/null
+++ b/db/migrate/20170622132212_add_foreign_key_for_merge_request_diffs.rb
@@ -0,0 +1,30 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddForeignKeyForMergeRequestDiffs < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ execute <<-EOF
+ DELETE FROM merge_request_diffs
+ WHERE NOT EXISTS (
+ SELECT true
+ FROM merge_requests
+ WHERE merge_requests.id = merge_request_diffs.merge_request_id
+ )
+ EOF
+
+ add_concurrent_foreign_key(:merge_request_diffs,
+ :merge_requests,
+ column: :merge_request_id)
+ end
+
+ def down
+ remove_foreign_key(:merge_request_diffs, column: :merge_request_id)
+ end
+end
diff --git a/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb
new file mode 100644
index 00000000000..858b3bebace
--- /dev/null
+++ b/db/migrate/20170629171610_rename_application_settings_signin_enabled_to_password_authentication_enabled.rb
@@ -0,0 +1,15 @@
+class RenameApplicationSettingsSigninEnabledToPasswordAuthenticationEnabled < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :application_settings, :signin_enabled, :password_authentication_enabled
+ end
+
+ def down
+ cleanup_concurrent_column_rename :application_settings, :password_authentication_enabled, :signin_enabled
+ end
+end
diff --git a/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
new file mode 100644
index 00000000000..68b947583d3
--- /dev/null
+++ b/db/migrate/20170703102400_add_stage_id_foreign_key_to_builds.rb
@@ -0,0 +1,35 @@
+class AddStageIdForeignKeyToBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ unless index_exists?(:ci_builds, :stage_id)
+ add_concurrent_index(:ci_builds, :stage_id)
+ end
+
+ unless foreign_key_exists?(:ci_builds, :stage_id)
+ add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
+ end
+ end
+
+ def down
+ if foreign_key_exists?(:ci_builds, :stage_id)
+ remove_foreign_key(:ci_builds, column: :stage_id)
+ end
+
+ if index_exists?(:ci_builds, :stage_id)
+ remove_concurrent_index(:ci_builds, :stage_id)
+ end
+ end
+
+ private
+
+ def foreign_key_exists?(table, column)
+ foreign_keys(:ci_builds).any? do |key|
+ key.options[:column] == column.to_s
+ end
+ end
+end
diff --git a/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb b/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb
new file mode 100644
index 00000000000..fe9970ddc71
--- /dev/null
+++ b/db/migrate/20170706151212_add_performance_bar_allowed_group_id_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddPerformanceBarAllowedGroupIdToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :performance_bar_allowed_group_id, :integer
+ end
+end
diff --git a/db/migrate/20170707183807_add_group_id_to_milestones.rb b/db/migrate/20170707183807_add_group_id_to_milestones.rb
new file mode 100644
index 00000000000..675ffd4a1c9
--- /dev/null
+++ b/db/migrate/20170707183807_add_group_id_to_milestones.rb
@@ -0,0 +1,20 @@
+class AddGroupIdToMilestones < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ return if column_exists? :milestones, :group_id
+
+ change_column_null :milestones, :project_id, true
+
+ add_column :milestones, :group_id, :integer
+ end
+
+ def down
+ # We cannot rollback project_id not null constraint if there are records
+ # with null values.
+ execute "DELETE from milestones WHERE project_id IS NULL"
+
+ remove_column :milestones, :group_id
+ change_column :milestones, :project_id, :integer, null: false
+ end
+end
diff --git a/db/migrate/20170707184243_add_group_milestone_id_indexes.rb b/db/migrate/20170707184243_add_group_milestone_id_indexes.rb
new file mode 100644
index 00000000000..aa48fe90cad
--- /dev/null
+++ b/db/migrate/20170707184243_add_group_milestone_id_indexes.rb
@@ -0,0 +1,21 @@
+class AddGroupMilestoneIdIndexes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ return if index_exists?(:milestones, :group_id)
+
+ add_concurrent_foreign_key :milestones, :namespaces, column: :group_id, on_delete: :cascade
+
+ add_concurrent_index :milestones, :group_id
+ end
+
+ def down
+ remove_foreign_key :milestones, column: :group_id
+
+ remove_concurrent_index :milestones, :group_id
+ end
+end
diff --git a/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb b/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb
new file mode 100644
index 00000000000..38536a8b06a
--- /dev/null
+++ b/db/migrate/20170707184244_remove_wrong_versions_from_schema_versions.rb
@@ -0,0 +1,10 @@
+class RemoveWrongVersionsFromSchemaVersions < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ execute("DELETE FROM schema_migrations WHERE version IN ('20170723183807', '20170724184243')")
+ end
+
+ def down
+ end
+end
diff --git a/db/migrate/20170710083355_clean_stage_id_reference_migration.rb b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb
new file mode 100644
index 00000000000..681203eaf40
--- /dev/null
+++ b/db/migrate/20170710083355_clean_stage_id_reference_migration.rb
@@ -0,0 +1,18 @@
+class CleanStageIdReferenceMigration < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ ##
+ # `MigrateStageIdReferenceInBackground` background migration cleanup.
+ #
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateBuildStageIdReference')
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
new file mode 100644
index 00000000000..c25d4fd5986
--- /dev/null
+++ b/db/migrate/20170713104829_add_foreign_key_to_merge_requests.rb
@@ -0,0 +1,45 @@
+class AddForeignKeyToMergeRequests < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+ include ::EachBatch
+ end
+
+ def up
+ scope = <<-SQL.strip_heredoc
+ head_pipeline_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM ci_pipelines
+ WHERE ci_pipelines.id = merge_requests.head_pipeline_id
+ )
+ SQL
+
+ MergeRequest.where(scope).each_batch(of: 1000) do |merge_requests|
+ merge_requests.update_all(head_pipeline_id: nil)
+ end
+
+ unless foreign_key_exists?(:merge_requests, :head_pipeline_id)
+ add_concurrent_foreign_key(:merge_requests, :ci_pipelines,
+ column: :head_pipeline_id, on_delete: :nullify)
+ end
+ end
+
+ def down
+ if foreign_key_exists?(:merge_requests, :head_pipeline_id)
+ remove_foreign_key(:merge_requests, column: :head_pipeline_id)
+ end
+ end
+
+ private
+
+ def foreign_key_exists?(table, column)
+ foreign_keys(table).any? do |key|
+ key.options[:column] == column.to_s
+ end
+ end
+end
diff --git a/db/migrate/20170717074009_move_system_upload_folder.rb b/db/migrate/20170717074009_move_system_upload_folder.rb
new file mode 100644
index 00000000000..cce31794115
--- /dev/null
+++ b/db/migrate/20170717074009_move_system_upload_folder.rb
@@ -0,0 +1,60 @@
+class MoveSystemUploadFolder < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ unless file_storage?
+ say 'Using object storage, no need to move.'
+ return
+ end
+
+ unless File.directory?(old_directory)
+ say "#{old_directory} doesn't exist, no need to move it."
+ return
+ end
+
+ FileUtils.mkdir_p(File.join(base_directory, '-'))
+
+ say "Moving #{old_directory} -> #{new_directory}"
+ FileUtils.mv(old_directory, new_directory)
+ FileUtils.ln_s(new_directory, old_directory)
+ end
+
+ def down
+ unless file_storage?
+ say 'Using object storage, no need to move.'
+ return
+ end
+
+ unless File.directory?(new_directory)
+ say "#{new_directory} doesn't exist, no need to move it."
+ return
+ end
+
+ if File.symlink?(old_directory)
+ say "Removing #{old_directory} -> #{new_directory} symlink"
+ FileUtils.rm(old_directory)
+ end
+
+ say "Moving #{new_directory} -> #{old_directory}"
+ FileUtils.mv(new_directory, old_directory)
+ end
+
+ def new_directory
+ File.join(base_directory, '-', 'system')
+ end
+
+ def old_directory
+ File.join(base_directory, 'system')
+ end
+
+ def base_directory
+ File.join(Rails.root, 'public', 'uploads')
+ end
+
+ def file_storage?
+ CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
+ end
+end
diff --git a/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
new file mode 100644
index 00000000000..db60c2087b9
--- /dev/null
+++ b/db/migrate/20170724214302_add_lower_path_index_to_redirect_routes.rb
@@ -0,0 +1,34 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLowerPathIndexToRedirectRoutes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_on_redirect_routes_lower_path'
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON redirect_routes (LOWER(path));"
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ # Why not use remove_concurrent_index_by_name?
+ #
+ # `index_exists?` doesn't work on this index. Perhaps this is related to the
+ # fact that the index doesn't show up in the schema. And apparently it isn't
+ # trivial to write a query that checks for an index. BUT there is a
+ # convenient `IF EXISTS` parameter for `DROP INDEX`.
+ if supports_drop_index_concurrently?
+ disable_statement_timeout
+ execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME};"
+ else
+ execute "DROP INDEX IF EXISTS #{INDEX_NAME};"
+ end
+ end
+end
diff --git a/db/migrate/20170725145659_add_binary_to_merge_request_diff_files.rb b/db/migrate/20170725145659_add_binary_to_merge_request_diff_files.rb
new file mode 100644
index 00000000000..1f5fa7e3d49
--- /dev/null
+++ b/db/migrate/20170725145659_add_binary_to_merge_request_diff_files.rb
@@ -0,0 +1,9 @@
+class AddBinaryToMergeRequestDiffFiles < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :merge_request_diff_files, :binary, :boolean
+ end
+end
diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
index 397a9a2d28e..cb1b4f1855d 100644
--- a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
+++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
@@ -56,7 +56,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
end
def activities(from, to, page: 1)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.zrangebyscore(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i,
with_scores: true,
limit: limit(page))
@@ -64,7 +64,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration
end
def activities_count(from, to)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.zcount(USER_ACTIVITY_SET_KEY, from.to_i, to.to_i)
end
end
diff --git a/db/post_migrate/20170406111121_clean_upload_symlinks.rb b/db/post_migrate/20170406111121_clean_upload_symlinks.rb
index 3ac9a6c10bc..fc3a4acc0bb 100644
--- a/db/post_migrate/20170406111121_clean_upload_symlinks.rb
+++ b/db/post_migrate/20170406111121_clean_upload_symlinks.rb
@@ -6,7 +6,7 @@ class CleanUploadSymlinks < ActiveRecord::Migration
disable_ddl_transaction!
DOWNTIME = false
- DIRECTORIES_TO_MOVE = %w(user project note group appeareance)
+ DIRECTORIES_TO_MOVE = %w(user project note group appearance)
def up
return unless file_storage?
diff --git a/db/post_migrate/20170612071012_move_personal_snippets_files.rb b/db/post_migrate/20170612071012_move_personal_snippets_files.rb
new file mode 100644
index 00000000000..33043364bde
--- /dev/null
+++ b/db/post_migrate/20170612071012_move_personal_snippets_files.rb
@@ -0,0 +1,91 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+class MovePersonalSnippetsFiles < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ return unless file_storage?
+
+ @source_relative_location = File.join('/uploads', 'personal_snippet')
+ @destination_relative_location = File.join('/uploads', 'system', 'personal_snippet')
+
+ move_personal_snippet_files
+ end
+
+ def down
+ return unless file_storage?
+
+ @source_relative_location = File.join('/uploads', 'system', 'personal_snippet')
+ @destination_relative_location = File.join('/uploads', 'personal_snippet')
+
+ move_personal_snippet_files
+ end
+
+ def move_personal_snippet_files
+ query = "SELECT uploads.path, uploads.model_id, snippets.description FROM uploads "\
+ "INNER JOIN snippets ON snippets.id = uploads.model_id WHERE uploader = 'PersonalFileUploader'"
+ select_all(query).each do |upload|
+ secret = upload['path'].split('/')[0]
+ file_name = upload['path'].split('/')[1]
+
+ next unless move_file(upload['model_id'], secret, file_name)
+ update_markdown(upload['model_id'], secret, file_name, upload['description'])
+ end
+ end
+
+ def move_file(snippet_id, secret, file_name)
+ source_dir = File.join(base_directory, @source_relative_location, snippet_id.to_s, secret)
+ destination_dir = File.join(base_directory, @destination_relative_location, snippet_id.to_s, secret)
+
+ source_file_path = File.join(source_dir, file_name)
+ destination_file_path = File.join(destination_dir, file_name)
+
+ unless File.exist?(source_file_path)
+ say "Source file `#{source_file_path}` doesn't exist. Skipping."
+ return
+ end
+
+ say "Moving file #{source_file_path} -> #{destination_file_path}"
+
+ FileUtils.mkdir_p(destination_dir)
+ FileUtils.move(source_file_path, destination_file_path)
+
+ true
+ end
+
+ def update_markdown(snippet_id, secret, file_name, description)
+ source_markdown_path = File.join(@source_relative_location, snippet_id.to_s, secret, file_name)
+ destination_markdown_path = File.join(@destination_relative_location, snippet_id.to_s, secret, file_name)
+
+ source_markdown = "](#{source_markdown_path})"
+ destination_markdown = "](#{destination_markdown_path})"
+
+ if description.present?
+ description = description.gsub(source_markdown, destination_markdown)
+ quoted_description = quote_string(description)
+
+ execute("UPDATE snippets SET description = '#{quoted_description}', description_html = NULL "\
+ "WHERE id = #{snippet_id}")
+ end
+
+ query = "SELECT id, note FROM notes WHERE noteable_id = #{snippet_id} "\
+ "AND noteable_type = 'Snippet' AND note IS NOT NULL"
+ select_all(query).each do |note|
+ text = note['note'].gsub(source_markdown, destination_markdown)
+ quoted_text = quote_string(text)
+
+ execute("UPDATE notes SET note = '#{quoted_text}', note_html = NULL WHERE id = #{note['id']}")
+ end
+ end
+
+ def base_directory
+ File.join(Rails.root, 'public')
+ end
+
+ def file_storage?
+ CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
+ end
+end
diff --git a/db/post_migrate/20170613111224_clean_appearance_symlinks.rb b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb
new file mode 100644
index 00000000000..acb895e426f
--- /dev/null
+++ b/db/post_migrate/20170613111224_clean_appearance_symlinks.rb
@@ -0,0 +1,52 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CleanAppearanceSymlinks < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ return unless file_storage?
+
+ symlink_location = File.join(old_upload_dir, dir)
+
+ return unless File.symlink?(symlink_location)
+ say "removing symlink: #{symlink_location}"
+ FileUtils.rm(symlink_location)
+ end
+
+ def down
+ return unless file_storage?
+
+ symlink = File.join(old_upload_dir, dir)
+ destination = File.join(new_upload_dir, dir)
+
+ return if File.directory?(symlink)
+ return unless File.directory?(destination)
+
+ say "Creating symlink #{symlink} -> #{destination}"
+ FileUtils.ln_s(destination, symlink)
+ end
+
+ def file_storage?
+ CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
+ end
+
+ def dir
+ 'appearance'
+ end
+
+ def base_directory
+ Rails.root
+ end
+
+ def old_upload_dir
+ File.join(base_directory, "public", "uploads")
+ end
+
+ def new_upload_dir
+ File.join(base_directory, "public", "uploads", "system")
+ end
+end
diff --git a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
index 7d6609b18bf..ac61b5c84a8 100644
--- a/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
+++ b/db/post_migrate/20170621102400_add_stage_id_index_to_builds.rb
@@ -3,19 +3,15 @@ class AddStageIdIndexToBuilds < ActiveRecord::Migration
DOWNTIME = false
- disable_ddl_transaction!
+ ##
+ # Improved in 20170703102400_add_stage_id_foreign_key_to_builds.rb
+ #
def up
- unless index_exists?(:ci_builds, :stage_id)
- add_concurrent_foreign_key(:ci_builds, :ci_stages, column: :stage_id, on_delete: :cascade)
- add_concurrent_index(:ci_builds, :stage_id)
- end
+ # noop
end
def down
- if index_exists?(:ci_builds, :stage_id)
- remove_foreign_key(:ci_builds, column: :stage_id)
- remove_concurrent_index(:ci_builds, :stage_id)
- end
+ # noop
end
end
diff --git a/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb b/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb
new file mode 100644
index 00000000000..f31015d77a3
--- /dev/null
+++ b/db/post_migrate/20170628080858_migrate_stage_id_reference_in_background.rb
@@ -0,0 +1,33 @@
+class MigrateStageIdReferenceInBackground < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10000
+ RANGE_SIZE = 1000
+ MIGRATION = 'MigrateBuildStageIdReference'.freeze
+
+ disable_ddl_transaction!
+
+ class Build < ActiveRecord::Base
+ self.table_name = 'ci_builds'
+ include ::EachBatch
+ end
+
+ ##
+ # It will take around 3 days to process 20M ci_builds.
+ #
+ def up
+ Build.where(stage_id: nil).each_batch(of: BATCH_SIZE) do |relation, index|
+ relation.each_batch(of: RANGE_SIZE) do |relation|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker
+ .perform_in(index * 2.minutes, MIGRATION, range)
+ end
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb
new file mode 100644
index 00000000000..52a773ddfee
--- /dev/null
+++ b/db/post_migrate/20170629180131_cleanup_application_settings_signin_enabled_rename.rb
@@ -0,0 +1,15 @@
+class CleanupApplicationSettingsSigninEnabledRename < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :application_settings, :signin_enabled, :password_authentication_enabled
+ end
+
+ def down
+ rename_column_concurrently :application_settings, :password_authentication_enabled, :signin_enabled
+ end
+end
diff --git a/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb
new file mode 100644
index 00000000000..26b99b61424
--- /dev/null
+++ b/db/post_migrate/20170717111152_cleanup_move_system_upload_folder_symlink.rb
@@ -0,0 +1,40 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CleanupMoveSystemUploadFolderSymlink < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ if File.symlink?(old_directory)
+ say "Removing #{old_directory} -> #{new_directory} symlink"
+ FileUtils.rm(old_directory)
+ else
+ say "Symlink #{old_directory} non existant, nothing to do."
+ end
+ end
+
+ def down
+ if File.directory?(new_directory)
+ say "Symlinking #{old_directory} -> #{new_directory}"
+ FileUtils.ln_s(new_directory, old_directory)
+ else
+ say "#{new_directory} doesn't exist, skipping."
+ end
+ end
+
+ def new_directory
+ File.join(base_directory, '-', 'system')
+ end
+
+ def old_directory
+ File.join(base_directory, 'system')
+ end
+
+ def base_directory
+ File.join(Rails.root, 'public', 'uploads')
+ end
+end
diff --git a/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb
new file mode 100644
index 00000000000..87069dce006
--- /dev/null
+++ b/db/post_migrate/20170717150329_enqueue_migrate_system_uploads_to_new_folder.rb
@@ -0,0 +1,20 @@
+class EnqueueMigrateSystemUploadsToNewFolder < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ OLD_FOLDER = 'uploads/system/'
+ NEW_FOLDER = 'uploads/-/system/'
+
+ disable_ddl_transaction!
+
+ def up
+ BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder',
+ [OLD_FOLDER, NEW_FOLDER])
+ end
+
+ def down
+ BackgroundMigrationWorker.perform_async('MigrateSystemUploadsToNewFolder',
+ [NEW_FOLDER, OLD_FOLDER])
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 993eea1f642..63030350c5d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170623080805) do
+ActiveRecord::Schema.define(version: 20170725145659) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -41,7 +41,6 @@ ActiveRecord::Schema.define(version: 20170623080805) do
create_table "application_settings", force: :cascade do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
- t.boolean "signin_enabled"
t.boolean "gravatar_enabled"
t.text "sign_in_text"
t.datetime "created_at"
@@ -126,6 +125,8 @@ ActiveRecord::Schema.define(version: 20170623080805) do
t.boolean "prometheus_metrics_enabled", default: false, null: false
t.boolean "help_page_hide_commercial_content", default: false
t.string "help_page_support_url"
+ t.integer "performance_bar_allowed_group_id"
+ t.boolean "password_authentication_enabled"
end
create_table "audit_events", force: :cascade do |t|
@@ -253,6 +254,33 @@ ActiveRecord::Schema.define(version: 20170623080805) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
+ create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
+ t.string "key", null: false
+ t.text "value"
+ t.text "encrypted_value"
+ t.string "encrypted_value_salt"
+ t.string "encrypted_value_iv"
+ t.integer "pipeline_schedule_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
+
+ create_table "ci_group_variables", force: :cascade do |t|
+ t.string "key", null: false
+ t.text "value"
+ t.text "encrypted_value"
+ t.string "encrypted_value_salt"
+ t.string "encrypted_value_iv"
+ t.integer "group_id", null: false
+ t.boolean "protected", default: false, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
+
create_table "ci_pipeline_schedules", force: :cascade do |t|
t.string "description"
t.string "ref"
@@ -512,6 +540,36 @@ ActiveRecord::Schema.define(version: 20170623080805) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
+ create_table "gpg_keys", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "user_id"
+ t.binary "primary_keyid"
+ t.binary "fingerprint"
+ t.text "key"
+ end
+
+ add_index "gpg_keys", ["fingerprint"], name: "index_gpg_keys_on_fingerprint", unique: true, using: :btree
+ add_index "gpg_keys", ["primary_keyid"], name: "index_gpg_keys_on_primary_keyid", unique: true, using: :btree
+ add_index "gpg_keys", ["user_id"], name: "index_gpg_keys_on_user_id", using: :btree
+
+ create_table "gpg_signatures", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "project_id"
+ t.integer "gpg_key_id"
+ t.boolean "valid_signature"
+ t.binary "commit_sha"
+ t.binary "gpg_key_primary_keyid"
+ t.text "gpg_key_user_name"
+ t.text "gpg_key_user_email"
+ end
+
+ add_index "gpg_signatures", ["commit_sha"], name: "index_gpg_signatures_on_commit_sha", unique: true, using: :btree
+ add_index "gpg_signatures", ["gpg_key_id"], name: "index_gpg_signatures_on_gpg_key_id", using: :btree
+ add_index "gpg_signatures", ["gpg_key_primary_keyid"], name: "index_gpg_signatures_on_gpg_key_primary_keyid", using: :btree
+ add_index "gpg_signatures", ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
+
create_table "identities", force: :cascade do |t|
t.string "extern_uid"
t.string "provider"
@@ -693,6 +751,21 @@ ActiveRecord::Schema.define(version: 20170623080805) do
add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
+ create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
+ t.datetime "authored_date"
+ t.datetime "committed_date"
+ t.integer "merge_request_diff_id", null: false
+ t.integer "relative_order", null: false
+ t.binary "sha", null: false
+ t.text "author_name"
+ t.text "author_email"
+ t.text "committer_name"
+ t.text "committer_email"
+ t.text "message"
+ end
+
+ add_index "merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true, using: :btree
+
create_table "merge_request_diff_files", id: false, force: :cascade do |t|
t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false
@@ -705,6 +778,7 @@ ActiveRecord::Schema.define(version: 20170623080805) do
t.text "new_path", null: false
t.text "old_path", null: false
t.text "diff", null: false
+ t.boolean "binary"
end
add_index "merge_request_diff_files", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true, using: :btree
@@ -800,7 +874,7 @@ ActiveRecord::Schema.define(version: 20170623080805) do
create_table "milestones", force: :cascade do |t|
t.string "title", null: false
- t.integer "project_id", null: false
+ t.integer "project_id"
t.text "description"
t.date "due_date"
t.datetime "created_at"
@@ -811,10 +885,12 @@ ActiveRecord::Schema.define(version: 20170623080805) do
t.text "description_html"
t.date "start_date"
t.integer "cached_markdown_version"
+ t.integer "group_id"
end
add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
+ add_index "milestones", ["group_id"], name: "index_milestones_on_group_id", using: :btree
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree
add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
@@ -971,6 +1047,7 @@ ActiveRecord::Schema.define(version: 20170623080805) do
end
add_index "pages_domains", ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
+ add_index "pages_domains", ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
create_table "personal_access_tokens", force: :cascade do |t|
t.integer "user_id", null: false
@@ -1020,6 +1097,7 @@ ActiveRecord::Schema.define(version: 20170623080805) do
end
add_index "project_group_links", ["group_id"], name: "index_project_group_links_on_group_id", using: :btree
+ add_index "project_group_links", ["project_id"], name: "index_project_group_links_on_project_id", using: :btree
create_table "project_import_data", force: :cascade do |t|
t.integer "project_id"
@@ -1086,6 +1164,8 @@ ActiveRecord::Schema.define(version: 20170623080805) do
t.string "import_jid"
t.integer "cached_markdown_version"
t.datetime "last_repository_updated_at"
+ t.string "ci_config_path"
+ t.text "delete_error"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
@@ -1527,48 +1607,83 @@ ActiveRecord::Schema.define(version: 20170623080805) do
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree
- add_foreign_key "boards", "projects"
+ add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
+ add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
+ add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
+ add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
+ add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
+ add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade
add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade
add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade
add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade
+ add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
add_foreign_key "container_repositories", "projects"
+ add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
+ add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade
+ add_foreign_key "events", "projects", name: "fk_0434b48643", on_delete: :cascade
+ add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
+ add_foreign_key "gpg_keys", "users", on_delete: :cascade
+ add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify
+ add_foreign_key "gpg_signatures", "projects", on_delete: :cascade
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
+ add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
add_foreign_key "labels", "namespaces", column: "group_id", on_delete: :cascade
- add_foreign_key "lists", "boards"
- add_foreign_key "lists", "labels"
+ add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
+ add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
+ add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
+ add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
+ add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify
+ add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
+ add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade
+ add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade
+ add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id"
+ add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade
add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
+ add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
+ add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
+ add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_statistics", "projects", on_delete: :cascade
- add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
- add_foreign_key "protected_branch_push_access_levels", "protected_branches"
+ add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade
+ add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade
+ add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade
add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id"
add_foreign_key "protected_tag_create_access_levels", "protected_tags"
add_foreign_key "protected_tag_create_access_levels", "users"
+ add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade
+ add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade
+ add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
+ add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
+ add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
+ add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
+ add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
end
diff --git a/doc/README.md b/doc/README.md
index fa755852304..5537f54ab2b 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -11,15 +11,11 @@ self-hosted, free to use. Every feature available in GitLab CE is also available
self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)** and **GitLab Enterprise Edition Premium (EEP)**.
- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
-**GitLab EE** contains all features available in **GitLab CE**,
+> **GitLab EE** contains all features available in **GitLab CE**,
plus premium features available in each version: **Enterprise Edition Starter**
(**EES**) and **Enterprise Edition Premium** (**EEP**). Everything available in
**EES** is also available in **EEP**.
-**Note:** _We are unifying the documentation for CE and EE. To check if certain feature is
-available in CE or EE, look for a note right below the page title containing the GitLab
-version which introduced that feature._
-
----
Shortcuts to GitLab's most visited docs:
@@ -40,6 +36,7 @@ Shortcuts to GitLab's most visited docs:
### User account
+- [User documentation](user/index.md)
- [Authentication](topics/authentication/index.md): Account security with two-factor authentication, setup your ssh keys and deploy keys for secure access to your projects.
- [Profile settings](profile/README.md): Manage your profile settings, two factor authentication and more.
- [User permissions](user/permissions.md): Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
@@ -50,8 +47,7 @@ Shortcuts to GitLab's most visited docs:
- [Fork a project](gitlab-basics/fork-project.md)
- [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Project access](public_access/public_access.md): Setting up your project's visibility to public, internal, or private.
-- [Groups](workflow/groups.md): Organize your projects in groups.
- - [Create a group](gitlab-basics/create-group.md)
+- [Groups](user/group/index.md): Organize your projects in groups.
- [GitLab Subgroups](user/group/subgroups/index.md)
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
- [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
@@ -93,6 +89,7 @@ Manage files and branches from the UI (user interface):
- [Git](topics/git/index.md): Getting started with Git, branching strategies, Git LFS, advanced use.
- [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf): Download a PDF describing the most used Git operations.
- [GitLab Flow](workflow/gitlab_flow.md): explore the best of Git with the GitLab Flow strategy.
+- [Signing commits](workflow/gpg_signed_commits/index.md): use GPG to sign your commits.
### Migrate and import your projects from other platforms
@@ -116,6 +113,7 @@ Manage files and branches from the UI (user interface):
- [Project Services](user/project/integrations/project_services.md): Integrate a project with external services, such as CI and chat.
- [GitLab Integration](integration/README.md): Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication.
+- [Trello Power-Up](integration/trello_power_up.md): Integrate with GitLab's Trello Power-Up
----
@@ -167,6 +165,7 @@ have access to GitLab administration tools and settings.
- [Operations](administration/operations.md): Keeping GitLab up and running.
- [Polling](administration/polling.md): Configure how often the GitLab UI polls for updates.
- [Request Profiling](administration/monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
+- [Performance Bar](administration/monitoring/performance/performance_bar.md): Get performance information for the current page.
### Customization
@@ -179,6 +178,7 @@ have access to GitLab administration tools and settings.
### Admin tools
+- [Gitaly](administration/gitaly/index.md): Configuring Gitaly, GitLab's Git repository storage service
- [Raketasks](raketasks/README.md): Backups, maintenance, automatic webhook setup and the importing of projects.
- [Backup and restore](raketasks/backup_restore.md): Backup and restore your GitLab instance.
- [Reply by email](administration/reply_by_email.md): Allow users to comment on issues and merge requests by replying to notification emails.
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index fb1a16b0f96..1528f1d2b17 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -32,7 +32,7 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
"app_id" => "YOUR_CLIENT_ID",
"app_secret" => "YOUR_CLIENT_SECRET",
"args" => {
- scope: 'aq:name email~rs aq:push'
+ "scope": 'aq:name email~rs address aq:push'
}
}
]
@@ -45,21 +45,20 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
app_id: 'YOUR_CLIENT_ID',
app_secret: 'YOUR_CLIENT_SECRET',
args: {
- scope: 'aq:name email~rs aq:push'
+ scope: 'aq:name email~rs address aq:push'
}
}
```
5. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
-See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq#scopes-and-redirect-uri-configuration) for more information on scopes and modifiers.
+See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers.
6. Change `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` to the Client credentials you received in step 1.
7. Save the configuration file.
-8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
- for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+8. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be an Authentiq icon below the regular sign in form. Click the icon to begin the authentication process.
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 725fc1f6076..a7395e03d1c 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -69,14 +69,42 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
# Example: 'ldap.mydomain.com'
host: '_your_ldap_server'
# This port is an example, it is sometimes different but it is always an integer and not a string
- port: 389
+ port: 389 # usually 636 for SSL
uid: 'sAMAccountName' # This should be the attribute, not the value that maps to uid.
- method: 'plain' # "tls" or "ssl" or "plain"
# Examples: 'america\\momo' or 'CN=Gitlab Git,CN=Users,DC=mydomain,DC=com'
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'
+ # Encryption method. The "method" key is deprecated in favor of
+ # "encryption".
+ #
+ # Examples: "start_tls" or "simple_tls" or "plain"
+ #
+ # Deprecated values: "tls" was replaced with "start_tls" and "ssl" was
+ # replaced with "simple_tls".
+ #
+ encryption: 'plain'
+
+ # Enables SSL certificate verification if encryption method is
+ # "start_tls" or "simple_tls". (Defaults to false for backward-
+ # compatibility)
+ verify_certificates: false
+
+ # Specifies the path to a file containing a PEM-format CA certificate,
+ # e.g. if you need to use an internal CA.
+ #
+ # Example: '/etc/ca.pem'
+ #
+ ca_cert: ''
+
+ # Specifies the SSL version for OpenSSL to use, if the OpenSSL default
+ # is not appropriate.
+ #
+ # Example: 'TLSv1_1'
+ #
+ ssl_version: ''
+
# Set a timeout, in seconds, for LDAP queries. This helps avoid blocking
# a request if the LDAP server becomes unresponsive.
# A value of 0 means there is no timeout.
@@ -116,8 +144,8 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
- # Below an example for get only specific users
- # Example: '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
+ # Example for getting only specific users:
+ # '(&(objectclass=user)(|(samaccountname=momo)(samaccountname=toto)))'
#
user_filter: ''
@@ -228,9 +256,14 @@ Tip: If you want to limit access to the nested members of an Active Directory
group you can use the following syntax:
```
-(memberOf=CN=My Group,DC=Example,DC=com)
+(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
```
+Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
+https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for
+nested members in the user filter should not be confused with
+[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes).
+
Please note that GitLab does not support the custom filter syntax used by
omniauth-ldap.
@@ -245,6 +278,19 @@ In other words, if an existing GitLab user wants to enable LDAP sign-in for
themselves, they should check that their GitLab email address matches their
LDAP email address, and then sign into GitLab via their LDAP credentials.
+## Encryption
+
+### TLS Server Authentication
+
+There are two encryption methods, `simple_tls` and `start_tls`.
+
+For either encryption method, if setting `validate_certificates: false`, TLS
+encryption is established with the LDAP server before any LDAP-protocol data is
+exchanged but no validation of the LDAP server's SSL certificate is performed.
+
+>**Note**: Before GitLab 9.5, `validate_certificates: false` is the default if
+unspecified.
+
## Limitations
### TLS Client Authentication
@@ -254,14 +300,6 @@ You should disable anonymous LDAP authentication and enable simple or SASL
authentication. The TLS client authentication setting in your LDAP server cannot
be mandatory and clients cannot be authenticated with the TLS protocol.
-### TLS Server Authentication
-
-Not supported by GitLab's configuration options.
-When setting `method: ssl`, the underlying authentication method used by
-`omniauth-ldap` is `simple_tls`. This method establishes TLS encryption with
-the LDAP server before any LDAP-protocol data is exchanged but no validation of
-the LDAP server's SSL certificate is performed.
-
## Troubleshooting
### Debug LDAP user filter with ldapsearch
@@ -301,9 +339,9 @@ tree and traverse it.
### Connection Refused
If you are getting 'Connection Refused' errors when trying to connect to the
-LDAP server please double-check the LDAP `port` and `method` settings used by
-GitLab. Common combinations are `method: 'plain'` and `port: 389`, OR
-`method: 'ssl'` and `port: 636`.
+LDAP server please double-check the LDAP `port` and `encryption` settings used by
+GitLab. Common combinations are `encryption: 'plain'` and `port: 389`, OR
+`encryption: 'simple_tls'` and `port: 636`.
### Troubleshooting
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index afafb6bf1f5..57e54815b68 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -112,7 +112,7 @@ GitLab from source respectively.
>**Note:**
Be careful to choose a port different than the one that Registry listens to (`5000` by default),
-otherwise you will run into conflicts .
+otherwise you will run into conflicts.
---
@@ -465,23 +465,42 @@ on how to achieve that.
## Disable Container Registry but use GitLab as an auth endpoint
-You can disable the embedded Container Registry to use an external one, but
-still use GitLab as an auth endpoint.
-
**Omnibus GitLab**
+
+You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
+
1. Open `/etc/gitlab/gitlab.rb` and set necessary configurations:
```ruby
- registry['enable'] = false
gitlab_rails['registry_enabled'] = true
gitlab_rails['registry_host'] = "registry.gitlab.example.com"
gitlab_rails['registry_port'] = "5005"
gitlab_rails['registry_api_url'] = "http://localhost:5000"
- gitlab_rails['registry_key_path'] = "/var/opt/gitlab/gitlab-rails/certificate.key"
gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
gitlab_rails['registry_issuer'] = "omnibus-gitlab-issuer"
```
+1. A certificate keypair is required for GitLab and the Container Registry to
+ communicate securely. By default omnibus-gitlab will generate one keypair,
+ which is saved to `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key`.
+ When using an non-bundled Container Registry, you will need to supply a
+ custom certificate key. To do that, add the following to
+ `/etc/gitlab/gitlab.rb`
+
+ ```ruby
+ gitlab_rails['registry_key_path'] = "/custom/path/to/registry-key.key"
+ # registry['internal_key'] should contain the contents of the custom key
+ # file. Line breaks in the key file should be marked using `\n` character
+ # Example:
+ registry['internal_key'] = "---BEGIN RSA PRIVATE KEY---\nMIIEpQIBAA\n"
+ ```
+
+ **Note:** The file specified at `registry_key_path` gets populated with the
+ content specified by `internal_key`, each time reconfigure is executed. If
+ no file is specified, omnibus-gitlab will default it to
+ `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` and will populate
+ it.
+
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
**Installations from source**
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 332457cb384..5732b6a1ca4 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -2,8 +2,7 @@
[Gitaly](https://gitlab.com/gitlab-org/gitaly) (introduced in GitLab
9.0) is a service that provides high-level RPC access to Git
-repositories. As of GitLab 9.3 it is still an optional component with
-limited scope.
+repositories. Gitaly is a mandatory component in GitLab 9.4 and newer.
GitLab components that access Git repositories (gitlab-rails,
gitlab-shell, gitlab-workhorse) act as clients to Gitaly. End users do
@@ -149,6 +148,8 @@ git_data_dirs({
{ 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
{ 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tcp://gitlab.internal:9999' } },
})
+
+gitlab_rails['gitaly_token'] = 'abc123secret'
```
Source installations:
@@ -164,6 +165,9 @@ gitlab:
storage1:
path: /mnt/gitlab/storage1/repositories
gitaly_address: tcp://gitlab.internal:9999
+
+ gitaly:
+ token: 'abc123secret'
```
Now reconfigure (Omnibus) or restart (source). When you tail the
@@ -172,36 +176,11 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
coming in. One sure way to trigger a Gitaly request is to clone a
repository from your GitLab server over HTTP.
-## Configuring GitLab to not use Gitaly
-
-Gitaly is still an optional component in GitLab 9.3. This means you
-can choose to not use it.
-
-In Omnibus you can make the following change in
-`/etc/gitlab/gitlab.rb` and reconfigure. This will both disable the
-Gitaly service and configure the rest of GitLab not to use it.
-
-```ruby
-gitaly['enable'] = false
-```
-
-In source installations, edit `/home/git/gitlab/config/gitlab.yml` and
-make sure `enabled` in the `gitaly` section is set to 'false'. This
-does not disable the Gitaly service in your init script; it only
-prevents it from being used.
-
-Apply the change with `service gitlab restart`.
-
-```yaml
- gitaly:
- enabled: false
-```
-
## Disabling or enabling the Gitaly service
-Be careful: if you disable Gitaly without instructing the rest of your
-GitLab installation not to use Gitaly, you may end up with errors
-because GitLab tries to access a service that is not running.
+If you are running Gitaly [as a remote
+service](#running-gitaly-on-its-own-server) you may want to disable
+the local Gitaly service that runs on your Gitlab server by default.
To disable the Gitaly service in your Omnibus installation, add the
following line to `/etc/gitlab/gitlab.rb`:
@@ -220,4 +199,4 @@ following to `/etc/default/gitlab`:
gitaly_enabled=false
```
-When you run `service gitlab restart` Gitaly will be disabled. \ No newline at end of file
+When you run `service gitlab restart` Gitaly will be disabled.
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index da9687aa849..ca6d8d2de67 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -97,9 +97,12 @@ If you use a cloud-managed service, or provide your own PostgreSQL:
Enter new password:
Enter it again:
```
-
-1. Enable the `pg_trgm` extension:
+1. Exit from editing `template1` prompt by typing `\q` and Enter.
+1. Enable the `pg_trgm` extension within the `gitlabhq_production` database:
+
```
+ gitlab-psql -d gitlabhq_production
+
CREATE EXTENSION pg_trgm;
# Output:
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 137fed35a73..e2cf281bc49 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -70,6 +70,12 @@ for each GitLab application server in your environment.
gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
gitlab_rails['redis_password'] = 'Redis Password'
```
+
+ > **Note:** To maintain uniformity of links across HA clusters, the `external_url`
+ on the master as well as all secondary application servers should point to the
+ eventual url that users will use to access GitLab. In a typical HA setup,
+ this will be the url of the load balancer which will route traffic to all
+ GitLab application servers in the HA cluster.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index fe982ea83c2..8b7a515a076 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -4,6 +4,11 @@ This is the documentation for configuring a Highly Available Redis setup when
you have installed Redis all by yourself and not using the bundled one that
comes with the Omnibus packages.
+Note also that you may elect to override all references to
+`/home/git/gitlab/config/resque.yml` in accordance with the advanced Redis
+settings outlined in
+[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/README.md).
+
We cannot stress enough the importance of reading the
[Overview section](redis.md#overview) of the Omnibus Redis HA as it provides
some invaluable information to the configuration of Redis. Please proceed to
diff --git a/doc/administration/monitoring/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md
new file mode 100644
index 00000000000..ad2773de132
--- /dev/null
+++ b/doc/administration/monitoring/ip_whitelist.md
@@ -0,0 +1,39 @@
+# IP whitelist
+
+> Introduced in GitLab 9.4.
+
+GitLab provides some [monitoring endpoints] that provide health check information
+when probed.
+
+To control access to those endpoints via IP whitelisting, you can add single
+hosts or use IP ranges:
+
+**For Omnibus installations**
+
+1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following:
+
+ ```ruby
+ gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
+ ```
+
+1. Save the file and [reconfigure] GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ monitoring:
+ # by default only local IPs are allowed to access monitoring resources
+ ip_whitelist:
+ - 127.0.0.0/8
+ - 192.168.0.1
+ ```
+
+1. Save the file and [restart] GitLab for the changes to take effect.
+
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: ../restart_gitlab.md#installations-from-source
+[monitoring endpoints]: ../../user/admin_area/monitoring/health_check.md
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
new file mode 100644
index 00000000000..d38293d2ed6
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png b/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
new file mode 100644
index 00000000000..2d64ef8c5fc
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_configuration_settings.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
new file mode 100644
index 00000000000..7868e2c46d1
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
new file mode 100644
index 00000000000..372ae021f6b
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
new file mode 100644
index 00000000000..ee680c7b258
--- /dev/null
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -0,0 +1,35 @@
+# Performance Bar
+
+A Performance Bar can be displayed, to dig into the performance of a page. When
+activated, it looks as follows:
+
+![Performance Bar](img/performance_bar.png)
+
+It allows you to:
+
+- see the current host serving the page
+- see the timing of the page (backend, frontend)
+- the number of DB queries, the time it took, and the detail of these queries
+![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
+- the number of calls to Redis, and the time it took
+- the number of background jobs created by Sidekiq, and the time it took
+- the number of Ruby GC calls, and the time it took
+- profile the code used to generate the page, line by line
+![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
+
+## Enable the Performance Bar via the Admin panel
+
+GitLab Performance Bar is disabled by default. To enable it for a given group,
+navigate to the Admin area in **Settings > Profiling - Performance Bar**
+(`/admin/application_settings`).
+
+The only required setting you need to set is the full path of the group that
+will be allowed to display the Performance Bar.
+Make sure _Enable the Performance Bar_ is checked and hit
+**Save** to save the changes.
+
+---
+
+![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
+
+---
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 07c05b5a6fb..6baae20d16a 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -1,10 +1,8 @@
# GitLab Prometheus metrics
>**Note:**
-Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For installations from source
-you'll have to configure it yourself.
-
-GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other [Prometheus] exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic.
+Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For
+installations from source you'll have to configure it yourself.
To enable the GitLab Prometheus metrics:
@@ -15,33 +13,57 @@ To enable the GitLab Prometheus metrics:
## Collecting the metrics
-Since the metrics endpoint is available on the same host and port as other traffic, it requires authentication. The token and URL to access is displayed on the [Health Check][health-check] page.
+GitLab monitors its own internal service metrics, and makes them available at the
+`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access
+it, the client IP needs to be [included in a whitelist][whitelist].
-Currently the embedded Prometheus server is not automatically configured to collect metrics from this endpoint. We recommend setting up another Prometheus server, because the embedded server configuration is overwritten one every reconfigure of GitLab. In the future this will not be required.
+Currently the embedded Prometheus server is not automatically configured to
+collect metrics from this endpoint. We recommend setting up another Prometheus
+server, because the embedded server configuration is overwritten once every
+[reconfigure of GitLab][reconfigure]. In the future this will not be required.
## Metrics available
In this experimental phase, only a few metrics are available:
-| Metric | Type | Description |
-| ------ | ---- | ----------- |
-| db_ping_timeout | Gauge | Whether or not the last database ping timed out |
-| db_ping_success | Gauge | Whether or not the last database ping succeeded |
-| db_ping_latency | Gauge | Round trip time of the database ping |
-| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out |
-| redis_ping_success | Gauge | Whether or not the last redis ping succeeded |
-| redis_ping_latency | Gauge | Round trip time of the redis ping |
-| filesystem_access_latency | gauge | Latency in accessing a specific filesystem |
-| filesystem_accessible | gauge | Whether or not a specific filesystem is accessible |
-| filesystem_write_latency | gauge | Write latency of a specific filesystem |
-| filesystem_writable | gauge | Whether or not the filesystem is writable |
-| filesystem_read_latency | gauge | Read latency of a specific filesystem |
-| filesystem_readable | gauge | Whether or not the filesystem is readable |
-| user_sessions_logins | Counter | Counter of how many users have logged in |
+| Metric | Type | Since | Description |
+|:--------------------------------- |:--------- |:----- |:----------- |
+| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out |
+| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded |
+| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping |
+| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem |
+| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible |
+| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem |
+| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable |
+| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem |
+| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable |
+| http_requests_total | Counter | 9.4 | Rack request count |
+| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware |
+| pipelines_created_total | Counter | 9.4 | Counter of pipelines created |
+| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count |
+| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out |
+| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
+| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
+| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
+
+## Metrics shared directory
+
+GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
+Those files are shared among all instances running under Unicorn server.
+The directory needs to be accessible to all running Unicorn's processes otherwise
+metrics will not function correctly.
+
+For best performance its advisable that this directory will be located in `tmpfs`.
+
+Its location is configured using environment variable `prometheus_multiproc_dir`.
+
+If GitLab is installed using Omnibus and `tmpfs` is available then metrics
+directory will be automatically configured.
[↠Back to the main Prometheus page](index.md)
[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
[Prometheus]: https://prometheus.io
[restart]: ../../restart_gitlab.md#omnibus-gitlab-restart
-[health-check]: ../../../user/admin_area/monitoring/health_check.md
+[whitelist]: ../ip_whitelist.md
+[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 695fdf09a87..f43c89dad87 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -95,8 +95,9 @@ Sample Prometheus queries:
## Configuring Prometheus to monitor Kubernetes
> Introduced in GitLab 9.0.
+> Pod monitoring introduced in GitLab 9.4.
-If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them.
+If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes and [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>) in the cluster, including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them.
To disable the monitoring of Kubernetes:
diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md
index 93521e976d5..3a35aff8366 100644
--- a/doc/administration/operations/cleaning_up_redis_sessions.md
+++ b/doc/administration/operations/cleaning_up_redis_sessions.md
@@ -15,6 +15,12 @@ prefixed with 'session:gitlab:', so they would look like
'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to
remove the keys in the old format.
+**Note:** the instructions below must be modified in accordance with your
+configuration settings if you have used the advanced Redis
+settings outlined in
+[Configuration Files Documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/README.md).
+
+
First we define a shell function with the proper Redis connection details.
```
diff --git a/doc/api/README.md b/doc/api/README.md
index b7f6ee69193..fe29563eaca 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -17,6 +17,7 @@ following locations:
- [Deploy Keys](deploy_keys.md)
- [Environments](environments.md)
- [Events](events.md)
+- [Feature flags](features.md)
- [Gitignores templates](templates/gitignores.md)
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Groups](groups.md)
@@ -28,7 +29,8 @@ following locations:
- [Keys](keys.md)
- [Labels](labels.md)
- [Merge Requests](merge_requests.md)
-- [Milestones](milestones.md)
+- [Project milestones](milestones.md)
+- [Group milestones](group_milestones.md)
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md)
@@ -338,7 +340,18 @@ URL-encoded.
For example, `/` is represented by `%2F`:
```
-/api/v4/projects/diaspora%2Fdiaspora
+GET /api/v4/projects/diaspora%2Fdiaspora
+```
+
+## Branches & tags name encoding
+
+If your branch or tag contains a `/`, make sure the branch/tag name is
+URL-encoded.
+
+For example, `/` is represented by `%2F`:
+
+```
+GET /api/v4/projects/1/branches/my%2Fbranch/commits
```
## `id` vs `iid`
diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md
index 6a4dca92cfe..e4a6dc809b1 100644
--- a/doc/api/ci/lint.md
+++ b/doc/api/ci/lint.md
@@ -47,3 +47,5 @@ Example responses:
"error": "content is missing"
}
```
+
+[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
diff --git a/doc/api/features.md b/doc/api/features.md
index 558869255cc..6861dbf00a2 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -1,4 +1,4 @@
-# Features API
+# Features flags API
All methods require administrator authorization.
@@ -61,7 +61,8 @@ POST /features/:name
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
-Note that `feature_group` and `user` are mutually exclusive.
+Note that you can enable or disable a feature for both a `feature_group` and a
+`user` with a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
new file mode 100644
index 00000000000..086fba7e91d
--- /dev/null
+++ b/doc/api/group_milestones.md
@@ -0,0 +1,120 @@
+# Group milestones API
+
+## List group milestones
+
+Returns a list of group milestones.
+
+```
+GET /groups/:id/milestones
+GET /groups/:id/milestones?iids=42
+GET /groups/:id/milestones?iids[]=42&iids[]=43
+GET /groups/:id/milestones?state=active
+GET /groups/:id/milestones?state=closed
+GET /groups/:id/milestones?search=version
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `iids` | Array[integer] | optional | Return only the milestones having the given `iids` |
+| `state` | string | optional | Return only `active` or `closed` milestones` |
+| `search` | string | optional | Return only milestones with a title or description matching the provided string |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/5/milestones
+```
+
+Example Response:
+
+```json
+[
+ {
+ "id": 12,
+ "iid": 3,
+ "group_id": 16,
+ "title": "10.0",
+ "description": "Version",
+ "due_date": "2013-11-29",
+ "start_date": "2013-11-10",
+ "state": "active",
+ "updated_at": "2013-10-02T09:24:18Z",
+ "created_at": "2013-10-02T09:24:18Z"
+ }
+]
+```
+
+
+## Get single milestone
+
+Gets a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of the group milestone
+
+## Create new milestone
+
+Creates a new group milestone.
+
+```
+POST /groups/:id/milestones
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `title` (required) - The title of an milestone
+- `description` (optional) - The description of the milestone
+- `due_date` (optional) - The due date of the milestone
+- `start_date` (optional) - The start date of the milestone
+
+## Edit milestone
+
+Updates an existing group milestone.
+
+```
+PUT /groups/:id/milestones/:milestone_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
+- `title` (optional) - The title of a milestone
+- `description` (optional) - The description of a milestone
+- `due_date` (optional) - The due date of the milestone
+- `start_date` (optional) - The start date of the milestone
+- `state_event` (optional) - The state event of the milestone (close|activate)
+
+## Get all issues assigned to a single milestone
+
+Gets all issues assigned to a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id/issues
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
+
+## Get all merge requests assigned to a single milestone
+
+Gets all merge requests assigned to a single group milestone.
+
+```
+GET /groups/:id/milestones/:milestone_id/merge_requests
+```
+
+Parameters:
+
+- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user
+- `milestone_id` (required) - The ID of a group milestone
diff --git a/doc/api/issues.md b/doc/api/issues.md
index df5666bb7b6..6cb2eac37c2 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -26,7 +26,8 @@ GET /issues?labels=foo,bar&state=opened
GET /issues?milestone=1.0.0
GET /issues?milestone=1.0.0&state=opened
GET /issues?iids[]=42&iids[]=43
-GET /issues?search=issue+title+or+description
+GET /issues?author_id=5
+GET /issues?assignee_id=5
```
| Attribute | Type | Required | Description |
@@ -34,6 +35,9 @@ GET /issues?search=issue+title+or+description
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` |
+| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
@@ -117,6 +121,8 @@ GET /groups/:id/issues?milestone=1.0.0
GET /groups/:id/issues?milestone=1.0.0&state=opened
GET /groups/:id/issues?iids[]=42&iids[]=43
GET /groups/:id/issues?search=issue+title+or+description
+GET /groups/:id/issues?author_id=5
+GET /groups/:id/issues?assignee_id=5
```
| Attribute | Type | Required | Description |
@@ -126,6 +132,9 @@ GET /groups/:id/issues?search=issue+title+or+description
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `iids` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` |
+| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` |
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search group issues against their `title` and `description` |
@@ -209,6 +218,8 @@ GET /projects/:id/issues?milestone=1.0.0
GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iids[]=42&iids[]=43
GET /projects/:id/issues?search=issue+title+or+description
+GET /projects/:id/issues?author_id=5
+GET /projects/:id/issues?assignee_id=5
```
| Attribute | Type | Required | Description |
@@ -218,6 +229,9 @@ GET /projects/:id/issues?search=issue+title+or+description
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title |
+| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` |
+| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. |
+| `assignee_id` | integer | no | Return issues assigned to the given user `id` |
| `order_by` | string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
@@ -356,7 +370,13 @@ Example response:
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/example/example/issues/1",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -418,7 +438,13 @@ Example response:
"user_notes_count": 0,
"due_date": null,
"web_url": "http://example.com/example/example/issues/14",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -481,7 +507,13 @@ Example response:
"user_notes_count": 0,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/15",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -567,7 +599,13 @@ Example response:
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -632,7 +670,13 @@ Example response:
},
"due_date": null,
"web_url": "http://example.com/example/example/issues/11",
- "confidential": false
+ "confidential": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects/1/issues/2",
+ "notes": "http://example.com/api/v4/projects/1/issues/2/notes",
+ "award_emoji": "http://example.com/api/v4/projects/1/issues/2/award_emoji",
+ "project": "http://example.com/api/v4/projects/1"
+ }
}
```
@@ -964,3 +1008,30 @@ Example response:
## Comments on issues
Comments are done via the [notes](notes.md) resource.
+
+## Get user agent details
+
+Available only for admins.
+
+```
+GET /projects/:id/issues/:issue_iid/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `issue_iid` | integer | yes | The internal ID of a project's issue |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/issues/93/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 3dc808c196d..c90d95e4dd0 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -25,6 +25,7 @@ Parameters:
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
| `milestone` | string | no | Return merge requests for a specific milestone |
+| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels |
| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) |
| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) |
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 07cb64cb373..77bb7a55d8c 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,59 +1,55 @@
# GitLab as an OAuth2 provider
-This document covers using the OAuth2 protocol to access GitLab.
+This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access Gitlab resources on user's behalf.
-If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
+If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [OAuth2 provider](../integration/oauth_provider.md)
+documentation.
-OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party.
+This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
-This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
+## Supported OAuth2 Flows
-## Web Application Flow
+Gitlab currently supports following authorization flows:
-This is the most common type of flow and is used by server-side clients that wish to access GitLab on a user's behalf.
+* *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
+* *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
+* *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
->**Note:**
-This flow **should not** be used for client-side clients as you would leak your `client_secret`. Client-side clients should use the Implicit Grant (which is currently unsupported).
+Please refer to [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out in details how all those flows work and pick the right one for your use case.
-For more detailed information, check out the [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1)
+Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
+in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
+you'll obtain `application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
-In the following sections you will be introduced to the three steps needed for this flow.
+>**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
+value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
+have been a requirement in the standard!
-### 1. Registering the client
+In the following sections you will find detailed instructions on how to obtain authorization with each flow.
-First, you should create an application (`/profile/applications`) in your user's account.
-Each application gets a unique App ID and App Secret parameters.
+### Web Application Flow
->**Note:**
-**You should not share/leak your App ID or App Secret.**
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1) for a detailed flow description
-### 2. Requesting authorization
+#### 1. Requesting authorization code
-To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint:
+To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint with following GET parameters:
```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=your_unique_state_hash
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
```
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided.
+This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect will
+include the GET `code` parameter, for example:
-The redirect will include the GET `code` parameter, for example:
-
-```
-http://myapp.com/oauth/redirect?code=1234567890&state=your_unique_state_hash
-```
+`http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH`
You should then use the `code` to request an access token.
->**Important:**
-It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and
-validate that value is returned and matches in the redirect request.
-This is important to prevent [CSRF attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow),
-`state` really should have been a requirement in the standard!
-
-### 3. Requesting the access token
+#### 2. Requesting access token
-Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example, we are using Ruby's `rest-client`:
+Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
+we are using Ruby's `rest-client`:
```
parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
@@ -72,28 +68,40 @@ The `redirect_uri` must match the `redirect_uri` used in the original authorizat
You can now make requests to the API with the access token returned.
-### Use the access token to access the API
-The access token allows you to make requests to the API on a behalf of a user.
+### Implicit Grant
+
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.2) for a detailed flow description.
+
+Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
+or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+
+>**Important:** Avoid using this flow for applications that store data outside of the Gitlab instance. If you do, make sure to verify `application id`
+associated with access token before granting access to the data
+(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
+
+
+#### 1. Requesting access token
+
+To request the access token, you should redirect the user to the `/oauth/authorize` endpoint using `token` response type:
```
-GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
```
-Or you can put the token to the Authorization header:
+This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
+will include a fragment with `access_token` as well as token details in GET parameters, for example:
```
-curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
+http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE_HASH&token_type=bearer&expires_in=3600
```
-## Resource Owner Password Credentials
+### Resource Owner Password Credentials
-## Deprecation Notice
+Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.3) for a detailed flow description.
-1. Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication turned on.
-2. These users can access the API using [personal access tokens] instead.
-
----
+> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
+turned on. These users can access the API using [personal access tokens] instead.
In this flow, a token is requested in exchange for the resource owner credentials (username and password).
The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
@@ -101,12 +109,16 @@ client is part of the device operating system or a highly privileged application
available (such as an authorization code).
>**Important:**
-Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens] are a better choice.
+Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
+are a better choice.
Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
-You can do POST request to `/oauth/token` with parameters:
+
+#### 1. Requesting access token
+
+POST request to `/oauth/token` with parameters:
```
{
@@ -134,4 +146,18 @@ access_token = client.password.get_token('user@example.com', 'secret')
puts access_token.token
```
+## Access Gitlab API with `access token`
+
+The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
+```
+GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
+```
+
+or you can put the token to the Authorization header:
+
+```
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
+```
+
[personal access tokens]: ../user/profile/personal_access_tokens.md
+[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow \ No newline at end of file
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index 92491de4daa..d74398c6e65 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -119,3 +119,35 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
+
+## Get user agent details
+
+> **Notes:**
+> [Introduced][ce-29508] in GitLab 9.4.
+
+
+Available only for admins.
+
+```
+GET /projects/:id/snippets/:snippet_id/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a snippet |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/1/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
+
+[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
diff --git a/doc/api/projects.md b/doc/api/projects.md
index cc1bb3911c8..d3f8e509612 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -99,6 +99,191 @@ Parameters:
"repository_size": 1038090,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ },
+ },
+ {
+ "id": 6,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
+ "http_url_to_repo": "http://example.com/brightbox/puppet.git",
+ "web_url": "http://example.com/brightbox/puppet",
+ "tag_list": [
+ "example",
+ "puppet"
+ ],
+ "owner": {
+ "id": 4,
+ "name": "Brightbox",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Puppet",
+ "name_with_namespace": "Brightbox / Puppet",
+ "path": "puppet",
+ "path_with_namespace": "brightbox/puppet",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 4,
+ "name": "Brightbox",
+ "path": "brightbox",
+ "kind": "group",
+ "full_path": "brightbox"
+ },
+ "import_status": "none",
+ "import_error": null,
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false,
+ "avatar_url": null,
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "statistics": {
+ "commit_count": 12,
+ "storage_size": 2066080,
+ "repository_size": 2066080,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
+ }
+]
+```
+
+### List a user's projects
+
+Get a list of visible projects for the given user. When accessed without authentication, only public projects are returned.
+
+```
+GET /users/:user_id/projects
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | string | yes | The ID or username of the user |
+| `archived` | boolean | no | Limit by archived status |
+| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` |
+| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` |
+| `search` | string | no | Return list of projects matching the search criteria |
+| `simple` | boolean | no | Return only the ID, URL, name, and path of each project |
+| `owned` | boolean | no | Limit by projects owned by the current user |
+| `membership` | boolean | no | Limit by projects that the current user is a member of |
+| `starred` | boolean | no | Limit by projects starred by the current user |
+| `statistics` | boolean | no | Include project statistics |
+| `with_issues_enabled` | boolean | no | Limit by enabled issues feature |
+| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature |
+
+```json
+[
+ {
+ "id": 4,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
+ "web_url": "http://example.com/diaspora/diaspora-client",
+ "tag_list": [
+ "example",
+ "disapora client"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Diaspora Client",
+ "name_with_namespace": "Diaspora / Diaspora Client",
+ "path": "diaspora-client",
+ "path_with_namespace": "diaspora/diaspora-client",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 3,
+ "name": "Diaspora",
+ "path": "diaspora",
+ "kind": "group",
+ "full_path": "diaspora"
+ },
+ "import_status": "none",
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "statistics": {
+ "commit_count": 37,
+ "storage_size": 1038090,
+ "repository_size": 1038090,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
},
{
@@ -168,6 +353,15 @@ Parameters:
"repository_size": 2066080,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
}
]
@@ -269,6 +463,15 @@ Parameters:
"repository_size": 1038090,
"lfs_objects_size": 0,
"job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
}
}
```
@@ -336,7 +539,7 @@ Parameters:
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
-| `visibility` | String | no | See [project visibility level](#project-visibility-level) |
+| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
| `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
@@ -346,6 +549,7 @@ Parameters:
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
### Create project for user
@@ -382,6 +586,7 @@ Parameters:
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `ci_config_path` | string | no | The path to CI config file |
### Edit project
@@ -416,6 +621,7 @@ Parameters:
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
+| `ci_config_path` | string | no | The path to CI config file |
### Fork project
@@ -498,7 +704,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -564,7 +779,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -648,7 +872,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -732,7 +965,16 @@ Example response:
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
- "request_access_enabled": false
+ "request_access_enabled": false,
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
}
```
@@ -1096,17 +1338,21 @@ endpoint can be accessed without authentication if the project is publicly
accessible.
```
-GET /projects/search/:query
+GET /projects
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `query` | string | yes | A string contained in the project name |
+| `search` | string | yes | A string contained in the project name |
| `order_by` | string | no | Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order |
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects?search=test
+```
+
## Start the Housekeeping task for a Project
>**Note:** This feature was introduced in GitLab 9.0
diff --git a/doc/api/settings.md b/doc/api/settings.md
index eefbdda42ce..0b4cc98cea6 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -25,7 +25,7 @@ Example response:
"id" : 1,
"default_branch_protection" : 2,
"restricted_visibility_levels" : [],
- "signin_enabled" : true,
+ "password_authentication_enabled" : true,
"after_sign_out_path" : null,
"max_attachment_size" : 10,
"user_oauth_applications" : true,
@@ -63,7 +63,7 @@ PUT /application/settings
| --------- | ---- | :------: | ----------- |
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000` |
| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
-| `signin_enabled` | boolean | no | Enable login via a GitLab account. Default is `true`. |
+| `password_authentication_enabled` | boolean | no | Enable authentication via a GitLab account password. Default is `true`. |
| `gravatar_enabled` | boolean | no | Enable Gravatar |
| `sign_in_text` | string | no | Text on login page |
| `home_page_url` | string | no | Redirect to this URL when not logged in |
@@ -102,7 +102,7 @@ Example response:
"id": 1,
"default_projects_limit": 100000,
"signup_enabled": true,
- "signin_enabled": true,
+ "password_authentication_enabled": true,
"gravatar_enabled": true,
"sign_in_text": "",
"created_at": "2015-06-12T15:51:55.432Z",
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index efaab712367..fdafbfb5b9e 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -234,3 +234,35 @@ Example response:
}
]
```
+
+## Get user agent details
+
+> **Notes:**
+> [Introduced][ce-29508] in GitLab 9.4.
+
+
+Available only for admins.
+
+```
+GET /snippets/:id/user_agent_detail
+```
+
+| Attribute | Type | Required | Description |
+|-------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a snippet |
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/snippets/1/user_agent_detail
+```
+
+Example response:
+
+```json
+{
+ "user_agent": "AppleWebKit/537.36",
+ "ip_address": "127.0.0.1",
+ "akismet_submitted": false
+}
+```
+
+[ce-[ce-29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29508
diff --git a/doc/api/users.md b/doc/api/users.md
index cf09b8f44aa..6e5ec3231c5 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -146,6 +146,12 @@ GET /users?extern_uid=1234567&provider=github
You can search for users who are external with: `/users?external=true`
+You can search users by creation date time range with:
+
+```
+GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
+```
+
## Single user
Get a single user.
@@ -358,7 +364,7 @@ GET /user
Parameters:
-- `sudo` (required) - the ID of a user
+- `sudo` (optional) - the ID of a user to make the call in their place
```
GET /user
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 9db8e0351cf..9835fab7c98 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -2,9 +2,11 @@
Since GitLab 9.0, API V4 is the preferred version to be used.
-API V3 will be removed in GitLab 9.5, to be released on August 22, 2017. In the
-meantime, we advise you to make any necessary changes to applications that use
-V3. The V3 API documentation is still [available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
+API V3 will be unsupported from GitLab 9.5, to be released on August
+22, 2017. It will be removed in GitLab 9.5 or later. In the meantime, we advise
+you to make any necessary changes to applications that use V3. The V3 API
+documentation is still
+[available](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-16-stable/doc/api/README.md).
Below are the changes made between V3 and V4.
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index 6892905dd94..130e8f542b4 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -120,7 +120,8 @@ gitlab_rails['ldap_servers'] = {
'host' => 'ad.example.org',
'port' => 636,
'uid' => 'sAMAccountName',
- 'method' => 'ssl',
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
'bind_dn' => 'CN=GitLabSRV,CN=Users,DC=GitLab,DC=org',
'password' => 'Password1',
'active_directory' => true,
@@ -255,7 +256,7 @@ If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab
## LDAP extended features on GitLab EE
-With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/giltab-ee/), besides everything we just described, you'll
+With [GitLab Enterprise Edition (EE)](https://about.gitlab.com/gitlab-ee/), besides everything we just described, you'll
have extended functionalities with LDAP, such as:
- Group sync
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 342fa88e80f..a4e41517d83 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -11,6 +11,7 @@ They are written by members of the GitLab Team and by
- **LDAP**
- [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md)
+ - [How to configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/)
## Git
@@ -23,3 +24,23 @@ They are written by members of the GitLab Team and by
- [Part 2: Quick start guide - Setting up GitLab Pages](../user/project/pages/getting_started_part_two.md)
- [Part 3: Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](../user/project/pages/getting_started_part_three.md)
- [Part 4: Creating and tweaking `.gitlab-ci.yml` for GitLab Pages](../user/project/pages/getting_started_part_four.md)
+- [Building a new GitLab Docs site with Nanoc, GitLab CI, and GitLab Pages](https://about.gitlab.com/2016/12/07/building-a-new-gitlab-docs-site-with-nanoc-gitlab-ci-and-gitlab-pages/)
+- [GitLab CI: Deployment & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
+
+## Sofware development
+
+- [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/)
+- [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/)
+- [Fast and Natural Continuous Integration with GitLab CI](https://about.gitlab.com/2017/05/22/fast-and-natural-continuous-integration-with-gitlab-ci/)
+- [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/)
+- [Continuous Integration, Delivery, and Deployment with GitLab](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/)
+
+## Build, test, and deploy with GitLab CI/CD
+
+**Build, test, and deploy** the software you develop with **[GitLab CI/CD](../ci/README.md)**
+
+- [Continuous Delivery of a Spring Boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
+- [Automated Debian Package Build with GitLab CI](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
+- [Building an Elixir Release into a Docker image using GitLab CI](https://about.gitlab.com/2016/08/11/building-an-elixir-release-into-docker-image-using-gitlab-ci-part-1/)
+- [Setting up GitLab CI for Android projects](https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/)
+- [How to use GitLab CI and MacStadium to build your macOS or iOS projects](https://about.gitlab.com/2017/05/15/how-to-use-macstadium-and-gitlab-ci-to-build-your-macos-or-ios-projects/)
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 3393030210e..df5c66a4c85 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -602,9 +602,8 @@ exist, you should see something like:
>**Notes:**
>
- For the monitor dashboard to appear, you need to:
- - Have enabled the [Kubernetes integration][kube]
- - Have your app deployed on Kubernetes
- Have enabled the [Prometheus integration][prom]
+ - Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/metrics.md)
- With GitLab 9.2, all deployments to an environment are shown directly on the
monitoring dashboard
diff --git a/doc/ci/img/environments_monitoring.png b/doc/ci/img/environments_monitoring.png
index 387b6c54b61..d9c46ea4c95 100644
--- a/doc/ci/img/environments_monitoring.png
+++ b/doc/ci/img/environments_monitoring.png
Binary files differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index d1f9881e51b..22e7f6879ed 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -9,8 +9,9 @@ and a list of **user-defined variables**.
The variables can be overwritten and they take precedence over each other in
this order:
-1. [Trigger variables][triggers] (take precedence over all)
-1. [Secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
+1. [Trigger variables][triggers] or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables) (take precedence over all)
+1. Project-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
+1. Group-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
1. YAML-defined [global variables](../yaml/README.md#variables)
1. [Deployment variables](#deployment-variables)
@@ -37,9 +38,10 @@ future GitLab releases.**
|-------------------------------- |--------|--------|-------------|
| **CI** | all | 0.4 | Mark that job is executed in CI environment |
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
-| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
@@ -141,25 +143,30 @@ script:
>**Notes:**
- This feature requires GitLab Runner 0.4.0 or higher.
+- Group-level secret variables added in GitLab 9.4.
- Be aware that secret variables are not masked, and their values can be shown
in the job logs if explicitly asked to do so. If your project is public or
internal, you can set the pipelines private from your project's Pipelines
settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables.
-GitLab CI allows you to define per-project **secret variables** that are set in
-the build environment. The secret variables are stored out of the repository
-(`.gitlab-ci.yml`) and are securely passed to GitLab Runner making them
-available in the build environment. It's the recommended method to use for
-storing things like passwords, secret keys and credentials.
+GitLab CI allows you to define per-project or per-group **secret variables**
+that are set in the build environment. The secret variables are stored out of
+the repository (`.gitlab-ci.yml`) and are securely passed to GitLab Runner
+making them available in the build environment. It's the recommended method to
+use for storing things like passwords, secret keys and credentials.
-Secret variables can be added by going to your project's
-**Settings âž” Pipelines**, then finding the section called
-**Secret variables**.
+Project-level secret variables can be added by going to your project's
+**Settings âž” Pipelines**, then finding the section called **Secret variables**.
-Once you set them, they will be available for all subsequent pipelines.
+Likewise, group-level secret variables can be added by going to your group's
+**Settings âž” Pipelines**, then finding the section called **Secret variables**.
+Any variables of [subgroups] will be inherited recursively.
+
+Once you set them, they will be available for all subsequent pipelines. You can also
+[protect your variables](#protected-secret-variables).
-## Protected secret variables
+### Protected secret variables
>**Notes:**
This feature requires GitLab 9.3 or higher.
@@ -425,10 +432,12 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
```
[ce-13784]: https://gitlab.com/gitlab-org/gitlab-ce/issues/13784
-[runner]: https://docs.gitlab.com/runner/
-[triggered]: ../triggers/README.md
-[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
+[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium"
+[envs]: ../environments.md
[protected branches]: ../../user/project/protected_branches.md
[protected tags]: ../../user/project/protected_tags.md
+[runner]: https://docs.gitlab.com/runner/
[shellexecutors]: https://docs.gitlab.com/runner/executors/
-[eep]: https://about.gitlab.com/gitlab-ee/ "Available only in GitLab Enterprise Edition Premium"
+[triggered]: ../triggers/README.md
+[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
+[subgroups]: ../../user/group/subgroups/index.md
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 8a0662db6fd..e12ef6e2685 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -306,6 +306,53 @@ cache:
untracked: true
```
+### cache:policy
+
+> Introduced in GitLab 9.4.
+
+The default behaviour of a caching job is to download the files at the start of
+execution, and to re-upload them at the end. This allows any changes made by the
+job to be persisted for future runs, and is known as the `pull-push` cache
+policy.
+
+If you know the job doesn't alter the cached files, you can skip the upload step
+by setting `policy: pull` in the job specification. Typically, this would be
+twinned with an ordinary cache job at an earlier stage to ensure the cache
+is updated from time to time:
+
+```yaml
+stages:
+ - setup
+ - test
+
+prepare:
+ stage: setup
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ script:
+ - bundle install --deployment
+
+rspec:
+ stage: test
+ cache:
+ key: gems
+ paths:
+ - vendor/bundle
+ policy: pull
+ script:
+ - bundle exec rspec ...
+```
+
+This helps to speed up job execution and reduce load on the cache server,
+especially when you have a large number of cache-using jobs executing in
+parallel.
+
+Additionally, if you have a job that unconditionally recreates the cache without
+reference to its previous contents, you can use `policy: push` in that job to
+skip the download step.
+
## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
@@ -348,6 +395,7 @@ job_name:
| after_script | no | Override a set of commands that are executed after job |
| environment | no | Defines a name of environment to which deployment is done by this job |
| coverage | no | Define code coverage settings for a given job |
+| retry | no | Define how many times a job can be auto-retried in case of a failure |
### script
@@ -1082,9 +1130,33 @@ A simple example:
```yaml
job1:
+ script: rspec
coverage: '/Code coverage: \d+\.\d+/'
```
+### retry
+
+**Notes:**
+- [Introduced][ce-3442] in GitLab 9.5.
+
+`retry` allows you to configure how many times a job is going to be retried in
+case of a failure.
+
+When a job fails, and has `retry` configured it is going to be processed again
+up to the amount of times specified by the `retry` keyword.
+
+If `retry` is set to 2, and a job succeeds in a second run (first retry), it won't be retried
+again. `retry` value has to be a positive integer, equal or larger than 0, but
+lower or equal to 2 (two retries maximum, three runs in total).
+
+A simple example:
+
+```yaml
+test:
+ script: rspec
+ retry: 2
+```
+
## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
@@ -1459,3 +1531,4 @@ CI with various languages.
[variables]: ../variables/README.md
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
+[ce-3442]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3442
diff --git a/doc/development/README.md b/doc/development/README.md
index a2a07c37ced..58993c52dcd 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -55,6 +55,7 @@
- [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
+- [Iterating Tables In Batches](iterating_tables_in_batches.md)
## i18n
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index 0239e6b3163..e67db9ff142 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -31,6 +31,18 @@ It's also possible for different migrations to be executed at the same time.
This means that different background migrations should not migrate data in a
way that would cause conflicts.
+## Idempotence
+
+Background migrations are executed in a context of a Sidekiq process.
+Usual Sidekiq rules apply, especially the rule that jobs should be small
+and idempotent.
+
+See [Sidekiq best practices guidelines](https://github.com/mperham/sidekiq/wiki/Best-Practices)
+for more details.
+
+Make sure that in case that your migration job is going to be retried data
+integrity is guarateed.
+
## How It Works
Background migrations are simple classes that define a `perform` method. A
@@ -50,14 +62,13 @@ your migration:
BackgroundMigrationWorker.perform_async('BackgroundMigrationClassName', [arg1, arg2, ...])
```
-Usually it's better to schedule jobs in bulk, for this you can use
+Usually it's better to enqueue jobs in bulk, for this you can use
`BackgroundMigrationWorker.perform_bulk`:
```ruby
BackgroundMigrationWorker.perform_bulk(
- ['BackgroundMigrationClassName', [1]],
- ['BackgroundMigrationClassName', [2]],
- ...
+ [['BackgroundMigrationClassName', [1]],
+ ['BackgroundMigrationClassName', [2]]]
)
```
@@ -68,6 +79,16 @@ consuming migrations it's best to schedule a background job using an
updates. Removals in turn can be handled by simply defining foreign keys with
cascading deletes.
+If you would like to schedule jobs in bulk with a delay, you can use
+`BackgroundMigrationWorker.perform_bulk_in`:
+
+```ruby
+jobs = [['BackgroundMigrationClassName', [1]],
+ ['BackgroundMigrationClassName', [2]]]
+
+BackgroundMigrationWorker.perform_bulk_in(5.minutes, jobs)
+```
+
## Cleaning Up
Because background migrations can take a long time you can't immediately clean
@@ -203,3 +224,27 @@ end
This migration will then process any jobs for the ExtractServicesUrl migration
and continue once all jobs have been processed. Once done you can safely remove
the `services.properties` column.
+
+## Testing
+
+It is required to write tests for background migrations' scheduling migration
+(either a regular migration or a post deployment migration), background
+migration itself and a cleanup migration. You can use the `:migration` RSpec
+tag when testing a regular / post deployment migration.
+See [README][migrations-readme].
+
+When you do that, keep in mind that `before` and `after` RSpec hooks are going
+to migrate you database down and up, which can result in other background
+migrations being called. That means that using `spy` test doubles with
+`have_received` is encouraged, instead of using regular test doubles, because
+your expectations defined in a `it` block can conflict with what is being
+called in RSpec hooks. See [gitlab-org/gitlab-ce#35351][issue-rspec-hooks]
+for more details.
+
+## Best practices
+
+1. Make sure that background migration jobs are idempotent.
+1. Make sure that tests you write are not false positives.
+
+[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
+[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4ed89146072..e3f37616757 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -133,6 +133,55 @@ reviewee.
tomorrow. When you are not able to find the right balance, ask other people
about their opinion.
+### GitLab-specific concerns
+
+GitLab is used in a lot of places. Many users use
+our [Omnibus packages](https://about.gitlab.com/installation/), but some use
+the [Docker images](https://docs.gitlab.com/omnibus/docker/), some are
+[installed from source](https://docs.gitlab.com/ce/install/installation.html),
+and there are other installation methods available. GitLab.com itself is a large
+Enterprise Edition instance. This has some implications:
+
+1. **Query changes** should be tested to ensure that they don't result in worse
+ performance at the scale of GitLab.com:
+ 1. Generating large quantities of data locally can help.
+ 2. Asking for query plans from GitLab.com is the most reliable way to validate
+ these.
+2. **Database migrations** must be:
+ 1. Reversible.
+ 2. Performant at the scale of GitLab.com - ask a maintainer to test the
+ migration on the staging environment if you aren't sure.
+ 3. Categorised correctly:
+ - Regular migrations run before the new code is running on the instance.
+ - [Post-deployment migrations](post_deployment_migrations.md) run _after_
+ the new code is deployed, when the instance is configured to do that.
+ - [Background migrations](background_migrations.md) run in Sidekiq, and
+ should only be done for migrations that would take an extreme amount of
+ time at GitLab.com scale.
+3. **Sidekiq workers**
+ [cannot change in a backwards-incompatible way](sidekiq_style_guide.md#removing-or-renaming-queues):
+ 1. Sidekiq queues are not drained before a deploy happens, so there will be
+ workers in the queue from the previous version of GitLab.
+ 2. If you need to change a method signature, try to do so across two releases,
+ and accept both the old and new arguments in the first of those.
+ 3. Similarly, if you need to remove a worker, stop it from being scheduled in
+ one release, then remove it in the next. This will allow existing jobs to
+ execute.
+ 4. Don't forget, not every instance will upgrade to every intermediate version
+ (some people may go from X.1.0 to X.10.0, or even try bigger upgrades!), so
+ try to be liberal in accepting the old format if it is cheap to do so.
+4. **Cached values** may persist across releases. If you are changing the type a
+ cached value returns (say, from a string or nil to an array), change the
+ cache key at the same time.
+5. **Settings** should be added as a
+ [last resort](https://about.gitlab.com/handbook/product/#convention-over-configuration).
+ If you're adding a new setting in `gitlab.yml`:
+ 1. Try to avoid that, and add to `ApplicationSetting` instead.
+ 2. Ensure that it is also
+ [added to Omnibus](https://docs.gitlab.com/omnibus/settings/gitlab.yml.html#adding-a-new-setting-to-gitlab-yml).
+6. **Filesystem access** can be slow, so try to avoid
+ [shared files](shared_files.md) when an alternative solution is available.
+
### Credits
Largely based on the [thoughtbot code review guide].
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 5b09f79f143..90d1d9657b9 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -105,8 +105,8 @@ merge request.
considered beta or experimental, put this info in a note, not in the heading.
- When introducing a new document, be careful for the headings to be
grammatically and syntactically correct. It is advised to mention one or all
- of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`,
- `@SeanPackham`. This is to ensure that no document with wrong heading is going
+ of the following GitLab members for a review: `@axil`, `@rspeicher`, `@marcia`.
+ This is to ensure that no document with wrong heading is going
live without an audit, thus preventing dead links and redirection issues when
corrected
- Leave exactly one newline after a heading
@@ -388,8 +388,8 @@ the style below as a guide:
1. Save the file and [restart] GitLab for the changes to take effect.
-[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure
-[restart]: path/to/administration/gitlab_restart.md#installations-from-source
+[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart]: path/to/administration/restart_gitlab.md#installations-from-source
````
In this case:
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index a549603c20d..94f3c8254a8 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -1,6 +1,6 @@
# InputSetter
-`InputSetter` is a plugin that allows for udating DOM out of the scope of droplab when a list item is clicked.
+`InputSetter` is a plugin that allows for updating DOM out of the scope of droplab when a list item is clicked.
## Usage
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 2ddcbe13afa..14ac1133cc0 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -23,6 +23,19 @@ controlled by the server.
1. The backend code will most likely be using etags. You do not and should not check for status
`304 Not Modified`. The browser will transform it for you.
+### Lazy Loading
+
+To improve the time to first render we are using lazy loading for images. This works by setting
+the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
+the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
+
+* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`
+* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+
+If you are asynchronously adding content which contains lazy images then you need to call the function
+`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
+But in general it should be handled automatically through a `MutationObserver` in the lazy loading function.
+
## Reducing Asset Footprint
### Page-specific JavaScript
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index ae844fa1051..149a0159680 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -447,6 +447,7 @@ A forEach will cause side effects, it will be mutating the array being iterated.
1. `name`
1. `props`
1. `mixins`
+ 1. `directives`
1. `data`
1. `components`
1. `computedProps`
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index a984bb6c94c..0742b202807 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -112,7 +112,50 @@ Vue Resource should only be imported in the service file.
Vue.use(VueResource);
```
-### CSRF token
+#### Vue-resource gotchas
+#### Headers
+Headers are being parsed into a plain object in an interceptor.
+In Vue-resource 1.x `headers` object was changed into an `Headers` object. In order to not change all old code, an interceptor was added.
+
+If you need to write a unit test that takes the headers in consideration, you need to include an interceptor to parse the headers after your test interceptor.
+You can see an example in `spec/javascripts/environments/environment_spec.js`:
+ ```javascript
+ import { headersInterceptor } from './helpers/vue_resource_helper';
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(myInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, myInterceptor);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
+ });
+ ```
+
+#### `.json()`
+When making a request to the server, you will most likely need to access the body of the response.
+Use `.json()` to convert. Because `.json()` returns a Promise the follwoing structure should be used:
+
+ ```javascript
+ service.get('url')
+ .then(resp => resp.json())
+ .then((data) => {
+ this.store.storeData(data);
+ })
+ .catch(() => new Flash('Something went wrong'));
+ ```
+
+When using `Poll` (`app/assets/javascripts/lib/utils/poll.js`), the `successCallback` needs to handle `.json()` as a Promise:
+ ```javascript
+ successCallback: (response) => {
+ return response.json().then((data) => {
+ // handle the response
+ });
+ }
+ ```
+
+#### CSRF token
We use a Vue Resource interceptor to manage the CSRF token.
`app/assets/javascripts/vue_shared/vue_resource_interceptor.js` holds all our common interceptors.
Note: You don't need to load `app/assets/javascripts/vue_shared/vue_resource_interceptor.js`
@@ -126,13 +169,13 @@ The following example shows an application:
// store.js
export default class Store {
- /**
+ /**
* This is where we will iniatialize the state of our data.
* Usually in a small SPA you don't need any options when starting the store. In the case you do
* need guarantee it's an Object and it's documented.
- *
- * @param {Object} options
- */
+ *
+ * @param {Object} options
+ */
constructor(options) {
this.options = options;
@@ -205,14 +248,14 @@ import Store from 'store';
import Service from 'service';
import TodoComponent from 'todoComponent';
export default {
- /**
+ /**
* Although most data belongs in the store, each component it's own state.
* We want to show a loading spinner while we are fetching the todos, this state belong
* in the component.
*
* We need to access the store methods through all methods of our component.
* We need to access the state of our store.
- */
+ */
data() {
const store = new Store();
@@ -396,42 +439,46 @@ need to test the rendered output. [Vue][vue-test] guide's to unit test show us e
[Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with
the response we need:
-```javascript
- // Mock the service to return data
- const interceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([{
- title: 'This is a todo',
- body: 'This is the text'
- }]), {
- status: 200,
- }));
- };
+ ```javascript
+ // Mock the service to return data
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify([{
+ title: 'This is a todo',
+ body: 'This is the text'
+ }]), {
+ status: 200,
+ }));
+ };
- beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- });
+ beforeEach(() => {
+ Vue.http.interceptors.push(interceptor);
+ });
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
- });
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ });
- it('should do something', (done) => {
- setTimeout(() => {
- // Test received data
- done();
- }, 0);
- });
-```
+ it('should do something', (done) => {
+ setTimeout(() => {
+ // Test received data
+ done();
+ }, 0);
+ });
+ ```
+
+1. Headers interceptor
+Refer to [this section](vue.md#headers)
1. Use `$.mount()` to mount the component
+
```javascript
- // bad
- new Component({
- el: document.createElement('div')
- });
+// bad
+new Component({
+ el: document.createElement('div')
+});
- // good
- new Component().$mount();
+// good
+new Component().$mount();
```
[vue-docs]: http://vuejs.org/guide/index.html
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 5c6316b9ac6..59e8a087e02 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -3,5 +3,19 @@
Starting from GitLab 9.3 we support feature flags via
[Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature`
class (defined in `lib/feature.rb`) in your code to get, set and list feature
-flags. During runtime you can set the values for the gates via the
-[admin API](../api/features.md).
+flags.
+
+During runtime you can set the values for the gates via the
+[features API](../api/features.md) (accessible to admins only).
+
+## Feature groups
+
+Starting from GitLab 9.4 we support feature groups via
+[Flipper groups](https://github.com/jnunemaker/flipper/blob/v0.10.2/docs/Gates.md#2-group).
+
+Feature groups must be defined statically in `lib/feature.rb` (in the
+`.register_feature_groups` method), but their implementation can obviously be
+dynamic (querying the DB etc.).
+
+Once defined in `lib/feature.rb`, you will be able to activate a
+feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 565d4b33457..c2ca8966a3f 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -3,35 +3,6 @@
The purpose of this guide is to document potential "gotchas" that contributors
might encounter or should avoid during development of GitLab CE and EE.
-## Do not `describe` symbols
-
-Consider the following model spec:
-
-```ruby
-require 'rails_helper'
-
-describe User do
- describe :to_param do
- it 'converts the username to a param' do
- user = described_class.new(username: 'John Smith')
-
- expect(user.to_param).to eq 'john-smith'
- end
- end
-end
-```
-
-When run, this spec doesn't do what we might expect:
-
-```sh
-spec/models/user_spec.rb|6 error| Failure/Error: u = described_class.new NoMethodError: undefined method `new' for :to_param:Symbol
-```
-
-### Solution
-
-Except for the top-level `describe` block, always provide a String argument to
-`describe`.
-
## Do not assert against the absolute value of a sequence-generated attribute
Consider the following factory:
diff --git a/doc/development/iterating_tables_in_batches.md b/doc/development/iterating_tables_in_batches.md
new file mode 100644
index 00000000000..590c8cbba2d
--- /dev/null
+++ b/doc/development/iterating_tables_in_batches.md
@@ -0,0 +1,37 @@
+# Iterating Tables In Batches
+
+Rails provides a method called `in_batches` that can be used to iterate over
+rows in batches. For example:
+
+```ruby
+User.in_batches(of: 10) do |relation|
+ relation.update_all(updated_at: Time.now)
+end
+```
+
+Unfortunately this method is implemented in a way that is not very efficient,
+both query and memory usage wise.
+
+To work around this you can include the `EachBatch` module into your models,
+then use the `each_batch` class method. For example:
+
+```ruby
+class User < ActiveRecord::Base
+ include EachBatch
+end
+
+User.each_batch(of: 10) do |relation|
+ relation.update_all(updated_at: Time.now)
+end
+```
+
+This will end up producing queries such as:
+
+```
+User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
+ (0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
+```
+
+The API of this method is similar to `in_batches`, though it doesn't support
+all of the arguments that `in_batches` supports. You should always use
+`each_batch` _unless_ you have a specific need for `in_batches`.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index fdaaa65fa28..42bb5e8619c 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -12,6 +12,56 @@ The `setup` task is a alias for `gitlab:setup`.
This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
+### Automation
+
+If you're very sure that you want to **wipe the current database** and refill
+seeds, you could:
+
+``` shell
+echo 'yes' | bundle exec rake setup
+```
+
+To save you from answering `yes` manually.
+
+### Discard stdout
+
+Since the script would print a lot of information, it could be slowing down
+your terminal, and it would generate more than 20G logs if you just redirect
+it to a file. If we don't care about the output, we could just redirect it to
+`/dev/null`:
+
+``` shell
+echo 'yes' | bundle exec rake setup > /dev/null
+```
+
+Note that since you can't see the questions from stdout, you might just want
+to `echo 'yes'` to keep it running. It would still print the errors on stderr
+so no worries about missing errors.
+
+### Notes for MySQL
+
+Since the seeds would contain various UTF-8 characters, such as emojis or so,
+we'll need to make sure that we're using `utf8mb4` for all the encoding
+settings and `utf8mb4_unicode_ci` for collation. Please check
+[MySQL utf8mb4 support](../install/database_mysql.md#mysql-utf8mb4-support)
+
+Make sure that `config/database.yml` has `encoding: utf8mb4`, too.
+
+Next, we'll need to update the schema to make the indices fit:
+
+``` shell
+sed -i 's/limit: 255/limit: 191/g' db/schema.rb
+```
+
+Then run the setup script:
+
+``` shell
+bundle exec rake setup
+```
+
+To make sure that indices still fit. You could find great details in:
+[How to support full Unicode in MySQL databases](https://mathiasbynens.be/notes/mysql-utf8mb4)
+
## Run tests
In order to run the test you can use the following commands:
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index e3a20f29a09..1e9fdbc65e2 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -36,3 +36,10 @@ slow jobs blocking work (even for different jobs) on the shared queue.
Each Sidekiq worker must be tested using RSpec, just like any other class. These
tests should be placed in `spec/workers`.
+
+## Removing or renaming queues
+
+Try to avoid renaming or removing queues in minor and patch releases.
+During online update instance can have pending jobs and removing the queue can
+lead to those jobs being stuck forever. If you can't write migration for those
+Sidekiq jobs, please consider doing rename or remove queue in major release only. \ No newline at end of file
diff --git a/doc/development/testing.md b/doc/development/testing.md
index cf3ea2ccfc2..e6aa4ae8f2f 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -195,7 +195,6 @@ Please consult the [dedicated "Frontend testing" guide](./fe_guide/testing.md).
- Use `context` to test branching logic.
- Use multi-line `do...end` blocks for `before` and `after`, even when it would
fit on a single line.
-- Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)).
- Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)).
- Don't supply the `:each` argument to hooks since it's the default.
- Prefer `not_to` to `to_not` (_this is enforced by RuboCop_).
@@ -479,6 +478,11 @@ slowest test files and try to improve them.
run the suite against MySQL for tags, `master`, and any branch that includes
`mysql` in the name.
- On EE, the test suite always runs both PostgreSQL and MySQL.
+- Rails logging to `log/test.log` is disabled by default in CI [for
+ performance reasons][logging]. To override this setting, provide the
+ `RAILS_ENABLE_TEST_LOG` environment variable.
+
+[logging]: https://jtway.co/speed-up-your-rails-test-suite-by-6-in-1-line-13fedb869ec4
## Spinach (feature) tests
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
index fe4b6d73771..75bae324585 100644
--- a/doc/downgrade_ee_to_ce/README.md
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -46,6 +46,19 @@ $ sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDepre
$ bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService']).delete_all" production
```
+### Secret variables environment scopes
+
+If you're using this feature and there are variables sharing the same
+key, but they have different scopes in a project, then you might want to
+revisit the environment scope setting for those variables.
+
+In CE, environment scopes are completely ignored, therefore you could
+accidentally get a variable which you're not expecting for a particular
+environment. Make sure that you have the right variables in this case.
+
+Data is completely preserved, so you could always upgrade back to EE and
+restore the behavior if you leave it alone.
+
## Downgrade to CE
After performing the above mentioned steps, you are now ready to downgrade your
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index 12466437edc..3d893ba53dd 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -6,7 +6,7 @@ Step-by-step guides on the basics of working with Git and GitLab.
- [Start using Git on the command line](start-using-git.md)
- [Create and add your SSH Keys](create-your-ssh-keys.md)
- [Create a project](create-project.md)
-- [Create a group](create-group.md)
+- [Create a group](../user/group/index.md#create-a-new-group)
- [Create a branch](create-branch.md)
- [Fork a project](fork-project.md)
- [Add a file](add-file.md)
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index b4889bb8818..985a52d88f5 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -1,50 +1,2 @@
-# How to create a group in GitLab
-Your projects in GitLab can be organized in 2 different ways:
-under your own namespace for single projects, such as `your-name/project-1` or
-under groups.
-
-If you organize your projects under a group, it works like a folder. You can
-manage your group members' permissions and access to the projects.
-
----
-
-To create a group:
-
-1. Expand the left sidebar by clicking the three bars at the upper left corner
- and then navigate to **Groups**.
-
- ![Go to groups](img/create_new_group_sidebar.png)
-
-1. Once in your groups dashboard, click on **New group**.
-
- ![Create new group information](img/create_new_group_info.png)
-
-1. Fill out the needed information:
-
- 1. Set the "Group path" which will be the namespace under which your projects
- will be hosted (path can contain only letters, digits, underscores, dashes
- and dots; it cannot start with dashes or end in dot).
- 1. The "Group name" will populate with the path. Optionally, you can change
- it. This is the name that will display in the group views.
- 1. Optionally, you can add a description so that others can briefly understand
- what this group is about.
- 1. Optionally, choose and avatar for your project.
- 1. Choose the [visibility level](../public_access/public_access.md).
-
-1. Finally, click the **Create group** button.
-
-## Add a new project to a group
-
-There are 2 different ways to add a new project to a group:
-
-- Select a group and then click on the **New project** button.
-
- ![New project](img/create_new_project_from_group.png)
-
- You can then continue on [creating a project](create-project.md).
-
-- While you are [creating a project](create-project.md), select a group namespace
- you've already created from the dropdown menu.
-
- ![Select group](img/select_group_dropdown.png)
+This document was moved to [another location](../user/group/index.md#create-a-new-group).
diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png
deleted file mode 100644
index fa88d1d51c0..00000000000
--- a/doc/gitlab-basics/img/create_new_group_sidebar.png
+++ /dev/null
Binary files differ
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 9a171d34671..bc75dc1447e 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -39,11 +39,14 @@ mysql> SET storage_engine=INNODB;
# If you have MySQL < 5.7.7 and want to enable utf8mb4 character set support with your GitLab install, you must set the following NOW:
mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_large_prefix=1;
+# If you use MySQL with replication, or just have MySQL configured with binary logging, you need to run the following to allow the use of `TRIGGER`:
+mysql> SET GLOBAL log_bin_trust_function_creators = 1;
+
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Grant the GitLab user necessary permissions on the database
-mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost';
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session
mysql> \q
@@ -60,7 +63,15 @@ mysql> \q
```
You are done installing the database for now and can go back to the rest of the installation.
-Please proceed to the rest of the installation before running through the utf8mb4 support section.
+Please proceed to the rest of the installation **before** running through the steps below.
+
+### `log_bin_trust_function_creators`
+
+If you use MySQL with replication, or just have MySQL configured with binary logging, all of your MySQL servers will need to have `log_bin_trust_function_creators` enabled to allow the use of `TRIGGER` in migrations. You have already set this global variable in the steps above, but to make it persistent, add the following to your `my.cnf` file:
+
+```
+log_bin_trust_function_creators=1
+```
### MySQL utf8mb4 support
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index 35220119e9b..c6b767fff02 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -2,13 +2,13 @@
![GCP landing page](img/gcp_landing.png)
->**Important note:**
-GitLab has no official images in Google Cloud Platform yet. This guide serves
-as a template for when the GitLab VM will be available.
-
The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through
the [Google Cloud Launcher][launcher] program.
+GitLab's official Google Launcher apps:
+1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public)
+2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public)
+
## Prerequisites
There are only two prerequisites in order to install GitLab on GCP:
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 992ff162efb..8ded607bcab 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
+ sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
If you want to use Kerberos for user authentication, then install libkrb5-dev:
@@ -294,9 +294,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 9-3-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 9-4-stable gitlab
-**Note:** You can change `9-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `9-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -420,6 +420,12 @@ GitLab Shell is an SSH access and repository management software developed speci
**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up GitLab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)".
+**Note:** GitLab Shell application startup time can be greatly reduced by disabling RubyGems. This can be done in several manners:
+
+* Export `RUBYOPT=--disable-gems` environment variable for the processes
+* Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommened for system-wide Ruby.
+* Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707)
+
### Install gitlab-workhorse
GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index b8bc0795f2e..515b2841d08 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -54,6 +54,13 @@ gitlabURL: http://gitlab.your-domain.com/
##
runnerRegistrationToken: ""
+## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use
+## Provide resource name for a Kubernetes Secret Object in the same namespace,
+## this is used to populate the /etc/gitlab-runner/certs directory
+## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
+##
+#certsSecretName:
+
## Configure the maximum number of concurrent jobs
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
@@ -135,6 +142,52 @@ runners:
privileged: true
```
+### Providing a custom certificate for accessing GitLab
+
+You can provide a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/)
+to the GitLab Runner Helm Chart, which will be used to populate the container's
+`/etc/gitlab-runner/certs` directory.
+
+Each key name in the Secret will be used as a filename in the directory, with the
+file content being the value associated with the key.
+
+More information on how GitLab Runner uses these certificates can be found in the
+[Runner Documentation](https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates).
+
+ - The key/file name used should be in the format `<gitlab-hostname>.crt`. For example: `gitlab.your-domain.com.crt`.
+ - Any intermediate certificates need to be concatenated to your server certificate in the same file.
+ - The hostname used should be the one the certificate is registered for.
+
+The GitLab Runner Helm Chart does not create a secret for you. In order to create
+the secret, you can prepare your certificate on you local machine, and then run
+the `kubectl create secret` command from the directory with the certificate
+
+```bash
+kubectl
+ --namespace <NAMESPACE>
+ create secret generic <SECRET_NAME>
+ --from-file=<CERTFICATE_FILENAME>
+```
+
+- `<NAMESPACE>` is the Kubernetes namespace where you want to install the GitLab Runner.
+- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
+- `<CERTFICATE_FILENAME>` is the filename for the certificate in your current directory that will be imported into the secret
+
+You then need to provide the secret's name to the GitLab Runner chart.
+
+Add the following to your `values.yaml`
+
+```yaml
+## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use
+## Provide resource name for a Kubernetes Secret Object in the same namespace,
+## this is used to populate the /etc/gitlab-runner/certs directory
+## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates
+##
+certsSecretName: <SECRET NAME>
+```
+
+- `<SECRET_NAME>` is the Kubernetes Secret resource name. For example: `gitlab-domain-cert`
+
## Installing GitLab Runner using the Helm Chart
Once you [have configured](#configuration) GitLab Runner in your `values.yml` file,
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index a3d676433e6..141df55f6bc 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -69,7 +69,7 @@ so keep in mind that you need at least 4GB available before running GitLab. With
less memory GitLab will give strange errors during the reconfigure run and 500
errors during usage.
-- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
+- 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the [unicorn worker section below](#unicorn-workers) for more advice.
- 2GB RAM + 2GB swap supports up to 100 users but it will be very slow
- **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
- 8GB RAM supports up to 1,000 users
diff --git a/doc/integration/README.md b/doc/integration/README.md
index e56e58498a6..d70b9a7f54b 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -5,19 +5,23 @@ trackers and external authentication.
See the documentation below for details on how to configure these services.
-- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Akismet](akismet.md) Configure Akismet to stop spam
+- [Auth0 OmniAuth](auth0.md) Enable the Auth0 OmniAuth provider
+- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your
+Bitbucket.org account
+- [CAS](cas.md) Configure GitLab to sign in using CAS
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
+- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
+- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
- [LDAP](ldap.md) Set up sign in via LDAP
-- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
-- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
-- [CAS](cas.md) Configure GitLab to sign in using CAS
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
+- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google, Bitbucket, Facebook, Shibboleth, SAML, Crowd, Azure and Authentiq ID
- [OpenID Connect](openid_connect_provider.md) Use GitLab as an identity provider
-- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
-- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
-- [Akismet](akismet.md) Configure Akismet to stop spam
-- [Koding](../administration/integration/koding.md) Configure Koding to use IDE integration
- [PlantUML](../administration/integration/plantuml.md) Configure PlantUML to use diagrams in AsciiDoc documents.
+- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
+- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
+- [Trello](trello_power_up.md) Integrate Trello with GitLab
> GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 2dd9b33273c..372e1909330 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -4,14 +4,12 @@ GitLab has a great issue tracker but you can also use an external one such as
Jira, Redmine, or Bugzilla. Issue trackers are configurable per GitLab project and allow
you to do the following:
-- the **Issues** link on the GitLab project pages takes you to the appropriate
- issue index of the external tracker
-- clicking **New issue** on the project dashboard creates a new issue on the
- external tracker
- you can reference these external issues inside GitLab interface
(merge requests, commits, comments) and they will be automatically converted
into links
+You can have enabled both external and internal GitLab issue trackers in parallel. The **Issues** link always opens the internal issue tracker and in case the internal issue tracker is disabled the link is not visible in the menu.
+
## Configuration
The configuration is done via a project's **Services**.
diff --git a/doc/integration/img/enable_trello_powerup.png b/doc/integration/img/enable_trello_powerup.png
new file mode 100644
index 00000000000..65d01f1c38c
--- /dev/null
+++ b/doc/integration/img/enable_trello_powerup.png
Binary files differ
diff --git a/doc/integration/img/trello_card_with_gitlab_powerup.png b/doc/integration/img/trello_card_with_gitlab_powerup.png
new file mode 100644
index 00000000000..2965dc35855
--- /dev/null
+++ b/doc/integration/img/trello_card_with_gitlab_powerup.png
Binary files differ
diff --git a/doc/integration/trello_power_up.md b/doc/integration/trello_power_up.md
new file mode 100644
index 00000000000..d264486a872
--- /dev/null
+++ b/doc/integration/trello_power_up.md
@@ -0,0 +1,42 @@
+# Trello Power-Up
+
+GitLab's Trello Power-Up enables you to seamlessly attach
+GitLab **merge requests** to Trello cards.
+
+![GitLab Trello PowerUp - Trello card](img/trello_card_with_gitlab_powerup.png)
+
+## Configuring the Power-Up
+
+In order to get started, you will need to configure your Power-Up.
+
+In Trello:
+
+1. Go to your Trello board
+1. Select `Power-Ups` to see a listing of all the available Power-Ups
+1. Look for a row that says `GitLab` and select the `Enable` button
+1. Select the `Settings` (gear) icon
+1. In the popup menu, select `Authorize Account`
+
+In this popup, fill in your `API URL` and `Personal Access Token`. After that, you will be able to attach any merge request to any Trello card on your selected Trello board.
+
+## What is my API URL?
+
+Your API URL should be your GitLab instance URL with `/api/v4` appended in the end of the URL.
+For example, if your GitLab instance URL is `https://gitlab.com`, your API URL would be `https://gitlab.com/api/v4`.
+If your instance's URL is `https://example.com`, your API URL will be `https://example.com/api/v4`.
+
+![configure GitLab Trello PowerUp in Trello](img/enable_trello_powerup.png)
+
+## What is my Personal Access Token?
+
+Your GitLab's personal access token will enable your GitLab account to be accessed
+from Trello.
+
+> Find it in GitLab by clicking on your avatar (upright corner), from which you access
+your user **Settings** > **Access Tokens**.
+
+Learn more about generating a personal access token in the
+[Personal Access Token Documentation][personal-access-token-documentation].
+Don't forget to check the API scope checkbox!
+
+[personal-access-token-documentation]: ../user/profile/personal_access_tokens.html
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index ff7c5b1c89b..10f5ab3370d 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -270,6 +270,15 @@ For installations from source:
remote_directory: 'gitlab_backups'
```
+### Specifying a custom directory for backups
+
+If you want to group your backups you can pass a `DIRECTORY` environment variable:
+
+```
+sudo gitlab-rake gitlab:backup:create DIRECTORY=daily
+sudo gitlab-rake gitlab:backup:create DIRECTORY=weekly
+```
+
### Backup archive permissions
The backup archives created by GitLab (`1393513186_2014_02_27_gitlab_backup.tar`)
diff --git a/doc/university/process/README.md b/doc/university/process/README.md
index 7ff53c2cc3f..04f2d52514f 100644
--- a/doc/university/process/README.md
+++ b/doc/university/process/README.md
@@ -27,4 +27,4 @@ please submit a merge request to add an upcoming class, assign to
1. Please upload any video recordings to our Youtube channel. We prefer them to
be public, if needed they can be unlisted but if so they should be linked from
this page.
-1. Please create a merge request and assign to [SeanPackham](https://gitlab.com/u/SeanPackham).
+1. Please create a merge request and assign to [Erica](https://gitlab.com/u/Erica).
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index d6b2f11d49a..42132f690d8 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -156,7 +156,7 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
-
+
For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index a2dc1dadcdc..910539acc70 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -156,7 +156,29 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update configuration files
+### 10. 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
+```
+
+### 11. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -230,7 +252,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 11. Install libs, migrations, etc.
+### 12. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -256,14 +278,14 @@ 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).
-### 12. Start application
+### 13. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 13. Check application status
+### 14. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index a712ce5a8b1..9540c36e7d0 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -72,8 +72,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
### 5. Update Go
-NOTE: GitLab 9.4 and higher only supports Go 1.8.3 and dropped support for Go 1.5.x through 1.7.x. Be
-sure to upgrade your installation if necessary
+NOTE: GitLab 9.2 and higher only supports Go 1.8.3 and dropped support for Go
+1.5.x through 1.7.x. Be sure to upgrade your installation if necessary.
You can check which version you are running with `go version`.
@@ -117,7 +117,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout 9-4-stable-ee
```
-### 5. Update gitlab-shell
+### 7. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
@@ -127,11 +127,10 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 6. Update gitlab-workhorse
+### 8. Update gitlab-workhorse
-Install and compile gitlab-workhorse. This requires
-[Go 1.8](https://golang.org/dl) which should already be on your system from
-GitLab 8.1. GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/).
+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.
@@ -143,11 +142,13 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
sudo -u git -H make
```
-### 7. Update Gitaly
+### 9. Update Gitaly
If you have not yet set up Gitaly then follow [Gitaly section of the installation
guide](../install/installation.md#install-gitaly).
+As of GitLab 9.4, Gitaly is a mandatory component of GitLab.
+
#### Check Gitaly configuration
Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
@@ -156,8 +157,7 @@ configuration file may contain syntax errors. The block name
file, should be `[[storage]]` instead.
```shell
-cd /home/git/gitaly
-sudo -u git -H editor config.toml
+sudo -u git -H sed -i.pre-9.4 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
```
#### Compile Gitaly
@@ -169,7 +169,29 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 10. Update configuration files
+### 10. 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
+```
+
+### 11. Update configuration files
#### New configuration options for `gitlab.yml`
@@ -243,7 +265,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 11. Install libs, migrations, etc.
+### 12. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
@@ -269,14 +291,14 @@ 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).
-### 12. Start application
+### 13. Start application
```bash
sudo service gitlab start
sudo service nginx restart
```
-### 13. Check application status
+### 14. Check application status
Check if GitLab and its environment are configured correctly:
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index a954840b8a6..70934f9960a 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -5,6 +5,8 @@
- The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
section.
+ - [Access token](#access-token) has been deprecated in GitLab 9.4
+ in favor of [IP whitelist](#ip-whitelist)
GitLab provides liveness and readiness probes to indicate service health and
reachability to required services. These probes report on the status of the
@@ -12,97 +14,101 @@ database connection, Redis connection, and access to the filesystem. These
endpoints [can be provided to schedulers like Kubernetes][kubernetes] to hold
traffic until the system is ready or restart the container as needed.
-## Access Token
+## IP whitelist
-An access token needs to be provided while accessing the probe endpoints. The current
-accepted token can be found under the **Admin area âž” Monitoring âž” Health check**
-(`admin/health_check`) page of your GitLab instance.
+To access monitoring resources, the client IP needs to be included in a whitelist.
-![access token](img/health_check_token.png)
+[Read how to add IPs to a whitelist for the monitoring endpoints.][admin].
-The access token can be passed as a URL parameter:
+## Using the endpoint
-```
-https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
-```
+With default whitelist settings, the probes can be accessed from localhost:
+
+- `http://localhost/-/readiness`
+- `http://localhost/-/liveness`
-which will then provide a report of system health in JSON format:
+which will then provide a report of system health in JSON format.
+
+Readiness example output:
```
{
- "db_check": {
- "status": "ok"
- },
- "redis_check": {
- "status": "ok"
- },
- "fs_shards_check": {
- "status": "ok",
- "labels": {
- "shard": "default"
- }
- }
+ "queues_check" : {
+ "status" : "ok"
+ },
+ "redis_check" : {
+ "status" : "ok"
+ },
+ "shared_state_check" : {
+ "status" : "ok"
+ },
+ "fs_shards_check" : {
+ "labels" : {
+ "shard" : "default"
+ },
+ "status" : "ok"
+ },
+ "db_check" : {
+ "status" : "ok"
+ },
+ "cache_check" : {
+ "status" : "ok"
+ }
}
```
-## Using the Endpoint
-
-Once you have the access token, the probes can be accessed:
+Liveness example output:
-- `https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN`
-- `https://gitlab.example.com/-/liveness?token=ACCESS_TOKEN`
+```
+{
+ "fs_shards_check" : {
+ "status" : "ok"
+ },
+ "cache_check" : {
+ "status" : "ok"
+ },
+ "db_check" : {
+ "status" : "ok"
+ },
+ "redis_check" : {
+ "status" : "ok"
+ },
+ "queues_check" : {
+ "status" : "ok"
+ },
+ "shared_state_check" : {
+ "status" : "ok"
+ }
+}
+```
## Status
On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
will return a valid successful HTTP status code, and a `success` message.
-## Old behavior
-
->**Notes:**
- - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
- - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and will
- be deprecated in GitLab 9.1. Read more in the [old behavior](#old-behavior)
- section.
-
-GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
-endpoint. The health check reports on the overall system status based on the status of
-the database connection, the state of the database migrations, and the ability to write
-and access the cache. This endpoint can be provided to uptime monitoring services like
-[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health].
+## Access token (Deprecated)
-Once you have the [access token](#access-token), health information can be
-retrieved as plain text, JSON, or XML using the `health_check` endpoint:
+>**Note:**
+Access token has been deprecated in GitLab 9.4
+in favor of [IP whitelist](#ip-whitelist)
-- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN`
-- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN`
-- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN`
-
-You can also ask for the status of specific services:
-
-- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN`
-- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN`
-- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN`
-
-For example, the JSON output of the following health check:
+An access token needs to be provided while accessing the probe endpoints. The current
+accepted token can be found under the **Admin area âž” Monitoring âž” Health check**
+(`admin/health_check`) page of your GitLab instance.
-```bash
-curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
-```
+![access token](img/health_check_token.png)
-would be like:
+The access token can be passed as a URL parameter:
```
-{"healthy":true,"message":"success"}
+https://gitlab.example.com/-/readiness?token=ACCESS_TOKEN
```
-On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
-will return a valid successful HTTP status code, and a `success` message. Ideally your
-uptime monitoring should look for the success message.
-
[ce-10416]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10416
[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888
[pingdom]: https://www.pingdom.com
[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html
[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring
[kubernetes]: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
+[admin]: ../../../administration/monitoring/ip_whitelist.md
diff --git a/doc/workflow/groups/access_requests_management.png b/doc/user/group/img/access_requests_management.png
index 36deaa89a70..36deaa89a70 100644
--- a/doc/workflow/groups/access_requests_management.png
+++ b/doc/user/group/img/access_requests_management.png
Binary files differ
diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png
new file mode 100644
index 00000000000..53f5596de23
--- /dev/null
+++ b/doc/user/group/img/add_new_members.png
Binary files differ
diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/user/group/img/create_new_group_info.png
index 8d2501d9f7a..8d2501d9f7a 100644
--- a/doc/gitlab-basics/img/create_new_group_info.png
+++ b/doc/user/group/img/create_new_group_info.png
Binary files differ
diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png
index c35234660db..c35234660db 100644
--- a/doc/gitlab-basics/img/create_new_project_from_group.png
+++ b/doc/user/group/img/create_new_project_from_group.png
Binary files differ
diff --git a/doc/user/group/img/group_settings.png b/doc/user/group/img/group_settings.png
new file mode 100644
index 00000000000..629cd0729aa
--- /dev/null
+++ b/doc/user/group/img/group_settings.png
Binary files differ
diff --git a/doc/user/group/img/groups.png b/doc/user/group/img/groups.png
new file mode 100644
index 00000000000..6211f999d5e
--- /dev/null
+++ b/doc/user/group/img/groups.png
Binary files differ
diff --git a/doc/user/group/img/membership_lock.png b/doc/user/group/img/membership_lock.png
new file mode 100644
index 00000000000..d31fbb43375
--- /dev/null
+++ b/doc/user/group/img/membership_lock.png
Binary files differ
diff --git a/doc/workflow/groups/new_group_form.png b/doc/user/group/img/new_group_form.png
index 91727ab5336..91727ab5336 100644
--- a/doc/workflow/groups/new_group_form.png
+++ b/doc/user/group/img/new_group_form.png
Binary files differ
diff --git a/doc/user/group/img/new_group_from_groups.png b/doc/user/group/img/new_group_from_groups.png
new file mode 100644
index 00000000000..baf34244cb2
--- /dev/null
+++ b/doc/user/group/img/new_group_from_groups.png
Binary files differ
diff --git a/doc/user/group/img/new_group_from_other_pages.png b/doc/user/group/img/new_group_from_other_pages.png
new file mode 100644
index 00000000000..014a7088af2
--- /dev/null
+++ b/doc/user/group/img/new_group_from_other_pages.png
Binary files differ
diff --git a/doc/workflow/groups/request_access_button.png b/doc/user/group/img/request_access_button.png
index f1aae6afed7..f1aae6afed7 100644
--- a/doc/workflow/groups/request_access_button.png
+++ b/doc/user/group/img/request_access_button.png
Binary files differ
diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/user/group/img/select_group_dropdown.png
index 68fc950304c..68fc950304c 100644
--- a/doc/gitlab-basics/img/select_group_dropdown.png
+++ b/doc/user/group/img/select_group_dropdown.png
Binary files differ
diff --git a/doc/user/group/img/share_with_group_lock.png b/doc/user/group/img/share_with_group_lock.png
new file mode 100644
index 00000000000..8df41bf9465
--- /dev/null
+++ b/doc/user/group/img/share_with_group_lock.png
Binary files differ
diff --git a/doc/user/group/img/transfer_project_to_other_group.png b/doc/user/group/img/transfer_project_to_other_group.png
new file mode 100644
index 00000000000..042c002f83f
--- /dev/null
+++ b/doc/user/group/img/transfer_project_to_other_group.png
Binary files differ
diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/user/group/img/withdraw_access_request_button.png
index c5d8ef6c04f..c5d8ef6c04f 100644
--- a/doc/workflow/groups/withdraw_access_request_button.png
+++ b/doc/user/group/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
new file mode 100644
index 00000000000..2691cf7d671
--- /dev/null
+++ b/doc/user/group/index.md
@@ -0,0 +1,208 @@
+# Groups
+
+With GitLab Groups you can assemble related projects together
+and grant members access to several projects at once.
+
+Groups can also be nested in [subgroups](subgroups/index.md).
+
+Find your groups by expanding the left menu and clicking **Groups**:
+
+![GitLab Groups](img/groups.png)
+
+The Groups page displays all groups you are a member of, how many projects it holds,
+how many members it has, the group visibility, and, if you have enough permissions,
+a link to the group settings. By clicking the last button you can leave that group.
+
+## Use cases
+
+You can create groups for numerous reasons. To name a few:
+
+- Organize related projects under the same [namespace](#namespaces), add members to that
+group and grant access to all their projects at once
+- Create a group, include members of your team, and make it easier to
+`@mention` all the team at once in issues and merge requests
+ - Create a group for your company members, and create [subgroups](subgroups/index.md)
+ for each individual team. Let's say you create a group called `company-team`, and among others,
+ you created subgroups in this group for each individual team `backend-team`,
+ `frontend-team`, and `production-team`:
+ 1. When you start a new implementation from an issue, you add a comment:
+ _"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_
+ 1. When your backend team needs help from frontend, they add a comment:
+ _"`@company-team/frontend-team` could you help us here please?"_
+ 1. When the frontend team completes their implementation, they comment:
+ _"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_
+
+## Namespaces
+
+In GitLab, a namespace is a unique name to be used as a user name, a group name, or a subgroup name.
+
+- `http://gitlab.example.com/username`
+- `http://gitlab.example.com/groupname`
+- `http://gitlab.example.com/groupname/subgroup_name`
+
+For example, consider a user called John:
+
+1. John creates his account on GitLab.com with the username `jonh`;
+his profile will be accessed under `https://gitlab.example.com/john`
+1. John creates a group for his team with the groupname `john-team`;
+his group and its projects will be accessed under `https://gitlab.example.com/john-team`
+1. John creates a subgroup of `john-team` with the subgroup name `marketing`;
+his subgroup and its projects will be accessed under `https://gitlab.example.com/john-team/marketing`
+
+By doing so:
+
+- Any team member mentions John with `@john`
+- John mentions everyone from his team with `@john-team`
+- John mentions only his marketing team with `@john-team/marketing`
+
+## Create a new group
+
+You can create a group in GitLab from:
+
+1. The Groups page: expand the left menu, click **Groups**, and click the green button **New group**:
+
+ ![new group from groups page](img/new_group_from_groups.png)
+
+1. Elsewhere: expand the `plus` sign button on the top navbar and choose **New group**:
+
+ ![new group from elsewhere](img/new_group_from_other_pages.png)
+
+Add the following information:
+
+![new group info](img/create_new_group_info.png)
+
+1. Set the **Group path** which will be the **namespace** under which your projects
+ will be hosted (path can contain only letters, digits, underscores, dashes
+ and dots; it cannot start with dashes or end in dot).
+1. The **Group name** will populate with the path. Optionally, you can change
+ it. This is the name that will display in the group views.
+1. Optionally, you can add a description so that others can briefly understand
+ what this group is about.
+1. Optionally, choose an avatar for your project.
+1. Choose the [visibility level](../../public_access/public_access.md).
+
+## Add users to a group
+
+Add members to a group by navigating to the group's dashboard, and clicking **Members**:
+
+![add members to group](img/add_new_members.png)
+
+Select the [permission level][permissions] and add the new member. You can also set the expiring
+date for that user, from which they will no longer have access to your group.
+
+One of the benefits of putting multiple projects in one group is that you can
+give a user to access to all projects in the group with one action.
+
+Consider we have a group with two projects:
+
+- On the **Group Members** page we can now add a new user to the group.
+- Now because this user is a **Developer** member of the group, he automatically
+gets **Developer** access to **all projects** within that group.
+
+If necessary, you can increase the access level of an individual user for a specific project,
+by adding them again as a new member to the project with the new permission levels.
+
+## Request access to a group
+
+As a group owner you can enable or disable non members to request access to
+your group. Go to the group settings and click on **Allow users to request access**.
+
+As a user, you can request to be a member of a group. Go to the group you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](img/request_access_button.png)
+
+---
+
+Group owners and masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](img/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](img/withdraw_access_request_button.png)
+
+## Add projects to a group
+
+There are two different ways to add a new project to a group:
+
+- Select a group and then click on the **New project** button.
+
+ ![New project](img/create_new_project_from_group.png)
+
+ You can then continue on [creating a project](../../gitlab-basics/create-project.md).
+
+- While you are creating a project, select a group namespace
+ you've already created from the dropdown menu.
+
+ ![Select group](img/select_group_dropdown.png)
+
+## Transfer an existing project into a group
+
+You can transfer an existing project into a group as long as you have at least **Master** [permissions][permissions] to that group
+and if you are an **Owner** of the project.
+
+![Transfer a project to a new namespace](img/transfer_project_to_other_group.png)
+
+Find this option under your project's settings.
+
+GitLab administrators can use the admin interface to move any project to any namespace if needed.
+
+## Manage group memberships via LDAP
+
+In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
+See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information.
+
+## Group settings
+
+Once you have created a group, you can manage its settings by navigating to
+the group's dashboard, and clicking **Settings**.
+
+![group settings](img/group_settings.png)
+
+### General settings
+
+Besides giving you the option to edit any settings you've previously
+set when [creating the group](#create-a-new-group), you can also
+access further configurations for your group.
+
+#### Enforce 2FA to group members
+
+Add a secury layer to your group by
+[enforcing two-factor authentication (2FA)](../../security/two_factor_authentication.md#enforcing-2fa-for-all-users-in-a-group)
+to all group members.
+
+#### Member Lock (EES/EEP)
+
+Available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/),
+with **Member Lock** it is possible to lock membership in project to the
+level of members in group.
+
+Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#member-lock-ees-eep).
+
+#### Share with group lock (EES/EEP)
+
+In [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
+it is possible to prevent projects in a group from [sharing
+a project with another group](../../workflow/share_projects_with_other_groups.md).
+This allows for tighter control over project access.
+
+Learn more about [Share with group lock](https://docs.gitlab.com/ee/user/group/index.html#share-with-group-lock-ees-eep).
+
+### Advanced settings
+
+- **Projects**: view all projects within that group, add members to each project,
+access each project's settings, and remove any project from the same screen.
+- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
+and [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group (Push Rules is available in [GitLab Enteprise Edition Starter][ee].)
+- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
+for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee]).
+- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
+
+[permissions]: ../permissions.md#permissions
+[ee]: https://about.gitlab.com/products/ \ No newline at end of file
diff --git a/doc/user/index.md b/doc/user/index.md
new file mode 100644
index 00000000000..f545dbffde3
--- /dev/null
+++ b/doc/user/index.md
@@ -0,0 +1,175 @@
+# User documentation
+
+Welcome to GitLab! We're glad to have you here!
+
+As a GitLab user you'll have access to all the features
+your [subscription](https://about.gitlab.com/products/)
+includes, except [GitLab administrator](../README.md#administrator-documentation)
+settings, unless you have admin privileges to install, configure,
+and upgrade your GitLab instance.
+
+For GitLab.com, admin privileges are restricted to the GitLab team.
+
+If you run your own GitLab instance and are looking for the administration settings,
+please refer to the [administration](../README.md#administrator-documentation)
+documentation.
+
+## Overview
+
+GitLab is a fully integrated software development platform that enables you
+and your team to work cohesively, faster, transparently, and effectively,
+since the discussion of a new idea until taking that idea to production all
+all the way through, from within the same platform.
+
+Please check this page for an overview on [GitLab's features](https://about.gitlab.com/features/).
+
+## Use cases
+
+GitLab is a git-based platforms that integrates a great number of essential tools for software development and deployment, and project management:
+
+- Code hosting in repositories with version control
+- Track proposals for new implementations, bug reports, and feedback with a
+fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
+- Organize and prioritize with [Issue Boards](project/issues/index.md#issue-boards)
+- Code review in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
+branch with [Review Apps](../ci/review_apps/index.md)
+- Build, test and deploy with built-in [Continuous Integration](../ci/README.md)
+- Deploy your personal and professional static websites with [GitLab Pages](project/pages/index.md)
+- Integrate with Docker with [GitLab Container Registry](project/container_registry.md)
+- Track the development lifecycle with [GitLab Cycle Analytics](project/cycle_analytics.md)
+
+With GitLab Enterprise Edition, you can also:
+
+- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html)
+- Improve collaboration with
+[Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
+[Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
+- Create formal relashionships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html)
+- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
+- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance
+- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html)
+- [Mirror a repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html) from elsewhere on your local server.
+- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html)
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
+- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
+- View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+- Leverage your continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
+
+### Articles
+
+For a complete workflow use case please check [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+
+For more use cases please check our [Technical Articles](../articles/index.md).
+
+## Projects
+
+In GitLab, you can create projects for numerous reasons, such as, host
+your code, use it as an issue tracker, collaborate on code, and continuously
+build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
+it all at once, from one single project.
+
+### Issues
+
+Explore the best of GitLab [Issues](project/issues/index.md).
+
+### Merge Requests
+
+Collanorate on code, gather reviews, live preview changes per branch, and
+request approvals with [Merge Requests](project/merge_requests/index.md).
+
+### Milestones
+
+Work on multiple issues and merge requests towards the same target date
+with [Milestones](project/milestones/index.md).
+
+### GitLab Pages
+
+Publish your static site directly from GitLab with [GitLab Pages](project/pages/index.md). You
+can [build, test, and deploy any Static Site Generator](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/) with Pages.
+
+### Container Registry
+
+Build and deploy Docker images with [GitLab Container Registry](project/container_registry.md).
+
+## GitLab CI/CD
+
+Use built-in [GitLab CI/CD](../ci/README.md) to test, build, and deploy your applications
+directly from GitLab. No third-party integrations needed.
+
+### Auto Deploy
+
+Deploy your application out-of-the-box with [GitLab Auto Deploy](../ci/autodeploy/index.md).
+
+### Review Apps
+
+Live-preview the changes introduced by a merge request with [Review Apps](../ci/review_apps/index.md).
+
+## Groups
+
+With GitLab [Groups](group/index.md) you can assemble related projects together
+and grant members access to several projects at once.
+
+### Subgroups
+
+Groups can also be nested in [subgroups](group/subgroups/index.md).
+
+## Account
+
+There is a lot you can customize and configure
+to enjoy the best of GitLab.
+
+Manage your user settings to change your personal info,
+personal access tokens, authorized applications, integrations, etc.
+
+### Authentication
+
+Read through the [authentication](../topics/authentication/index.md) methods available in GitLab.
+
+### Permissions
+
+Learn the different set of [permissions](permissions.md) for user type (guest, reporter, developer, master, owner).
+
+## Integrations
+
+[Integrate GitLab](../integration/README.md) with your preferred tool,
+such as Trello, JIRA, etc.
+
+## Git and GitLab
+
+Learn what is [Git](../topics/git/index.md) and its best practices.
+
+## Discussions
+
+In GitLab, you can comment and mention collaborators in issues,
+merge requests, code snippets, and commits.
+
+When performing inline reviews to implementations
+to your codebase through merge requests you can
+gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
+
+## Todos
+
+Never forget to reply to your collaborators. [GitLab Todos](../workflow/todos.md)
+are a tool for working faster and more effectively with your team,
+by listing all user or group mentions, as well as issues and merge
+requests you're assigned to.
+
+## Snippets
+
+[Snippets](snippets.md) are code blocks that you want to store in GitLab, from which
+you have quick access to. You can also gather feedback on them through
+[discussions](#discussions).
+
+## Webhooks
+
+Configure [webhooks](project/integrations/webhooks.html) to listen for
+specific events like pushes, issues or merge requests. GitLab will send a
+POST request with data to the webhook URL.
+
+## API
+
+Automate GitLab via [API](../api/README.html).
+
diff --git a/doc/user/project/integrations/bugzilla.md b/doc/user/project/integrations/bugzilla.md
index 6a040516231..ba2adc1afda 100644
--- a/doc/user/project/integrations/bugzilla.md
+++ b/doc/user/project/integrations/bugzilla.md
@@ -20,10 +20,12 @@ Once you have configured and enabled Bugzilla:
## Referencing issues in Bugzilla
Issues in Bugzilla can be referenced in two alternative ways:
-1. `#<ID>` where `<ID>` is a number (example `#143`)
+1. `#<ID>` where `<ID>` is a number (example `#143`).
2. `<PROJECT>-<ID>` where `<PROJECT>` starts with a capital letter which is
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
+We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
+
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
diff --git a/doc/user/project/integrations/img/webhook_testing.png b/doc/user/project/integrations/img/webhook_testing.png
new file mode 100644
index 00000000000..176dcec9d8a
--- /dev/null
+++ b/doc/user/project/integrations/img/webhook_testing.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index cf03f2a9033..cfa4c8a93f8 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -98,11 +98,11 @@ in the table below.
| Field | Description |
| ----- | ----------- |
| `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
-| `JIRA API URL` | The base URL to the JIRA instance API. E.g., `https://jira-api.example.com`. This is optional. If not entered, the Web URL value be used. |
+| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
| `Project key` | Put a JIRA project key (in uppercase), e.g. `MARS` in this field. This is only for testing the configuration settings. JIRA integration in GitLab works with _all_ JIRA projects in your JIRA instance. This field will be removed in a future release. |
| `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
-| `JIRA issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
+| `Transition ID` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
After saving the configuration, your GitLab project will be able to interact
with all JIRA projects in your JIRA instance.
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index 73fa83d72a8..f4000523938 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -19,10 +19,10 @@ of your project and select the **Kubernetes** service to configure it.
The Kubernetes service takes the following arguments:
-1. Kubernetes namespace
1. API URL
-1. Service token
1. Custom CA bundle
+1. Kubernetes namespace
+1. Service token
The API URL is the URL that GitLab uses to access the Kubernetes API. Kubernetes
exposes several APIs - we want the "base" URL that is common to all of them,
@@ -55,6 +55,7 @@ GitLab CI build environment:
- `KUBE_CA_PEM_FILE` - only present if a custom CA bundle was specified. Path
to a file containing PEM data.
- `KUBE_CA_PEM` (deprecated)- only if a custom CA bundle was specified. Raw PEM data.
+- `KUBECONFIG` - Path to a file containing kubeconfig for this deployment. CA bundle would be embedded if specified.
## Web terminals
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 86ceb14b965..6f15765751c 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -17,35 +17,30 @@ the settings page with a default template. To configure the template, see the
Integration with Prometheus requires the following:
1. GitLab 9.0 or higher
-1. The [Kubernetes integration must be enabled][kube] on your project
-1. Your app must be deployed on [Kubernetes][]
-1. Prometheus must be configured to collect Kubernetes metrics
+1. Prometheus must be configured to collect one of the [supported metrics](prometheus_library/metrics.md)
1. Each metric must be have a label to indicate the environment
-1. GitLab must have network connectivity to the Prometheus sever
+1. GitLab must have network connectivity to the Prometheus server
-There are a few steps necessary to set up integration between Prometheus and
-GitLab.
+## Getting started with Prometheus monitoring
-## Configuring Prometheus to collect Kubernetes metrics
+Depending on your deployment and where you have located your GitLab server, there are a few options to get started with Prometheus monitoring.
-In order for Prometheus to collect Kubernetes metrics, you first must have a
-Prometheus server up and running. You have two options here:
+* If both GitLab and your applications are installed in the same Kubernetes cluster, you can leverage the [bundled Prometheus server within GitLab](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes).
+* If your applications are deployed on Kubernetes, but GitLab is not in the same cluster, then you can [configure a Prometheus server in your Kubernetes cluster](#configuring-your-own-prometheus-server-within-kubernetes).
+* If your applications are not running in Kubernetes, [get started with Prometheus](#getting-started-with-prometheus-outside-of-kubernetes).
-- If you installed Omnibus GitLab inside of Kubernetes, you can simply use the
- [bundled version of Prometheus][promgldocs]. In that case, follow the info in the
- [Omnibus GitLab section](#configuring-omnibus-gitlab-prometheus-to-monitor-kubernetes)
- below.
-- If you are using GitLab.com or installed GitLab outside of Kubernetes, you
- will likely need to run a Prometheus server within the Kubernetes cluster.
- Once installed, the easiest way to monitor Kubernetes is to simply use
- Prometheus' support for [Kubernetes Service Discovery][prometheus-k8s-sd].
- In that case, follow the instructions on
- [configuring your own Prometheus server within Kubernetes](#configuring-your-own-prometheus-server-within-kubernetes).
+### Getting started with Prometheus outside of Kubernetes
-### Configuring Omnibus GitLab Prometheus to monitor Kubernetes
+Installing and configuring Prometheus to monitor applications is fairly straight forward.
+
+1. [Install Prometheus](https://prometheus.io/docs/introduction/install/)
+1. Set up one of the [supported monitoring targets](prometheus_library/metrics.md)
+1. Configure the Prometheus server to [collect their metrics](https://prometheus.io/docs/operating/configuration/#scrape_config)
+
+### Configuring Omnibus GitLab Prometheus to monitor Kubernetes deployments
With Omnibus GitLab running inside of Kubernetes, you can leverage the bundled
-version of Prometheus to collect the required metrics.
+version of Prometheus to collect the supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>).
1. Read how to configure the bundled Prometheus server in the
[Administration guide][gitlab-prometheus-k8s-monitor].
@@ -74,7 +69,7 @@ kubectl apply -f path/to/prometheus.yml
Once deployed, you should see the Prometheus service, deployment, and
pod start within the `prometheus` namespace. The server will begin to collect
metrics from each Kubernetes Node in the cluster, based on the configuration
-provided in the template.
+provided in the template. It will also attempt to collect metrics from any Kubernetes Pods that have been [annotated for Prometheus](https://prometheus.io/docs/operating/configuration/#pod).
Since GitLab is not running within Kubernetes, the template provides external
network access via a `NodePort` running on `30090`. This method allows access
@@ -133,30 +128,6 @@ to integrate with.
![Configure Prometheus Service](img/prometheus_service_configuration.png)
-## Metrics and Labels
-
-GitLab retrieves performance data from two metrics, `container_cpu_usage_seconds_total`
-and `container_memory_usage_bytes`. These metrics are collected from the
-Kubernetes pods via Prometheus, and report CPU and Memory utilization of each
-container or Pod running in the cluster.
-
-In order to isolate and only display relevant metrics for a given environment
-however, GitLab needs a method to detect which pods are associated. To do that,
-GitLab will specifically request metrics that have an `environment` tag that
-matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug].
-
-If you are using [GitLab Auto-Deploy][autodeploy] and one of the methods of
-configuring Prometheus above, the `environment` will be automatically added.
-
-### GitLab Prometheus queries
-
-The queries utilized by GitLab are shown in the following table.
-
-| Metric | Query |
-| ------ | ----- |
-| Average Memory (MB) | `(sum(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) / count(container_memory_usage_bytes{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"})) /1024/1024` |
-| Average CPU Utilization (%) | `sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="$CI_ENVIRONMENT_SLUG"}) * 100` |
-
## Monitoring CI/CD Environments
Once configured, GitLab will attempt to retrieve performance metrics for any
@@ -168,8 +139,9 @@ environment which has had a successful deployment.
> [Introduced][ce-10408] in GitLab 9.2.
> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
+> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics
-Developers can view the performance impact of their changes within the merge
+Developers can view theperformance impact of their changes within the merge
request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
indicates when the current changes were deployed, with up to 30 minutes of
performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md
new file mode 100644
index 00000000000..cc5cee36d28
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md
@@ -0,0 +1,25 @@
+# Monitoring AWS Resources
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
+
+GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(aws_elb_request_count_sum{%{environment_filter}}) / 60 |
+| Latency (ms) | avg(aws_elb_latency_average{%{environment_filter}}) * 1000 |
+| HTTP Error Rate (%) | sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}}) |
+
+## Configuring Prometheus to monitor for Cloudwatch metrics
+
+To get started with Cloudwatch monitoring, you should install and configure the [Cloudwatch exporter](https://github.com/hnlq715/nginx-vts-exporter) which retrieves and parses the specified Cloudwatch metrics and translates them into a Prometheus monitoring endpoint.
+
+Right now, the only AWS resource supported is the Elastic Load Balancer, whose Cloudwatch metrics can be found [here](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-cloudwatch-metrics.html).
+
+A sample Cloudwatch Exporter configuration file, configured for basic AWS ELB monitoring, is [available for download](../samples/cloudwatch.yml).
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
new file mode 100644
index 00000000000..309da610cc0
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -0,0 +1,20 @@
+# Monitoring HA Proxy
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
+
+GitLab has support for automatically detecting and monitoring HA Proxy. This is provided by leveraging the [HA Proxy Exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates HA Proxy statistics into a Prometheus readable form.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
+| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_requests_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_requests_total{%{environment_filter}}[2m])) |
+
+## Configuring Prometheus to monitor for HA Proxy metrics
+
+To get started with NGINX monitoring, you should install and configure the [HA Proxy exporter](https://github.com/prometheus/haproxy_exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint.
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
new file mode 100644
index 00000000000..eb8cd821ddc
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -0,0 +1,26 @@
+# Monitoring Kubernetes
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
+
+GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). No additional services or exporters are needed.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Average Memory Usage (MB) | (sum(container_memory_usage_bytes{container_name!="POD",%{environment_filter}}) / count(container_memory_usage_bytes{container_name!="POD",%{environment_filter}})) /1024/1024 |
+| Average CPU Utilization (%) | sum(rate(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",%{environment_filter}}) * 100 |
+
+## Configuring Prometheus to monitor for Kubernetes node metrics
+
+In order for Prometheus to collect Kubernetes metrics, you first must have a
+Prometheus server up and running. You have two options here:
+
+- If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes).
+- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/) or [our guide](../../../../administration/monitoring/prometheus/index.md#configuring-your-own-prometheus-server-within-kubernetes).
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
+
+If you are using [GitLab Auto-Deploy][autodeploy] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
new file mode 100644
index 00000000000..546e1f51df5
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -0,0 +1,26 @@
+# Prometheus Metrics library
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
+
+GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
+* [Kubernetes](kubernetes.md)
+* [NGINX](nginx.md)
+* [HA Proxy](haproxy.md)
+* [Amazon Cloud Watch](cloudwatch.md)
+
+We have tried to surface the most important metrics for each exporter, and will be continuing to add support for additional exporters in future releases. If you would like to add support for other official exporters, [contributions](#adding-to-the-library) are welcome.
+
+## Identifying Environments
+
+GitLab retrieves performance data from the configured Prometheus server, and attempts to identifying the presence of known metrics. Once identified, GitLab then needs to be able to map the data to a particular environment.
+
+In order to isolate and only display relevant metrics for a given environment, GitLab needs a method to detect which labels are associated. To do that,
+GitLab will look for the required metrics which have a label that
+matches the [$CI_ENVIRONMENT_SLUG][ci-environment-slug].
+
+For example if you are deploying to an environment named `production`, there must be a label for the metric with the value of `production`.
+
+## Adding to the library
+
+We strive to support the 2-4 most important metrics for each common system service that supports Prometheus. If you are looking for support for a particular exporter which has not yet been added to the library, additions can be made [to the `additional_metrics.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/prometheus/additional_metrics.yml) file.
+
+> Note: The library is only for monitoring public, common, system services which all customers can benefit from. Support for monitoring [customer proprietary metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/2273) will be added in a subsequent release.
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
new file mode 100644
index 00000000000..b3470773996
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -0,0 +1,23 @@
+# Monitoring NGINX
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
+
+GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_requests_total{server_zone!="*", server_zone!="_", %{environment_filter}}[2m])) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000 |
+| HTTP Error Rate (%) | sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m])) |
+
+## Configuring Prometheus to monitor for NGINX metrics
+
+To get started with NGINX monitoring, you should first enable the [VTS statistics](https://github.com/vozlt/nginx-module-vts)) module for your NGINX server. This will capture and display statistics in an HTML readable form. Next, you should install and configure the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter) which parses these statistics and translates them into a Prometheus monitoring endpoint.
+
+If you are using NGINX as your Kubernetes ingress, there is [upcoming direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release.
+
+## Specifying the Environment label
+
+In order to isolate and only display relevant metrics for a given environment
+however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 8026f1f57bc..cf92465da53 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -30,5 +30,7 @@ Issues in Redmine can be referenced in two alternative ways:
then followed by capital letters, numbers or underscores, and `<ID>` is
a number (example `API_32-143`).
+We suggest using the longer format if you have both internal and external issue trackers enabled. If you use the shorter format and an issue with the same ID exists in the internal issue tracker the internal issue will be linked.
+
Please note that `<PROJECT>` part is ignored and links always point to the
address specified in `issues_url`.
diff --git a/doc/user/project/integrations/samples/cloudwatch.yml b/doc/user/project/integrations/samples/cloudwatch.yml
new file mode 100644
index 00000000000..d9b58f52c32
--- /dev/null
+++ b/doc/user/project/integrations/samples/cloudwatch.yml
@@ -0,0 +1,26 @@
+region: us-east-1
+ metrics:
+ - aws_namespace: AWS/ELB
+ aws_metric_name: RequestCount
+ aws_dimensions: [AvailabilityZone, LoadBalancerName]
+ aws_dimension_select:
+ LoadBalancerName: [gitlab-ha-lb]
+ aws_statistics: [Sum]
+ - aws_namespace: AWS/ELB
+ aws_metric_name: Latency
+ aws_dimensions: [AvailabilityZone, LoadBalancerName]
+ aws_dimension_select:
+ LoadBalancerName: [gitlab-ha-lb]
+ aws_statistics: [Average]
+ - aws_namespace: AWS/ELB
+ aws_metric_name: HTTPCode_Backend_2XX
+ aws_dimensions: [AvailabilityZone, LoadBalancerName]
+ aws_dimension_select:
+ LoadBalancerName: [gitlab-ha-lb]
+ aws_statistics: [Sum]
+ - aws_namespace: AWS/ELB
+ aws_metric_name: HTTPCode_Backend_5XX
+ aws_dimensions: [AvailabilityZone, LoadBalancerName]
+ aws_dimension_select:
+ LoadBalancerName: [gitlab-ha-lb]
+ aws_statistics: [Sum]
diff --git a/doc/user/project/integrations/samples/prometheus.yml b/doc/user/project/integrations/samples/prometheus.yml
index 01bbcaffe1e..30b59e172a1 100644
--- a/doc/user/project/integrations/samples/prometheus.yml
+++ b/doc/user/project/integrations/samples/prometheus.yml
@@ -24,6 +24,44 @@ data:
target_label: environment
regex: (.+)-.+-.+
replacement: $1
+ - job_name: kubernetes-pods
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ insecure_skip_verify: true
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ kubernetes_sd_configs:
+ - role: pod
+ api_server: https://kubernetes.default.svc:443
+ tls_config:
+ ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
+ bearer_token_file: "/var/run/secrets/kubernetes.io/serviceaccount/token"
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: 'true'
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ target_label: __metrics_path__
+ regex: "(.+)"
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: "([^:]+)(?::[0-9]+)?;([0-9]+)"
+ replacement: "$1:$2"
+ target_label: __address__
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name
---
apiVersion: v1
kind: Service
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 023c6932e41..c03a2df9a72 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1014,6 +1014,13 @@ X-Gitlab-Event: Build Hook
}
```
+## Testing webhooks
+
+You can trigger the webhook manually. Sample data from the project will be used.Sample data will take from the project.
+> For example: for triggering `Push Events` your project should have at least one commit.
+
+![Webhook testing](img/webhook_testing.png)
+
## Troubleshoot webhooks
Gitlab stores each perform of the webhook.
@@ -1056,7 +1063,7 @@ Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb
8000`. Then add your server as a webhook receiver in GitLab as
`http://my.host:8000/`.
-When you press 'Test Hook' in GitLab, you should see something like this in the
+When you press 'Test' in GitLab, you should see something like this in the
console:
```
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index e55e2aea023..1f78849a92c 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -5,13 +5,13 @@ for tracking the evolution of a new idea or the process
of solving a problem.
It allows you, your team, and your collaborators to share
-and discuss proposals, before and while implementing them.
+and discuss proposals before and while implementing them.
Issues and the GitLab Issue Tracker are available in all
[GitLab Products](https://about.gitlab.com/products/) as
part of the [GitLab Workflow](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/).
-## Use-Cases
+## Use cases
Issues can have endless applications. Just to exemplify, these are
some cases for which creating issues are most used:
@@ -23,7 +23,28 @@ some cases for which creating issues are most used:
- Obtaining support
- Elaborating new code implementations
-See also the blog post [Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/).
+See also the blog post "[Always start a discussion with an issue](https://about.gitlab.com/2016/03/03/start-with-an-issue/)".
+
+### Keep private things private
+
+For instance, let's assume you have a public project but want to start a discussion on something
+you don't want to be public. With [Confidential Issues](#confidential-issues),
+you can discuss private matters among the project members, and still keep
+your project public, open to collaboration.
+
+### Streamline collaboration
+
+With [Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
+available in [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/)
+you can streamline collaboration and allow shared responsibilities to be clearly displayed.
+All assignees are shown across your workflows and receive notifications (as they
+would as single assignees), simplifying communication and ownership.
+
+### Consistent collaboration
+
+Create [issue templates](#issue-templates) to make collaboration consistent and
+containing all information you need. For example, you can create a template
+for feature proposals and another one for bug reports.
## Issue Tracker
@@ -96,8 +117,8 @@ Find GitLab Issue Boards by navigating to your **Project's Dashboard** > **Issue
Read through the documentation for [Issue Boards](../issue_board.md)
to find out more about this feature.
-[Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
-are available only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/).
+With [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/), you can also
+create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
### Issue's API
diff --git a/doc/user/project/issues/issues_functionalities.md b/doc/user/project/issues/issues_functionalities.md
index 294176e61f9..138276edf07 100644
--- a/doc/user/project/issues/issues_functionalities.md
+++ b/doc/user/project/issues/issues_functionalities.md
@@ -43,7 +43,7 @@ assigned to them if they created the issue themselves.
##### 3.1. Multiple Assignees (EES/EEP)
-Issue Weights are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/).
+Multiple Assignees are only available in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/).
Often multiple people likely work on the same issue together,
which can especially be difficult to track in large teams
@@ -52,9 +52,7 @@ where there is shared ownership of an issue.
In GitLab Enterprise Edition, you can also select multiple assignees
to an issue.
-> **Note:**
-Multiple Assignees was [introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1904)
-in [GitLab Enterprise Edition 9.2](https://about.gitlab.com/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues).
+Learn more on the [Multiple Assignees documentation](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html).
#### 4. Milestone
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 954454f7e7a..9bdf2a998d3 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -3,6 +3,59 @@
Merge requests allow you to exchange changes you made to source code and
collaborate with other people on the same project.
+## Overview
+
+A Merge Request (**MR**) is the basis of GitLab as a code collaboration
+and version control platform.
+Is it simple as the name implies: a _request_ to _merge_ one branch into another.
+
+With GitLab merge requests, you can:
+
+- Compare the changes between two [branches](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell#_git_branching)
+- [Review and discuss](../../discussions/index.md#discussions) the proposed modifications inline
+- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project
+- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md)
+- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests)
+- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs)
+- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request
+- Assign it to any registered user, and change the assignee how many times you need
+- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation
+- Organize your issues and merge requests consistently throughout the project with [labels](../../project/labels.md)
+- Add a time estimation and the time spent with that merge request with [Time Tracking](../../../workflow/time_tracking.html#time-tracking)
+- [Resolve merge conflicts from the UI](#resolve-conflicts)
+
+With **[GitLab Enterprise Edition][ee]**, you can also:
+
+- View the deployment process across projects with [Multi-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html#multi-project-pipeline-graphs) (available only in GitLab Enterprise Edition Premium)
+- Request [approvals](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers (available in GitLab Enterprise Edition Starter)
+- Enable [fast-forward merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/fast_forward_merge.html) (available in GitLab Enterprise Edition Starter)
+- [Squash and merge](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html) for a cleaner commit history (available in GitLab Enterprise Edition Starter)
+- Enable [semi-linear history merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#semi-linear-history-merge-requests) as another security layer to guarantee the pipeline is passing in the target branch (available in GitLab Enterprise Edition Starter)
+- Analise the impact of your changes with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter)
+
+## Use cases
+
+A. Consider you are a software developer working in a team:
+
+1. You checkout a new branch, and submit your changes through a merge request
+1. You gather feedback from your team
+1. You work on the implementation optimizing code with [Code Quality reports](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html) (available in GitLab Enterprise Edition Starter)
+1. You build and test your changes with GitLab CI/CD
+1. You request the approval from your manager
+1. Your manager pushes a commit with his final review, [approves the merge request](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Enterprise Edition Starter)
+1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#manual-actions) for GitLab CI/CD
+1. Your implementations were successfully shipped to your customer
+
+B. Consider you're a web developer writing a webpage for your company's:
+
+1. You checkout a new branch, and submit a new page through a merge request
+1. You gather feedback from your reviewers
+1. Your changes are previewed with [Review Apps](../../../ci/review_apps/index.md)
+1. You request your web designers for their implementation
+1. You request the [approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your manager (available in GitLab Enterprise Edition Starter)
+1. Once approved, your merge request is [squashed and merged](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html), and [deployed to staging with GitLab Pages](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/) (Squash and Merge is available in GitLab Enterprise Edition Starter)
+1. Your production team [cherry picks](#cherry-pick-changes) the merge commit into production
+
## Authorization for merge requests
There are two main ways to have a merge request flow with GitLab:
@@ -79,6 +132,16 @@ specific commit page.
You can append `?w=1` while on the diffs page of a merge request to ignore any
whitespace changes.
+## Live preview with Review Apps
+
+If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
+you can preview the changes submitted to a feature-branch through a merge request
+in a per-branch basis. No need to checkout the branch, install and preview locally;
+all your changes will be available to preview by anyone with the Review Apps link.
+
+[Read more about Review Apps.](../../../ci/review_apps/index.md)
+
+
## Tips
Here are some tips that will help you be more efficient with merge requests in
@@ -167,3 +230,4 @@ git checkout origin/merge-requests/1
```
[protected branches]: ../protected_branches.md
+[ee]: https://about.gitlab.com/gitlab-ee/ "GitLab Enterprise Edition"
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 99233ed5ae2..1848514e2dd 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -21,14 +21,11 @@ Once you fill in all the details, hit the **Create milestone** button.
>**Note:**
You need [Master permissions](../../permissions.md) in order to create a milestone.
-You can create a milestone for several projects in the same group simultaneously.
-On the group's **Issues âž” Milestones** page, you will be able to see the status
-of that milestone across all of the selected projects. To create a new milestone
-for selected projects in the group, click the **New milestone** button. The
-form is the same as when creating a milestone for a specific project with the
-addition of the selection of the projects you want to inherit this milestone.
-
-![Creating a group milestone](img/milestone_group_create.png)
+You can create a milestone for a group that will be shared across group projects.
+On the group's **Issues âž” Milestones** page, you will be able to see the state
+of that milestone and the issues/merge requests count that it shares across the group projects. To create a new milestone click the **New milestone** button. The form is the same as when creating a milestone for a specific project which you can find in the previous item.
+
+In addition to that you will be able to filter issues or merge requests by group milestones in all projects that belongs to the milestone group.
## Special milestone filters
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 2f104c7becc..46fa4378fe7 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -41,7 +41,7 @@ server up and running for your GitLab instance.
Before we begin, let's understand a few concepts first.
-### Static sites
+## Static sites
GitLab Pages only supports static websites, meaning,
your output files must be HTML, CSS, and JavaScript only.
@@ -51,14 +51,14 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
to simplify your code and build the static site for you,
which is highly recommendable and much faster than hardcoding.
-#### Further Reading
+### Further reading
- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
- Fork an [example project](https://gitlab.com/pages) to build your website based upon
-### GitLab Pages domain
+## GitLab Pages domain
If you set up a GitLab Pages project on GitLab.com,
it will automatically be accessible under a
@@ -73,9 +73,9 @@ Pages wildcard domain. This guide is valid for any GitLab instance,
you just need to replace Pages wildcard domain on GitLab.com
(`*.gitlab.io`) with your own.
-#### Practical examples
+### Practical examples
-**Project Websites:**
+#### Project Websites
- You created a project called `blog` under your username `john`,
therefore your project URL is `https://gitlab.com/john/blog/`.
@@ -87,16 +87,21 @@ URL is `https://gitlab.com/websites/blog/`. Once you enable
GitLab Pages for this project, the site will live under
`https://websites.gitlab.io/blog/`.
-**User and Group Websites:**
+#### User and Group Websites
- Under your username, `john`, you created a project called
`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
Once you enable GitLab Pages for your project, your website
will be published under `https://john.gitlab.io`.
- Under your group `websites`, you created a project called
-`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project,
+`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`.
+Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
+>**Note:**
+GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest level group website.
+
**General example:**
- On GitLab.com, a project site will always be available under
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index deaceabb7c5..9ecf7a3a8e7 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -398,6 +398,9 @@ don't redirect HTTP to HTTPS.
[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
+GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest level group website.
+
## Redirects in GitLab Pages
Since you cannot use any custom server configuration files, like `.htaccess` or
diff --git a/doc/user/project/pipelines/img/pipeline_schedule_variables.png b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
new file mode 100644
index 00000000000..47a0c6f3697
--- /dev/null
+++ b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
index ea5394fa8a6..5a0e5965992 100644
--- a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
Binary files differ
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 17cc21238ff..9ad15a12c3c 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -31,6 +31,15 @@ is installed on.
![Schedules list](img/pipeline_schedules_list.png)
+### Making use of scheduled pipeline variables
+
+> [Introduced][ce-12328] in GitLab 9.4.
+
+You can pass any number of arbitrary variables and they will be available in
+GitLab CI so that they can be used in your `.gitlab-ci.yml` file.
+
+![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
+
## Using only and except
To configure that a job can be executed only when the pipeline has been
@@ -62,9 +71,10 @@ The next time a pipeline is scheduled, your credentials will be used.
>**Note:**
When the owner of the schedule doesn't have the ability to create pipelines
-anymore, due to e.g., being blocked or removed from the project, the schedule
-is deactivated. Another user can take ownership and activate it, so the
-schedule can be run again.
+anymore, due to e.g., being blocked or removed from the project, or lacking
+the permission to run on protected branches or tags. When this happened, the
+schedule is deactivated. Another user can take ownership and activate it, so
+the schedule can be run again.
## Advanced admin configuration
@@ -79,4 +89,5 @@ don't have admin access to the server, ask your administrator.
[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
+[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328
[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index a992a348c0f..3ff5a08d72c 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -27,6 +27,22 @@ The default value is 60 minutes. Decrease the time limit if you want to impose
a hard limit on your jobs' running time or increase it otherwise. In any case,
if the job surpasses the threshold, it is marked as failed.
+## Custom CI config path
+
+> - [Introduced][ce-12509] in GitLab 9.4.
+
+By default we look for the `.gitlab-ci.yml` file in the project's root
+directory. If you require a different location **within** the repository,
+you can set a custom filepath that will be used to lookup the config file,
+this filepath should be **relative** to the root.
+
+Here are some valid examples:
+
+> * .gitlab-ci.yml
+> * .my-custom-file.yml
+> * my/path/.gitlab-ci.yml
+> * my/path/.my-custom-file.yml
+
## Test coverage parsing
If you use test coverage in your code, GitLab can capture its output in the
@@ -59,8 +75,8 @@ pipelines** checkbox and save the changes.
> [Introduced][ce-9362] in GitLab 9.1.
-If you want to auto-cancel all pending non-HEAD pipelines on branch, when
-new pipeline will be created (after your git push or manually from UI),
+If you want to auto-cancel all pending non-HEAD pipelines on branch, when
+new pipeline will be created (after your git push or manually from UI),
check **Auto-cancel pending pipelines** checkbox and save the changes.
## Badges
@@ -115,3 +131,4 @@ into your `README.md`:
[var]: ../../../ci/yaml/README.md#git-strategy
[coverage report]: #test-coverage-parsing
[ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362
+[ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 19b51c83222..ce4dd4e99d5 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -37,3 +37,4 @@ do.
| `/target_branch <Branch Name>` | Set target branch for current merge request |
| `/award :emoji:` | Toggle award for :emoji: |
| `/board_move ~column` | Move issue to column on the board |
+| `/duplicate #issue` | Closes this issue and marks it as a duplicate of another issue |
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 54d4028a50a..925bbf76d49 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -6,7 +6,7 @@
- [Description templates](../user/project/description_templates.md)
- [Feature branch workflow](workflow.md)
- [GitLab Flow](gitlab_flow.md)
-- [Groups](groups.md)
+- [Groups](../user/group/index.md)
- Issues - The GitLab Issue Tracker is an advanced and complete tool for
tracking the evolution of a new idea or the process of solving a problem.
- [Confidential issues](../user/project/issues/confidential_issues.md)
diff --git a/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys.png b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys.png
new file mode 100644
index 00000000000..e525083918b
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png
new file mode 100644
index 00000000000..8e26d98f1b0
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_paste_pub.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png
new file mode 100644
index 00000000000..f715c46adc3
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/profile_settings_gpg_keys_single_key.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/img/project_signed_and_unsigned_commits.png b/doc/workflow/gpg_signed_commits/img/project_signed_and_unsigned_commits.png
new file mode 100644
index 00000000000..16ec2d031ae
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/project_signed_and_unsigned_commits.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/img/project_signed_commit_unverified_signature.png b/doc/workflow/gpg_signed_commits/img/project_signed_commit_unverified_signature.png
new file mode 100644
index 00000000000..22565cf7c7e
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/project_signed_commit_unverified_signature.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/img/project_signed_commit_verified_signature.png b/doc/workflow/gpg_signed_commits/img/project_signed_commit_verified_signature.png
new file mode 100644
index 00000000000..1778b2ddf2b
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/img/project_signed_commit_verified_signature.png
Binary files differ
diff --git a/doc/workflow/gpg_signed_commits/index.md b/doc/workflow/gpg_signed_commits/index.md
new file mode 100644
index 00000000000..7d5762d2b9d
--- /dev/null
+++ b/doc/workflow/gpg_signed_commits/index.md
@@ -0,0 +1,84 @@
+# Signing commits with GPG
+
+## Getting started
+
+- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
+- [Git Tools - Signing Your Work: GPG introduction](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work#_gpg_introduction)
+- [Git Tools - Signing Your Work: Signing commits](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work#_signing_commits)
+
+## How GitLab handles GPG
+
+GitLab uses its own keyring to verify the GPG signature. It does not access any
+public key server.
+
+In order to have a commit verified on GitLab the corresponding public key needs
+to be uploaded to GitLab.
+
+For a signature to be verified two prerequisites need to be met:
+
+1. The public key needs to be added to GitLab
+1. One of the emails in the GPG key matches your **primary** email
+
+## Add a GPG key
+
+1. On the upper right corner, click on your avatar and go to your **Settings**.
+
+ ![Settings dropdown](../../gitlab-basics/img/profile_settings.png)
+
+1. Navigate to the **GPG keys** tab.
+
+ ![GPG Keys](img/profile_settings_gpg_keys.png)
+
+1. Paste your **public** key in the 'Key' box.
+
+ ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png)
+
+1. Finally, click on **Add key** to add it to GitLab. You will be able to see
+ its fingerprint, the corresponding email address and creation date.
+
+ ![GPG key single page](img/profile_settings_gpg_keys_single_key.png)
+
+>**Note:**
+Once you add a key, you cannot edit it, only remove it. In case the paste
+didn't work, you will have to remove the offending key and re-add it.
+
+## Remove a GPG key
+
+1. On the upper right corner, click on your avatar and go to your **Settings**.
+
+1. Navigate to the **GPG keys** tab.
+
+1. Click on the trash icon besides the GPG key you want to delete.
+
+>**Note:**
+Removing a key **does not unverify** already signed commits. Commits that were
+verified by using this key will stay verified. Only unpushed commits will stay
+unverified once you remove this key.
+
+## Revoke a GPG key
+
+1. On the upper right corner, click on your avatar and go to your **Settings**.
+
+1. Navigate to the **GPG keys** tab.
+
+1. Click on **Revoke** besides the GPG key you want to delete.
+
+>**Note:**
+Revoking a key **unverifies** already signed commits. Commits that were
+verified by using this key will change to an unverified state. Future commits
+will also stay unverified once you revoke this key. This action should be used
+in case your key has been compromised.
+
+## Verifying commits
+
+1. Within a project navigate to the **Commits** tag. Signed commits will show a
+ badge containing either "Verified" or "Unverified", depending on the
+ verification status of the GPG signature.
+
+ ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png)
+
+1. By clicking on the GPG badge details of the signature are displayed.
+
+ ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png)
+
+ ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png)
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 1645e7e8d65..06eec1ed928 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -1,97 +1,2 @@
-# GitLab Groups
-GitLab groups allow you to group projects into directories and give users access to several projects at once.
-
-When you create a new project in GitLab, the default namespace for the project is the personal namespace associated with your GitLab user.
-In this document we will see how to create groups, put projects in groups and manage who can access the projects in a group.
-
-## Creating groups
-
-You can create a group by going to the 'Groups' tab of the GitLab dashboard and clicking the 'New group' button.
-
-![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png)
-
-Next, enter the path and name (required) and the optional description and group avatar.
-
-![Fill in the path for your new group](groups/new_group_form.png)
-
-When your group has been created you are presented with the group dashboard feed, which will be empty.
-
-![Group dashboard](groups/group_dashboard.png)
-
-You can use the 'New project' button to add a project to the new group.
-
-## Transferring an existing project into a group
-
-You can transfer an existing project into a group you have at least Master access in from the project settings page.
-The option to transfer a project is only available if you are the Owner of the project.
-First scroll down to the 'Dangerous settings' and click 'Show them to me'.
-Now you can pick any of the groups you have at least Master access in as the new namespace for the group.
-
-![Transfer a project to a new namespace](groups/transfer_project.png)
-
-GitLab administrators can use the admin interface to move any project to any namespace if needed.
-
-## Adding users to a group
-
-One of the benefits of putting multiple projects in one group is that you can give a user to access to all projects in the group with one action.
-
-Suppose we have a group with two projects.
-
-![Group with two projects](groups/group_with_two_projects.png)
-
-On the 'Group Members' page we can now add a new user Barry to the group.
-
-![Add user Barry to the group](groups/add_member_to_group.png)
-
-Now because Barry is a 'Developer' member of the 'Open Source' group, he automatically gets 'Developer' access to all projects in the 'Open Source' group.
-
-![Barry has 'Developer' access to GitLab CI](groups/project_members_via_group.png)
-
-If necessary, you can increase the access level of an individual user for a specific project, by adding them as a Member to the project.
-
-![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png)
-
-## Requesting access to a group
-
-As a group owner you can enable or disable non members to request access to
-your group. Go to the group settings and click on **Allow users to request access**.
-
-As a user, you can request to be a member of a group. Go to the group you'd
-like to be a member of, and click the **Request Access** button on the right
-side of your screen.
-
-![Request access button](groups/request_access_button.png)
-
----
-
-Group owners & masters will be notified of your request and will be able to approve or
-decline it on the members page.
-
-![Manage access requests](groups/access_requests_management.png)
-
----
-
-If you change your mind before your request is approved, just click the
-**Withdraw Access Request** button.
-
-![Withdraw access request button](groups/withdraw_access_request_button.png)
-
-## Managing group memberships via LDAP
-
-In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
-See [the GitLab Enterprise Edition documentation](http://docs.gitlab.com/ee/integration/ldap.html) for more information.
-
-## Allowing only admins to create groups
-
-By default, any GitLab user can create new groups.
-This ability can be disabled for individual users from the admin panel.
-It is also possible to configure GitLab so that new users default to not being able to create groups:
-
-```
-# For omnibus-gitlab, put the following in /etc/gitlab/gitlab.rb
-gitlab_rails['gitlab_default_can_create_group'] = false
-
-# For installations from source, uncomment the 'default_can_create_group'
-# line in /home/git/gitlab/config/gitlab.yml
-```
+This document was moved to [another location](../user/group/index.md).
diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png
deleted file mode 100644
index a10d5032bb0..00000000000
--- a/doc/workflow/groups/add_member_to_group.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png
deleted file mode 100644
index a5829f25808..00000000000
--- a/doc/workflow/groups/group_dashboard.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png
deleted file mode 100644
index 76d0a1b8ab2..00000000000
--- a/doc/workflow/groups/group_with_two_projects.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png
deleted file mode 100644
index 7155d6280bd..00000000000
--- a/doc/workflow/groups/new_group_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png
deleted file mode 100644
index 2b3e9a49842..00000000000
--- a/doc/workflow/groups/override_access_level.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png
deleted file mode 100644
index 878c9a03ac9..00000000000
--- a/doc/workflow/groups/project_members_via_group.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png
deleted file mode 100644
index 52161817f11..00000000000
--- a/doc/workflow/groups/transfer_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md
index 8e50cb03e63..40d756bc199 100644
--- a/doc/workflow/share_projects_with_other_groups.md
+++ b/doc/workflow/share_projects_with_other_groups.md
@@ -5,7 +5,7 @@ to a project with a single action.
## Groups as collections of users
-Groups are used primarily to [create collections of projects](groups.md), but you can also
+Groups are used primarily to [create collections of projects](../user/group/index.md), but you can also
take advantage of the fact that groups define collections of _users_, namely the group
members.
diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature
deleted file mode 100644
index 1af4d46dec9..00000000000
--- a/features/dashboard/dashboard.feature
+++ /dev/null
@@ -1,70 +0,0 @@
-@dashboard
-Feature: Dashboard
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And project "Shop" has push event
- And project "Shop" has CI enabled
- And project "Shop" has CI build
- And project "Shop" has labels: "bug", "feature", "enhancement"
- And project "Shop" has issue: "bug report"
- And I visit dashboard page
-
- Scenario: I should see projects list
- Then I should see "New Project" link
- Then I should see "Shop" project link
- Then I should see "Shop" project CI status
-
- @javascript
- Scenario: I should see activity list
- And I visit dashboard activity page
- Then I should see project "Shop" activity feed
-
- Scenario: I should see groups list
- Given I have group with projects
- And I visit dashboard page
- Then I should see groups list
-
- @javascript
- Scenario: I should see last push widget
- Then I should see last push widget
- And I click "Create Merge Request" link
- Then I see prefilled new Merge Request page
-
- @javascript
- Scenario: Sorting Issues
- Given I visit dashboard issues page
- And I sort the list by "Oldest updated"
- And I visit dashboard activity page
- And I visit dashboard issues page
- Then The list should be sorted by "Oldest updated"
-
- @javascript
- Scenario: Filtering Issues by label
- Given project "Shop" has issue "Bugfix1" with label "feature"
- When I visit dashboard issues page
- And I filter the list by label "feature"
- Then I should see "Bugfix1" in issues list
-
- @javascript
- Scenario: Visiting Project's issues after sorting
- Given I visit dashboard issues page
- And I sort the list by "Oldest updated"
- And I visit project "Shop" issues page
- Then The list should be sorted by "Oldest updated"
-
- @javascript
- Scenario: Sorting Merge Requests
- Given I visit dashboard merge requests page
- And I sort the list by "Oldest updated"
- And I visit dashboard activity page
- And I visit dashboard merge requests page
- Then The list should be sorted by "Oldest updated"
-
- @javascript
- Scenario: Visiting Project's merge requests after sorting
- Given project "Shop" has a "Bugfix MR" merge request open
- And I visit dashboard merge requests page
- And I sort the list by "Oldest updated"
- And I visit project "Shop" merge requests page
- Then The list should be sorted by "Oldest updated"
diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature
deleted file mode 100644
index 8c3ff64164f..00000000000
--- a/features/dashboard/event_filters.feature
+++ /dev/null
@@ -1,58 +0,0 @@
-@dashboard
-Feature: Event Filters
- Background:
- Given I sign in as a user
- And I own a project
- And this project has push event
- And this project has new member event
- And this project has merge request event
- And I visit dashboard activity page
-
- @javascript
- Scenario: I should see all events
- Then I should see push event
- And I should see new member event
- And I should see merge request event
-
- @javascript
- Scenario: I should see only pushed events
- When I click "push" event filter
- Then I should see push event
- And I should not see new member event
- And I should not see merge request event
-
- @javascript
- Scenario: I should see only joined events
- When I click "team" event filter
- Then I should see new member event
- And I should not see push event
- And I should not see merge request event
-
- @javascript
- Scenario: I should see only merged events
- When I click "merge" event filter
- Then I should see merge request event
- And I should not see push event
- And I should not see new member event
-
- @javascript
- Scenario: I should see only selected events while page reloaded
- When I click "push" event filter
- And I visit dashboard activity page
- Then I should see push event
- And I should not see new member event
- When I click "team" event filter
- And I visit dashboard activity page
- Then I should not see push event
- And I should see new member event
- And I should not see merge request event
- When I click "push" event filter
- And I visit dashboard activity page
- Then I should see push event
- And I should not see new member event
- And I should not see merge request event
- When I click "merge" event filter
- And I visit dashboard activity page
- Then I should see merge request event
- And I should not see push event
- And I should not see new member event
diff --git a/features/group/milestones.feature b/features/group/milestones.feature
index 1c1539b3e12..2211acfee20 100644
--- a/features/group/milestones.feature
+++ b/features/group/milestones.feature
@@ -22,12 +22,12 @@ Feature: Group Milestones
Then I should see group milestone with descriptions and expiry date
And I should see group milestone with all issues and MRs assigned to that milestone
- Scenario: Create multiple milestones with one form
+ Scenario: Create group milestones
Given I visit group "Owned" milestones page
And I click new milestone button
And I fill milestone name
When I press create mileston button
- Then milestone in each project should be created
+ Then group milestone should be created
Scenario: I should see Issues listed with labels
Given Group has projects with milestones
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 34201cd8486..3ea0aab5a67 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -31,6 +31,11 @@ Feature: Project Active Tab
Then the active main tab should be Wiki
And no other main tabs should be active
+ Scenario: On Project Members
+ Given I visit my project's members page
+ Then the active main tab should be Members
+ And no other main tabs should be active
+
# Sub Tabs: Home
Scenario: On Project Home/Show
@@ -63,12 +68,6 @@ Feature: Project Active Tab
And no other sub tabs should be active
And the active main tab should be Settings
- Scenario: On Project Members
- Given I visit my project's members page
- Then the active sub tab should be Members
- And no other sub tabs should be active
- And the active main tab should be Settings
-
# Sub Tabs: Repository
Scenario: On Project Repository/Files
diff --git a/features/project/badges/build.feature b/features/project/badges/build.feature
deleted file mode 100644
index bcf80ed620e..00000000000
--- a/features/project/badges/build.feature
+++ /dev/null
@@ -1,27 +0,0 @@
-Feature: Project Badges Build
- Background:
- Given I sign in as a user
- And I own a project
- And project has CI enabled
- And project has a recent build
-
- Scenario: I want to see a badge for successfully built project
- Given recent build is successful
- When I display builds badge for a master branch
- Then I should see a build success badge
-
- Scenario: I want to see a badge for project with failed builds
- Given recent build failed
- When I display builds badge for a master branch
- Then I should see a build failed badge
-
- Scenario: I want to see a badge for project with running builds
- Given recent build is successful
- And project has another build that is running
- When I display builds badge for a master branch
- Then I should see a build running badge
-
- Scenario: I want to see a fresh badge on each request
- Given recent build is successful
- When I display builds badge for a master branch
- Then I should see a badge that has not been cached
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
deleted file mode 100644
index 472ec9544f3..00000000000
--- a/features/project/source/browse_files.feature
+++ /dev/null
@@ -1,333 +0,0 @@
-Feature: Project Source Browse Files
- Background:
- Given I sign in as a user
- And I own project "Shop"
- Given I visit project source page
-
- Scenario: I browse files from master branch
- Then I should see files from repository
-
- Scenario: I browse files for specific ref
- Given I visit project source page for "6d39438"
- Then I should see files from repository for "6d39438"
-
- @javascript
- Scenario: I browse file content
- Given I click on ".gitignore" file in repo
- Then I should see its content
-
- Scenario: I browse raw file
- Given I visit blob file from repo
- And I click link "Raw"
- Then I should see raw file content
-
- Scenario: I can create file
- Given I click on "New file" link in repo
- Then I can see new file page
-
- Scenario: I can create file when I don't have write access
- Given I don't have write access
- And I click on "New file" link in repo
- Then I should see a notice about a new fork having been created
- Then I can see new file page
-
- @javascript
- Scenario: I can create and commit file
- Given I click on "New file" link in repo
- And I edit code
- And I fill the new file name
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the new file
- And I should see its new content
-
- @javascript
- Scenario: I can create and commit file when I don't have write access
- Given I don't have write access
- And I click on "New file" link in repo
- And I edit code
- And I fill the new file name
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the fork's new merge request page
- And I can see the new commit message
-
- @javascript
- Scenario: I can create and commit file with new lines at the end of file
- Given I click on "New file" link in repo
- And I edit code with new lines at end of file
- And I fill the new file name
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the new file
- And I click button "Edit"
- And I should see its content with new lines preserved at end of file
-
- @javascript
- Scenario: I can create and commit file and specify new branch
- Given I click on "New file" link in repo
- And I edit code
- And I fill the new file name
- And I fill the commit message
- And I fill the new branch name
- And I click on "Commit changes"
- Then I am redirected to the new merge request page
- When I click on "Changes" tab
- And I should see its new content
-
- @javascript
- Scenario: I can upload file and commit
- Given I click on "Upload file" link in repo
- And I upload a new text file
- And I fill the upload file commit message
- And I fill the new branch name
- And I click on "Upload file"
- Then I can see the new commit message
- And I am redirected to the new merge request page
- When I click on "Changes" tab
- Then I can see the new text file
-
- @javascript
- Scenario: I can upload file and commit when I don't have write access
- Given I don't have write access
- And I click on "Upload file" link in repo
- Then I should see a notice about a new fork having been created
- When I click on "Upload file" link in repo
- And I upload a new text file
- And I fill the upload file commit message
- And I click on "Upload file"
- Then I can see the new commit message
- And I am redirected to the fork's new merge request page
- When I click on "Changes" tab
- Then I can see the new text file
-
- @javascript
- Scenario: I can replace file and commit
- Given I click on ".gitignore" file in repo
- And I see the ".gitignore"
- And I click on "Replace"
- And I replace it with a text file
- And I fill the replace file commit message
- And I click on "Replace file"
- Then I can see the new text file
- And I can see the replacement commit message
-
- @javascript
- Scenario: I can replace file and commit when I don't have write access
- Given I don't have write access
- And I click on ".gitignore" file in repo
- And I see the ".gitignore"
- And I click on "Replace"
- Then I should see a Fork/Cancel combo
- And I click button "Fork"
- Then I should see a notice about a new fork having been created
- When I click on "Replace"
- And I replace it with a text file
- And I fill the replace file commit message
- And I click on "Replace file"
- And I can see the replacement commit message
- And I am redirected to the fork's new merge request page
- When I click on "Changes" tab
- Then I can see the new text file
-
- @javascript
- Scenario: If I enter an illegal file name I see an error message
- Given I click on "New file" link in repo
- And I fill the new file name with an illegal name
- And I edit code
- And I fill the commit message
- And I click on "Commit changes"
- Then I am on the new file page
- And I see "Path can contain only..."
-
- @javascript
- Scenario: I can create file with a directory name
- Given I click on "New file" link in repo
- And I fill the new file name with a new directory
- And I edit code
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the new file with directory
- And I should see its new content
-
- @javascript
- Scenario: I can edit file
- Given I click on ".gitignore" file in repo
- And I click button "Edit"
- Then I can edit code
-
- @javascript
- Scenario: I can edit file when I don't have write access
- Given I don't have write access
- And I click on ".gitignore" file in repo
- And I click button "Edit"
- Then I should see a Fork/Cancel combo
- And I click button "Fork"
- Then I should see a notice about a new fork having been created
- And I can edit code
-
- Scenario: If the file is binary the edit link is hidden
- Given I visit a binary file in the repo
- Then I cannot see the edit button
-
- @javascript
- Scenario: I can edit and commit file
- Given I click on ".gitignore" file in repo
- And I click button "Edit"
- And I edit code
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the ".gitignore"
- And I should see its new content
-
- @javascript
- Scenario: I can edit and commit file when I don't have write access
- Given I don't have write access
- And I click on ".gitignore" file in repo
- And I click button "Edit"
- Then I should see a Fork/Cancel combo
- And I click button "Fork"
- And I edit code
- And I fill the commit message
- And I click on "Commit changes"
- Then I am redirected to the fork's new merge request page
- And I can see the new commit message
-
- @javascript
- Scenario: I can edit and commit file to new branch
- Given I click on ".gitignore" file in repo
- And I click button "Edit"
- And I edit code
- And I fill the commit message
- And I fill the new branch name
- And I click on "Commit changes"
- Then I am redirected to the new merge request page
- Then I click on "Changes" tab
- And I should see its new content
-
- @javascript @wip
- Scenario: If I don't change the content of the file I see an error message
- Given I click on ".gitignore" file in repo
- And I click button "edit"
- And I fill the commit message
- And I click on "Commit changes"
- # Test fails because carriage returns are added to the file.
- Then I am on the ".gitignore" edit file page
- And I see a commit error message
-
- @javascript
- Scenario: I can create directory in repo
- When I click on "New directory" link in repo
- And I fill the new directory name
- And I fill the commit message
- And I fill the new branch name
- And I click on "Create directory"
- Then I am redirected to the new merge request page
-
- @javascript
- Scenario: I can create directory in repo when I don't have write access
- Given I don't have write access
- When I click on "New directory" link in repo
- Then I should see a notice about a new fork having been created
- When I click on "New directory" link in repo
- And I fill the new directory name
- And I fill the commit message
- And I click on "Create directory"
- Then I am redirected to the fork's new merge request page
-
- @javascript
- Scenario: I attempt to create an existing directory
- When I click on "New directory" link in repo
- And I fill an existing directory name
- And I fill the commit message
- And I click on "Create directory"
- Then I see "Unable to create directory"
- And I am redirected to the root directory
-
- @javascript
- Scenario: I can see editing preview
- Given I click on ".gitignore" file in repo
- And I click button "Edit"
- And I edit code
- And I click link "Diff"
- Then I see diff
-
- @javascript
- Scenario: I can delete file and commit
- Given I click on ".gitignore" file in repo
- And I see the ".gitignore"
- And I click on "Delete"
- And I fill the commit message
- And I click on "Delete file"
- Then I am redirected to the files URL
- And I don't see the ".gitignore"
-
- @javascript
- Scenario: I can delete file and commit when I don't have write access
- Given I don't have write access
- And I click on ".gitignore" file in repo
- And I see the ".gitignore"
- And I click on "Delete"
- Then I should see a Fork/Cancel combo
- And I click button "Fork"
- Then I should see a notice about a new fork having been created
- When I click on "Delete"
- And I fill the commit message
- And I click on "Delete file"
- Then I am redirected to the fork's new merge request page
- And I can see the new commit message
-
- Scenario: I can browse directory with Browse Dir
- Given I click on files directory
- And I click on History link
- Then I see Browse dir link
-
- Scenario: I can browse file with Browse File
- Given I click on readme file
- And I click on History link
- Then I see Browse file link
-
- Scenario: I can browse code with Browse Code
- Given I click on History link
- Then I see Browse code link
-
- # Permalink
-
- Scenario: I click on the permalink link from a branch ref
- Given I click on ".gitignore" file in repo
- And I click on Permalink
- Then I am redirected to the permalink URL
-
- Scenario: I don't see the permalink link from a SHA ref
- Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f"
- And I click on ".gitignore" file in repo
- Then I don't see the permalink link
-
- @javascript
- Scenario: I browse code with single quotes in the ref
- Given I switch ref to 'test'
- And I see the ref 'test' has been selected
- And I visit the 'test' tree
- Then I see the commit data
-
- @javascript
- Scenario: I browse code with a leading dot in the directory
- Given I switch ref to fix
- And I visit the fix tree
- Then I see the commit data for a directory with a leading dot
-
- Scenario: I browse LFS object
- Given I click on "files/lfs/lfs_object.iso" file in repo
- Then I should see download link and object size
- And I should not see lfs pointer details
- And I should see buttons for allowed commands
-
- @javascript
- Scenario: I preview an SVG file
- Given I click on "Upload file" link in repo
- And I upload a new SVG file
- And I fill the upload file commit message
- And I fill the new branch name
- And I click on "Upload file"
- Given I visit the SVG file
- Then I can see the new rendered SVG image
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
deleted file mode 100644
index 1ad02780229..00000000000
--- a/features/snippets/snippets.feature
+++ /dev/null
@@ -1,40 +0,0 @@
-@snippets
-Feature: Snippets
- Background:
- Given I sign in as a user
- And I have public "Personal snippet one" snippet
- And I have private "Personal snippet private" snippet
-
- @javascript
- Scenario: I create new snippet
- Given I visit new snippet page
- And I submit new snippet "Personal snippet three"
- Then I should see snippet "Personal snippet three"
-
- Scenario: I update "Personal snippet one"
- Given I visit snippet page "Personal snippet one"
- And I click link "Edit"
- And I submit new title "Personal snippet new title"
- Then I should see "Personal snippet new title"
-
- Scenario: Set "Personal snippet one" public
- Given I visit snippet page "Personal snippet one"
- And I click link "Edit"
- And I uncheck "Private" checkbox
- Then I should see "Personal snippet one" public
-
- Scenario: I destroy "Personal snippet one"
- Given I visit snippet page "Personal snippet one"
- And I click link "Delete"
- Then I should not see "Personal snippet one" in snippets
-
- Scenario: I create new internal snippet
- Given I logout directly
- And I sign in as an admin
- Then I visit new snippet page
- And I submit new internal snippet
- Then I visit snippet page "Internal personal snippet one"
- And I logout directly
- Then I sign in as a user
- Given I visit new snippet page
- Then I visit snippet page "Internal personal snippet one"
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
deleted file mode 100644
index 3c06b188f3e..00000000000
--- a/features/steps/dashboard/dashboard.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-class Spinach::Features::Dashboard < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedIssuable
-
- step 'I should see "New Project" link' do
- expect(page).to have_link "New project"
- end
-
- step 'I should see "Shop" project link' do
- expect(page).to have_link "Shop"
- end
-
- step 'I should see "Shop" project CI status' do
- expect(page).to have_link "Commit: skipped"
- end
-
- step 'I should see last push widget' do
- expect(page).to have_content "You pushed to fix"
- expect(page).to have_link "Create merge request"
- end
-
- step 'I click "Create merge request" link' do
- find_link("Create merge request", visible: false).trigger('click')
- end
-
- step 'I see prefilled new Merge Request page' do
- expect(page).to have_selector('.merge-request-form')
- expect(current_path).to eq namespace_project_new_merge_request_path(@project.namespace, @project)
- expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
- expect(find("input#merge_request_source_branch").value).to eq "fix"
- expect(find("input#merge_request_target_branch").value).to eq "master"
- end
-
- step 'I have group with projects' do
- @group = create(:group)
- @project = create(:empty_project, namespace: @group)
- @event = create(:closed_issue_event, project: @project)
-
- @project.team << [current_user, :master]
- end
-
- step 'I should see projects list' do
- @user.authorized_projects.all.each do |project|
- expect(page).to have_link project.name_with_namespace
- end
- end
-
- step 'I should see groups list' do
- Group.all.each do |group|
- expect(page).to have_link group.name
- end
- end
-
- step 'group has a projects that does not belongs to me' do
- @forbidden_project1 = create(:empty_project, group: @group)
- @forbidden_project2 = create(:empty_project, group: @group)
- end
-
- step 'I should see 1 project at group list' do
- expect(find('span.last_activity/span')).to have_content('1')
- end
-
- step 'I filter the list by label "feature"' do
- page.within ".labels-filter" do
- find('.dropdown').click
- click_link "feature"
- end
- end
-
- step 'I should see "Bugfix1" in issues list' do
- page.within "ul.content-list" do
- expect(page).to have_content "Bugfix1"
- end
- end
-
- step 'project "Shop" has issue "Bugfix1" with label "feature"' do
- project = Project.find_by(name: "Shop")
- issue = create(:issue, title: "Bugfix1", project: project, assignees: [current_user])
- issue.labels << project.labels.find_by(title: 'feature')
- end
-end
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
deleted file mode 100644
index a745254cc31..00000000000
--- a/features/steps/dashboard/event_filters.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-class Spinach::Features::EventFilters < Spinach::FeatureSteps
- include WaitForRequests
- include SharedAuthentication
- include SharedPaths
- include SharedProject
-
- step 'I should see push event' do
- expect(page).to have_selector('span.pushed')
- end
-
- step 'I should not see push event' do
- expect(page).not_to have_selector('span.pushed')
- end
-
- step 'I should see new member event' do
- expect(page).to have_selector('span.joined')
- end
-
- step 'I should not see new member event' do
- expect(page).not_to have_selector('span.joined')
- end
-
- step 'I should see merge request event' do
- expect(page).to have_selector('span.accepted')
- end
-
- step 'I should not see merge request event' do
- expect(page).not_to have_selector('span.accepted')
- end
-
- step 'this project has push event' do
- data = {
- before: Gitlab::Git::BLANK_SHA,
- after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
- ref: "refs/heads/new_design",
- user_id: @user.id,
- user_name: @user.name,
- repository: {
- name: @project.name,
- url: "localhost/rubinius",
- description: "",
- homepage: "localhost/rubinius",
- private: true
- }
- }
-
- @event = Event.create(
- project: @project,
- action: Event::PUSHED,
- data: data,
- author_id: @user.id
- )
- end
-
- step 'this project has new member event' do
- user = create(:user, { name: "John Doe" })
- Event.create(
- project: @project,
- author_id: user.id,
- action: Event::JOINED
- )
- end
-
- step 'this project has merge request event' do
- merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project
- Event.create(
- project: @project,
- action: Event::MERGED,
- target_id: merge_request.id,
- target_type: "MergeRequest",
- author_id: @user.id
- )
- end
-
- When 'I click "push" event filter' do
- wait_for_requests
- click_link("Push events")
- wait_for_requests
- end
-
- When 'I click "team" event filter' do
- wait_for_requests
- click_link("Team")
- wait_for_requests
- end
-
- When 'I click "merge" event filter' do
- wait_for_requests
- click_link("Merge events")
- wait_for_requests
- end
-end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 1a55f40abb9..f1288c15084 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -66,7 +66,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New feature",
project: public_project
)
- visit namespace_project_issues_path(public_project.namespace, public_project)
+ visit project_issues_path(public_project)
end
step 'I should see list of issues for "Community" project' do
@@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "New internal feature",
project: internal_project
)
- visit namespace_project_issues_path(internal_project.namespace, internal_project)
+ visit project_issues_path(internal_project)
end
step 'I should see list of issues for "Internal" project' do
@@ -94,7 +94,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Community" merge requests page' do
- visit namespace_project_merge_requests_path(public_project.namespace, public_project)
+ visit project_merge_requests_path(public_project)
end
step 'project "Community" has "Bug fix" open merge request' do
@@ -111,7 +111,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
end
step 'I visit "Internal" merge requests page' do
- visit namespace_project_merge_requests_path(internal_project.namespace, internal_project)
+ visit project_merge_requests_path(internal_project)
end
step 'project "Internal" has "Feature implemented" open merge request' do
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 0542b06c0ab..915d766dd60 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -39,7 +39,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
expect(page).to have_content('Milestone GL-113')
expect(page).to have_content('Issues 3 Open: 3 Closed: 0')
issue = Milestone.find_by(name: 'GL-113').issues.first
- expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
+ expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end
step 'I fill milestone name' do
@@ -54,14 +54,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
click_button "Create milestone"
end
- step 'milestone in each project should be created' do
+ step 'group milestone should be created' do
group = Group.find_by(name: 'Owned')
- expect(page).to have_content "Milestone v2.9.0"
- expect(group.projects).to be_present
-
- group.projects.each do |project|
- expect(page).to have_content project.name
- end
+ expect(page).to have_content group.milestones.find_by_title('v2.9.0').title
end
step 'I should see the "bug" label' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 0aedc422563..6b288b47da4 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -81,7 +81,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see new group "Owned" avatar' do
expect(owned_group.avatar).to be_instance_of AvatarUploader
- expect(owned_group.avatar.url).to eq "/uploads/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif"
+ expect(owned_group.avatar.url).to eq "/uploads/-/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 254c26bb6af..4b88cb5e27f 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see new avatar' do
expect(@user.avatar).to be_instance_of AvatarUploader
- expect(@user.avatar.url).to eq "/uploads/system/user/avatar/#{@user.id}/banana_sample.gif"
+ expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb
index b6f1d417e21..e4847180be9 100644
--- a/features/steps/project/archived.rb
+++ b/features/steps/project/archived.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectArchived < Spinach::FeatureSteps
When 'I visit project "Forum" page' do
project = Project.find_by(name: "Forum")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I should not see "Archived"' do
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
deleted file mode 100644
index 96c59322f9b..00000000000
--- a/features/steps/project/badges/build.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedBuilds
- include RepoHelpers
-
- step 'I display builds badge for a master branch' do
- visit build_namespace_project_badges_path(@project.namespace, @project, ref: :master, format: :svg)
- end
-
- step 'I should see a build success badge' do
- expect_badge('success')
- end
-
- step 'I should see a build failed badge' do
- expect_badge('failed')
- end
-
- step 'I should see a build running badge' do
- expect_badge('running')
- end
-
- step 'I should see a badge that has not been cached' do
- expect(page.response_headers['Cache-Control']).to include 'no-cache'
- end
-
- def expect_badge(status)
- svg = Nokogiri::XML.parse(page.body)
- expect(page.response_headers['Content-Type']).to include('image/svg+xml')
- expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
- end
-end
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index f19fa1c7600..305fff37c41 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I see commit info' do
@@ -73,7 +73,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit commits list page for feature branch' do
- visit namespace_project_commits_path(@project.namespace, @project, 'feature', { limit: 5 })
+ visit project_commits_path(@project, 'feature', { limit: 5 })
end
step 'I see feature branch commits' do
@@ -119,7 +119,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
step 'I should see button to the merge request' do
merge_request = MergeRequest.find_by(title: 'Feature')
- expect(page).to have_link "View open merge request", href: namespace_project_merge_request_path(@project.namespace, @project, merge_request)
+ expect(page).to have_link "View open merge request", href: project_merge_request_path(@project, merge_request)
end
step 'I see breadcrumb links' do
@@ -135,7 +135,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit a commit with an image that changed' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
+ visit project_commit_path(@project, sample_image_commit.id)
end
step 'The diff links to both the previous and current image' do
diff --git a/features/steps/project/commits/revert.rb b/features/steps/project/commits/revert.rb
index 114de129d19..ebfa7a878bb 100644
--- a/features/steps/project/commits/revert.rb
+++ b/features/steps/project/commits/revert.rb
@@ -6,7 +6,7 @@ class Spinach::Features::RevertCommits < Spinach::FeatureSteps
include RepoHelpers
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I click on the revert button' do
diff --git a/features/steps/project/commits/user_lookup.rb b/features/steps/project/commits/user_lookup.rb
index 2d43be5a386..4599e0d032a 100644
--- a/features/steps/project/commits/user_lookup.rb
+++ b/features/steps/project/commits/user_lookup.rb
@@ -4,11 +4,11 @@ class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps
include SharedPaths
step 'I click on commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I click on another commit link' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id)
+ visit project_commit_path(@project, sample_commit.parent_id)
end
step 'I have user with primary email' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index 28be9c6df5b..60fa232672e 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -7,12 +7,12 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
fill_in 'project_path', with: 'Empty'
page.within '#content-body' do
click_button "Create project"
- end
+ end
end
step 'I should see project page' do
expect(page).to have_content "Empty"
- expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last)
+ expect(current_path).to eq project_path(Project.last)
end
step 'I should see empty project instructions' do
diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb
index 8ad9d4a4741..b58d595c7c6 100644
--- a/features/steps/project/deploy_keys.rb
+++ b/features/steps/project/deploy_keys.rb
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps
end
step 'I should be on deploy keys page' do
- expect(current_path).to eq namespace_project_settings_repository_path(@project.namespace, @project)
+ expect(current_path).to eq project_settings_repository_path(@project)
end
step 'I should see newly created deploy key' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index 35df403a85f..dd4dff7f7a9 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -53,7 +53,7 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I visit the forks page of the "Shop" project' do
@project = Project.where(name: 'Shop').last
- visit namespace_project_forks_path(@project.namespace, @project)
+ visit project_forks_path(@project)
end
step 'I should see my fork on the list' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 2d9d3efd9d4..c6cabace25b 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
step 'I should see merge request "Merge Request On Forked Project"' do
expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq project_merge_request_path(@project, @merge_request)
expect(@merge_request.title).to eq "Merge Request On Forked Project"
expect(@merge_request.source_project).to eq @forked_project
expect(@merge_request.source_branch).to eq "fix"
@@ -77,7 +77,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_content "An Edited Forked Merge Request"
expect(@project.merge_requests.size).to be >= 1
@merge_request = @project.merge_requests.last
- expect(current_path).to eq namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq project_merge_request_path(@project, @merge_request)
expect(@merge_request.source_project).to eq @forked_project
expect(@merge_request.source_branch).to eq "fix"
expect(@merge_request.target_branch).to eq "master"
@@ -97,7 +97,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I see the edit page prefilled for "Merge Request On Forked Project"' do
- expect(current_path).to eq edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ expect(current_path).to eq edit_project_merge_request_path(@project, @merge_request)
expect(page).to have_content "Edit merge request #{@merge_request.to_reference}"
expect(find("#merge_request_title").value).to eq "Merge Request On Forked Project"
end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 176d04d721c..e78e25318a6 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -7,19 +7,19 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
When 'I visit project "Shop" graph page' do
- visit namespace_project_graph_path(project.namespace, project, "master")
+ visit project_graph_path(project, "master")
end
step 'I visit project "Shop" commits graph page' do
- visit commits_namespace_project_graph_path(project.namespace, project, "master")
+ visit commits_project_graph_path(project, "master")
end
step 'I visit project "Shop" languages graph page' do
- visit languages_namespace_project_graph_path(project.namespace, project, "master")
+ visit languages_project_graph_path(project, "master")
end
step 'I visit project "Shop" chart page' do
- visit charts_namespace_project_graph_path(project.namespace, project, "master")
+ visit charts_project_graph_path(project, "master")
end
step 'page should have languages graphs' do
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
end
step 'I visit project "Shop" CI graph page' do
- visit ci_namespace_project_graph_path(project.namespace, project, 'master')
+ visit ci_project_graph_path(project, 'master')
end
step 'page should have CI graphs' do
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 2324edda975..bbd284b4633 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -5,7 +5,7 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
include Select2Helper
step 'I visit "Bugfix" issue page' do
- visit namespace_project_issue_path(@project.namespace, @project, @issue)
+ visit project_issue_path(@project, @issue)
end
step 'I click the thumbsup award Emoji' do
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index e4a559d8ff5..2deef9036d3 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -247,7 +247,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When 'I visit empty project page' do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I see empty project details with ssh clone info' do
@@ -259,12 +259,12 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
When "I visit project \"Community\" issues page" do
project = Project.find_by(name: 'Community')
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
step 'I leave a comment with code block' do
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index 2828e41f731..dac18c537ac 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
include SharedPaths
step 'I visit \'bug\' label edit page' do
- visit edit_namespace_project_label_path(project.namespace, project, bug_label)
+ visit edit_project_label_path(project, bug_label)
end
step 'I remove label \'bug\'' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index dceeed5aafe..810cd75591b 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -256,7 +256,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I switch to the merge request\'s comments tab' do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
step 'I click on the commit in the merge request' do
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 870dc862992..3c640e3512a 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -1,6 +1,5 @@
class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
include LoginHelpers
- include GitlabRoutingHelper
include WaitForRequests
step 'I am on the Merge Request detail page' do
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index 98d990f112f..25ccf5ab180 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -1,6 +1,5 @@
class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
include LoginHelpers
- include GitlabRoutingHelper
include WaitForRequests
step 'I click on the revert button' do
diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb
index 370e46265c7..ba98d861e7b 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -12,11 +12,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
Network::Graph.stub(max_count: 10)
@project = Project.find_by(name: "Shop")
- visit namespace_project_network_path(@project.namespace, @project, "master")
+ visit project_network_path(@project, "master")
end
step "I visit project network page on branch 'test'" do
- visit namespace_project_network_path(@project.namespace, @project, "'test'")
+ visit project_network_path(@project, "'test'")
end
step 'page should select "master" in select box' do
diff --git a/features/steps/project/pages.rb b/features/steps/project/pages.rb
index 4e6830f738b..275fb4fc010 100644
--- a/features/steps/project/pages.rb
+++ b/features/steps/project/pages.rb
@@ -15,7 +15,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
end
step 'I visit the Project Pages' do
- visit namespace_project_pages_path(@project.namespace, @project)
+ visit project_pages_path(@project)
end
step 'I should see the usage of GitLab Pages' do
@@ -75,7 +75,7 @@ class Spinach::Features::ProjectPages < Spinach::FeatureSteps
end
step 'I visit add a new Pages Domain' do
- visit new_namespace_project_pages_domain_path(@project.namespace, @project)
+ visit new_project_pages_domain_path(@project)
end
step 'I fill the domain' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 7d34331db46..170e2f16c80 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -38,7 +38,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see new project avatar' do
expect(@project.avatar).to be_instance_of AvatarUploader
url = @project.avatar.url
- expect(url).to eq "/uploads/system/project/avatar/#{@project.id}/banana_sample.gif"
+ expect(url).to eq "/uploads/-/system/project/avatar/#{@project.id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/project_group_links.rb b/features/steps/project/project_group_links.rb
index 5280a38ce81..47ee31786a6 100644
--- a/features/steps/project/project_group_links.rb
+++ b/features/steps/project/project_group_links.rb
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectGroupLinks < Spinach::FeatureSteps
end
step 'I visit project group links page' do
- visit namespace_project_group_links_path(project.namespace, project)
+ visit project_group_links_path(project)
end
def project
diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb
index 92936f27c20..b2ceb8dd9a8 100644
--- a/features/steps/project/redirects.rb
+++ b/features/steps/project/redirects.rb
@@ -13,7 +13,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Community" page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I should see project "Community" home page' do
@@ -25,12 +25,12 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: 'Enterprise')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "CommunityDoesNotExist" page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_path(project.namespace, project) + 'DoesNotExist'
+ visit project_path(project) + 'DoesNotExist'
end
step 'I click on "Sign In"' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 6bac4df16f8..7e2a357f6b2 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
include SharedPaths
step 'I visit project "Shop" services page' do
- visit namespace_project_settings_integrations_path(@project.namespace, @project)
+ visit project_settings_integrations_path(@project)
end
step 'I should see list of available services' do
@@ -175,7 +175,6 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
fill_in 'JIRA API URL', with: 'http://jira.example/api'
fill_in 'Username', with: 'gitlab'
fill_in 'Password', with: 'gitlab'
- fill_in 'Project Key', with: 'GITLAB'
click_button 'Save'
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index dd49701a3d9..b0407d3f07d 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -91,7 +91,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I visit snippet page "Snippet one"' do
- visit namespace_project_snippet_path(project.namespace, project, project_snippet)
+ visit project_snippet_path(project, project_snippet)
end
def project_snippet
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 02434319a08..621cae5d80d 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -9,7 +9,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step "I don't have write access" do
@project = create(:project, :repository, name: "Other Project", path: "other-project")
@project.team << [@user, :reporter]
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I should see files from repository' do
@@ -19,7 +19,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see files from repository for "6d39438"' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ expect(current_path).to eq project_tree_path(@project, "6d39438")
expect(page).to have_content ".gitignore"
expect(page).to have_content "LICENSE"
end
@@ -92,10 +92,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
fill_in :branch_name, with: 'new_branch_name', visible: true
end
- step 'I fill the new file name with an illegal name' do
- fill_in :file_name, with: 'Spaces Not Allowed'
- end
-
step 'I fill the new file name with a new directory' do
fill_in :file_name, with: new_file_name_with_directory
end
@@ -240,16 +236,16 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I am redirected to the files URL' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, 'master')
+ expect(current_path).to eq project_tree_path(@project, 'master')
end
step 'I am redirected to the ".gitignore"' do
- expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
+ expect(current_path).to eq(project_blob_path(@project, 'master/.gitignore'))
end
step 'I am redirected to the permalink URL' do
expect(current_path).to(
- eq(namespace_project_blob_path(@project.namespace, @project,
+ eq(project_blob_path(@project,
@project.repository.commit.sha +
'/.gitignore'))
)
@@ -257,26 +253,26 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I am redirected to the new file' do
expect(current_path).to eq(
- namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name))
+ project_blob_path(@project, 'master/' + new_file_name))
end
step 'I am redirected to the new file with directory' do
expect(current_path).to eq(
- namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory))
+ project_blob_path(@project, 'master/' + new_file_name_with_directory))
end
step 'I am redirected to the new merge request page' do
- expect(current_path).to eq(namespace_project_new_merge_request_path(@project.namespace, @project))
+ expect(current_path).to eq(project_new_merge_request_path(@project))
end
step "I am redirected to the fork's new merge request page" do
fork = @user.fork_of(@project)
- expect(current_path).to eq(namespace_project_new_merge_request_path(fork.namespace, fork))
+ expect(current_path).to eq(project_new_merge_request_path(fork))
end
step 'I am redirected to the root directory' do
expect(current_path).to eq(
- namespace_project_tree_path(@project.namespace, @project, 'master'))
+ project_tree_path(@project, 'master'))
end
step "I don't see the permalink link" do
@@ -327,11 +323,11 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step "I visit the 'test' tree" do
- visit namespace_project_tree_path(@project.namespace, @project, "'test'")
+ visit project_tree_path(@project, "'test'")
end
step "I visit the fix tree" do
- visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
+ visit project_tree_path(@project, "fix/.testdir")
end
step 'I see the commit data' do
@@ -346,7 +342,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "files/lfs/lfs_object.iso" file in repo' do
allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
- visit namespace_project_tree_path(@project.namespace, @project, "lfs")
+ visit project_tree_path(@project, "lfs")
click_link 'files'
click_link "lfs"
click_link "lfs_object.iso"
@@ -389,7 +385,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I visit the SVG file' do
- visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
+ visit project_blob_path(@project, 'new_branch_name/logo_sample.svg')
end
step 'I can see the new rendered SVG image' do
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index cf31e61437e..243a0f54f7f 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -14,7 +14,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see files from repository in markdown' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(current_path).to eq project_tree_path(@project, "markdown")
expect(page).to have_content "README.md"
expect(page).to have_content "CHANGELOG"
end
@@ -34,7 +34,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
@@ -44,7 +44,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct directory rendered' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
expect(page).to have_content "backup_restore.md"
expect(page).to have_content "maintenance.md"
end
@@ -54,7 +54,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct doc/api directory rendered' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
expect(page).to have_content "README.md"
expect(page).to have_content "users.md"
end
@@ -64,7 +64,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct maintenance file rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/raketasks/maintenance.md")
wait_for_requests
expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
@@ -98,7 +98,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see correct file rendered' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
@@ -110,7 +110,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct document file' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
expect(page).to have_content "Get a list of users."
end
@@ -121,30 +121,30 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
# Markdown branch
When 'I visit markdown branch' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown")
+ visit project_tree_path(@project, "markdown")
wait_for_requests
end
When 'I visit markdown branch "README.md" blob' do
- visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ visit project_blob_path(@project, "markdown/README.md")
end
When 'I visit markdown branch "d" tree' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ visit project_tree_path(@project, "markdown/d")
end
When 'I visit markdown branch "d/README.md" blob' do
- visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ visit project_blob_path(@project, "markdown/d/README.md")
end
step 'I should see files from repository in markdown branch' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown")
+ expect(current_path).to eq project_tree_path(@project, "markdown")
expect(page).to have_content "README.md"
expect(page).to have_content "CHANGELOG"
end
step 'I see correct file rendered in markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
@@ -152,19 +152,19 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see correct document rendered for markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/README.md")
wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
step 'I should see correct directory rendered for markdown branch' do
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/raketasks")
expect(page).to have_content "backup_restore.md"
expect(page).to have_content "maintenance.md"
end
step 'I should see the users document file in markdown branch' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
expect(page).to have_content "Get a list of users."
end
@@ -172,54 +172,54 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'The link with text "empty" should have url "tree/markdown"' do
wait_for_requests
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown")
end
step 'The link with text "empty" should have url "blob/markdown/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md")
end
step 'The link with text "empty" should have url "tree/markdown/d"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d")
+ find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d")
end
step 'The link with text "empty" should have '\
'url "blob/markdown/d/README.md"' do
- find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md")
+ find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md")
end
step 'The link with text "ID" should have url "tree/markdownID"' do
- find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "/ID" should have url "tree/markdownID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id'
end
step 'The link with text "README.mdID" '\
'should have url "blob/markdown/README.mdID"' do
- find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
step 'The link with text "d/README.mdID" should have '\
'url "blob/markdown/d/README.mdID"' do
- find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id'
+ find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id'
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
wait_for_requests
- find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do
- find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
+ find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id'
end
# Wiki
step 'I go to wiki page' do
click_link "Wiki"
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
end
step 'I add various links to the wiki page' do
@@ -231,7 +231,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'Wiki page should have added links' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
expect(page).to have_content "test GitLab API doc Rake tasks"
end
@@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see new wiki page named test' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test")
+ expect(current_path).to eq project_wiki_path(@project, "test")
page.within(:css, ".nav-text") do
expect(page).to have_content "Test"
@@ -261,8 +261,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
When 'I go back to wiki page home' do
- visit namespace_project_wiki_path(@project.namespace, @project, "home")
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "home")
+ visit project_wiki_path(@project, "home")
+ expect(current_path).to eq project_wiki_path(@project, "home")
end
step 'I click on GitLab API doc link' do
@@ -270,7 +270,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Gitlab API document' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api")
+ expect(current_path).to eq project_wiki_path(@project, "api")
page.within(:css, ".nav-text") do
expect(page).to have_content "Create"
@@ -283,7 +283,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I see Rake tasks directory' do
- expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks")
+ expect(current_path).to eq project_wiki_path(@project, "raketasks")
page.within(:css, ".nav-text") do
expect(page).to have_content "Create"
@@ -292,8 +292,8 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I go directory which contains README file' do
- visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
- expect(current_path).to eq namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api")
+ visit project_tree_path(@project, "markdown/doc/api")
+ expect(current_path).to eq project_tree_path(@project, "markdown/doc/api")
end
step 'I click on a relative link in README' do
@@ -301,7 +301,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'I should see the correct markdown' do
- expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
+ expect(current_path).to eq project_blob_path(@project, "markdown/doc/api/users.md")
wait_for_requests
expect(page).to have_content "List users"
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 517c257d892..9d38939378d 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to the Edit Home Wiki page' do
- expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, :home)
+ expect(current_path).to eq project_wiki_path(project, :home)
end
step 'I create the Wiki Home page' do
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to that Wiki page' do
- visit namespace_project_wiki_path(project.namespace, project, @page)
+ visit project_wiki_path(project, @page)
end
step 'I click on the Edit button' do
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I should be redirected back to that Wiki page' do
- expect(current_path).to eq namespace_project_wiki_path(project.namespace, project, @page)
+ expect(current_path).to eq project_wiki_path(project, @page)
end
step 'That page has two revisions' do
@@ -95,7 +95,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I browse to wiki page with images' do
- visit namespace_project_wiki_path(project.namespace, project, @wiki_page)
+ visit project_wiki_path(project, @wiki_page)
end
step 'I click on existing image link' do
@@ -114,7 +114,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'Image should be shown on the page' do
- expect(page).to have_xpath("//img[@src=\"image.jpg\"]")
+ expect(page).to have_xpath("//img[@data-src=\"image.jpg\"]")
end
step 'I click on image link' do
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 624f1a7858b..3b4c98ec00d 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -27,11 +27,11 @@ module SharedBuilds
end
step 'I visit recent build details page' do
- visit namespace_project_job_path(@project.namespace, @project, @build)
+ visit project_job_path(@project, @build)
end
step 'I visit project builds page' do
- visit namespace_project_jobs_path(@project.namespace, @project)
+ visit project_jobs_path(@project)
end
step 'recent build has artifacts available' do
@@ -56,7 +56,7 @@ module SharedBuilds
end
step 'I access artifacts download page' do
- visit download_namespace_project_job_artifacts_path(@project.namespace, @project, @build)
+ visit download_project_job_artifacts_path(@project, @build)
end
step 'I see details of a build' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 3d9cedf5c2d..7c842ba88fb 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -51,22 +51,22 @@ module SharedIssuable
step 'I visit issue page "Enterprise issue"' do
issue = Issue.find_by(title: 'Enterprise issue')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit merge request page "Enterprise fix"' do
mr = MergeRequest.find_by(title: 'Enterprise fix')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
end
step 'I visit issue page "Community issue"' do
issue = Issue.find_by(title: 'Community issue')
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit issue page "Community fix"' do
mr = MergeRequest.find_by(title: 'Community fix')
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
end
step 'I should not see any related merge requests' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 8a5b4112ffe..830263fd038 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -201,67 +201,67 @@ module SharedPaths
# ----------------------------------------
step "I visit my project's home page" do
- visit namespace_project_path(@project.namespace, @project)
+ visit project_path(@project)
end
step "I visit my project's settings page" do
- visit edit_namespace_project_path(@project.namespace, @project)
+ visit edit_project_path(@project)
end
step "I visit my project's files page" do
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I visit a binary file in the repo' do
- visit namespace_project_blob_path(@project.namespace, @project,
+ visit project_blob_path(@project,
File.join(root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+ visit project_commits_path(@project, root_ref, { limit: 5 })
end
step "I visit my project's commits page for a specific path" do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", { limit: 5 })
+ visit project_commits_path(@project, root_ref + "/app/models/project.rb", { limit: 5 })
end
step 'I visit my project\'s commits stats page' do
- visit stats_namespace_project_repository_path(@project.namespace, @project)
+ visit stats_project_repository_path(@project)
end
step "I visit my project's graph page" do
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
- visit namespace_project_network_path(@project.namespace, @project, root_ref)
+ visit project_network_path(@project, root_ref)
end
step "I visit my project's issues page" do
- visit namespace_project_issues_path(@project.namespace, @project)
+ visit project_issues_path(@project)
end
step "I visit my project's merge requests page" do
- visit namespace_project_merge_requests_path(@project.namespace, @project)
+ visit project_merge_requests_path(@project)
end
step "I visit my project's members page" do
- visit namespace_project_project_members_path(@project.namespace, @project)
+ visit project_project_members_path(@project)
end
step "I visit my project's wiki page" do
- visit namespace_project_wiki_path(@project.namespace, @project, :home)
+ visit project_wiki_path(@project, :home)
end
step 'I visit project hooks page' do
- visit namespace_project_settings_integrations_path(@project.namespace, @project)
+ visit project_settings_integrations_path(@project)
end
step 'I visit project deploy keys page' do
- visit namespace_project_deploy_keys_path(@project.namespace, @project)
+ visit project_deploy_keys_path(@project)
end
step 'I visit project find file page' do
- visit namespace_project_find_file_path(@project.namespace, @project, root_ref)
+ visit project_find_file_path(@project, root_ref)
end
# ----------------------------------------
@@ -269,107 +269,107 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Shop" activity page' do
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
end
step 'I visit project "Forked Shop" merge requests page' do
- visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
+ visit project_merge_requests_path(@forked_project)
end
step 'I visit edit project "Shop" page' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
step 'I visit project branches page' do
- visit namespace_project_branches_path(@project.namespace, @project)
+ visit project_branches_path(@project)
end
step 'I visit project protected branches page' do
- visit namespace_project_protected_branches_path(@project.namespace, @project)
+ visit project_protected_branches_path(@project)
end
step 'I visit compare refs page' do
- visit namespace_project_compare_index_path(@project.namespace, @project)
+ visit project_compare_index_path(@project)
end
step 'I visit project commits page' do
- visit namespace_project_commits_path(@project.namespace, @project, root_ref, { limit: 5 })
+ visit project_commits_path(@project, root_ref, { limit: 5 })
end
step 'I visit project commits page for stable branch' do
- visit namespace_project_commits_path(@project.namespace, @project, 'stable', { limit: 5 })
+ visit project_commits_path(@project, 'stable', { limit: 5 })
end
step 'I visit project source page' do
- visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ visit project_tree_path(@project, root_ref)
end
step 'I visit blob file from repo' do
- visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path))
+ visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path))
end
step 'I visit ".gitignore" file in repo' do
- visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore'))
+ visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
end
step 'I am on the new file page' do
- expect(current_path).to eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref))
+ expect(current_path).to eq(project_create_blob_path(@project, root_ref))
end
step 'I am on the ".gitignore" edit file page' do
expect(current_path).to eq(
- namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')))
+ project_edit_blob_path(@project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
- visit namespace_project_tree_path(@project.namespace, @project, "6d39438")
+ visit project_tree_path(@project, "6d39438")
end
step 'I visit project source page for' \
' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do
- visit namespace_project_tree_path(@project.namespace, @project,
+ visit project_tree_path(@project,
'6d394385cf567f80a8fd85055db1ab4c5295806f')
end
step 'I visit project tags page' do
- visit namespace_project_tags_path(@project.namespace, @project)
+ visit project_tags_path(@project)
end
step 'I visit project commit page' do
- visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id)
+ visit project_commit_path(@project, sample_commit.id)
end
step 'I visit project "Shop" issues page' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
step 'I visit issue page "Release 0.4"' do
issue = Issue.find_by(title: "Release 0.4")
- visit namespace_project_issue_path(issue.project.namespace, issue.project, issue)
+ visit project_issue_path(issue.project, issue)
end
step 'I visit project "Shop" labels page' do
project = Project.find_by(name: 'Shop')
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
end
step 'I visit project "Forum" labels page' do
project = Project.find_by(name: 'Forum')
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
end
step 'I visit project "Shop" new label page' do
project = Project.find_by(name: 'Shop')
- visit new_namespace_project_label_path(project.namespace, project)
+ visit new_project_label_path(project)
end
step 'I visit project "Forum" new label page' do
project = Project.find_by(name: 'Forum')
- visit new_namespace_project_label_path(project.namespace, project)
+ visit new_project_label_path(project)
end
step 'I visit merge request page "Bug NS-04"' do
@@ -394,28 +394,28 @@ module SharedPaths
step 'I visit merge request page "Bug CO-01"' do
mr = MergeRequest.find_by(title: "Bug CO-01")
- visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ visit project_merge_request_path(mr.target_project, mr)
wait_for_requests
end
step 'I visit project "Shop" merge requests page' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
step 'I visit forked project "Shop" merge requests page' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
step 'I visit project "Shop" milestones page' do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
end
step 'I visit project "Shop" team page' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
end
step 'I visit project wiki page' do
- visit namespace_project_wiki_path(@project.namespace, @project, :home)
+ visit project_wiki_path(@project, :home)
end
# ----------------------------------------
@@ -424,22 +424,22 @@ module SharedPaths
step 'I visit project "Community" page' do
project = Project.find_by(name: "Community")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Community" source page' do
project = Project.find_by(name: 'Community')
- visit namespace_project_tree_path(project.namespace, project, root_ref)
+ visit project_tree_path(project, root_ref)
end
step 'I visit project "Internal" page' do
project = Project.find_by(name: "Internal")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Enterprise" page' do
project = Project.find_by(name: "Enterprise")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
# ----------------------------------------
@@ -448,7 +448,7 @@ module SharedPaths
step "I visit empty project page" do
project = Project.find_by(name: "Empty Public Project")
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step "I should not see command line instructions" do
@@ -480,17 +480,13 @@ module SharedPaths
# ----------------------------------------
step 'I visit project "Shop" snippets page' do
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
end
step 'I visit snippets page' do
visit explore_snippets_path
end
- step 'I visit new snippet page' do
- visit new_snippet_path
- end
-
def root_ref
@project.repository.root_ref
end
@@ -501,7 +497,7 @@ module SharedPaths
def merge_request_path(title)
mr = MergeRequest.find_by(title: title)
- namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
+ project_merge_request_path(mr.target_project, mr)
end
# ----------------------------------------
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index c4f1c57836f..da1cdd9f897 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -61,12 +61,12 @@ module SharedProject
step 'I visit my empty project page' do
project = Project.find_by(name: 'Empty Project')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'I visit project "Shop" activity page' do
project = Project.find_by(name: 'Shop')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
step 'project "Shop" has push event' do
@@ -101,7 +101,7 @@ module SharedProject
end
step 'I should see project settings' do
- expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
+ expect(current_path).to eq edit_project_path(@project)
expect(page).to have_content("Project name")
expect(page).to have_content("Sharing & Permissions")
end
@@ -239,11 +239,6 @@ module SharedProject
create(:label, project: project, title: 'enhancement')
end
- step 'project "Shop" has issue: "bug report"' do
- project = Project.find_by(name: "Shop")
- create(:issue, project: project, title: "bug report")
- end
-
step 'project "Shop" has CI enabled' do
project = Project.find_by(name: "Shop")
project.enable_ci
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 0cb9229dbae..901f7f76ee9 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -32,6 +32,10 @@ module SharedProjectTab
ensure_active_main_tab('Wiki')
end
+ step 'the active main tab should be Members' do
+ ensure_active_main_tab('Members')
+ end
+
step 'the active main tab should be Settings' do
ensure_active_main_tab('Settings')
end
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
deleted file mode 100644
index bb596c1620a..00000000000
--- a/features/steps/shared/snippet.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-module SharedSnippet
- include Spinach::DSL
-
- step 'I have public "Personal snippet one" snippet' do
- create(:personal_snippet,
- title: "Personal snippet one",
- content: "Test content",
- file_name: "snippet.rb",
- visibility_level: Snippet::PUBLIC,
- author: current_user)
- end
-
- step 'I have private "Personal snippet private" snippet' do
- create(:personal_snippet,
- title: "Personal snippet private",
- content: "Provate content",
- file_name: "private_snippet.rb",
- visibility_level: Snippet::PRIVATE,
- author: current_user)
- end
-
- step 'I have internal "Personal snippet internal" snippet' do
- create(:personal_snippet,
- title: "Personal snippet internal",
- content: "Provate content",
- file_name: "internal_snippet.rb",
- visibility_level: Snippet::INTERNAL,
- author: current_user)
- end
-
- step 'I have a public many lined snippet' do
- create(:personal_snippet,
- title: 'Many lined snippet',
- content: <<-END.gsub(/^\s+\|/, ''),
- |line one
- |line two
- |line three
- |line four
- |line five
- |line six
- |line seven
- |line eight
- |line nine
- |line ten
- |line eleven
- |line twelve
- |line thirteen
- |line fourteen
- END
- file_name: 'many_lined_snippet.rb',
- visibility_level: Snippet::PUBLIC,
- author: current_user)
- end
-
- step 'There is public "Personal snippet one" snippet' do
- create(:personal_snippet,
- title: "Personal snippet one",
- content: "Test content",
- file_name: "snippet.rb",
- visibility_level: Snippet::PUBLIC,
- author: create(:user))
- end
-end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
deleted file mode 100644
index a4fc77746ee..00000000000
--- a/features/steps/snippets/snippets.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-class Spinach::Features::Snippets < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedSnippet
- include WaitForRequests
-
- step 'I click link "Personal snippet one"' do
- click_link "Personal snippet one"
- end
-
- step 'I should not see "Personal snippet one" in snippets' do
- expect(page).not_to have_content "Personal snippet one"
- end
-
- step 'I click link "Edit"' do
- page.within ".detail-page-header" do
- first(:link, "Edit").click
- end
- end
-
- step 'I click link "Delete"' do
- first(:link, "Delete").click
- end
-
- step 'I submit new snippet "Personal snippet three"' do
- fill_in "personal_snippet_title", with: "Personal snippet three"
- fill_in "personal_snippet_file_name", with: "my_snippet.rb"
- page.within('.file-editor') do
- find('.ace_editor').native.send_keys 'Content of snippet three'
- end
- click_button "Create snippet"
- wait_for_requests
- end
-
- step 'I submit new internal snippet' do
- fill_in "personal_snippet_title", with: "Internal personal snippet one"
- fill_in "personal_snippet_file_name", with: "my_snippet.rb"
- choose 'personal_snippet_visibility_level_10'
-
- page.within('.file-editor') do
- find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
- end
-
- click_button "Create snippet"
- end
-
- step 'I should see snippet "Personal snippet three"' do
- expect(page).to have_content "Personal snippet three"
- expect(page).to have_content "Content of snippet three"
- end
-
- step 'I submit new title "Personal snippet new title"' do
- fill_in "personal_snippet_title", with: "Personal snippet new title"
- click_button "Save"
- end
-
- step 'I should see "Personal snippet new title"' do
- expect(page).to have_content "Personal snippet new title"
- end
-
- step 'I uncheck "Private" checkbox' do
- choose "Internal"
- click_button "Save"
- end
-
- step 'I should see "Personal snippet one" public' do
- expect(page).to have_no_xpath("//i[@class='public-snippet']")
- end
-
- step 'I visit snippet page "Personal snippet one"' do
- visit snippet_path(snippet)
- end
-
- step 'I visit snippet page "Internal personal snippet one"' do
- visit snippet_path(internal_snippet)
- end
-
- def snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
- end
-
- def internal_snippet
- @snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
- end
-end
diff --git a/features/support/env.rb b/features/support/env.rb
index 1690465d9b2..608d988755c 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -28,6 +28,7 @@ Spinach.hooks.before_run do
TestEnv.disable_pre_receive
include FactoryGirl::Syntax::Methods
+ include GitlabRoutingHelper
end
Spinach.hooks.after_scenario do |scenario_data, step_definitions|
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index c9b5f58c557..cdacf9839e5 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -68,6 +68,7 @@ module API
delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id])
+ status 204
::Members::DestroyService.new(source, current_user, params)
.execute(:requesters)
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d767af36e8e..045a0db1842 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -2,6 +2,9 @@ module API
class API < Grape::API
include APIGuard
+ allow_access_with_scope :api
+ prefix :api
+
version %w(v3 v4), using: :path
version 'v3', using: :path do
@@ -44,7 +47,6 @@ module API
mount ::API::V3::Variables
end
- before { allow_access_with_scope :api }
before { header['X-Frame-Options'] = 'SAMEORIGIN' }
before { Gitlab::I18n.locale = current_user&.preferred_language }
@@ -84,6 +86,9 @@ module API
helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers
+ NO_SLASH_URL_PART_REGEX = %r{[^/]+}
+ PROJECT_ENDPOINT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
+
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::AwardEmoji
@@ -108,7 +113,8 @@ module API
mount ::API::Members
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
- mount ::API::Milestones
+ mount ::API::ProjectMilestones
+ mount ::API::GroupMilestones
mount ::API::Namespaces
mount ::API::Notes
mount ::API::NotificationSettings
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 9fcf04efa38..0d2d71e336a 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -23,6 +23,23 @@ module API
install_error_responders(base)
end
+ class_methods do
+ # Set the authorization scope(s) allowed for an API endpoint.
+ #
+ # A call to this method maps the given scope(s) to the current API
+ # endpoint class. If this method is called multiple times on the same class,
+ # the scopes are all aggregated.
+ def allow_access_with_scope(scopes, options = {})
+ Array(scopes).each do |scope|
+ allowed_scopes << Scope.new(scope, options)
+ end
+ end
+
+ def allowed_scopes
+ @scopes ||= []
+ end
+ end
+
# Helper Methods for Grape Endpoint
module HelperMethods
# Invokes the doorkeeper guard.
@@ -47,7 +64,7 @@ module API
access_token = find_access_token
return nil unless access_token
- case AccessTokenValidationService.new(access_token).validate(scopes: scopes)
+ case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
when AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
@@ -74,18 +91,6 @@ module API
@current_user
end
- # Set the authorization scope(s) allowed for the current request.
- #
- # Note: A call to this method adds to any previous scopes in place. This is done because
- # `Grape` callbacks run from the outside-in: the top-level callback (API::API) runs first, then
- # the next-level callback (API::API::Users, for example) runs. All these scopes are valid for the
- # given endpoint (GET `/api/users` is accessible by the `api` and `read_user` scopes), and so they
- # need to be stored.
- def allow_access_with_scope(*scopes)
- @scopes ||= []
- @scopes.concat(scopes.map(&:to_s))
- end
-
private
def find_user_by_authentication_token(token_string)
@@ -96,7 +101,7 @@ module API
access_token = PersonalAccessToken.active.find_by_token(token_string)
return unless access_token
- if AccessTokenValidationService.new(access_token).include_any_scope?(scopes)
+ if AccessTokenValidationService.new(access_token, request: request).include_any_scope?(scopes)
User.find(access_token.user_id)
end
end
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 56f19f89642..5a028fc9d0b 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -88,6 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
+ status 204
award.destroy
end
end
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 3d816f8771d..9dd60d1833b 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -4,19 +4,21 @@ module API
class Branches < Grape::API
include PaginationParams
+ BRANCH_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX)
+
before { authorize! :download_code, user_project }
params do
requires :id, type: String, desc: 'The ID of a project'
end
- resource :projects, requirements: { id: %r{[^/]+} } do
+ resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a project repository branches' do
success Entities::RepoBranch
end
params do
use :pagination
end
- get ":id/repository/branches" do
+ get ':id/repository/branches' do
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: Entities::RepoBranch, project: user_project
@@ -28,7 +30,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do
+ get ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
branch = user_project.repository.find_branch(params[:branch])
not_found!("Branch") unless branch
@@ -46,7 +48,7 @@ module API
optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch'
optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch'
end
- put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do
+ put ':id/repository/branches/:branch/protect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project
branch = user_project.repository.find_branch(params[:branch])
@@ -81,7 +83,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do
+ put ':id/repository/branches/:branch/unprotect', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_admin_project
branch = user_project.repository.find_branch(params[:branch])
@@ -99,7 +101,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch'
requires :ref, type: String, desc: 'Create branch from commit sha or existing branch'
end
- post ":id/repository/branches" do
+ post ':id/repository/branches' do
authorize_push_project
result = CreateBranchService.new(user_project, current_user)
@@ -118,7 +120,7 @@ module API
params do
requires :branch, type: String, desc: 'The name of the branch'
end
- delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do
+ delete ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
authorize_push_project
result = DeleteBranchService.new(user_project, current_user)
@@ -130,7 +132,7 @@ module API
end
desc 'Delete all merged branches'
- delete ":id/repository/merged_branches" do
+ delete ':id/repository/merged_branches' do
DeleteMergedBranchesService.new(user_project, current_user).async_execute
accepted!
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index 395c401203c..9980aec4752 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -91,6 +91,7 @@ module API
delete ':id' do
message = find_message
+ status 204
message.destroy
end
end
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index d5c2f3d5094..42e7c1486b0 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -125,6 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key
+ status 204
key.destroy
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index cef5a0abe12..5cdc441e8cb 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -82,6 +82,38 @@ module API
end
class Project < Grape::Entity
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ expose :_links do
+ expose :self do |project|
+ expose_url(api_v4_projects_path(id: project.id))
+ end
+
+ expose :issues, if: -> (*args) { issues_available?(*args) } do |project|
+ expose_url(api_v4_projects_issues_path(id: project.id))
+ end
+
+ expose :merge_requests, if: -> (*args) { mrs_available?(*args) } do |project|
+ expose_url(api_v4_projects_merge_requests_path(id: project.id))
+ end
+
+ expose :repo_branches do |project|
+ expose_url(api_v4_projects_repository_branches_path(id: project.id))
+ end
+
+ expose :labels do |project|
+ expose_url(api_v4_projects_labels_path(id: project.id))
+ end
+
+ expose :events do |project|
+ expose_url(api_v4_projects_events_path(id: project.id))
+ end
+
+ expose :members do |project|
+ expose_url(api_v4_projects_members_path(id: project.id))
+ end
+ end
+
expose :id, :description, :default_branch, :tag_list
expose :archived?, as: :archived
expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url
@@ -109,9 +141,10 @@ module API
user.avatar_url(only_path: false)
end
expose :star_count, :forks_count
- expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
+ expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
+ expose :ci_config_path
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
@@ -254,7 +287,7 @@ module API
class ProjectEntity < Grape::Entity
expose :id, :iid
- expose(:project_id) { |entity| entity.project.id }
+ expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
expose :state, :created_at, :updated_at
end
@@ -266,7 +299,12 @@ module API
expose :deleted_file?, as: :deleted_file
end
- class Milestone < ProjectEntity
+ class Milestone < Grape::Entity
+ expose :id, :iid
+ expose :project_id, if: -> (entity, options) { entity&.project_id }
+ expose :group_id, if: -> (entity, options) { entity&.group_id }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
expose :due_date
expose :start_date
end
@@ -291,6 +329,26 @@ module API
end
class Issue < IssueBasic
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ expose :_links do
+ expose :self do |issue|
+ expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid))
+ end
+
+ expose :notes do |issue|
+ expose_url(api_v4_projects_issues_notes_path(id: issue.project_id, noteable_id: issue.iid))
+ end
+
+ expose :award_emoji do |issue|
+ expose_url(api_v4_projects_issues_award_emoji_path(id: issue.project_id, issue_iid: issue.iid))
+ end
+
+ expose :project do |issue|
+ expose_url(api_v4_projects_path(id: issue.project_id))
+ end
+ end
+
expose :subscribed do |issue, options|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
end
@@ -308,12 +366,35 @@ module API
expose :id
end
+ class MergeRequestSimple < ProjectEntity
+ expose :title
+ expose :web_url do |merge_request, options|
+ Gitlab::UrlBuilder.build(merge_request)
+ end
+ end
+
class MergeRequestBasic < ProjectEntity
expose :target_branch, :source_branch
- expose :upvotes, :downvotes
+ expose :upvotes do |merge_request, options|
+ if options[:issuable_metadata]
+ options[:issuable_metadata][merge_request.id].upvotes
+ else
+ merge_request.upvotes
+ end
+ end
+ expose :downvotes do |merge_request, options|
+ if options[:issuable_metadata]
+ options[:issuable_metadata][merge_request.id].downvotes
+ else
+ merge_request.downvotes
+ end
+ end
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
- expose :label_names, as: :labels
+ expose :labels do |merge_request, options|
+ # Avoids an N+1 query since labels are preloaded
+ merge_request.labels.map(&:title).sort
+ end
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
@@ -434,7 +515,7 @@ module API
target_url = "namespace_project_#{target_type}_url"
target_anchor = "note_#{todo.note_id}" if todo.note_id?
- Gitlab::Application.routes.url_helpers.public_send(target_url,
+ Gitlab::Routing.url_helpers.public_send(target_url,
todo.project.namespace, todo.project, todo.target, anchor: target_anchor)
end
@@ -502,12 +583,20 @@ module API
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
- project.project_members.find_by(user_id: options[:current_user].id)
+ if options.key?(:project_members)
+ (options[:project_members] || []).find { |member| member.source_id == project.id }
+ else
+ project.project_members.find_by(user_id: options[:current_user].id)
+ end
end
expose :group_access, using: Entities::GroupAccess do |project, options|
if project.group
- project.group.group_members.find_by(user_id: options[:current_user].id)
+ if options.key?(:group_members)
+ (options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
+ else
+ project.group.group_members.find_by(user_id: options[:current_user].id)
+ end
end
end
end
@@ -584,7 +673,8 @@ module API
expose :id
expose :default_projects_limit
expose :signup_enabled
- expose :signin_enabled
+ expose :password_authentication_enabled
+ expose :password_authentication_enabled, as: :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
expose :after_sign_up_text
@@ -831,7 +921,7 @@ module API
end
class Cache < Grape::Entity
- expose :key, :untracked, :paths
+ expose :key, :untracked, :paths, :policy
end
class Credentials < Grape::Entity
@@ -874,5 +964,11 @@ module API
expose :dependencies, using: Dependency
end
end
+
+ class UserAgentDetail < Grape::Entity
+ expose :user_agent
+ expose :ip_address
+ expose :submitted, as: :akismet_submitted
+ end
end
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 945771d46f3..c774a5c6685 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -79,6 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
+ status 204
environment.destroy
end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 21745916463..9385c6ca174 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -14,14 +14,12 @@ module API
end
end
- def gate_target(params)
- if params[:feature_group]
- Feature.group(params[:feature_group])
- elsif params[:user]
- User.find_by_username(params[:user])
- else
- gate_value(params)
- end
+ def gate_targets(params)
+ targets = []
+ targets << Feature.group(params[:feature_group]) if params[:feature_group]
+ targets << User.find_by_username(params[:user]) if params[:user]
+
+ targets
end
end
@@ -42,18 +40,25 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
- mutually_exclusive :feature_group, :user
end
post ':name' do
feature = Feature.get(params[:name])
- target = gate_target(params)
+ targets = gate_targets(params)
value = gate_value(params)
case value
when true
- feature.enable(target)
+ if targets.present?
+ targets.each { |target| feature.enable(target) }
+ else
+ feature.enable
+ end
when false
- feature.disable(target)
+ if targets.present?
+ targets.each { |target| feature.disable(target) }
+ else
+ feature.disable
+ end
else
feature.enable_percentage_of_time(value)
end
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
new file mode 100644
index 00000000000..b85eb59dc0a
--- /dev/null
+++ b/lib/api/group_milestones.rb
@@ -0,0 +1,85 @@
+module API
+ class GroupMilestones < Grape::API
+ include MilestoneResponses
+ include PaginationParams
+
+ before do
+ authenticate!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: { id: %r{[^/]+} } do
+ desc 'Get a list of group milestones' do
+ success Entities::Milestone
+ end
+ params do
+ use :list_params
+ end
+ get ":id/milestones" do
+ list_milestones_for(user_group)
+ end
+
+ desc 'Get a single group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ end
+ get ":id/milestones/:milestone_id" do
+ authorize! :read_group, user_group
+
+ get_milestone_for(user_group)
+ end
+
+ desc 'Create a new group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :title, type: String, desc: 'The title of the milestone'
+ use :optional_params
+ end
+ post ":id/milestones" do
+ authorize! :admin_milestones, user_group
+
+ create_milestone_for(user_group)
+ end
+
+ desc 'Update an existing group milestone' do
+ success Entities::Milestone
+ end
+ params do
+ use :update_params
+ end
+ put ":id/milestones/:milestone_id" do
+ authorize! :admin_milestones, user_group
+
+ update_milestone_for(user_group)
+ end
+
+ desc 'Get all issues for a single group milestone' do
+ success Entities::IssueBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ use :pagination
+ end
+ get ":id/milestones/:milestone_id/issues" do
+ milestone_issuables_for(user_group, :issue)
+ end
+
+ desc 'Get all merge requests for a single group milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequestBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a group milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ milestone_issuables_for(user_group, :merge_request)
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ebbaed0cbb7..49c3b2278c7 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -125,6 +125,8 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
+
+ status 204
::Groups::DestroyService.new(group, current_user).execute
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 2c73a6fdc4e..234825480f2 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -25,6 +25,10 @@ module API
initial_current_user != current_user
end
+ def user_group
+ @group ||= find_group!(params[:id])
+ end
+
def user_project
@project ||= find_project!(params[:id])
end
@@ -268,6 +272,7 @@ module API
finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
finder_params[:archived] = params[:archived]
finder_params[:search] = params[:search] if params[:search]
+ finder_params[:user] = params.delete(:user) if params[:user]
finder_params
end
@@ -313,7 +318,7 @@ module API
def present_artifacts!(artifacts_file)
return not_found! unless artifacts_file.exists?
-
+
if artifacts_file.file_storage?
present_file!(artifacts_file.path, artifacts_file.filename)
else
@@ -331,19 +336,21 @@ module API
env['warden']
end
+ # Check if the request is GET/HEAD, or if CSRF token is valid.
+ def verified_request?
+ Gitlab::RequestForgeryProtection.verified?(env)
+ end
+
# Check the Rails session for valid authentication details
- #
- # Until CSRF protection is added to the API, disallow this method for
- # state-changing endpoints
def find_user_from_warden
- warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD'])
+ warden.try(:authenticate) if verified_request?
end
def initial_current_user
return @initial_current_user if defined?(@initial_current_user)
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
- @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
- @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
+ @initial_current_user ||= find_user_by_private_token(scopes: scopes_registered_for_endpoint)
+ @initial_current_user ||= doorkeeper_guard(scopes: scopes_registered_for_endpoint)
@initial_current_user ||= find_user_from_warden
unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
@@ -407,5 +414,22 @@ module API
exception.status == 500
end
+
+ # An array of scopes that were registered (using `allow_access_with_scope`)
+ # for the current endpoint class. It also returns scopes registered on
+ # `API::API`, since these are meant to apply to all API routes.
+ def scopes_registered_for_endpoint
+ @scopes_registered_for_endpoint ||=
+ begin
+ endpoint_classes = [options[:for].presence, ::API::API].compact
+ endpoint_classes.reduce([]) do |memo, endpoint|
+ if endpoint.respond_to?(:allowed_scopes)
+ memo.concat(endpoint.allowed_scopes)
+ else
+ memo
+ end
+ end
+ end
+ end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 5e9cf5e68b1..ecb79317093 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -1,6 +1,11 @@
module API
module Helpers
module InternalHelpers
+ SSH_GITALY_FEATURES = {
+ 'git-receive-pack' => :ssh_receive_pack,
+ 'git-upload-pack' => :ssh_upload_pack
+ }.freeze
+
def wiki?
set_project unless defined?(@wiki)
@wiki
@@ -10,7 +15,7 @@ module API
set_project unless defined?(@project)
@project
end
-
+
def redirected_path
@redirected_path
end
@@ -54,15 +59,33 @@ module API
Gitlab::GlRepository.gl_repository(project, wiki?)
end
- # Return the repository full path so that gitlab-shell has it when
- # handling ssh commands
- def repository_path
+ # Return the repository depending on whether we want the wiki or the
+ # regular repository
+ def repository
if wiki?
- project.wiki.repository.path_to_repo
+ project.wiki.repository
else
- project.repository.path_to_repo
+ project.repository
end
end
+
+ # Return the repository full path so that gitlab-shell has it when
+ # handling ssh commands
+ def repository_path
+ repository.path_to_repo
+ end
+
+ # Return the Gitaly Address if it is enabled
+ def gitaly_payload(action)
+ feature = SSH_GITALY_FEATURES[action]
+ return unless feature && Gitlab::GitalyClient.feature_enabled?(feature)
+
+ {
+ repository: repository.gitaly_repository,
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ }
+ end
end
end
end
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
new file mode 100644
index 00000000000..769cc1457fc
--- /dev/null
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -0,0 +1,28 @@
+module API
+ module Helpers
+ module RelatedResourcesHelpers
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ def issues_available?(project, options)
+ available?(:issues, project, options[:current_user])
+ end
+
+ def mrs_available?(project, options)
+ available?(:merge_requests, project, options[:current_user])
+ end
+
+ def expose_url(path)
+ url_options = Rails.application.routes.default_url_options
+ protocol, host, port = url_options.slice(:protocol, :host, :port).values
+
+ URI::HTTP.build(scheme: protocol, host: host, port: port, path: path).to_s
+ end
+
+ private
+
+ def available?(feature, project, current_user)
+ project.feature_available?(feature, current_user)
+ end
+ end
+ end
+end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index f1c79970ba4..8b007869dc3 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -47,7 +47,8 @@ module API
{
status: true,
gl_repository: gl_repository,
- repository_path: repository_path
+ repository_path: repository_path,
+ gitaly: gitaly_payload(params[:action])
}
end
@@ -100,7 +101,7 @@ module API
end
get "/broadcast_message" do
- if message = BroadcastMessage.current.last
+ if message = BroadcastMessage.current&.last
present message, with: Entities::BroadcastMessage
else
{}
@@ -149,7 +150,7 @@ module API
#
# begin
# repository = wiki? ? project.wiki.repository : project.repository
- # Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive
+ # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive
# rescue GRPC::Unavailable => e
# render_api_error!(e, 500)
# end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 09dca0dff8b..009c6d6bcd4 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -29,6 +29,10 @@ module API
optional :search, type: String, desc: 'Search issues for text present in the title or description'
optional :created_after, type: DateTime, desc: 'Return issues created after the specified time'
optional :created_before, type: DateTime, desc: 'Return issues created before the specified time'
+ optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID'
+ optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID'
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all],
+ desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
use :pagination
end
@@ -55,9 +59,11 @@ module API
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :issues_params
+ optional :scope, type: String, values: %w[created-by-me assigned-to-me all], default: 'created-by-me',
+ desc: 'Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`'
end
get do
- issues = find_issues(scope: 'authored')
+ issues = find_issues
present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
@@ -112,7 +118,7 @@ module API
params do
requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- get ":id/issues/:issue_iid" do
+ get ":id/issues/:issue_iid", as: :api_v4_project_issue do
issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
@@ -224,6 +230,7 @@ module API
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
+ status 204
issue.destroy
end
@@ -241,6 +248,22 @@ module API
present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
+
+ desc 'Get the user agent details for an issue' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
+ end
+ get ":id/issues/:issue_iid/user_agent_detail" do
+ authenticated_as_admin!
+
+ issue = find_project_issue(params[:issue_iid])
+
+ return not_found!('UserAgentDetail') unless issue.user_agent_detail
+
+ present issue.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 20b25529d0c..4520c98d951 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -56,6 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
+ status 204
label.destroy
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index c200e46a328..bb970b7cd54 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -96,6 +96,7 @@ module API
# Ensure that memeber exists
source.members.find_by!(user_id: params[:user_id])
+ status 204
::Members::DestroyService.new(source, current_user, declared_params).execute
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 1118fc7465b..f64ac659413 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -10,6 +10,8 @@ module API
resource :projects, requirements: { id: %r{[^/]+} } do
include TimeTrackingEndpoints
+ helpers ::Gitlab::IssuableMetadata
+
helpers do
def handle_merge_request_errors!(errors)
if errors[:project_access].any?
@@ -27,23 +29,21 @@ module API
render_api_error!(errors, 400)
end
- def issue_entity(project)
- if project.has_external_issue_tracker?
- Entities::ExternalIssue
- else
- Entities::IssueBasic
- end
- end
-
def find_merge_requests(args = {})
args = params.merge(args)
args[:milestone_title] = args.delete(:milestone)
args[:label_name] = args.delete(:labels)
- merge_requests = MergeRequestsFinder.new(current_user, args).execute.inc_notes_with_associations
+ merge_requests = MergeRequestsFinder.new(current_user, args).execute
+ .reorder(args[:order_by] => args[:sort])
+ merge_requests = paginate(merge_requests)
+ .preload(:target_project)
+
+ return merge_requests if args[:view] == 'simple'
- merge_requests.reorder(args[:order_by] => args[:sort])
+ merge_requests
+ .preload(:notes, :author, :assignee, :milestone, :merge_request_diff, :labels)
end
params :optional_params_ce do
@@ -74,6 +74,7 @@ module API
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
+ optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request'
use :pagination
end
get ":id/merge_requests" do
@@ -81,7 +82,17 @@ module API
merge_requests = find_merge_requests(project_id: user_project.id)
- present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
+ options = { with: Entities::MergeRequestBasic,
+ current_user: current_user,
+ project: user_project }
+
+ if params[:view] == 'simple'
+ options[:with] = Entities::MergeRequestSimple
+ else
+ options[:issuable_metadata] = issuable_meta_data(merge_requests, 'MergeRequest')
+ end
+
+ present merge_requests, options
end
desc 'Create a merge request' do
@@ -118,6 +129,7 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
+ status 204
merge_request.destroy
end
@@ -258,7 +270,14 @@ module API
get ':id/merge_requests/:merge_request_iid/closes_issues' do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
- present paginate(issues), with: issue_entity(user_project), current_user: current_user
+ issues = paginate(issues)
+
+ external_issues, internal_issues = issues.partition { |issue| issue.is_a?(ExternalIssue) }
+
+ data = Entities::IssueBasic.represent(internal_issues, current_user: current_user)
+ data += Entities::ExternalIssue.represent(external_issues, current_user: current_user)
+
+ data.as_json
end
end
end
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
new file mode 100644
index 00000000000..ef09d9505d2
--- /dev/null
+++ b/lib/api/milestone_responses.rb
@@ -0,0 +1,98 @@
+module API
+ module MilestoneResponses
+ extend ActiveSupport::Concern
+
+ included do
+ helpers do
+ params :optional_params do
+ optional :description, type: String, desc: 'The description of the milestone'
+ optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
+ optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
+ end
+
+ params :list_params do
+ optional :state, type: String, values: %w[active closed all], default: 'all',
+ desc: 'Return "active", "closed", or "all" milestones'
+ optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
+ optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
+ use :pagination
+ end
+
+ params :update_params do
+ requires :milestone_id, type: Integer, desc: 'The milestone ID number'
+ optional :title, type: String, desc: 'The title of the milestone'
+ optional :state_event, type: String, values: %w[close activate],
+ desc: 'The state event of the milestone '
+ use :optional_params
+ at_least_one_of :title, :description, :due_date, :state_event
+ end
+
+ def list_milestones_for(parent)
+ milestones = parent.milestones
+ milestones = Milestone.filter_by_state(milestones, params[:state])
+ milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
+ milestones = filter_by_search(milestones, params[:search]) if params[:search]
+
+ present paginate(milestones), with: Entities::Milestone
+ end
+
+ def get_milestone_for(parent)
+ milestone = parent.milestones.find(params[:milestone_id])
+ present milestone, with: Entities::Milestone
+ end
+
+ def create_milestone_for(parent)
+ milestone = ::Milestones::CreateService.new(parent, current_user, declared_params).execute
+
+ if milestone.valid?
+ present milestone, with: Entities::Milestone
+ else
+ render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
+ end
+ end
+
+ def update_milestone_for(parent)
+ milestone = parent.milestones.find(params.delete(:milestone_id))
+
+ milestone_params = declared_params(include_missing: false)
+ milestone = ::Milestones::UpdateService.new(parent, current_user, milestone_params).execute(milestone)
+
+ if milestone.valid?
+ present milestone, with: Entities::Milestone
+ else
+ render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
+ end
+ end
+
+ def milestone_issuables_for(parent, type)
+ milestone = parent.milestones.find(params[:milestone_id])
+
+ finder_klass, entity = get_finder_and_entity(type)
+
+ params = build_finder_params(milestone, parent)
+
+ issuables = finder_klass.new(current_user, params).execute
+ present paginate(issuables), with: entity, current_user: current_user
+ end
+
+ def build_finder_params(milestone, parent)
+ finder_params = { milestone_title: milestone.title, sort: 'label_priority' }
+
+ if parent.is_a?(Group)
+ finder_params.merge(group_id: parent.id)
+ else
+ finder_params.merge(project_id: parent.id)
+ end
+ end
+
+ def get_finder_and_entity(type)
+ if type == :issue
+ [IssuesFinder, Entities::IssueBasic]
+ else
+ [MergeRequestsFinder, Entities::MergeRequestBasic]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
deleted file mode 100644
index 3541d3c95fb..00000000000
--- a/lib/api/milestones.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-module API
- class Milestones < Grape::API
- include PaginationParams
-
- before { authenticate! }
-
- helpers do
- def filter_milestones_state(milestones, state)
- case state
- when 'active' then milestones.active
- when 'closed' then milestones.closed
- else milestones
- end
- end
-
- params :optional_params do
- optional :description, type: String, desc: 'The description of the milestone'
- optional :due_date, type: String, desc: 'The due date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
- optional :start_date, type: String, desc: 'The start date of the milestone. The ISO 8601 date format (%Y-%m-%d)'
- end
- end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: { id: %r{[^/]+} } do
- desc 'Get a list of project milestones' do
- success Entities::Milestone
- end
- params do
- optional :state, type: String, values: %w[active closed all], default: 'all',
- desc: 'Return "active", "closed", or "all" milestones'
- optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
- optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
- use :pagination
- end
- get ":id/milestones" do
- authorize! :read_milestone, user_project
-
- milestones = user_project.milestones
- milestones = filter_milestones_state(milestones, params[:state])
- milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
- milestones = filter_by_search(milestones, params[:search]) if params[:search]
-
- present paginate(milestones), with: Entities::Milestone
- end
-
- desc 'Get a single project milestone' do
- success Entities::Milestone
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- end
- get ":id/milestones/:milestone_id" do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
- present milestone, with: Entities::Milestone
- end
-
- desc 'Create a new project milestone' do
- success Entities::Milestone
- end
- params do
- requires :title, type: String, desc: 'The title of the milestone'
- use :optional_params
- end
- post ":id/milestones" do
- authorize! :admin_milestone, user_project
-
- milestone = ::Milestones::CreateService.new(user_project, current_user, declared_params).execute
-
- if milestone.valid?
- present milestone, with: Entities::Milestone
- else
- render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400)
- end
- end
-
- desc 'Update an existing project milestone' do
- success Entities::Milestone
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- optional :title, type: String, desc: 'The title of the milestone'
- optional :state_event, type: String, values: %w[close activate],
- desc: 'The state event of the milestone '
- use :optional_params
- at_least_one_of :title, :description, :due_date, :state_event
- end
- put ":id/milestones/:milestone_id" do
- authorize! :admin_milestone, user_project
- milestone = user_project.milestones.find(params.delete(:milestone_id))
-
- milestone_params = declared_params(include_missing: false)
- milestone = ::Milestones::UpdateService.new(user_project, current_user, milestone_params).execute(milestone)
-
- if milestone.valid?
- present milestone, with: Entities::Milestone
- else
- render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400)
- end
- end
-
- desc 'Get all issues for a single project milestone' do
- success Entities::IssueBasic
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- use :pagination
- end
- get ":id/milestones/:milestone_id/issues" do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
-
- finder_params = {
- project_id: user_project.id,
- milestone_title: milestone.title,
- sort: 'label_priority'
- }
-
- issues = IssuesFinder.new(current_user, finder_params).execute
- present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
- end
-
- desc 'Get all merge requests for a single project milestone' do
- detail 'This feature was introduced in GitLab 9.'
- success Entities::MergeRequestBasic
- end
- params do
- requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
- use :pagination
- end
- get ':id/milestones/:milestone_id/merge_requests' do
- authorize! :read_milestone, user_project
-
- milestone = user_project.milestones.find(params[:milestone_id])
-
- finder_params = {
- project_id: user_project.id,
- milestone_title: milestone.title,
- sort: 'label_priority'
- }
-
- merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
- present paginate(merge_requests),
- with: Entities::MergeRequestBasic,
- current_user: current_user,
- project: user_project
- end
- end
- end
-end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 01ca62b593f..65ff89edf65 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -131,6 +131,7 @@ module API
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
+ status 204
::Notes::DestroyService.new(user_project, current_user).execute(note)
end
end
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index 93d89209934..dbeaf9e17ef 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -74,9 +74,10 @@ module API
optional :active, type: Boolean, desc: 'The activation of pipeline schedule'
end
put ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :update_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.update(declared_params(include_missing: false))
present pipeline_schedule, with: Entities::PipelineScheduleDetails
@@ -92,9 +93,10 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
- authorize! :update_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :update_pipeline_schedule, pipeline_schedule
if pipeline_schedule.own!(current_user)
present pipeline_schedule, with: Entities::PipelineScheduleDetails
@@ -110,9 +112,10 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
delete ':id/pipeline_schedules/:pipeline_schedule_id' do
- authorize! :admin_pipeline_schedule, user_project
+ authorize! :read_pipeline_schedule, user_project
not_found!('PipelineSchedule') unless pipeline_schedule
+ authorize! :admin_pipeline_schedule, pipeline_schedule
status :accepted
present pipeline_schedule.destroy, with: Entities::PipelineScheduleDetails
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 7a345289617..649dd891f56 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -96,6 +96,7 @@ module API
delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
+ status 204
hook.destroy
end
end
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
new file mode 100644
index 00000000000..451998c726a
--- /dev/null
+++ b/lib/api/project_milestones.rb
@@ -0,0 +1,91 @@
+module API
+ class ProjectMilestones < Grape::API
+ include PaginationParams
+ include MilestoneResponses
+
+ before do
+ authenticate!
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: { id: %r{[^/]+} } do
+ desc 'Get a list of project milestones' do
+ success Entities::Milestone
+ end
+ params do
+ use :list_params
+ end
+ get ":id/milestones" do
+ authorize! :read_milestone, user_project
+
+ list_milestones_for(user_project)
+ end
+
+ desc 'Get a single project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ end
+ get ":id/milestones/:milestone_id" do
+ authorize! :read_milestone, user_project
+
+ get_milestone_for(user_project)
+ end
+
+ desc 'Create a new project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ requires :title, type: String, desc: 'The title of the milestone'
+ use :optional_params
+ end
+ post ":id/milestones" do
+ authorize! :admin_milestone, user_project
+
+ create_milestone_for(user_project)
+ end
+
+ desc 'Update an existing project milestone' do
+ success Entities::Milestone
+ end
+ params do
+ use :update_params
+ end
+ put ":id/milestones/:milestone_id" do
+ authorize! :admin_milestone, user_project
+
+ update_milestone_for(user_project)
+ end
+
+ desc 'Get all issues for a single project milestone' do
+ success Entities::IssueBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ":id/milestones/:milestone_id/issues" do
+ authorize! :read_milestone, user_project
+
+ milestone_issuables_for(user_project, :issue)
+ end
+
+ desc 'Get all merge requests for a single project milestone' do
+ detail 'This feature was introduced in GitLab 9.'
+ success Entities::MergeRequestBasic
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/merge_requests' do
+ authorize! :read_milestone, user_project
+
+ milestone_issuables_for(user_project, :merge_request)
+ end
+ end
+ end
+end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 64efe82a937..f3d905b0068 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -116,6 +116,7 @@ module API
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
+ status 204
snippet.destroy
end
@@ -131,6 +132,22 @@ module API
content_type 'text/plain'
present snippet.content
end
+
+ desc 'Get the user agent details for a project snippet' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
+ end
+ get ":id/snippets/:snippet_id/user_agent_detail" do
+ authenticated_as_admin!
+
+ snippet = Snippet.find_by!(id: params[:id])
+
+ return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+
+ present snippet.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index d0bd64b2972..89dda88d3f5 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -10,6 +10,7 @@ module API
helpers do
params :optional_params_ce do
optional :description, type: String, desc: 'The description of the project'
+ optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
@@ -35,61 +36,86 @@ module API
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
- end
- resource :projects do
- helpers do
- params :collection_params do
- use :sort_params
- use :filter_params
- use :pagination
-
- optional :simple, type: Boolean, default: false,
- desc: 'Return only the ID, URL, name, and path of each project'
- end
+ params :collection_params do
+ use :sort_params
+ use :filter_params
+ use :pagination
- params :sort_params do
- optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
- default: 'created_at', desc: 'Return projects ordered by field'
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return projects sorted in ascending and descending order'
- end
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+ end
- params :filter_params do
- optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
- optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
- desc: 'Limit by visibility'
- optional :search, type: String, desc: 'Return list of projects matching the search criteria'
- optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
- optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
- optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
- optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
- optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
- end
+ params :sort_params do
+ optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+ default: 'created_at', desc: 'Return projects ordered by field'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return projects sorted in ascending and descending order'
+ end
- params :create_params do
- optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
- optional :import_url, type: String, desc: 'URL from which the project is imported'
- end
+ params :filter_params do
+ optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
+ desc: 'Limit by visibility'
+ optional :search, type: String, desc: 'Return list of projects matching the search criteria'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+ optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
+ optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature'
+ optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
+ end
+
+ params :create_params do
+ optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
+ optional :import_url, type: String, desc: 'URL from which the project is imported'
+ end
- def present_projects(options = {})
- projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
- projects = reorder_projects(projects)
- projects = projects.with_statistics if params[:statistics]
- projects = projects.with_issues_enabled if params[:with_issues_enabled]
- projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
-
- options = options.reverse_merge(
- with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
- statistics: params[:statistics],
- current_user: current_user
- )
- options[:with] = Entities::BasicProjectDetails if params[:simple]
-
- present paginate(projects), options
+ def present_projects(options = {})
+ projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute
+ projects = reorder_projects(projects)
+ projects = projects.with_statistics if params[:statistics]
+ projects = projects.with_issues_enabled if params[:with_issues_enabled]
+ projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+
+ if current_user
+ projects = projects.includes(:route, :taggings, namespace: :route)
+ project_members = current_user.project_members
+ group_members = current_user.group_members
end
+
+ options = options.reverse_merge(
+ with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
+ statistics: params[:statistics],
+ project_members: project_members,
+ group_members: group_members,
+ current_user: current_user
+ )
+ options[:with] = Entities::BasicProjectDetails if params[:simple]
+
+ present paginate(projects), options
end
+ end
+ resource :users, requirements: { user_id: %r{[^/]+} } do
+ desc 'Get a user projects' do
+ success Entities::BasicProjectDetails
+ end
+ params do
+ requires :user_id, type: String, desc: 'The ID or username of the user'
+ use :collection_params
+ use :statistics_params
+ end
+ get ":user_id/projects" do
+ user = find_user(params[:user_id])
+ not_found!('User') unless user
+
+ params[:user] = user
+
+ present_projects
+ end
+ end
+
+ resource :projects do
desc 'Get a list of visible projects for authenticated user' do
success Entities::BasicProjectDetails
end
@@ -335,6 +361,7 @@ module API
authorize! :remove_fork_project, user_project
if user_project.forked?
+ status 204
user_project.forked_project_link.destroy
else
not_modified!
@@ -379,6 +406,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
+ status 204
link.destroy
end
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index 4552115b3e2..405d25ca3c1 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -45,6 +45,7 @@ module API
end
delete '/' do
authenticate_runner!
+ status 204
Ci::Runner.find_by_token(params[:token]).destroy
end
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index db6c7c59092..5bf5a18e42f 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -79,6 +79,7 @@ module API
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
+ status 204
runner.destroy!
end
end
@@ -134,6 +135,7 @@ module API
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
+ status 204
runner_project.destroy
end
end
diff --git a/lib/api/scope.rb b/lib/api/scope.rb
new file mode 100644
index 00000000000..d5165b2e482
--- /dev/null
+++ b/lib/api/scope.rb
@@ -0,0 +1,23 @@
+# Encapsulate a scope used for authorization, such as `api`, or `read_user`
+module API
+ class Scope
+ attr_reader :name, :if
+
+ def initialize(name, options = {})
+ @name = name.to_sym
+ @if = options[:if]
+ end
+
+ # Are the `scopes` passed in sufficient to adequately authorize the passed
+ # request for the scope represented by the current instance of this class?
+ def sufficient?(scopes, request)
+ scopes.include?(self.name) && verify_if_condition(request)
+ end
+
+ private
+
+ def verify_if_condition(request)
+ self.if.nil? || self.if.call(request)
+ end
+ end
+end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 7488f95a9b7..843c05ae32e 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -313,12 +313,6 @@ module API
desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
},
{
- required: true,
- name: :project_key,
- type: String,
- desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
- },
- {
required: false,
name: :username,
type: String,
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index d598f9a62a2..b19095d1252 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -65,6 +65,7 @@ module API
:shared_runners_enabled,
:sidekiq_throttling_enabled,
:sign_in_text,
+ :password_authentication_enabled,
:signin_enabled,
:signup_enabled,
:terminal_max_session_time,
@@ -95,7 +96,9 @@ module API
requires :domain_blacklist, type: String, desc: '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'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ mutually_exclusive :password_authentication_enabled, :signin_enabled
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
@@ -176,6 +179,10 @@ module API
put "application/settings" do
attrs = declared_params(include_missing: false)
+ if attrs.has_key?(:signin_enabled)
+ attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ end
+
if current_settings.update_attributes(attrs)
present current_settings, with: Entities::ApplicationSetting
else
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index c630c24c339..35ece56c65c 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -123,6 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet
+ status 204
snippet.destroy
end
@@ -140,6 +141,22 @@ module API
content_type 'text/plain'
present snippet.content
end
+
+ desc 'Get the user agent details for a snippet' do
+ success Entities::UserAgentDetail
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ end
+ get ":id/user_agent_detail" do
+ authenticated_as_admin!
+
+ snippet = Snippet.find_by!(id: params[:id])
+
+ return not_found!('UserAgentDetail') unless snippet.user_agent_detail
+
+ present snippet.user_agent_detail, with: Entities::UserAgentDetail
+ end
end
end
end
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index ed7b23b474a..c0179037440 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -66,6 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
+ status 204
hook.destroy
end
end
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index a9f2ca2608e..9375e7eb768 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -27,12 +27,13 @@ module API
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
- if trigger_request
- present trigger_request.pipeline, with: Entities::Pipeline
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present pipeline, with: Entities::Pipeline
else
- errors = 'No pipeline created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
@@ -142,6 +143,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
+ status 204
trigger.destroy
end
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 5b9d9a71be4..a590f2692a2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,10 +1,9 @@
module API
class Users < Grape::API
include PaginationParams
+ include APIGuard
- before do
- allow_access_with_scope :read_user if request.get?
- end
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
@@ -49,6 +48,8 @@ module API
optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
+ optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
+ optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider
use :pagination
@@ -56,6 +57,10 @@ module API
get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
+ unless current_user&.admin?
+ params.except!(:created_after, :created_before)
+ end
+
users = UsersFinder.new(current_user, params).execute
authorized = can?(current_user, :read_users_list)
@@ -230,6 +235,7 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ status 204
key.destroy
end
@@ -301,6 +307,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
+ status 204
user.delete_async(deleted_by: current_user, params: params)
end
@@ -401,6 +408,7 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
delete ':impersonation_token_id' do
+ status 204
find_impersonation_token.revoke!
end
end
@@ -416,7 +424,16 @@ module API
success Entities::UserPublic
end
get do
- present current_user, with: sudo? ? Entities::UserWithPrivateDetails : Entities::UserPublic
+ entity =
+ if sudo?
+ Entities::UserWithPrivateDetails
+ elsif current_user.admin?
+ Entities::UserWithAdmin
+ else
+ Entities::UserPublic
+ end
+
+ present current_user, with: entity
end
desc "Get the currently authenticated user's SSH keys" do
@@ -469,6 +486,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
+ status 204
key.destroy
end
@@ -520,6 +538,7 @@ module API
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
+ status 204
Emails::DestroyService.new(current_user, email: email.email).execute
end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index c848f52723b..773f667abe0 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -161,7 +161,8 @@ module API
expose :id
expose :default_projects_limit
expose :signup_enabled
- expose :signin_enabled
+ expose :password_authentication_enabled
+ expose :password_authentication_enabled, as: :signin_enabled
expose :gravatar_enabled
expose :sign_in_text
expose :after_sign_up_text
@@ -258,11 +259,40 @@ module API
expose :job_events, as: :build_events
end
- class Issue < ::API::Entities::Issue
+ class ProjectEntity < Grape::Entity
+ expose :id, :iid
+ expose(:project_id) { |entity| entity&.project.try(:id) }
+ expose :title, :description
+ expose :state, :created_at, :updated_at
+ end
+
+ class IssueBasic < ProjectEntity
+ expose :label_names, as: :labels
+ expose :milestone, using: ::API::Entities::Milestone
+ expose :assignees, :author, using: ::API::Entities::UserBasic
+
+ expose :assignee, using: ::API::Entities::UserBasic do |issue, options|
+ issue.assignees.first
+ end
+
+ expose :user_notes_count
+ expose :upvotes, :downvotes
+ expose :due_date
+ expose :confidential
+
+ expose :web_url do |issue, options|
+ Gitlab::UrlBuilder.build(issue)
+ end
+ end
+
+ class Issue < IssueBasic
unexpose :assignees
expose :assignee do |issue, options|
::API::Entities::UserBasic.represent(issue.assignees.first, options)
end
+ expose :subscribed do |issue, options|
+ issue.subscribed?(options[:current_user], options[:project] || issue.project)
+ end
end
end
end
diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb
index 748d6b97d4f..202011cfcbe 100644
--- a/lib/api/v3/settings.rb
+++ b/lib/api/v3/settings.rb
@@ -44,7 +44,9 @@ module API
requires :domain_blacklist, type: String, desc: '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'
end
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
- optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled'
+ optional :password_authentication_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if password authentication is enabled'
+ mutually_exclusive :password_authentication_enabled, :signin_enabled
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
given require_two_factor_authentication: ->(val) { val } do
requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
@@ -116,7 +118,7 @@ module API
:max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources,
:user_oauth_applications, :user_default_external, :signup_enabled,
:send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled,
- :after_sign_up_text, :signin_enabled, :require_two_factor_authentication,
+ :after_sign_up_text, :password_authentication_enabled, :signin_enabled, :require_two_factor_authentication,
:home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text,
:shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay,
:metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
@@ -126,7 +128,13 @@ module API
:housekeeping_enabled, :terminal_max_session_time
end
put "application/settings" do
- if current_settings.update_attributes(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
+
+ if attrs.has_key?(:signin_enabled)
+ attrs[:password_authentication_enabled] = attrs.delete(:signin_enabled)
+ end
+
+ if current_settings.update_attributes(attrs)
present current_settings, with: Entities::ApplicationSetting
else
render_validation_error!(current_settings)
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index a23d6b6b48c..e9d4c35307b 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -28,12 +28,13 @@ module API
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
- if trigger_request
- present trigger_request, with: ::API::V3::Entities::TriggerRequest
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref].to_s, variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present result.trigger_request, with: ::API::V3::Entities::TriggerRequest
else
- errors = 'No builds created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb
index 37020019e07..cf106f2552d 100644
--- a/lib/api/v3/users.rb
+++ b/lib/api/v3/users.rb
@@ -2,9 +2,11 @@ module API
module V3
class Users < Grape::API
include PaginationParams
+ include APIGuard
+
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
before do
- allow_access_with_scope :read_user if request.get?
authenticate!
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 10374995497..7c0fdd3d1be 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -45,7 +45,9 @@ module API
optional :protected, type: String, desc: 'Whether the variable is protected'
end
post ':id/variables' do
- variable = user_project.variables.create(declared_params(include_missing: false))
+ variable_params = declared_params(include_missing: false)
+
+ variable = user_project.variables.create(variable_params)
if variable.valid?
present variable, with: Entities::Variable
@@ -67,7 +69,9 @@ module API
return not_found!('Variable') unless variable
- if variable.update(declared_params(include_missing: false).except(:key))
+ variable_params = declared_params(include_missing: false).except(:key)
+
+ if variable.update(variable_params)
present variable, with: Entities::Variable
else
render_validation_error!(variable)
@@ -84,6 +88,7 @@ module API
variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable
+ status 204
variable.destroy
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index f755c99ea4a..ca6d6848d41 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -8,18 +8,9 @@ module Backup
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
- # saving additional informations
- s = {}
- s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
- s[:backup_created_at] = Time.now
- s[:gitlab_version] = Gitlab::VERSION
- s[:tar_version] = tar_version
- s[:skipped] = ENV["SKIP"]
- tar_file = "#{s[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{s[:gitlab_version]}#{FILE_NAME_SUFFIX}"
-
Dir.chdir(backup_path) do
File.open("#{backup_path}/backup_information.yml", "w+") do |file|
- file << s.to_yaml.gsub(/^---\n/, '')
+ file << backup_information.to_yaml.gsub(/^---\n/, '')
end
# create archive
@@ -33,11 +24,11 @@ module Backup
abort 'Backup failed'
end
- upload(tar_file)
+ upload
end
end
- def upload(tar_file)
+ def upload
$progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection
@@ -48,7 +39,7 @@ module Backup
directory = connect_to_remote_directory(connection_settings)
- if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
+ if directory.files.create(key: remote_target, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
encryption: Gitlab.config.backup.upload.encryption,
storage_class: Gitlab.config.backup.upload.storage_class)
@@ -177,7 +168,8 @@ module Backup
end
def connect_to_remote_directory(connection_settings)
- connection = ::Fog::Storage.new(connection_settings)
+ # our settings use string keys, but Fog expects symbols
+ connection = ::Fog::Storage.new(connection_settings.symbolize_keys)
# We only attempt to create the directory for local backups. For AWS
# and other cloud providers, we cannot guarantee the user will have
@@ -193,6 +185,14 @@ module Backup
Gitlab.config.backup.upload.remote_directory
end
+ def remote_target
+ if ENV['DIRECTORY']
+ File.join(ENV['DIRECTORY'], tar_file)
+ else
+ tar_file
+ end
+ end
+
def backup_contents
folders_to_backup + archives_to_backup + ["backup_information.yml"]
end
@@ -214,5 +214,19 @@ module Backup
def settings
@settings ||= YAML.load_file("backup_information.yml")
end
+
+ def tar_file
+ @tar_file ||= "#{backup_information[:backup_created_at].strftime('%s_%Y_%m_%d_')}#{backup_information[:gitlab_version]}#{FILE_NAME_SUFFIX}"
+ end
+
+ def backup_information
+ @backup_information ||= {
+ db_version: ActiveRecord::Migrator.current_version.to_s,
+ backup_created_at: Time.now,
+ gitlab_version: Gitlab::VERSION,
+ tar_version: tar_version,
+ skipped: ENV["SKIP"]
+ }
+ end
end
end
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index eaacb9591b1..21bcb1c5ca8 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -30,7 +30,7 @@ module Banzai
def url_for_object(range, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_compare_url(project.namespace, project,
+ h.project_compare_url(project,
range.to_param.merge(only_path: context[:only_path]))
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 69c06117eda..714e0319025 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -24,7 +24,7 @@ module Banzai
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_commit_url(project.namespace, project, commit,
+ h.project_commit_url(project, commit,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 0ea4eeaed5b..2e259904673 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -118,7 +118,7 @@ module Banzai
end
if path
- content_tag(:img, nil, src: path, class: 'gfm')
+ content_tag(:img, nil, data: { src: path }, class: 'gfm')
end
end
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
new file mode 100644
index 00000000000..7a81d583b82
--- /dev/null
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -0,0 +1,16 @@
+module Banzai
+ module Filter
+ # HTML filter that moves the value of the src attribute to the data-src attribute so it can be lazy loaded
+ class ImageLazyLoadFilter < HTML::Pipeline::Filter
+ def call
+ doc.xpath('descendant-or-self::img').each do |img|
+ img['class'] ||= '' << 'lazy'
+ img['data-src'] = img['src']
+ img['src'] = LazyImageTagHelper.placeholder_image
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index 123c92fd250..f318c425962 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -10,7 +10,7 @@ module Banzai
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
- href: img['src'],
+ href: img['data-src'] || img['src'],
target: '_blank',
rel: 'noopener noreferrer'
)
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index ba1a5ac84b3..ce1ab977d3b 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -20,7 +20,7 @@ module Banzai
end
def url_for_object(issue, project)
- IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path])
+ IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
end
def project_from_ref(ref)
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index a605dea149e..5364984c9d3 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -61,8 +61,7 @@ module Banzai
def url_for_object(label, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_issues_url(project.namespace, project, label_name: label.name,
- only_path: context[:only_path])
+ h.project_issues_url(project, label_name: label.name, only_path: context[:only_path])
end
def object_link_text(object, matches)
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 3888acf935e..0eab865ac04 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_merge_request_url(project.namespace, project, mr,
+ h.project_merge_request_url(project, mr,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index f12014e191f..45c033d32a8 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -49,7 +49,7 @@ module Banzai
def url_for_object(milestone, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_milestone_url(project.namespace, project, milestone,
+ h.project_milestone_url(project, milestone,
only_path: context[:only_path])
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 6640168bfa2..a6f8650ed3d 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -30,6 +30,8 @@ module Banzai
attributes = attributes.reject { |_, v| v.nil? }
attributes[:reference_type] ||= self.class.reference_type
+ attributes[:container] ||= 'body'
+ attributes[:placement] ||= 'bottom'
attributes.delete(:original) if context[:no_original_data]
attributes.map do |key, value|
%Q(data-#{key.to_s.dasherize}="#{escape_once(value)}")
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 9e23c8f8c55..c2fed57a0d8 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -22,6 +22,7 @@ module Banzai
doc.css('img, video').each do |el|
process_link_attr el.attribute('src')
+ process_link_attr el.attribute('data-src')
end
doc
diff --git a/lib/banzai/filter/snippet_reference_filter.rb b/lib/banzai/filter/snippet_reference_filter.rb
index 212a0bbf2a0..134a192c22b 100644
--- a/lib/banzai/filter/snippet_reference_filter.rb
+++ b/lib/banzai/filter/snippet_reference_filter.rb
@@ -17,7 +17,7 @@ module Banzai
def url_for_object(snippet, project)
h = Gitlab::Routing.url_helpers
- h.namespace_project_snippet_url(project.namespace, project, snippet,
+ h.project_snippet_url(project, snippet,
only_path: context[:only_path])
end
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index a798927823f..f3356d6c51e 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -107,7 +107,7 @@ module Banzai
if author && !project.team.member?(author)
link_content
else
- url = urls.namespace_project_url(project.namespace, project,
+ url = urls.project_url(project,
only_path: context[:only_path])
data = data_attribute(project: project.id, author: author.try(:id))
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index bd4d1aa9ff8..3208abfc538 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -16,6 +16,7 @@ module Banzai
Filter::MathFilter,
Filter::UploadLinkFilter,
Filter::VideoLinkFilter,
+ Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
Filter::EmojiFilter,
Filter::TableOfContentsFilter,
diff --git a/lib/banzai/reference_parser/external_issue_parser.rb b/lib/banzai/reference_parser/external_issue_parser.rb
index 6307c1b571a..1802cd04854 100644
--- a/lib/banzai/reference_parser/external_issue_parser.rb
+++ b/lib/banzai/reference_parser/external_issue_parser.rb
@@ -21,10 +21,14 @@ module Banzai
gather_attributes_per_project(nodes, self.class.data_attribute)
end
- private
-
+ # we extract only external issue trackers references here, we don't extract cross-project references,
+ # so we don't need to do anything here.
def can_read_reference?(user, ref_project, node)
- can?(user, :read_issue, ref_project)
+ true
+ end
+
+ def nodes_visible_to_user(user, nodes)
+ nodes
end
end
end
diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb
index 6b82b2b4f13..31f66dd5a58 100644
--- a/lib/ci/api/entities.rb
+++ b/lib/ci/api/entities.rb
@@ -52,7 +52,7 @@ module Ci
# when old API will be removed (planned for August 2017).
model.options.dup.tap do |options|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
- options[:services].map! do |service|
+ options[:services]&.map! do |service|
if service.is_a?(Hash)
service[:name]
else
diff --git a/lib/ci/api/triggers.rb b/lib/ci/api/triggers.rb
index 6e622601680..6225203f223 100644
--- a/lib/ci/api/triggers.rb
+++ b/lib/ci/api/triggers.rb
@@ -24,12 +24,13 @@ module Ci
end
# create request and trigger builds
- trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref], variables)
- if trigger_request
- present trigger_request, with: Entities::TriggerRequest
+ result = Ci::CreateTriggerRequestService.execute(project, trigger, params[:ref], variables)
+ pipeline = result.pipeline
+
+ if pipeline.persisted?
+ present result.trigger_request, with: Entities::TriggerRequest
else
- errors = 'No builds created'
- render_api_error!(errors, 400)
+ render_validation_error!(pipeline)
end
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 56ad2c77c7d..3a4911b23b0 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -80,8 +80,11 @@ module Ci
artifacts: job[:artifacts],
cache: job[:cache],
dependencies: job[:dependencies],
+ before_script: job[:before_script],
+ script: job[:script],
after_script: job[:after_script],
- environment: job[:environment]
+ environment: job[:environment],
+ retry: job[:retry]
}.compact }
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index d9959bc1aff..b1eb1a6cef1 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -8,7 +8,12 @@ require_dependency 'declarative_policy/step'
require_dependency 'declarative_policy/base'
+require 'thread'
+
module DeclarativePolicy
+ CLASS_CACHE_MUTEX = Mutex.new
+ CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE
+
class << self
def policy_for(user, subject, opts = {})
cache = opts[:cache] || {}
@@ -23,7 +28,36 @@ module DeclarativePolicy
subject = find_delegate(subject)
- subject.class.ancestors.each do |klass|
+ class_for_class(subject.class)
+ end
+
+ private
+
+ # This method is heavily cached because there are a lot of anonymous
+ # modules in play in a typical rails app, and #name performs quite
+ # slowly for anonymous classes and modules.
+ #
+ # See https://bugs.ruby-lang.org/issues/11119
+ #
+ # if the above bug is resolved, this caching could likely be removed.
+ def class_for_class(subject_class)
+ unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
+ CLASS_CACHE_MUTEX.synchronize do
+ # re-check in case of a race
+ break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
+
+ policy_class = compute_class_for_class(subject_class)
+ subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class)
+ end
+ end
+
+ policy_class = subject_class.instance_variable_get(CLASS_CACHE_IVAR)
+ raise "no policy for #{subject.class.name}" if policy_class.nil?
+ policy_class
+ end
+
+ def compute_class_for_class(subject_class)
+ subject_class.ancestors.each do |klass|
next unless klass.name
begin
@@ -37,12 +71,8 @@ module DeclarativePolicy
nil
end
end
-
- raise "no policy for #{subject.class.name}"
end
- private
-
def find_delegate(subject)
seen = Set.new
diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb
index b8cc60074c7..0804edba016 100644
--- a/lib/declarative_policy/cache.rb
+++ b/lib/declarative_policy/cache.rb
@@ -21,11 +21,14 @@ module DeclarativePolicy
private
def id_for(obj)
- if obj.respond_to?(:id) && obj.id
- obj.id.to_s
- else
- "##{obj.object_id}"
- end
+ id =
+ begin
+ obj.id
+ rescue NoMethodError
+ nil
+ end
+
+ id || "##{obj.object_id}"
end
end
end
diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb
index 9d7cf6b9726..51c4a8b2bbe 100644
--- a/lib/declarative_policy/condition.rb
+++ b/lib/declarative_policy/condition.rb
@@ -82,13 +82,14 @@ module DeclarativePolicy
# depending on the scope, we may cache only by the user or only by
# the subject, resulting in sharing across different policy objects.
def cache_key
- case @condition.scope
- when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
- when :user then "/dp/condition/#{@condition.key}/#{user_key}"
- when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
- when :global then "/dp/condition/#{@condition.key}"
- else raise 'invalid scope'
- end
+ @cache_key ||=
+ case @condition.scope
+ when :normal then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
+ when :user then "/dp/condition/#{@condition.key}/#{user_key}"
+ when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
+ when :global then "/dp/condition/#{@condition.key}"
+ else raise 'invalid scope'
+ end
end
def user_key
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index dd864eea3fa..721ed97bb6b 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -126,8 +126,7 @@ module ExtractsPath
raise InvalidPathError unless @commit
@hex_path = Digest::SHA1.hexdigest(@path)
- @logs_path = logs_file_namespace_project_ref_path(@project.namespace,
- @project, @ref, @path)
+ @logs_path = logs_file_project_ref_path(@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
render_404
diff --git a/lib/feature.rb b/lib/feature.rb
index 363f66ba60e..4bd29aed687 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -57,5 +57,11 @@ class Feature
Flipper.new(adapter)
end
end
+
+ # This method is called from config/initializers/flipper.rb and can be used
+ # to register Flipper groups.
+ # See https://docs.gitlab.com/ee/development/feature_flags.html#feature-groups
+ def register_feature_groups
+ end
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 3933c3b04dd..9bed81e7327 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -37,7 +37,7 @@ module Gitlab
rate_limit!(ip, success: result.success?, login: login)
Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
- return result if result.success? || current_application_settings.signin_enabled? || Gitlab::LDAP::Config.enabled?
+ return result if result.success? || current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled?
# If sign-in is disabled and LDAP is not configured, recommend a
# personal access token on failed auth attempts
@@ -48,6 +48,10 @@ module Gitlab
# Avoid resource intensive login checks if password is not provided
return unless password.present?
+ # Nothing to do here if internal auth is disabled and LDAP is
+ # not configured
+ return unless current_application_settings.password_authentication_enabled? || Gitlab::LDAP::Config.enabled?
+
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
user = User.by_login(login)
@@ -130,13 +134,13 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
- if token && valid_scoped_token?(token, AVAILABLE_SCOPES.map(&:to_s))
+ if token && valid_scoped_token?(token, AVAILABLE_SCOPES)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
end
end
def valid_oauth_token?(token)
- token && token.accessible? && valid_scoped_token?(token, ["api"])
+ token && token.accessible? && valid_scoped_token?(token, [:api])
end
def valid_scoped_token?(token, scopes)
diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb
index bf2239ca150..baa1f802d8a 100644
--- a/lib/gitlab/auth/unique_ips_limiter.rb
+++ b/lib/gitlab/auth/unique_ips_limiter.rb
@@ -27,7 +27,7 @@ module Gitlab
time = Time.now.utc.to_i
key = "#{USER_UNIQUE_IPS_PREFIX}:#{user_id}"
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
unique_ips_count = nil
redis.multi do |r|
r.zadd(key, time, ip)
diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb
index d95ecd7b291..d3f66877672 100644
--- a/lib/gitlab/background_migration.rb
+++ b/lib/gitlab/background_migration.rb
@@ -1,24 +1,45 @@
module Gitlab
module BackgroundMigration
+ def self.queue
+ @queue ||= BackgroundMigrationWorker.sidekiq_options['queue']
+ end
+
# Begins stealing jobs from the background migrations queue, blocking the
# caller until all jobs have been completed.
#
+ # When a migration raises a StandardError is is going to be retries up to
+ # three times, for example, to recover from a deadlock.
+ #
+ # When Exception is being raised, it enqueues the migration again, and
+ # re-raises the exception.
+ #
# steal_class - The name of the class for which to steal jobs.
def self.steal(steal_class)
- queue = Sidekiq::Queue
- .new(BackgroundMigrationWorker.sidekiq_options['queue'])
+ enqueued = Sidekiq::Queue.new(self.queue)
+ scheduled = Sidekiq::ScheduledSet.new
- queue.each do |job|
- migration_class, migration_args = job.args
+ [scheduled, enqueued].each do |queue|
+ queue.each do |job|
+ migration_class, migration_args = job.args
- next unless migration_class == steal_class
+ next unless job.queue == self.queue
+ next unless migration_class == steal_class
- perform(migration_class, migration_args)
+ begin
+ perform(migration_class, migration_args) if job.delete
+ rescue Exception # rubocop:disable Lint/RescueException
+ BackgroundMigrationWorker # enqueue this migration again
+ .perform_async(migration_class, migration_args)
- job.delete
+ raise
+ end
+ end
end
end
+ ##
+ # Performs a background migration.
+ #
# class_name - The name of the background migration class as defined in the
# Gitlab::BackgroundMigration namespace.
#
diff --git a/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
new file mode 100644
index 00000000000..91540127ea9
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_build_stage_id_reference.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module BackgroundMigration
+ class MigrateBuildStageIdReference
+ def perform(start_id, stop_id)
+ sql = <<-SQL.strip_heredoc
+ UPDATE ci_builds
+ SET stage_id =
+ (SELECT id FROM ci_stages
+ WHERE ci_stages.pipeline_id = ci_builds.commit_id
+ AND ci_stages.name = ci_builds.stage)
+ WHERE ci_builds.id BETWEEN #{start_id.to_i} AND #{stop_id.to_i}
+ AND ci_builds.stage_id IS NULL
+ SQL
+
+ ActiveRecord::Base.connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb
new file mode 100644
index 00000000000..0881244ed49
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module BackgroundMigration
+ class MigrateSystemUploadsToNewFolder
+ include Gitlab::Database::MigrationHelpers
+ attr_reader :old_folder, :new_folder
+
+ class Upload < ActiveRecord::Base
+ self.table_name = 'uploads'
+ include EachBatch
+ end
+
+ def perform(old_folder, new_folder)
+ replace_sql = replace_sql(uploads[:path], old_folder, new_folder)
+ affected_uploads = Upload.where(uploads[:path].matches("#{old_folder}%"))
+
+ affected_uploads.each_batch do |batch|
+ batch.update_all("path = #{replace_sql}")
+ end
+ end
+
+ def uploads
+ Arel::Table.new('uploads')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
index 53588185622..e898f5d790e 100644
--- a/lib/gitlab/badge/coverage/metadata.rb
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -16,13 +16,11 @@ module Gitlab
end
def image_url
- coverage_namespace_project_badges_url(@project.namespace,
- @project, @ref,
- format: :svg)
+ coverage_project_badges_url(@project, @ref, format: :svg)
end
def link_url
- namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ project_commits_url(@project, @ref)
end
end
end
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
index 4a049ef758d..8ad6f3cb986 100644
--- a/lib/gitlab/badge/metadata.rb
+++ b/lib/gitlab/badge/metadata.rb
@@ -4,7 +4,7 @@ module Gitlab
# Abstract class for badge metadata
#
class Metadata
- include Gitlab::Application.routes.url_helpers
+ include Gitlab::Routing
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::UrlHelper
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/pipeline/metadata.rb
index f87a7b7942e..db1e9f8cfb8 100644
--- a/lib/gitlab/badge/build/metadata.rb
+++ b/lib/gitlab/badge/pipeline/metadata.rb
@@ -1,8 +1,8 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Class that describes build badge metadata
+ # Class that describes pipeline badge metadata
#
class Metadata < Badge::Metadata
def initialize(badge)
@@ -11,16 +11,15 @@ module Gitlab
end
def title
- 'build status'
+ 'pipeline status'
end
def image_url
- build_namespace_project_badges_url(@project.namespace,
- @project, @ref, format: :svg)
+ pipeline_project_badges_url(@project, @ref, format: :svg)
end
def link_url
- namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ project_commits_url(@project, id: @ref)
end
end
end
diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/pipeline/status.rb
index b762d85b6e5..5fee7a93475 100644
--- a/lib/gitlab/badge/build/status.rb
+++ b/lib/gitlab/badge/pipeline/status.rb
@@ -1,8 +1,8 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Build status badge
+ # Pipeline status badge
#
class Status < Badge::Base
attr_reader :project, :ref
@@ -15,7 +15,7 @@ module Gitlab
end
def entity
- 'build'
+ 'pipeline'
end
def status
@@ -25,11 +25,11 @@ module Gitlab
end
def metadata
- @metadata ||= Build::Metadata.new(self)
+ @metadata ||= Pipeline::Metadata.new(self)
end
def template
- @template ||= Build::Template.new(self)
+ @template ||= Pipeline::Template.new(self)
end
end
end
diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/pipeline/template.rb
index bc0e0cd441d..e09db32262d 100644
--- a/lib/gitlab/badge/build/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -1,12 +1,13 @@
module Gitlab
module Badge
- module Build
+ module Pipeline
##
- # Class that represents a build badge template.
+ # Class that represents a pipeline badge template.
#
# Template object will be passed to badge.svg.erb template.
#
class Template < Badge::Template
+ STATUS_RENAME = { 'success' => 'passed' }.freeze
STATUS_COLOR = {
success: '#4c1',
failed: '#e05d44',
@@ -27,11 +28,11 @@ module Gitlab
end
def value_text
- @status.to_s
+ STATUS_RENAME[@status.to_s] || @status.to_s
end
def key_width
- 38
+ 62
end
def value_width
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index 9c2e09943b0..dba37892863 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def self.cached_results_for_projects(projects)
- result = Gitlab::Redis.with do |redis|
+ result = Gitlab::Redis::Cache.with do |redis|
redis.multi do
projects.each do |project|
cache_key = cache_key_for_project(project)
@@ -100,19 +100,19 @@ module Gitlab
end
def load_from_cache
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
end
end
def store_in_cache
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
end
end
def delete_from_cache
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.del(cache_key)
end
end
@@ -120,7 +120,7 @@ module Gitlab
def has_cache?
return self.loaded unless self.loaded.nil?
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.exists(cache_key)
end
end
diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb
new file mode 100644
index 00000000000..f1a04affd38
--- /dev/null
+++ b/lib/gitlab/cache/request_cache.rb
@@ -0,0 +1,94 @@
+module Gitlab
+ module Cache
+ # This module provides a simple way to cache values in RequestStore,
+ # and the cache key would be based on the class name, method name,
+ # optionally customized instance level values, optionally customized
+ # method level values, and optional method arguments.
+ #
+ # A simple example:
+ #
+ # class UserAccess
+ # extend Gitlab::Cache::RequestCache
+ #
+ # request_cache_key do
+ # [user&.id, project&.id]
+ # end
+ #
+ # request_cache def can_push_to_branch?(ref)
+ # # ...
+ # end
+ # end
+ #
+ # This way, the result of `can_push_to_branch?` would be cached in
+ # `RequestStore.store` based on the cache key. If RequestStore is not
+ # currently active, then it would be stored in a hash saved in an
+ # instance variable, so the cache logic would be the same.
+ # Here's another example using customized method level values:
+ #
+ # class Commit
+ # extend Gitlab::Cache::RequestCache
+ #
+ # def author
+ # User.find_by_any_email(author_email.downcase)
+ # end
+ # request_cache(:author) { author_email.downcase }
+ # end
+ #
+ # So that we could have different strategies for different methods
+ #
+ module RequestCache
+ def self.extended(klass)
+ return if klass < self
+
+ extension = Module.new
+ klass.const_set(:RequestCacheExtension, extension)
+ klass.prepend(extension)
+ end
+
+ def request_cache_key(&block)
+ if block_given?
+ @request_cache_key = block
+ else
+ @request_cache_key
+ end
+ end
+
+ def request_cache(method_name, &method_key_block)
+ const_get(:RequestCacheExtension).module_eval do
+ cache_key_method_name = "#{method_name}_cache_key"
+
+ define_method(method_name) do |*args|
+ store =
+ if RequestStore.active?
+ RequestStore.store
+ else
+ ivar_name = # ! and ? cannot be used as ivar name
+ "@cache_#{method_name.to_s.tr('!?', "\u2605\u2606")}"
+
+ instance_variable_get(ivar_name) ||
+ instance_variable_set(ivar_name, {})
+ end
+
+ key = __send__(cache_key_method_name, args)
+
+ store.fetch(key) { store[key] = super(*args) }
+ end
+
+ define_method(cache_key_method_name) do |args|
+ klass = self.class
+
+ instance_key = instance_exec(&klass.request_cache_key) if
+ klass.request_cache_key
+
+ method_key = instance_exec(&method_key_block) if method_key_block
+
+ [klass.name, method_name, *instance_key, *method_key, *args]
+ .join(':')
+ end
+
+ private cache_key_method_name
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_name_token.rb b/lib/gitlab/chat_name_token.rb
index 1b081aa9b1d..e63e5437331 100644
--- a/lib/gitlab/chat_name_token.rb
+++ b/lib/gitlab/chat_name_token.rb
@@ -12,23 +12,23 @@ module Gitlab
end
def get
- Gitlab::Redis.with do |redis|
- data = redis.get(redis_key)
+ Gitlab::Redis::SharedState.with do |redis|
+ data = redis.get(redis_shared_state_key)
JSON.parse(data, symbolize_names: true) if data
end
end
def store!(params)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
params = params.to_json
- redis.set(redis_key, params, ex: EXPIRY_TIME)
+ redis.set(redis_shared_state_key, params, ex: EXPIRY_TIME)
token
end
end
def delete
- Gitlab::Redis.with do |redis|
- redis.del(redis_key)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.del(redis_shared_state_key)
end
end
@@ -38,7 +38,7 @@ module Gitlab
Devise.friendly_token(TOKEN_LENGTH)
end
- def redis_key
+ def redis_shared_state_key
"gitlab:chat_names:#{token}"
end
end
diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb
index ee034d9cc56..411f67f8ce7 100644
--- a/lib/gitlab/ci/build/step.rb
+++ b/lib/gitlab/ci/build/step.rb
@@ -12,7 +12,8 @@ module Gitlab
class << self
def from_commands(job)
self.new(:script).tap do |step|
- step.script = job.commands.split("\n")
+ step.script = job.options[:before_script].to_a + job.options[:script].to_a
+ step.script = job.commands.split("\n") if step.script.empty?
step.timeout = job.timeout
step.when = WHEN_ON_SUCCESS
end
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index f074df9c7a1..d7e09acbbf3 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -7,11 +7,14 @@ module Gitlab
#
class Cache < Node
include Configurable
+ include Attributable
- ALLOWED_KEYS = %i[key untracked paths].freeze
+ ALLOWED_KEYS = %i[key untracked paths policy].freeze
+ DEFAULT_POLICY = 'pull-push'.freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
+ validates :policy, inclusion: { in: %w[pull-push push pull], message: 'should be pull-push, push, or pull' }, allow_blank: true
end
entry :key, Entry::Key,
@@ -25,8 +28,15 @@ module Gitlab
helpers :key
+ attributes :policy
+
def value
- super.merge(key: key_value)
+ result = super
+
+ result[:key] = key_value
+ result[:policy] = policy || DEFAULT_POLICY
+
+ result
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 176301bcca1..32f5c6ab142 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
- after_script variables environment coverage].freeze
+ after_script variables environment coverage retry].freeze
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -23,6 +23,9 @@ module Gitlab
with_options allow_nil: true do
validates :tags, array_of_strings: true
validates :allow_failure, boolean: true
+ validates :retry, numericality: { only_integer: true,
+ greater_than_or_equal_to: 0,
+ less_than_or_equal_to: 2 }
validates :when,
inclusion: { in: %w[on_success on_failure always manual],
message: 'should be on_success, on_failure, ' \
@@ -76,9 +79,9 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
- :artifacts, :commands, :environment, :coverage
+ :artifacts, :commands, :environment, :coverage, :retry
- attributes :script, :tags, :allow_failure, :when, :dependencies
+ attributes :script, :tags, :allow_failure, :when, :dependencies, :retry
def compose!(deps = nil)
super do
@@ -142,6 +145,7 @@ module Gitlab
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
+ retry: retry_defined? ? retry_value.to_i : nil,
artifacts: artifacts_value,
after_script: after_script_value,
ignore: ignored? }
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 439ef0ce015..8ad3e57e59d 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -12,9 +12,7 @@ module Gitlab
end
def action_path
- cancel_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ cancel_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb
index b173c23fba4..c0c7c7f5b5d 100644
--- a/lib/gitlab/ci/status/build/common.rb
+++ b/lib/gitlab/ci/status/build/common.rb
@@ -8,9 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ project_job_path(subject.project, subject)
end
end
end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index e80f3263794..c7726543599 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -20,9 +20,7 @@ module Gitlab
end
def action_path
- play_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ play_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 56303e4cb17..8c8fdc56d75 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -16,9 +16,7 @@ module Gitlab
end
def action_path
- retry_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ retry_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 2778d6f3b52..d464738deaf 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -20,9 +20,7 @@ module Gitlab
end
def action_path
- play_namespace_project_job_path(subject.project.namespace,
- subject.project,
- subject)
+ play_project_job_path(subject.project, subject)
end
def action_method
diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb
index 76bfd18bf40..61bb07beb0f 100644
--- a/lib/gitlab/ci/status/pipeline/common.rb
+++ b/lib/gitlab/ci/status/pipeline/common.rb
@@ -8,9 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_pipeline_path(subject.project.namespace,
- subject.project,
- subject)
+ project_pipeline_path(subject.project, subject)
end
def has_action?
diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb
index 7852f492e1d..bc99d925347 100644
--- a/lib/gitlab/ci/status/stage/common.rb
+++ b/lib/gitlab/ci/status/stage/common.rb
@@ -8,10 +8,7 @@ module Gitlab
end
def details_path
- namespace_project_pipeline_path(subject.project.namespace,
- subject.project,
- subject.pipeline,
- anchor: subject.name)
+ project_pipeline_path(subject.project, subject.pipeline, anchor: subject.name)
end
def has_action?
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index c4c0623df6c..8503ecf8700 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -67,14 +67,15 @@ module Gitlab
def extract_coverage(regex)
return unless valid?
- return unless regex
+ return unless regex.present?
- regex = Regexp.new(regex)
+ regex = Gitlab::UntrustedRegexp.new(regex)
match = ""
reverse_line do |line|
- matches = line.scan(regex)
+ line.chomp!
+ matches = regex.scan(line)
next unless matches.is_a?(Array)
next if matches.empty?
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index 75a213ef752..98dfe900044 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -1,7 +1,7 @@
module Gitlab
module Conflict
class File
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include IconsHelper
MissingResolution = Class.new(ResolutionError)
@@ -205,9 +205,7 @@ module Gitlab
old_path: their_path,
new_path: our_path,
blob_icon: file_type_icon_class('file', our_mode, our_path),
- blob_path: namespace_project_blob_path(merge_request.project.namespace,
- merge_request.project,
- ::File.join(merge_request.diff_refs.head_sha, our_path))
+ blob_path: project_blob_path(merge_request.project, ::File.join(merge_request.diff_refs.head_sha, our_path))
}
json_hash.tap do |json_hash|
@@ -223,11 +221,10 @@ module Gitlab
end
def content_path
- conflict_for_path_namespace_project_merge_request_path(merge_request.project.namespace,
- merge_request.project,
- merge_request,
- old_path: their_path,
- new_path: our_path)
+ conflict_for_path_project_merge_request_path(merge_request.project,
+ merge_request,
+ old_path: their_path,
+ new_path: our_path)
end
# Don't try to print merge_request or repository.
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 818b3d9c46b..7fa02f3d7b3 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -25,7 +25,7 @@ module Gitlab
def cached_application_settings
begin
::ApplicationSetting.cached
- rescue ::Redis::BaseError, ::Errno::ENOENT
+ rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL
# In case Redis isn't running or the Redis UNIX socket file is not available
end
end
@@ -33,12 +33,7 @@ module Gitlab
def uncached_application_settings
return fake_application_settings unless connect_to_db?
- # This loads from the database into the cache, so handle Redis errors
- begin
- db_settings = ::ApplicationSetting.current
- rescue ::Redis::BaseError, ::Errno::ENOENT
- # In case Redis isn't running or the Redis UNIX socket file is not available
- end
+ db_settings = ::ApplicationSetting.current
# If there are pending migrations, it's possible there are columns that
# need to be added to the application settings. To prevent Rake tasks
diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb
index 9d25ef078e8..f5d08c0b658 100644
--- a/lib/gitlab/cycle_analytics/metrics_tables.rb
+++ b/lib/gitlab/cycle_analytics/metrics_tables.rb
@@ -13,6 +13,10 @@ module Gitlab
MergeRequestDiff.arel_table
end
+ def mr_diff_commits_table
+ MergeRequestDiffCommit.arel_table
+ end
+
def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 7d342a2d2cb..b260822788d 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -2,40 +2,59 @@ module Gitlab
module CycleAnalytics
class PlanEventFetcher < BaseEventFetcher
def initialize(*args)
- @projections = [mr_diff_table[:st_commits].as('commits'),
+ @projections = [mr_diff_table[:id],
+ mr_diff_table[:st_commits],
issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args)
end
def events_query
- base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
+ base_query
+ .join(mr_diff_table)
+ .on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
super
end
private
+ def merge_request_diff_commits
+ @merge_request_diff_commits ||=
+ MergeRequestDiffCommit
+ .where(merge_request_diff_id: event_result.map { |event| event['id'] })
+ .group_by(&:merge_request_diff_id)
+ end
+
def serialize(event)
- st_commit = first_time_reference_commit(event.delete('commits'), event)
+ commit = first_time_reference_commit(event)
- return unless st_commit
+ return unless commit
- serialize_commit(event, st_commit, query)
+ serialize_commit(event, commit, query)
end
- def first_time_reference_commit(commits, event)
+ def first_time_reference_commit(event)
+ return nil unless event && merge_request_diff_commits
+
+ commits =
+ if event['st_commits'].present?
+ YAML.load(event['st_commits'])
+ else
+ merge_request_diff_commits[event['id'].to_i]
+ end
+
return nil if commits.blank?
- YAML.load(commits).find do |commit|
+ commits.find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end
end
- def serialize_commit(event, st_commit, query)
- commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
+ def serialize_commit(event, commit, query)
+ commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index e81d19a7a2e..8c8729b6557 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -74,6 +74,8 @@ module Gitlab
build(project, user, commits.last&.id, commits.first&.id, ref, commits)
end
+ private
+
def checkout_sha(repository, newrev, ref)
# Checkout sha is nil when we remove branch or tag
return if Gitlab::Git.blank_ref?(newrev)
diff --git a/lib/gitlab/data_builder/wiki_page.rb b/lib/gitlab/data_builder/wiki_page.rb
new file mode 100644
index 00000000000..226974b698c
--- /dev/null
+++ b/lib/gitlab/data_builder/wiki_page.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module DataBuilder
+ module WikiPage
+ extend self
+
+ def build(wiki_page, user, action)
+ wiki = wiki_page.wiki
+
+ {
+ object_kind: wiki_page.class.name.underscore,
+ user: user.hook_attrs,
+ project: wiki.project.hook_attrs,
+ wiki: wiki.hook_attrs,
+ object_attributes: wiki_page.hook_attrs.merge(
+ url: Gitlab::UrlBuilder.build(wiki_page),
+ action: action
+ )
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0643c56db9b..69ca9aa596b 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -140,6 +140,8 @@ module Gitlab
return add_foreign_key(source, target,
column: column,
on_delete: on_delete)
+ else
+ on_delete = 'SET NULL' if on_delete == :nullify
end
disable_statement_timeout
@@ -155,7 +157,7 @@ module Gitlab
ADD CONSTRAINT #{key_name}
FOREIGN KEY (#{column})
REFERENCES #{target} (id)
- #{on_delete ? "ON DELETE #{on_delete}" : ''}
+ #{on_delete ? "ON DELETE #{on_delete.upcase}" : ''}
NOT VALID;
EOF
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index 33f8939bc61..1a697396ff1 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -145,7 +145,7 @@ module Gitlab
def track_rename(type, old_path, new_path)
key = redis_key_for_type(type)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.lpush(key, [old_path, new_path].to_json)
redis.expire(key, 2.weeks.to_i)
end
@@ -155,7 +155,7 @@ module Gitlab
def reverts_for_type(type)
key = redis_key_for_type(type)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
failed_reverts = []
while rename_info = redis.lpop(key)
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 1a5887dab7e..85e6db0a689 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -237,6 +237,10 @@ module Gitlab
branch_name.parameterize << '.patch'
end
+ def patch_url
+ "https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/#{ENV['CI_JOB_ID']}/artifacts/raw/ee_compat_check/patches/#{ce_patch_name}"
+ end
+
def step(desc, cmd = nil)
puts "\n=> #{desc}\n"
@@ -303,14 +307,11 @@ module Gitlab
2. Apply your branch's patch to EE
- # In the CE repo
- $ git fetch origin master
- $ git diff --binary origin/master...HEAD -- > #{ce_branch}.patch
-
# In the EE repo
$ git fetch origin master
$ git checkout -b #{ee_branch_prefix} origin/master
- $ git apply --3way path/to/#{ce_branch}.patch
+ $ wget #{patch_url}
+ $ git apply --3way #{ce_patch_name}
At this point you might have conflicts such as:
@@ -324,7 +325,7 @@ module Gitlab
If the patch couldn't be applied cleanly, use the following command:
# In the EE repo
- $ git apply --reject path/to/#{ce_branch}.patch
+ $ git apply --reject #{ce_patch_name}
This option makes git apply the parts of the patch that are applicable,
and leave the rejected hunks in corresponding `.rej` files.
@@ -337,7 +338,7 @@ module Gitlab
# In the EE repo
$ git push origin #{ee_branch_prefix}
- âš ï¸ Also, don't forget to create a new merge request on gitlab-ce and
+ âš ï¸ Also, don't forget to create a new merge request on gitlab-ee and
cross-link it with the CE merge request.
Once this is done, you can retry this failed build, and it should pass.
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index ea035e33eff..dd1d9dcd555 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -4,7 +4,7 @@ module Gitlab
class RepositoryPush
attr_reader :author_id, :ref, :action
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include DiffHelper
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
@@ -96,20 +96,13 @@ module Gitlab
def target_url
if @action == :push && commits
if commits.length > 1
- namespace_project_compare_url(project_namespace,
- project,
- from: compare.start_commit,
- to: compare.head_commit)
+ project_compare_url(project, from: compare.start_commit, to: compare.head_commit)
else
- namespace_project_commit_url(project_namespace,
- project,
- commits.first)
+ project_commit_url(project, commits.first)
end
else
unless @action == :delete
- namespace_project_tree_url(project_namespace,
- project,
- ref_name)
+ project_tree_url(project, ref_name)
end
end
end
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index 072fcfc65e6..21172ff8d93 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -2,17 +2,17 @@ module Gitlab
module EtagCaching
class Store
EXPIRY_TIME = 20.minutes
- REDIS_NAMESPACE = 'etag:'.freeze
+ SHARED_STATE_NAMESPACE = 'etag:'.freeze
def get(key)
- Gitlab::Redis.with { |redis| redis.get(redis_key(key)) }
+ Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
end
def touch(key, only_if_missing: false)
etag = generate_etag
- Gitlab::Redis.with do |redis|
- redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(redis_shared_state_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
end
etag
@@ -24,10 +24,10 @@ module Gitlab
SecureRandom.hex
end
- def redis_key(key)
+ def redis_shared_state_key(key)
raise 'Invalid key' if !Rails.env.production? && !Gitlab::EtagCaching::Router.match(key)
- "#{REDIS_NAMESPACE}#{key}"
+ "#{SHARED_STATE_NAMESPACE}#{key}"
end
end
end
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index a0f46594eb1..3784f6c4947 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -26,17 +26,17 @@ module Gitlab
EOS
def self.cancel(key, uuid)
- Gitlab::Redis.with do |redis|
- redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_key(key)], argv: [uuid])
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.eval(LUA_CANCEL_SCRIPT, keys: [redis_shared_state_key(key)], argv: [uuid])
end
end
- def self.redis_key(key)
+ def self.redis_shared_state_key(key)
"gitlab:exclusive_lease:#{key}"
end
def initialize(key, timeout:)
- @redis_key = self.class.redis_key(key)
+ @redis_shared_state_key = self.class.redis_shared_state_key(key)
@timeout = timeout
@uuid = SecureRandom.uuid
end
@@ -45,24 +45,24 @@ module Gitlab
# false if the lease is already taken.
def try_obtain
# Performing a single SET is atomic
- Gitlab::Redis.with do |redis|
- redis.set(@redis_key, @uuid, nx: true, ex: @timeout) && @uuid
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(@redis_shared_state_key, @uuid, nx: true, ex: @timeout) && @uuid
end
end
# Try to renew an existing lease. Return lease UUID on success,
# false if the lease is taken by a different UUID or inexistent.
def renew
- Gitlab::Redis.with do |redis|
- result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_key], argv: [@uuid, @timeout])
+ Gitlab::Redis::SharedState.with do |redis|
+ result = redis.eval(LUA_RENEW_SCRIPT, keys: [@redis_shared_state_key], argv: [@uuid, @timeout])
result == @uuid
end
end
# Returns true if the key for this lease is set.
def exists?
- Gitlab::Redis.with do |redis|
- redis.exists(@redis_key)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.exists(@redis_shared_state_key)
end
end
end
diff --git a/lib/gitlab/git/attributes.rb b/lib/gitlab/git/attributes.rb
index 42140ecc993..2d20cd473a7 100644
--- a/lib/gitlab/git/attributes.rb
+++ b/lib/gitlab/git/attributes.rb
@@ -1,3 +1,8 @@
+# Gitaly note: JV: not sure what to make of this class. Why does it use
+# the full disk path of the repository to look up attributes This is
+# problematic in Gitaly, because Gitaly hides the full disk path to the
+# repository from gitlab-ce.
+
module Gitlab
module Git
# Class for parsing Git attribute files and extracting the attributes for
diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb
index 66829a03c2e..0deaab01b5b 100644
--- a/lib/gitlab/git/blame.rb
+++ b/lib/gitlab/git/blame.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: needs 1 RPC for #load_blame.
+
module Gitlab
module Git
class Blame
@@ -24,6 +26,7 @@ module Gitlab
private
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/376
def load_blame
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
# Read in binary mode to ensure ASCII-8BIT
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index a7aceab4c14..db6cfc9671f 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: seems to be completely migrated (behind feature flags).
+
module Gitlab
module Git
class Blob
@@ -27,7 +29,7 @@ module Gitlab
path = path.sub(/\A\/*/, '')
path = '/' if path.empty?
name = File.basename(path)
- entry = Gitlab::GitalyClient::Commit.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
+ entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
return unless entry
case entry.type
@@ -41,10 +43,6 @@ module Gitlab
commit_id: sha
)
when :BLOB
- # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
- # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
- # which is what we use below to keep a consistent behavior.
- detect = CharlockHolmes::EncodingDetector.new(8000).detect(entry.data)
new(
id: entry.oid,
name: name,
@@ -53,7 +51,7 @@ module Gitlab
mode: entry.mode.to_s(8),
path: path,
commit_id: sha,
- binary: detect && detect[:type] == :binary
+ binary: binary?(entry.data)
)
end
end
@@ -87,16 +85,32 @@ module Gitlab
end
def raw(repository, sha)
- blob = repository.lookup(sha)
+ Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled|
+ if is_enabled
+ Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
+ else
+ blob = repository.lookup(sha)
- new(
- id: blob.oid,
- size: blob.size,
- data: blob.content(MAX_DATA_DISPLAY_SIZE),
- binary: blob.binary?
- )
+ new(
+ id: blob.oid,
+ size: blob.size,
+ data: blob.content(MAX_DATA_DISPLAY_SIZE),
+ binary: blob.binary?
+ )
+ end
+ end
+ end
+
+ def binary?(data)
+ # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
+ # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
+ # which is what we use below to keep a consistent behavior.
+ detect = CharlockHolmes::EncodingDetector.new(8000).detect(data)
+ detect && detect[:type] == :binary
end
+ private
+
# Recursive search of blob id by path
#
# Ex.
@@ -165,8 +179,17 @@ module Gitlab
return if @data == '' # don't mess with submodule blobs
return @data if @loaded_all_data
+ Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
+ @data = begin
+ if is_enabled
+ Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: id, limit: -1).data
+ else
+ repository.lookup(id).content
+ end
+ end
+ end
+
@loaded_all_data = true
- @data = repository.lookup(id).content
@loaded_size = @data.bytesize
@binary = nil
end
@@ -175,6 +198,10 @@ module Gitlab
encode! @name
end
+ def path
+ encode! @path
+ end
+
def truncated?
size && (size > loaded_size)
end
diff --git a/lib/gitlab/git/blob_snippet.rb b/lib/gitlab/git/blob_snippet.rb
index d7975f88aaa..68116e775c6 100644
--- a/lib/gitlab/git/blob_snippet.rb
+++ b/lib/gitlab/git/blob_snippet.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class BlobSnippet
diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb
index 124526e4b59..c53882787f1 100644
--- a/lib/gitlab/git/branch.rb
+++ b/lib/gitlab/git/branch.rb
@@ -1,39 +1,10 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class Branch < Ref
- def initialize(repository, name, target)
- if target.is_a?(Gitaly::FindLocalBranchResponse)
- target = target_from_gitaly_local_branches_response(target)
- end
-
- super(repository, name, target)
- end
-
- def target_from_gitaly_local_branches_response(response)
- # Git messages have no encoding enforcements. However, in the UI we only
- # handle UTF-8, so basically we cross our fingers that the message force
- # encoded to UTF-8 is readable.
- message = response.commit_subject.dup.force_encoding('UTF-8')
-
- # NOTE: For ease of parsing in Gitaly, we have only the subject of
- # the commit and not the full message. This is ok, since all the
- # code that uses `local_branches` only cares at most about the
- # commit message.
- # TODO: Once gitaly "takes over" Rugged consider separating the
- # subject from the message to make it clearer when there's one
- # available but not the other.
- hash = {
- id: response.commit_id,
- message: message,
- authored_date: Time.at(response.commit_author.date.seconds),
- author_name: response.commit_author.name,
- author_email: response.commit_author.email,
- committed_date: Time.at(response.commit_committer.date.seconds),
- committer_name: response.commit_committer.name,
- committer_email: response.commit_committer.email
- }
-
- Gitlab::Git::Commit.decorate(hash)
+ def initialize(repository, name, target, target_commit)
+ super(repository, name, target, target_commit)
end
end
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 9c0606d780a..ca7e3a7c4be 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -38,7 +38,7 @@ module Gitlab
repo = options.delete(:repo)
raise 'Gitlab::Git::Repository is required' unless repo.respond_to?(:log)
- repo.log(options).map { |c| decorate(c) }
+ repo.log(options)
end
# Get single commit
@@ -48,6 +48,7 @@ module Gitlab
#
# Commit.find(repo, 'master')
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/321
def find(repo, commit_id = "HEAD")
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
@@ -97,8 +98,12 @@ module Gitlab
# Commit.between(repo, '29eda46b', 'master')
#
def between(repo, base, head)
- repo.commits_between(base, head).map do |commit|
- decorate(commit)
+ Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled|
+ if is_enabled
+ repo.gitaly_commit_client.between(base, head)
+ else
+ repo.commits_between(base, head).map { |c| decorate(c) }
+ end
end
rescue Rugged::ReferenceError
[]
@@ -124,7 +129,18 @@ module Gitlab
# are documented here:
# http://www.rubydoc.info/github/libgit2/rugged/Rugged#SORT_NONE-constant)
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326
def find_all(repo, options = {})
+ Gitlab::GitalyClient.migrate(:find_all_commits) do |is_enabled|
+ if is_enabled
+ find_all_by_gitaly(repo, options)
+ else
+ find_all_by_rugged(repo, options)
+ end
+ end
+ end
+
+ def find_all_by_rugged(repo, options = {})
actual_options = options.dup
allowed_options = [:ref, :max_count, :skip, :order]
@@ -163,6 +179,10 @@ module Gitlab
[]
end
+ def find_all_by_gitaly(repo, options = {})
+ Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
+ end
+
def decorate(commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref)
end
@@ -204,10 +224,13 @@ module Gitlab
def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit
- if raw_commit.is_a?(Hash)
+ case raw_commit
+ when Hash
init_from_hash(raw_commit)
- elsif raw_commit.is_a?(Rugged::Commit)
+ when Rugged::Commit
init_from_rugged(raw_commit)
+ when Gitlab::GitalyClient::Commit
+ init_from_gitaly(raw_commit)
else
raise "Invalid raw commit type: #{raw_commit.class}"
end
@@ -243,6 +266,8 @@ module Gitlab
# Shows the diff between the commit's parent and the commit.
#
# Cuts out the header and stats from #to_patch and returns only the diff.
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324
def to_diff
diff_from_parent.patch
end
@@ -284,7 +309,23 @@ module Gitlab
end
def parents
- raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
+ case raw_commit
+ when Rugged::Commit
+ raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
+ when Gitlab::GitalyClient::Commit
+ parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact
+ else
+ raise NotImplementedError, "commit source doesn't support #parents"
+ end
+ end
+
+ # Get the gpg signature of this commit.
+ #
+ # Ex.
+ # commit.signature(repo)
+ #
+ def signature(repo)
+ Rugged::Commit.extract_signature(repo.rugged, sha)
end
def stats
@@ -295,7 +336,7 @@ module Gitlab
begin
raw_commit.to_mbox(options)
rescue Rugged::InvalidError => ex
- if ex.message =~ /Commit \w+ is a merge commit/
+ if ex.message =~ /commit \w+ is a merge commit/i
'Patch format is not currently supported for merge commits.'
end
end
@@ -367,6 +408,22 @@ module Gitlab
@parent_ids = commit.parents.map(&:oid)
end
+ def init_from_gitaly(commit)
+ @raw_commit = commit
+ @id = commit.id
+ # TODO: Once gitaly "takes over" Rugged consider separating the
+ # subject from the message to make it clearer when there's one
+ # available but not the other.
+ @message = (commit.body.presence || commit.subject).dup
+ @authored_date = Time.at(commit.author.date.seconds)
+ @author_name = commit.author.name.dup
+ @author_email = commit.author.email.dup
+ @committed_date = Time.at(commit.committer.date.seconds)
+ @committer_name = commit.committer.name.dup
+ @committer_email = commit.committer.email.dup
+ @parent_ids = commit.parent_ids
+ end
+
def serialize_keys
SERIALIZE_KEYS
end
diff --git a/lib/gitlab/git/commit_stats.rb b/lib/gitlab/git/commit_stats.rb
index e9118bbed0e..57c29ad112c 100644
--- a/lib/gitlab/git/commit_stats.rb
+++ b/lib/gitlab/git/commit_stats.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: 1 RPC, migration in progress.
+
# Gitlab::Git::CommitStats counts the additions, deletions, and total changes
# in a commit.
module Gitlab
@@ -6,6 +8,8 @@ module Gitlab
attr_reader :id, :additions, :deletions, :total
# Instantiate a CommitStats object
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/323
def initialize(commit)
@id = commit.id
@additions = 0
diff --git a/lib/gitlab/git/compare.rb b/lib/gitlab/git/compare.rb
index 78e440395a5..7cb842256d0 100644
--- a/lib/gitlab/git/compare.rb
+++ b/lib/gitlab/git/compare.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class Compare
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index cf7829a583b..9e00abefd02 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: needs RPC for Gitlab::Git::Diff.between.
+
# Gitlab::Git::Diff is a wrapper around native Rugged::Diff object
module Gitlab
module Git
@@ -81,110 +83,16 @@ module Gitlab
# Return a copy of the +options+ hash containing only keys that can be
# passed to Rugged. Allowed options are:
#
- # :max_size ::
- # An integer specifying the maximum byte size of a file before a it
- # will be treated as binary. The default value is 512MB.
- #
- # :context_lines ::
- # The number of unchanged lines that define the boundary of a hunk
- # (and to display before and after the actual changes). The default is
- # 3.
- #
- # :interhunk_lines ::
- # The maximum number of unchanged lines between hunk boundaries before
- # the hunks will be merged into a one. The default is 0.
- #
- # :old_prefix ::
- # The virtual "directory" to prefix to old filenames in hunk headers.
- # The default is "a".
- #
- # :new_prefix ::
- # The virtual "directory" to prefix to new filenames in hunk headers.
- # The default is "b".
- #
- # :reverse ::
- # If true, the sides of the diff will be reversed.
- #
- # :force_text ::
- # If true, all files will be treated as text, disabling binary
- # attributes & detection.
- #
- # :ignore_whitespace ::
- # If true, all whitespace will be ignored.
- #
# :ignore_whitespace_change ::
# If true, changes in amount of whitespace will be ignored.
#
- # :ignore_whitespace_eol ::
- # If true, whitespace at end of line will be ignored.
- #
- # :ignore_submodules ::
- # if true, submodules will be excluded from the diff completely.
- #
- # :patience ::
- # If true, the "patience diff" algorithm will be used (currenlty
- # unimplemented).
- #
- # :include_ignored ::
- # If true, ignored files will be included in the diff.
- #
- # :include_untracked ::
- # If true, untracked files will be included in the diff.
- #
- # :include_unmodified ::
- # If true, unmodified files will be included in the diff.
- #
- # :recurse_untracked_dirs ::
- # Even if +:include_untracked+ is true, untracked directories will
- # only be marked with a single entry in the diff. If this flag is set
- # to true, all files under ignored directories will be included in the
- # diff, too.
- #
# :disable_pathspec_match ::
# If true, the given +*paths+ will be applied as exact matches,
# instead of as fnmatch patterns.
#
- # :deltas_are_icase ::
- # If true, filename comparisons will be made with case-insensitivity.
- #
- # :include_untracked_content ::
- # if true, untracked content will be contained in the the diff patch
- # text.
- #
- # :skip_binary_check ::
- # If true, diff deltas will be generated without spending time on
- # binary detection. This is useful to improve performance in cases
- # where the actual file content difference is not needed.
- #
- # :include_typechange ::
- # If true, type changes for files will not be interpreted as deletion
- # of the "old file" and addition of the "new file", but will generate
- # typechange records.
- #
- # :include_typechange_trees ::
- # Even if +:include_typechange+ is true, blob -> tree changes will
- # still usually be handled as a deletion of the blob. If this flag is
- # set to true, blob -> tree changes will be marked as typechanges.
- #
- # :ignore_filemode ::
- # If true, file mode changes will be ignored.
- #
- # :recurse_ignored_dirs ::
- # Even if +:include_ignored+ is true, ignored directories will only be
- # marked with a single entry in the diff. If this flag is set to true,
- # all files under ignored directories will be included in the diff,
- # too.
def filter_diff_options(options, default_options = {})
- allowed_options = [:max_size, :context_lines, :interhunk_lines,
- :old_prefix, :new_prefix, :reverse, :force_text,
- :ignore_whitespace, :ignore_whitespace_change,
- :ignore_whitespace_eol, :ignore_submodules,
- :patience, :include_ignored, :include_untracked,
- :include_unmodified, :recurse_untracked_dirs,
- :disable_pathspec_match, :deltas_are_icase,
- :include_untracked_content, :skip_binary_check,
- :include_typechange, :include_typechange_trees,
- :ignore_filemode, :recurse_ignored_dirs, :paths,
+ allowed_options = [:ignore_whitespace_change,
+ :disable_pathspec_match, :paths,
:max_files, :max_lines, :limits, :expanded]
if default_options
@@ -326,6 +234,8 @@ module Gitlab
@new_file = diff.from_id == BLANK_SHA
@renamed_file = diff.from_path != diff.to_path
@deleted_file = diff.to_id == BLANK_SHA
+
+ collapse! if diff.respond_to?(:collapsed) && diff.collapsed
end
def prune_diff_if_eligible
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 555894907cc..87ed9c3ea26 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class DiffCollection
@@ -5,16 +7,28 @@ module Gitlab
DEFAULT_LIMITS = { max_files: 100, max_lines: 5000 }.freeze
+ attr_reader :limits
+
+ delegate :max_files, :max_lines, :max_bytes, :safe_max_files, :safe_max_lines, :safe_max_bytes, to: :limits
+
+ def self.collection_limits(options = {})
+ limits = {}
+ limits[:max_files] = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
+ limits[:max_lines] = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
+ limits[:max_bytes] = limits[:max_files] * 5.kilobytes # Average 5 KB per file
+ limits[:safe_max_files] = [limits[:max_files], DEFAULT_LIMITS[:max_files]].min
+ limits[:safe_max_lines] = [limits[:max_lines], DEFAULT_LIMITS[:max_lines]].min
+ limits[:safe_max_bytes] = limits[:safe_max_files] * 5.kilobytes # Average 5 KB per file
+
+ OpenStruct.new(limits)
+ end
+
def initialize(iterator, options = {})
@iterator = iterator
- @max_files = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
- @max_lines = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
- @max_bytes = @max_files * 5.kilobytes # Average 5 KB per file
- @safe_max_files = [@max_files, DEFAULT_LIMITS[:max_files]].min
- @safe_max_lines = [@max_lines, DEFAULT_LIMITS[:max_lines]].min
- @safe_max_bytes = @safe_max_files * 5.kilobytes # Average 5 KB per file
+ @limits = self.class.collection_limits(options)
@enforce_limits = !!options.fetch(:limits, true)
@expanded = !!options.fetch(:expanded, true)
+ @from_gitaly = options.fetch(:from_gitaly, false)
@line_count = 0
@byte_count = 0
@@ -24,9 +38,23 @@ module Gitlab
end
def each(&block)
- Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
- each_patch(&block)
+ @array.each(&block)
+
+ return if @overflow
+ return if @iterator.nil?
+
+ Gitlab::GitalyClient.migrate(:commit_raw_diffs) do |is_enabled|
+ if is_enabled && @from_gitaly
+ each_gitaly_patch(&block)
+ else
+ each_rugged_patch(&block)
+ end
end
+
+ @populated = true
+
+ # Allow iterator to be garbage-collected. It cannot be reused anyway.
+ @iterator = nil
end
def empty?
@@ -72,23 +100,32 @@ module Gitlab
end
def over_safe_limits?(files)
- files >= @safe_max_files || @line_count > @safe_max_lines || @byte_count >= @safe_max_bytes
+ files >= safe_max_files || @line_count > safe_max_lines || @byte_count >= safe_max_bytes
end
- def each_patch
- i = 0
- @array.each do |diff|
- yield diff
+ def each_gitaly_patch
+ i = @array.length
+
+ @iterator.each do |raw|
+ diff = Gitlab::Git::Diff.new(raw, expanded: !@enforce_limits || @expanded)
+
+ if raw.overflow_marker
+ @overflow = true
+ break
+ end
+
+ yield @array[i] = diff
i += 1
end
+ end
- return if @overflow
- return if @iterator.nil?
+ def each_rugged_patch
+ i = @array.length
@iterator.each do |raw|
@empty = false
- if @enforce_limits && i >= @max_files
+ if @enforce_limits && i >= max_files
@overflow = true
break
end
@@ -104,7 +141,7 @@ module Gitlab
@line_count += diff.line_count
@byte_count += diff.diff.bytesize
- if @enforce_limits && (@line_count >= @max_lines || @byte_count >= @max_bytes)
+ if @enforce_limits && (@line_count >= max_lines || @byte_count >= max_bytes)
# This last Diff instance pushes us over the lines limit. We stop and
# discard it.
@overflow = true
@@ -114,11 +151,6 @@ module Gitlab
yield @array[i] = diff
i += 1
end
-
- @populated = true
-
- # Allow iterator to be garbage-collected. It cannot be reused anyway.
- @iterator = nil
end
end
end
diff --git a/lib/gitlab/git/env.rb b/lib/gitlab/git/env.rb
index 0fdc57ec954..f80193ac553 100644
--- a/lib/gitlab/git/env.rb
+++ b/lib/gitlab/git/env.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
# Ephemeral (per request) storage for environment variables that some Git
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
index f4e3b5e5129..4a43b9b444d 100644
--- a/lib/gitlab/git/gitmodules_parser.rb
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class GitmodulesParser
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 5042916343b..8f0c377ef4f 100644
--- a/lib/gitlab/git/hook.rb
+++ b/lib/gitlab/git/hook.rb
@@ -1,3 +1,7 @@
+# Gitaly note: JV: looks like this is only used by GitHooksService in
+# app/services. We shouldn't bother migrating this until we know how
+# GitHooksService will be migrated.
+
module Gitlab
module Git
class Hook
diff --git a/lib/gitlab/git/index.rb b/lib/gitlab/git/index.rb
index 1add037fa5f..db532600d1b 100644
--- a/lib/gitlab/git/index.rb
+++ b/lib/gitlab/git/index.rb
@@ -1,3 +1,7 @@
+# Gitaly note: JV: When the time comes I think we will want to copy this
+# class into Gitaly. None of its methods look like they should be RPC's.
+# The RPC's will be at a higher level.
+
module Gitlab
module Git
class Index
@@ -110,10 +114,6 @@ module Gitlab
if segment == '..'
raise IndexError, 'Path cannot include directory traversal'
end
-
- unless segment =~ Gitlab::Regex.file_name_regex
- raise IndexError, "Path #{Gitlab::Regex.file_name_regex_message}"
- end
end
pathname.to_s
diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb
index 0148cd8df05..42c80aabd0a 100644
--- a/lib/gitlab/git/path_helper.rb
+++ b/lib/gitlab/git/path_helper.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
class PathHelper
diff --git a/lib/gitlab/git/popen.rb b/lib/gitlab/git/popen.rb
index df9ca3ee5ac..25fa62ce4bd 100644
--- a/lib/gitlab/git/popen.rb
+++ b/lib/gitlab/git/popen.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
require 'open3'
module Gitlab
diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb
index ebf7393dc61..372ce005b94 100644
--- a/lib/gitlab/git/ref.rb
+++ b/lib/gitlab/git/ref.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: probably no RPC's here (just one interaction with Rugged).
+
module Gitlab
module Git
class Ref
@@ -24,16 +26,16 @@ module Gitlab
str.gsub(/\Arefs\/heads\//, '')
end
+ # Gitaly: this method will probably be migrated indirectly via its call sites.
def self.dereference_object(object)
object = object.target while object.is_a?(Rugged::Tag::Annotation)
object
end
- def initialize(repository, name, target)
- encode! name
- @name = name.gsub(/\Arefs\/(tags|heads)\//, '')
- @dereferenced_target = Gitlab::Git::Commit.find(repository, target)
+ def initialize(repository, name, target, derefenced_target)
+ @name = Gitlab::Git.ref_name(name)
+ @dereferenced_target = derefenced_target
@target = if target.respond_to?(:oid)
target.oid
elsif target.respond_to?(:name)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 23d0c8a9bdb..3e27fd7b682 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -45,6 +45,8 @@ module Gitlab
:bare?,
to: :rugged
+ delegate :exists?, to: :gitaly_repository_client
+
# Default branch in the repository
def root_ref
@root_ref ||= gitaly_migrate(:root_ref) do |is_enabled|
@@ -80,16 +82,10 @@ module Gitlab
end
# Returns an Array of Branches
- def branches(filter: nil, sort_by: nil)
- branches = rugged.branches.each(filter).map do |rugged_ref|
- begin
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target)
- rescue Rugged::ReferenceError
- # Omit invalid branch
- end
- end.compact
-
- sort_branches(branches, sort_by)
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/389
+ def branches(sort_by: nil)
+ branches_filter(sort_by: sort_by)
end
def reload_rugged
@@ -107,7 +103,10 @@ module Gitlab
reload_rugged if force_reload
rugged_ref = rugged.branches[name]
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref
+ if rugged_ref
+ target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
+ Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ end
end
def local_branches(sort_by: nil)
@@ -115,7 +114,7 @@ module Gitlab
if is_enabled
gitaly_ref_client.local_branches(sort_by: sort_by)
else
- branches(filter: :local, sort_by: sort_by)
+ branches_filter(filter: :local, sort_by: sort_by)
end
end
end
@@ -162,6 +161,8 @@ module Gitlab
end
# Returns an Array of Tags
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/390
def tags
rugged.references.each("refs/tags/*").map do |ref|
message = nil
@@ -174,7 +175,8 @@ module Gitlab
end
end
- Gitlab::Git::Tag.new(self, ref.name, ref.target, message)
+ target_commit = Gitlab::Git::Commit.find(self, ref.target)
+ Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message)
end.sort_by(&:name)
end
@@ -204,21 +206,10 @@ module Gitlab
branch_names + tag_names
end
- # Deprecated. Will be removed in 5.2
- def heads
- rugged.references.each("refs/heads/*").map do |head|
- Gitlab::Git::Ref.new(self, head.name, head.target)
- end.sort_by(&:name)
- end
-
def has_commits?
!empty?
end
- def repo_exists?
- !!rugged
- end
-
# Discovers the default branch based on the repository's available branches
#
# - If no branches are present, returns nil
@@ -297,28 +288,6 @@ module Gitlab
(size.to_f / 1024).round(2)
end
- # Returns an array of BlobSnippets for files at the specified +ref+ that
- # contain the +query+ string.
- def search_files(query, ref = nil)
- greps = []
- ref ||= root_ref
-
- populated_index(ref).each do |entry|
- # Discard submodules
- next if submodule?(entry)
-
- blob = Gitlab::Git::Blob.raw(self, entry[:oid])
-
- # Skip binary files
- next if blob.data.encoding == Encoding::ASCII_8BIT
-
- blob.load_all_data!(self)
- greps += build_greps(blob.data, query, ref, entry[:path])
- end
-
- greps
- end
-
# Use the Rugged Walker API to build an array of commits.
#
# Usage.
@@ -331,85 +300,10 @@ module Gitlab
# )
#
def log(options)
- default_options = {
- limit: 10,
- offset: 0,
- path: nil,
- follow: false,
- skip_merges: false,
- disable_walk: false,
- after: nil,
- before: nil
- }
-
- options = default_options.merge(options)
- options[:limit] ||= 0
- options[:offset] ||= 0
- actual_ref = options[:ref] || root_ref
- begin
- sha = sha_from_ref(actual_ref)
- rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
- # Return an empty array if the ref wasn't found
- return []
- end
-
- if log_using_shell?(options)
- log_by_shell(sha, options)
- else
- log_by_walk(sha, options)
- end
- end
-
- def log_using_shell?(options)
- options[:path].present? ||
- options[:disable_walk] ||
- options[:skip_merges] ||
- options[:after] ||
- options[:before]
- end
-
- def log_by_walk(sha, options)
- walk_options = {
- show: sha,
- sort: Rugged::SORT_NONE,
- limit: options[:limit],
- offset: options[:offset]
- }
- Rugged::Walker.walk(rugged, walk_options).to_a
- end
-
- def log_by_shell(sha, options)
- limit = options[:limit].to_i
- offset = options[:offset].to_i
- use_follow_flag = options[:follow] && options[:path].present?
-
- # We will perform the offset in Ruby because --follow doesn't play well with --skip.
- # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
- offset_in_ruby = use_follow_flag && options[:offset].present?
- limit += offset if offset_in_ruby
-
- cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
- cmd << "--max-count=#{limit}"
- cmd << '--format=%H'
- cmd << "--skip=#{offset}" unless offset_in_ruby
- cmd << '--follow' if use_follow_flag
- cmd << '--no-merges' if options[:skip_merges]
- cmd << "--after=#{options[:after].iso8601}" if options[:after]
- cmd << "--before=#{options[:before].iso8601}" if options[:before]
- cmd << sha
-
- # :path can be a string or an array of strings
- if options[:path].present?
- cmd << '--'
- cmd += Array(options[:path])
- end
-
- raw_output = IO.popen(cmd) { |io| io.read }
- lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
-
- lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
+ raw_log(options).map { |c| Commit.decorate(c) }
end
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/382
def count_commits(options)
cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} rev-list]
cmd << "--after=#{options[:after].iso8601}" if options[:after]
@@ -454,7 +348,7 @@ module Gitlab
# Counts the amount of commits between `from` and `to`.
def count_commits_between(from, to)
- commits_between(from, to).size
+ Commit.between(self, from, to).size
end
# Returns the SHA of the most recent common ancestor of +from+ and +to+
@@ -553,23 +447,35 @@ module Gitlab
# @repository.submodule_url_for('master', 'rack')
# # => git@localhost:rack.git
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/329
def submodule_url_for(ref, path)
- if submodules(ref).any?
- submodule = submodules(ref)[path]
-
- if submodule
- submodule['url']
+ Gitlab::GitalyClient.migrate(:submodule_url_for) do |is_enabled|
+ if is_enabled
+ gitaly_submodule_url_for(ref, path)
+ else
+ if submodules(ref).any?
+ submodule = submodules(ref)[path]
+ submodule['url'] if submodule
+ end
end
end
end
# Return total commits count accessible from passed ref
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/330
def commit_count(ref)
- walker = Rugged::Walker.new(rugged)
- walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
- oid = rugged.rev_parse_oid(ref)
- walker.push(oid)
- walker.count
+ gitaly_migrate(:commit_count) do |is_enabled|
+ if is_enabled
+ gitaly_commit_client.commit_count(ref)
+ else
+ walker = Rugged::Walker.new(rugged)
+ walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
+ oid = rugged.rev_parse_oid(ref)
+ walker.push(oid)
+ walker.count
+ end
+ end
end
# Sets HEAD to the commit specified by +ref+; +ref+ can be a branch or
@@ -770,7 +676,8 @@ module Gitlab
# create_branch("other-feature", "master")
def create_branch(ref, start_point = "HEAD")
rugged_ref = rugged.branches.create(ref, start_point)
- Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target)
+ target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
+ Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
rescue Rugged::ReferenceError => e
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
raise InvalidRef.new("Invalid reference #{start_point}")
@@ -829,6 +736,7 @@ module Gitlab
# Ex.
# repo.ls_files('master')
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/327
def ls_files(ref)
actual_ref = ref || root_ref
@@ -855,6 +763,7 @@ module Gitlab
raw_output.compact
end
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/328
def copy_gitattributes(ref)
begin
commit = lookup(ref)
@@ -896,8 +805,117 @@ module Gitlab
Gitlab::GitalyClient::Util.repository(@storage, @relative_path)
end
+ def gitaly_ref_client
+ @gitaly_ref_client ||= Gitlab::GitalyClient::RefService.new(self)
+ end
+
+ def gitaly_commit_client
+ @gitaly_commit_client ||= Gitlab::GitalyClient::CommitService.new(self)
+ end
+
+ def gitaly_repository_client
+ @gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
+ end
+
private
+ # Gitaly note: JV: Trying to get rid of the 'filter' option so we can implement this with 'git'.
+ def branches_filter(filter: nil, sort_by: nil)
+ branches = rugged.branches.each(filter).map do |rugged_ref|
+ begin
+ target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target)
+ Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit)
+ rescue Rugged::ReferenceError
+ # Omit invalid branch
+ end
+ end.compact
+
+ sort_branches(branches, sort_by)
+ end
+
+ def raw_log(options)
+ default_options = {
+ limit: 10,
+ offset: 0,
+ path: nil,
+ follow: false,
+ skip_merges: false,
+ disable_walk: false,
+ after: nil,
+ before: nil
+ }
+
+ options = default_options.merge(options)
+ options[:limit] ||= 0
+ options[:offset] ||= 0
+ actual_ref = options[:ref] || root_ref
+ begin
+ sha = sha_from_ref(actual_ref)
+ rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
+ # Return an empty array if the ref wasn't found
+ return []
+ end
+
+ if log_using_shell?(options)
+ log_by_shell(sha, options)
+ else
+ log_by_walk(sha, options)
+ end
+ end
+
+ def log_using_shell?(options)
+ options[:path].present? ||
+ options[:disable_walk] ||
+ options[:skip_merges] ||
+ options[:after] ||
+ options[:before]
+ end
+
+ def log_by_walk(sha, options)
+ walk_options = {
+ show: sha,
+ sort: Rugged::SORT_NONE,
+ limit: options[:limit],
+ offset: options[:offset]
+ }
+ Rugged::Walker.walk(rugged, walk_options).to_a
+ end
+
+ # Gitaly note: JV: although #log_by_shell shells out to Git I think the
+ # complexity is such that we should migrate it as Ruby before trying to
+ # do it in Go.
+ def log_by_shell(sha, options)
+ limit = options[:limit].to_i
+ offset = options[:offset].to_i
+ use_follow_flag = options[:follow] && options[:path].present?
+
+ # We will perform the offset in Ruby because --follow doesn't play well with --skip.
+ # See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
+ offset_in_ruby = use_follow_flag && options[:offset].present?
+ limit += offset if offset_in_ruby
+
+ cmd = %W[#{Gitlab.config.git.bin_path} --git-dir=#{path} log]
+ cmd << "--max-count=#{limit}"
+ cmd << '--format=%H'
+ cmd << "--skip=#{offset}" unless offset_in_ruby
+ cmd << '--follow' if use_follow_flag
+ cmd << '--no-merges' if options[:skip_merges]
+ cmd << "--after=#{options[:after].iso8601}" if options[:after]
+ cmd << "--before=#{options[:before].iso8601}" if options[:before]
+ cmd << sha
+
+ # :path can be a string or an array of strings
+ if options[:path].present?
+ cmd << '--'
+ cmd += Array(options[:path])
+ end
+
+ raw_output = IO.popen(cmd) { |io| io.read }
+ lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
+
+ lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
+ end
+
# We are trying to deprecate this method because it does a lot of work
# but it seems to be used only to look up submodule URL's.
# https://gitlab.com/gitlab-org/gitaly/issues/329
@@ -915,6 +933,18 @@ module Gitlab
fill_submodule_ids(commit, parser.parse)
end
+ def gitaly_submodule_url_for(ref, path)
+ # We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited.
+ commit_object = gitaly_commit_client.tree_entry(ref, path, 1)
+
+ return unless commit_object && commit_object.type == :COMMIT
+
+ gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
+ found_module = GitmodulesParser.new(gitmodules.data).parse[path]
+
+ found_module && found_module['url']
+ end
+
def alternate_object_directories
Gitlab::Git::Env.all.values_at(*ALLOWED_OBJECT_DIRECTORIES_VARIABLES).compact
end
@@ -1057,73 +1087,6 @@ module Gitlab
index
end
- # Return an array of BlobSnippets for lines in +file_contents+ that match
- # +query+
- def build_greps(file_contents, query, ref, filename)
- # The file_contents string is potentially huge so we make sure to loop
- # through it one line at a time. This gives Ruby the chance to GC lines
- # we are not interested in.
- #
- # We need to do a little extra work because we are not looking for just
- # the lines that matches the query, but also for the context
- # (surrounding lines). We will use Enumerable#each_cons to efficiently
- # loop through the lines while keeping surrounding lines on hand.
- #
- # First, we turn "foo\nbar\nbaz" into
- # [
- # [nil, -3], [nil, -2], [nil, -1],
- # ['foo', 0], ['bar', 1], ['baz', 3],
- # [nil, 4], [nil, 5], [nil, 6]
- # ]
- lines_with_index = Enumerator.new do |yielder|
- # Yield fake 'before' lines for the first line of file_contents
- (-SEARCH_CONTEXT_LINES..-1).each do |i|
- yielder.yield [nil, i]
- end
-
- # Yield the actual file contents
- count = 0
- file_contents.each_line do |line|
- line.chomp!
- yielder.yield [line, count]
- count += 1
- end
-
- # Yield fake 'after' lines for the last line of file_contents
- (count + 1..count + SEARCH_CONTEXT_LINES).each do |i|
- yielder.yield [nil, i]
- end
- end
-
- greps = []
-
- # Loop through consecutive blocks of lines with indexes
- lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block|
- # Get the 'middle' line and index from the block
- line, _ = line_block[SEARCH_CONTEXT_LINES]
-
- next unless line && line.match(/#{Regexp.escape(query)}/i)
-
- # Yay, 'line' contains a match!
- # Get an array with just the context lines (no indexes)
- match_with_context = line_block.map(&:first)
- # Remove 'nil' lines in case we are close to the first or last line
- match_with_context.compact!
-
- # Get the line number (1-indexed) of the first context line
- first_context_line_number = line_block[0][1] + 1
-
- greps << Gitlab::Git::BlobSnippet.new(
- ref,
- match_with_context,
- first_context_line_number,
- filename
- )
- end
-
- greps
- end
-
# Return the Rugged patches for the diff between +from+ and +to+.
def diff_patches(from, to, options = {}, *paths)
options ||= {}
@@ -1152,14 +1115,6 @@ module Gitlab
end
end
- def gitaly_ref_client
- @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
- end
-
- def gitaly_commit_client
- @gitaly_commit_client ||= Gitlab::GitalyClient::Commit.new(self)
- end
-
def gitaly_migrate(method, &block)
Gitlab::GitalyClient.migrate(method, &block)
rescue GRPC::NotFound => e
diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb
index a16b0ed76f4..2b5785a1f08 100644
--- a/lib/gitlab/git/rev_list.rb
+++ b/lib/gitlab/git/rev_list.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: will probably be migrated indirectly by migrating the call sites.
+
module Gitlab
module Git
class RevList
@@ -15,6 +17,8 @@ module Gitlab
end
# This methods returns an array of missed references
+ #
+ # Should become obsolete after https://gitlab.com/gitlab-org/gitaly/issues/348.
def missed_ref
execute([*base_args, '--max-count=1', oldrev, "^#{newrev}"])
end
diff --git a/lib/gitlab/git/tag.rb b/lib/gitlab/git/tag.rb
index b5342c3d310..bc4e160dce9 100644
--- a/lib/gitlab/git/tag.rb
+++ b/lib/gitlab/git/tag.rb
@@ -1,10 +1,12 @@
+# Gitaly note: JV: no RPC's here.
+#
module Gitlab
module Git
class Tag < Ref
attr_reader :object_sha
- def initialize(repository, name, target, message = nil)
- super(repository, name, target)
+ def initialize(repository, name, target, target_commit, message = nil)
+ super(repository, name, target, target_commit)
@message = message
end
diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb
index b9afa05c819..8e959c57c7c 100644
--- a/lib/gitlab/git/tree.rb
+++ b/lib/gitlab/git/tree.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: needs 1 RPC, migration is in progress.
+
module Gitlab
module Git
class Tree
@@ -10,36 +12,23 @@ module Gitlab
# Get list of tree objects
# for repository based on commit sha and path
# Uses rugged for raw objects
+ #
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320
def where(repository, sha, path = nil)
path = nil if path == '' || path == '/'
- commit = repository.lookup(sha)
- root_tree = commit.tree
-
- tree = if path
- id = find_id_by_path(repository, root_tree.oid, path)
- if id
- repository.lookup(id)
- else
- []
- end
- else
- root_tree
- end
-
- tree.map do |entry|
- new(
- id: entry[:oid],
- root_id: root_tree.oid,
- name: entry[:name],
- type: entry[:type],
- mode: entry[:filemode].to_s(8),
- path: path ? File.join(path, entry[:name]) : entry[:name],
- commit_id: sha
- )
+ Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
+ if is_enabled
+ client = Gitlab::GitalyClient::CommitService.new(repository)
+ client.tree_entries(repository, sha, path)
+ else
+ tree_entries_from_rugged(repository, sha, path)
+ end
end
end
+ private
+
# Recursive search of tree id for path
#
# Ex.
@@ -68,6 +57,34 @@ module Gitlab
entry[:oid]
end
end
+
+ def tree_entries_from_rugged(repository, sha, path)
+ commit = repository.lookup(sha)
+ root_tree = commit.tree
+
+ tree = if path
+ id = find_id_by_path(repository, root_tree.oid, path)
+ if id
+ repository.lookup(id)
+ else
+ []
+ end
+ else
+ root_tree
+ end
+
+ tree.map do |entry|
+ new(
+ id: entry[:oid],
+ root_id: root_tree.oid,
+ name: entry[:name],
+ type: entry[:type],
+ mode: entry[:filemode].to_s(8),
+ path: path ? File.join(path, entry[:name]) : entry[:name],
+ commit_id: sha
+ )
+ end
+ end
end
def initialize(options)
@@ -80,6 +97,10 @@ module Gitlab
encode! @name
end
+ def path
+ encode! @path
+ end
+
def dir?
type == :tree
end
diff --git a/lib/gitlab/git/util.rb b/lib/gitlab/git/util.rb
index 7973da2e8f8..4708f22dcb3 100644
--- a/lib/gitlab/git/util.rb
+++ b/lib/gitlab/git/util.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: no RPC's here.
+
module Gitlab
module Git
module Util
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 0e87ee30c98..a3c6b21a6a1 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -1,3 +1,5 @@
+# Gitaly note: JV: does not need to be migrated, works without a repo.
+
module Gitlab
module GitRefValidator
extend self
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index f605c06dfc3..c90ef282fdd 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -57,7 +57,7 @@ module Gitlab
metadata = yield(metadata) if block_given?
stub(service, storage).send(rpc, request, metadata)
end
-
+
def self.request_metadata(storage)
encoded_token = Base64.strict_encode64(token(storage).to_s)
{ metadata: { 'authorization' => "Bearer #{encoded_token}" } }
@@ -70,12 +70,8 @@ module Gitlab
params['gitaly_token'].presence || Gitlab.config.gitaly['token']
end
- def self.enabled?
- Gitlab.config.gitaly.enabled
- end
-
def self.feature_enabled?(feature, status: MigrationStatus::OPT_IN)
- return false if !enabled? || status == MigrationStatus::DISABLED
+ return false if status == MigrationStatus::DISABLED
feature = Feature.get("gitaly_#{feature}")
@@ -90,8 +86,8 @@ module Gitlab
feature.enabled?
end
- def self.migrate(feature)
- is_enabled = feature_enabled?(feature)
+ def self.migrate(feature, status: MigrationStatus::OPT_IN)
+ is_enabled = feature_enabled?(feature, status: status)
metric_name = feature.to_s
metric_name += "_gitaly" if is_enabled
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
new file mode 100644
index 00000000000..7ea8e8d0857
--- /dev/null
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module GitalyClient
+ class BlobService
+ def initialize(repository)
+ @gitaly_repo = repository.gitaly_repository
+ end
+
+ def get_blob(oid:, limit:)
+ request = Gitaly::GetBlobRequest.new(
+ repository: @gitaly_repo,
+ oid: oid,
+ limit: limit
+ )
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_blob, request)
+
+ blob = response.first
+ return unless blob.oid.present?
+
+ data = response.reduce(blob.data.dup) { |memo, msg| memo << msg.data.dup }
+
+ Gitlab::Git::Blob.new(
+ id: blob.oid,
+ size: blob.size,
+ data: data,
+ binary: Gitlab::Git::Blob.binary?(data)
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index b8877619797..61fe462d762 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -1,72 +1,13 @@
module Gitlab
module GitalyClient
class Commit
- # The ID of empty tree.
- # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
- EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
+ attr_reader :repository, :gitaly_commit
- def initialize(repository)
- @gitaly_repo = repository.gitaly_repository
- @repository = repository
- end
-
- def is_ancestor(ancestor_id, child_id)
- request = Gitaly::CommitIsAncestorRequest.new(
- repository: @gitaly_repo,
- ancestor_id: ancestor_id,
- child_id: child_id
- )
-
- GitalyClient.call(@repository.storage, :commit, :commit_is_ancestor, request).value
- end
-
- def diff_from_parent(commit, options = {})
- request_params = commit_diff_request_params(commit, options)
- request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
- request = Gitaly::CommitDiffRequest.new(request_params)
- response = GitalyClient.call(@repository.storage, :diff, :commit_diff, request)
- Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options)
- end
-
- def commit_deltas(commit)
- request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit))
- response = GitalyClient.call(@repository.storage, :diff, :commit_delta, request)
- response.flat_map do |msg|
- msg.deltas.map { |d| Gitlab::Git::Diff.new(d) }
- end
- end
+ delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit
- def tree_entry(ref, path, limit = nil)
- request = Gitaly::TreeEntryRequest.new(
- repository: @gitaly_repo,
- revision: ref,
- path: path.dup.force_encoding(Encoding::ASCII_8BIT),
- limit: limit.to_i
- )
-
- response = GitalyClient.call(@repository.storage, :commit, :tree_entry, request)
- entry = response.first
- return unless entry.oid.present?
-
- if entry.type == :BLOB
- rest_of_data = response.reduce("") { |memo, msg| memo << msg.data }
- entry.data += rest_of_data
- end
-
- entry
- end
-
- private
-
- def commit_diff_request_params(commit, options = {})
- parent_id = commit.parents[0]&.id || EMPTY_TREE_ID
-
- {
- repository: @gitaly_repo,
- left_commit_id: parent_id,
- right_commit_id: commit.id,
- paths: options.fetch(:paths, [])
- }
+ def initialize(repository, gitaly_commit)
+ @repository = repository
+ @gitaly_commit = gitaly_commit
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
new file mode 100644
index 00000000000..c6e52b530b3
--- /dev/null
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -0,0 +1,144 @@
+module Gitlab
+ module GitalyClient
+ class CommitService
+ # The ID of empty tree.
+ # See http://stackoverflow.com/a/40884093/1856239 and https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
+ EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
+
+ def initialize(repository)
+ @gitaly_repo = repository.gitaly_repository
+ @repository = repository
+ end
+
+ def is_ancestor(ancestor_id, child_id)
+ request = Gitaly::CommitIsAncestorRequest.new(
+ repository: @gitaly_repo,
+ ancestor_id: ancestor_id,
+ child_id: child_id
+ )
+
+ GitalyClient.call(@repository.storage, :commit_service, :commit_is_ancestor, request).value
+ end
+
+ def diff_from_parent(commit, options = {})
+ request_params = commit_diff_request_params(commit, options)
+ request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
+ request_params[:enforce_limits] = options.fetch(:limits, true)
+ request_params[:collapse_diffs] = request_params[:enforce_limits] || !options.fetch(:expanded, true)
+ request_params.merge!(Gitlab::Git::DiffCollection.collection_limits(options).to_h)
+
+ request = Gitaly::CommitDiffRequest.new(request_params)
+ response = GitalyClient.call(@repository.storage, :diff_service, :commit_diff, request)
+ Gitlab::Git::DiffCollection.new(GitalyClient::DiffStitcher.new(response), options.merge(from_gitaly: true))
+ end
+
+ def commit_deltas(commit)
+ request = Gitaly::CommitDeltaRequest.new(commit_diff_request_params(commit))
+ response = GitalyClient.call(@repository.storage, :diff_service, :commit_delta, request)
+ response.flat_map do |msg|
+ msg.deltas.map { |d| Gitlab::Git::Diff.new(d) }
+ end
+ end
+
+ def tree_entry(ref, path, limit = nil)
+ request = Gitaly::TreeEntryRequest.new(
+ repository: @gitaly_repo,
+ revision: ref,
+ path: path.dup.force_encoding(Encoding::ASCII_8BIT),
+ limit: limit.to_i
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :tree_entry, request)
+ entry = response.first
+ return unless entry.oid.present?
+
+ if entry.type == :BLOB
+ rest_of_data = response.reduce("") { |memo, msg| memo << msg.data }
+ entry.data += rest_of_data
+ end
+
+ entry
+ end
+
+ def tree_entries(repository, revision, path)
+ request = Gitaly::GetTreeEntriesRequest.new(
+ repository: @gitaly_repo,
+ revision: revision,
+ path: path.presence || '.'
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request)
+
+ response.flat_map do |message|
+ message.entries.map do |gitaly_tree_entry|
+ entry_path = gitaly_tree_entry.path.dup
+ Gitlab::Git::Tree.new(
+ id: gitaly_tree_entry.oid,
+ root_id: gitaly_tree_entry.root_oid,
+ type: gitaly_tree_entry.type.downcase,
+ mode: gitaly_tree_entry.mode.to_s(8),
+ name: File.basename(entry_path),
+ path: entry_path,
+ commit_id: gitaly_tree_entry.commit_oid
+ )
+ end
+ end
+ end
+
+ def commit_count(ref)
+ request = Gitaly::CountCommitsRequest.new(
+ repository: @gitaly_repo,
+ revision: ref
+ )
+
+ GitalyClient.call(@repository.storage, :commit_service, :count_commits, request).count
+ end
+
+ def between(from, to)
+ request = Gitaly::CommitsBetweenRequest.new(
+ repository: @gitaly_repo,
+ from: from,
+ to: to
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :commits_between, request)
+ consume_commits_response(response)
+ end
+
+ def find_all_commits(opts = {})
+ request = Gitaly::FindAllCommitsRequest.new(
+ repository: @gitaly_repo,
+ revision: opts[:ref].to_s,
+ max_count: opts[:max_count].to_i,
+ skip: opts[:skip].to_i
+ )
+ request.order = opts[:order].upcase if opts[:order].present?
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request)
+ consume_commits_response(response)
+ end
+
+ private
+
+ def commit_diff_request_params(commit, options = {})
+ parent_id = commit.parents[0]&.id || EMPTY_TREE_ID
+
+ {
+ repository: @gitaly_repo,
+ left_commit_id: parent_id,
+ right_commit_id: commit.id,
+ paths: options.fetch(:paths, [])
+ }
+ end
+
+ def consume_commits_response(response)
+ response.flat_map do |message|
+ message.commits.map do |gitaly_commit|
+ commit = GitalyClient::Commit.new(@repository, gitaly_commit)
+ Gitlab::Git::Commit.new(commit)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/diff.rb b/lib/gitlab/gitaly_client/diff.rb
index 1e117b7e74a..d459c9a88fb 100644
--- a/lib/gitlab/gitaly_client/diff.rb
+++ b/lib/gitlab/gitaly_client/diff.rb
@@ -1,7 +1,7 @@
module Gitlab
module GitalyClient
class Diff
- FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch).freeze
+ FIELDS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed).freeze
attr_accessor(*FIELDS)
diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notification_service.rb
index 78ed433e6b8..326e6f7dafc 100644
--- a/lib/gitlab/gitaly_client/notifications.rb
+++ b/lib/gitlab/gitaly_client/notification_service.rb
@@ -1,6 +1,6 @@
module Gitlab
module GitalyClient
- class Notifications
+ class NotificationService
# 'repository' is a Gitlab::Git::Repository
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@@ -10,7 +10,7 @@ module Gitlab
def post_receive
GitalyClient.call(
@storage,
- :notifications,
+ :notification_service,
:post_receive,
Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
)
diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref_service.rb
index 6edc69de078..2c3d53410ac 100644
--- a/lib/gitlab/gitaly_client/ref.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -1,6 +1,6 @@
module Gitlab
module GitalyClient
- class Ref
+ class RefService
include Gitlab::EncodingHelper
# 'repository' is a Gitlab::Git::Repository
@@ -12,19 +12,19 @@ module Gitlab
def default_branch_name
request = Gitaly::FindDefaultBranchNameRequest.new(repository: @gitaly_repo)
- response = GitalyClient.call(@storage, :ref, :find_default_branch_name, request)
+ response = GitalyClient.call(@storage, :ref_service, :find_default_branch_name, request)
Gitlab::Git.branch_name(response.name)
end
def branch_names
request = Gitaly::FindAllBranchNamesRequest.new(repository: @gitaly_repo)
- response = GitalyClient.call(@storage, :ref, :find_all_branch_names, request)
+ response = GitalyClient.call(@storage, :ref_service, :find_all_branch_names, request)
consume_refs_response(response) { |name| Gitlab::Git.branch_name(name) }
end
def tag_names
request = Gitaly::FindAllTagNamesRequest.new(repository: @gitaly_repo)
- response = GitalyClient.call(@storage, :ref, :find_all_tag_names, request)
+ response = GitalyClient.call(@storage, :ref_service, :find_all_tag_names, request)
consume_refs_response(response) { |name| Gitlab::Git.tag_name(name) }
end
@@ -34,7 +34,7 @@ module Gitlab
commit_id: commit_id,
prefix: ref_prefix
)
- encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup)
+ encode!(GitalyClient.call(@storage, :ref_service, :find_ref_name, request).name.dup)
end
def count_tag_names
@@ -48,7 +48,7 @@ module Gitlab
def local_branches(sort_by: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by
- response = GitalyClient.call(@storage, :ref, :find_local_branches, request)
+ response = GitalyClient.call(@storage, :ref_service, :find_local_branches, request)
consume_branches_response(response)
end
@@ -72,11 +72,39 @@ module Gitlab
Gitlab::Git::Branch.new(
@repository,
encode!(gitaly_branch.name.dup),
- gitaly_branch.commit_id
+ gitaly_branch.commit_id,
+ commit_from_local_branches_response(gitaly_branch)
)
end
end
end
+
+ def commit_from_local_branches_response(response)
+ # Git messages have no encoding enforcements. However, in the UI we only
+ # handle UTF-8, so basically we cross our fingers that the message force
+ # encoded to UTF-8 is readable.
+ message = response.commit_subject.dup.force_encoding('UTF-8')
+
+ # NOTE: For ease of parsing in Gitaly, we have only the subject of
+ # the commit and not the full message. This is ok, since all the
+ # code that uses `local_branches` only cares at most about the
+ # commit message.
+ # TODO: Once gitaly "takes over" Rugged consider separating the
+ # subject from the message to make it clearer when there's one
+ # available but not the other.
+ hash = {
+ id: response.commit_id,
+ message: message,
+ authored_date: Time.at(response.commit_author.date.seconds),
+ author_name: response.commit_author.name.dup,
+ author_email: response.commit_author.email.dup,
+ committed_date: Time.at(response.commit_committer.date.seconds),
+ committer_name: response.commit_committer.name.dup,
+ committer_email: response.commit_committer.email.dup
+ }
+
+ Gitlab::Git::Commit.decorate(hash)
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
new file mode 100644
index 00000000000..f5d84ea8762
--- /dev/null
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -0,0 +1,16 @@
+module Gitlab
+ module GitalyClient
+ class RepositoryService
+ def initialize(repository)
+ @repository = repository
+ @gitaly_repo = repository.gitaly_repository
+ end
+
+ def exists?
+ request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
+
+ GitalyClient.call(@repository.storage, :repository_service, :exists, request).exists
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
new file mode 100644
index 00000000000..e1d1724295a
--- /dev/null
+++ b/lib/gitlab/gpg.rb
@@ -0,0 +1,62 @@
+module Gitlab
+ module Gpg
+ extend self
+
+ module CurrentKeyChain
+ extend self
+
+ def add(key)
+ GPGME::Key.import(key)
+ end
+
+ def fingerprints_from_key(key)
+ import = GPGME::Key.import(key)
+
+ return [] if import.imported == 0
+
+ import.imports.map(&:fingerprint)
+ end
+ end
+
+ def fingerprints_from_key(key)
+ using_tmp_keychain do
+ CurrentKeyChain.fingerprints_from_key(key)
+ end
+ end
+
+ def primary_keyids_from_key(key)
+ using_tmp_keychain do
+ fingerprints = CurrentKeyChain.fingerprints_from_key(key)
+
+ GPGME::Key.find(:public, fingerprints).map { |raw_key| raw_key.primary_subkey.keyid }
+ end
+ end
+
+ def user_infos_from_key(key)
+ using_tmp_keychain do
+ fingerprints = CurrentKeyChain.fingerprints_from_key(key)
+
+ GPGME::Key.find(:public, fingerprints).flat_map do |raw_key|
+ raw_key.uids.map { |uid| { name: uid.name, email: uid.email } }
+ end
+ end
+ end
+
+ def using_tmp_keychain
+ Dir.mktmpdir do |dir|
+ @original_dirs ||= [GPGME::Engine.dirinfo('homedir')]
+ @original_dirs.push(dir)
+
+ GPGME::Engine.home_dir = dir
+
+ return_value = yield
+
+ @original_dirs.pop
+
+ GPGME::Engine.home_dir = @original_dirs[-1]
+
+ return_value
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
new file mode 100644
index 00000000000..55428b85207
--- /dev/null
+++ b/lib/gitlab/gpg/commit.rb
@@ -0,0 +1,85 @@
+module Gitlab
+ module Gpg
+ class Commit
+ attr_reader :commit
+
+ def initialize(commit)
+ @commit = commit
+
+ @signature_text, @signed_text = commit.raw.signature(commit.project.repository)
+ end
+
+ def has_signature?
+ !!(@signature_text && @signed_text)
+ end
+
+ def signature
+ return unless has_signature?
+
+ cached_signature = GpgSignature.find_by(commit_sha: commit.sha)
+ return cached_signature if cached_signature.present?
+
+ using_keychain do |gpg_key|
+ create_cached_signature!(gpg_key)
+ end
+ end
+
+ def update_signature!(cached_signature)
+ using_keychain do |gpg_key|
+ cached_signature.update_attributes!(attributes(gpg_key))
+ end
+ end
+
+ private
+
+ def using_keychain
+ Gitlab::Gpg.using_tmp_keychain do
+ # first we need to get the keyid from the signature to query the gpg
+ # key belonging to the keyid.
+ # This way we can add the key to the temporary keychain and extract
+ # the proper signature.
+ gpg_key = GpgKey.find_by(primary_keyid: verified_signature.fingerprint)
+
+ if gpg_key
+ Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
+ @verified_signature = nil
+ end
+
+ yield gpg_key
+ end
+ end
+
+ def verified_signature
+ @verified_signature ||= GPGME::Crypto.new.verify(@signature_text, signed_text: @signed_text) do |verified_signature|
+ break verified_signature
+ end
+ end
+
+ def create_cached_signature!(gpg_key)
+ GpgSignature.create!(attributes(gpg_key))
+ end
+
+ def attributes(gpg_key)
+ user_infos = user_infos(gpg_key)
+
+ {
+ commit_sha: commit.sha,
+ project: commit.project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: gpg_key&.primary_keyid || verified_signature.fingerprint,
+ gpg_key_user_name: user_infos[:name],
+ gpg_key_user_email: user_infos[:email],
+ valid_signature: gpg_signature_valid_signature_value(gpg_key)
+ }
+ end
+
+ def gpg_signature_valid_signature_value(gpg_key)
+ !!(gpg_key && gpg_key.verified? && verified_signature.valid?)
+ end
+
+ def user_infos(gpg_key)
+ gpg_key&.verified_user_infos&.first || gpg_key&.user_infos&.first || {}
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg/invalid_gpg_signature_updater.rb b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
new file mode 100644
index 00000000000..3bb491120ba
--- /dev/null
+++ b/lib/gitlab/gpg/invalid_gpg_signature_updater.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module Gpg
+ class InvalidGpgSignatureUpdater
+ def initialize(gpg_key)
+ @gpg_key = gpg_key
+ end
+
+ def run
+ GpgSignature
+ .select(:id, :commit_sha, :project_id)
+ .where('gpg_key_id IS NULL OR valid_signature = ?', false)
+ .where(gpg_key_primary_keyid: @gpg_key.primary_keyid)
+ .find_each do |gpg_signature|
+ Gitlab::Gpg::Commit.new(gpg_signature.commit).update_signature!(gpg_signature)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/base_abstract_check.rb b/lib/gitlab/health_checks/base_abstract_check.rb
index 7de6d4d9367..8b365dab185 100644
--- a/lib/gitlab/health_checks/base_abstract_check.rb
+++ b/lib/gitlab/health_checks/base_abstract_check.rb
@@ -27,10 +27,10 @@ module Gitlab
Metric.new(name, value, labels)
end
- def with_timing(proc)
+ def with_timing
start = Time.now
- result = proc.call
- yield result, Time.now.to_f - start.to_f
+ result = yield
+ [result, Time.now.to_f - start.to_f]
end
def catch_timeout(seconds, &block)
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index e78b7f22e03..9e91c135956 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -10,49 +10,47 @@ module Gitlab
def readiness
repository_storages.map do |storage_name|
begin
- tmp_file_path = tmp_file_path(storage_name)
-
if !storage_stat_test(storage_name)
HealthChecks::Result.new(false, 'cannot stat storage', shard: storage_name)
- elsif !storage_write_test(tmp_file_path)
- HealthChecks::Result.new(false, 'cannot write to storage', shard: storage_name)
- elsif !storage_read_test(tmp_file_path)
- HealthChecks::Result.new(false, 'cannot read from storage', shard: storage_name)
else
- HealthChecks::Result.new(true, nil, shard: storage_name)
+ with_temp_file(storage_name) do |tmp_file_path|
+ if !storage_write_test(tmp_file_path)
+ HealthChecks::Result.new(false, 'cannot write to storage', shard: storage_name)
+ elsif !storage_read_test(tmp_file_path)
+ HealthChecks::Result.new(false, 'cannot read from storage', shard: storage_name)
+ else
+ HealthChecks::Result.new(true, nil, shard: storage_name)
+ end
+ end
end
rescue RuntimeError => ex
message = "unexpected error #{ex} when checking storage #{storage_name}"
Rails.logger.error(message)
HealthChecks::Result.new(false, message, shard: storage_name)
- ensure
- delete_test_file(tmp_file_path)
end
end
end
def metrics
repository_storages.flat_map do |storage_name|
- tmp_file_path = tmp_file_path(storage_name)
[
- operation_metrics(:filesystem_accessible, :filesystem_access_latency, -> { storage_stat_test(storage_name) }, shard: storage_name),
- operation_metrics(:filesystem_writable, :filesystem_write_latency, -> { storage_write_test(tmp_file_path) }, shard: storage_name),
- operation_metrics(:filesystem_readable, :filesystem_read_latency, -> { storage_read_test(tmp_file_path) }, shard: storage_name)
+ storage_stat_metrics(storage_name),
+ storage_write_metrics(storage_name),
+ storage_read_metrics(storage_name)
].flatten
end
end
private
- def operation_metrics(ok_metric, latency_metric, operation, **labels)
- with_timing operation do |result, elapsed|
- [
- metric(latency_metric, elapsed, **labels),
- metric(ok_metric, result ? 1 : 0, **labels)
- ]
- end
+ def operation_metrics(ok_metric, latency_metric, **labels)
+ result, elapsed = yield
+ [
+ metric(latency_metric, elapsed, **labels),
+ metric(ok_metric, result ? 1 : 0, **labels)
+ ]
rescue RuntimeError => ex
- Rails.logger("unexpected error #{ex} when checking #{ok_metric}")
+ Rails.logger.error("unexpected error #{ex} when checking #{ok_metric}")
[metric(ok_metric, 0, **labels)]
end
@@ -68,19 +66,36 @@ module Gitlab
Gitlab::Popen.popen([TIMEOUT_EXECUTABLE, COMMAND_TIMEOUT].concat(cmd_args), *args, &block)
end
- def tmp_file_path(storage_name)
- Dir::Tmpname.create(%w(fs_shards_check +deleted), path(storage_name)) { |path| path }
+ def with_temp_file(storage_name)
+ temp_file_path = Dir::Tmpname.create(%w(fs_shards_check +deleted), storage_path(storage_name)) { |path| path }
+ yield temp_file_path
+ ensure
+ delete_test_file(temp_file_path)
end
- def path(storage_name)
+ def storage_path(storage_name)
storages_paths&.dig(storage_name, 'path')
end
+ # All below test methods use shell commands to perform actions on storage volumes.
+ # In case a storage volume have connectivity problems causing pure Ruby IO operation to wait indefinitely,
+ # we can rely on shell commands to be terminated once `timeout` kills them.
+ #
+ # However we also fallback to pure Ruby file operations in case a specific shell command is missing
+ # so we are still able to perform healthchecks and gather metrics from such system.
+
+ def delete_test_file(tmp_path)
+ _, status = exec_with_timeout(%W{ rm -f #{tmp_path} })
+ status.zero?
+ rescue Errno::ENOENT
+ File.delete(tmp_path) rescue Errno::ENOENT
+ end
+
def storage_stat_test(storage_name)
- stat_path = File.join(path(storage_name), '.')
+ stat_path = File.join(storage_path(storage_name), '.')
begin
_, status = exec_with_timeout(%W{ stat #{stat_path} })
- status == 0
+ status.zero?
rescue Errno::ENOENT
File.exist?(stat_path) && File::Stat.new(stat_path).readable?
end
@@ -90,7 +105,7 @@ module Gitlab
_, status = exec_with_timeout(%W{ tee #{tmp_path} }) do |stdin|
stdin.write(RANDOM_STRING)
end
- status == 0
+ status.zero?
rescue Errno::ENOENT
written_bytes = File.write(tmp_path, RANDOM_STRING) rescue Errno::ENOENT
written_bytes == RANDOM_STRING.length
@@ -100,17 +115,33 @@ module Gitlab
_, status = exec_with_timeout(%W{ diff #{tmp_path} - }) do |stdin|
stdin.write(RANDOM_STRING)
end
- status == 0
+ status.zero?
rescue Errno::ENOENT
file_contents = File.read(tmp_path) rescue Errno::ENOENT
file_contents == RANDOM_STRING
end
- def delete_test_file(tmp_path)
- _, status = exec_with_timeout(%W{ rm -f #{tmp_path} })
- status == 0
- rescue Errno::ENOENT
- File.delete(tmp_path) rescue Errno::ENOENT
+ def storage_stat_metrics(storage_name)
+ operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, shard: storage_name) do
+ with_timing { storage_stat_test(storage_name) }
+ end
+ end
+
+ def storage_write_metrics(storage_name)
+ operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, shard: storage_name) do
+ with_temp_file(storage_name) do |tmp_file_path|
+ with_timing { storage_write_test(tmp_file_path) }
+ end
+ end
+ end
+
+ def storage_read_metrics(storage_name)
+ operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, shard: storage_name) do
+ with_temp_file(storage_name) do |tmp_file_path|
+ storage_write_test(tmp_file_path) # writes data used by read test
+ with_timing { storage_read_test(tmp_file_path) }
+ end
+ end
end
end
end
diff --git a/lib/gitlab/health_checks/redis/cache_check.rb b/lib/gitlab/health_checks/redis/cache_check.rb
new file mode 100644
index 00000000000..a28658d42d4
--- /dev/null
+++ b/lib/gitlab/health_checks/redis/cache_check.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module HealthChecks
+ module Redis
+ class CacheCheck
+ extend SimpleAbstractCheck
+
+ class << self
+ def check_up
+ check
+ end
+
+ private
+
+ def metric_prefix
+ 'redis_cache_ping'
+ end
+
+ def is_successful?(result)
+ result == 'PONG'
+ end
+
+ def check
+ catch_timeout 10.seconds do
+ Gitlab::Redis::Cache.with(&:ping)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis/queues_check.rb b/lib/gitlab/health_checks/redis/queues_check.rb
new file mode 100644
index 00000000000..f97d50d3947
--- /dev/null
+++ b/lib/gitlab/health_checks/redis/queues_check.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module HealthChecks
+ module Redis
+ class QueuesCheck
+ extend SimpleAbstractCheck
+
+ class << self
+ def check_up
+ check
+ end
+
+ private
+
+ def metric_prefix
+ 'redis_queues_ping'
+ end
+
+ def is_successful?(result)
+ result == 'PONG'
+ end
+
+ def check
+ catch_timeout 10.seconds do
+ Gitlab::Redis::Queues.with(&:ping)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis/redis_check.rb b/lib/gitlab/health_checks/redis/redis_check.rb
new file mode 100644
index 00000000000..fe4e3c4a3ab
--- /dev/null
+++ b/lib/gitlab/health_checks/redis/redis_check.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module HealthChecks
+ module Redis
+ class RedisCheck
+ extend SimpleAbstractCheck
+
+ class << self
+ private
+
+ def metric_prefix
+ 'redis_ping'
+ end
+
+ def is_successful?(result)
+ result == 'PONG'
+ end
+
+ def check
+ ::Gitlab::HealthChecks::Redis::CacheCheck.check_up &&
+ ::Gitlab::HealthChecks::Redis::QueuesCheck.check_up &&
+ ::Gitlab::HealthChecks::Redis::SharedStateCheck.check_up
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis/shared_state_check.rb b/lib/gitlab/health_checks/redis/shared_state_check.rb
new file mode 100644
index 00000000000..e3244392902
--- /dev/null
+++ b/lib/gitlab/health_checks/redis/shared_state_check.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module HealthChecks
+ module Redis
+ class SharedStateCheck
+ extend SimpleAbstractCheck
+
+ class << self
+ def check_up
+ check
+ end
+
+ private
+
+ def metric_prefix
+ 'redis_shared_state_ping'
+ end
+
+ def is_successful?(result)
+ result == 'PONG'
+ end
+
+ def check
+ catch_timeout 10.seconds do
+ Gitlab::Redis::SharedState.with(&:ping)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis_check.rb b/lib/gitlab/health_checks/redis_check.rb
deleted file mode 100644
index 57bbe5b3ad0..00000000000
--- a/lib/gitlab/health_checks/redis_check.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Gitlab
- module HealthChecks
- class RedisCheck
- extend SimpleAbstractCheck
-
- class << self
- private
-
- def metric_prefix
- 'redis_ping'
- end
-
- def is_successful?(result)
- result == 'PONG'
- end
-
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis.with(&:ping)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb
index fbe1645c1b1..f5026171ba4 100644
--- a/lib/gitlab/health_checks/simple_abstract_check.rb
+++ b/lib/gitlab/health_checks/simple_abstract_check.rb
@@ -15,14 +15,13 @@ module Gitlab
end
def metrics
- with_timing method(:check) do |result, elapsed|
- Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless is_successful?(result)
- [
- metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
- metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
- metric("#{metric_prefix}_latency", elapsed)
- ]
- end
+ result, elapsed = with_timing(&method(:check))
+ Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless is_successful?(result)
+ [
+ metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
+ metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
+ metric("#{metric_prefix}_latency_seconds", elapsed)
+ ]
end
private
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index db7cdf4b5c7..cc282d1415b 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -7,12 +7,16 @@ module Gitlab
'es' => 'Español',
'de' => 'Deutsch',
'fr' => 'Français',
- 'pt_BR' => 'Português(Brasil)',
+ 'pt_BR' => 'Português (Brasil)',
'zh_CN' => '简体中文',
- 'zh_HK' => 'ç¹é«”中文(香港)',
- 'zh_TW' => 'ç¹é«”中文(臺ç£)',
+ 'zh_HK' => 'ç¹é«”中文 (香港)',
+ 'zh_TW' => 'ç¹é«”中文 (臺ç£)',
'bg' => 'българÑки',
- 'eo' => 'Esperanto'
+ 'ru' => 'РуÑÑкий',
+ 'eo' => 'Esperanto',
+ 'it' => 'Italiano',
+ 'uk' => 'УкраїнÑька',
+ 'ja' => '日本語'
}.freeze
def available_locales
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 1860352c96d..c8ad3a7a5e0 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -27,6 +27,7 @@ project_tree:
- :author
- :events
- merge_request_diff:
+ - :merge_request_diff_commits
- :merge_request_diff_files
- :events
- :timelogs
diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb
new file mode 100644
index 00000000000..977c05910d3
--- /dev/null
+++ b/lib/gitlab/issuable_metadata.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module IssuableMetadata
+ def issuable_meta_data(issuable_collection, collection_type)
+ # map has to be used here since using pluck or select will
+ # throw an error when ordering issuables by priority which inserts
+ # a new order into the collection.
+ # We cannot use reorder to not mess up the paginated collection.
+ issuable_ids = issuable_collection.map(&:id)
+
+ return {} if issuable_ids.empty?
+
+ issuable_note_count = ::Note.count_for_collection(issuable_ids, collection_type)
+ issuable_votes_count = ::AwardEmoji.votes_for_collection(issuable_ids, collection_type)
+ issuable_merge_requests_count =
+ if collection_type == 'Issue'
+ ::MergeRequestsClosingIssues.count_for_collection(issuable_ids)
+ else
+ []
+ end
+
+ issuable_ids.each_with_object({}) do |id, issuable_meta|
+ downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? }
+ upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? }
+ notes = issuable_note_count.find { |notes| notes.noteable_id == id }
+ merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id }
+
+ issuable_meta[id] = ::Issuable::IssuableMeta.new(
+ upvotes.try(:count).to_i,
+ downvotes.try(:count).to_i,
+ notes.try(:count).to_i,
+ merge_requests.try(:last).to_i
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index c56c1a4322f..cdbdfa10d0e 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -76,5 +76,44 @@ module Gitlab
url.to_s
end
+
+ def to_kubeconfig(url:, namespace:, token:, ca_pem: nil)
+ config = {
+ apiVersion: 'v1',
+ clusters: [
+ name: 'gitlab-deploy',
+ cluster: {
+ server: url
+ }
+ ],
+ contexts: [
+ name: 'gitlab-deploy',
+ context: {
+ cluster: 'gitlab-deploy',
+ namespace: namespace,
+ user: 'gitlab-deploy'
+ }
+ ],
+ 'current-context': 'gitlab-deploy',
+ kind: 'Config',
+ users: [
+ {
+ name: 'gitlab-deploy',
+ user: { token: token }
+ }
+ ]
+ }
+
+ kubeconfig_embed_ca_pem(config, ca_pem) if ca_pem
+
+ config.deep_stringify_keys
+ end
+
+ private
+
+ def kubeconfig_embed_ca_pem(config, ca_pem)
+ cluster = config.dig(:clusters, 0, :cluster)
+ cluster[:'certificate-authority-data'] = Base64.encode64(ca_pem)
+ end
end
end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index 7b05290e5cc..8867a91c244 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -101,7 +101,7 @@ module Gitlab
end
def user_attributes
- %W(#{config.uid} cn mail dn)
+ %W(#{config.uid} cn dn) + config.attributes['username'] + config.attributes['email']
end
end
end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index 6fdf68641e2..8eda3ea03f9 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -2,6 +2,12 @@
module Gitlab
module LDAP
class Config
+ NET_LDAP_ENCRYPTION_METHOD = {
+ simple_tls: :simple_tls,
+ start_tls: :start_tls,
+ plain: nil
+ }.freeze
+
attr_accessor :provider, :options
def self.enabled?
@@ -39,7 +45,7 @@ module Gitlab
def adapter_options
opts = base_options.merge(
- encryption: encryption
+ encryption: encryption_options
)
opts.merge!(auth_options) if has_auth?
@@ -50,9 +56,10 @@ module Gitlab
def omniauth_options
opts = base_options.merge(
base: base,
- method: options['method'],
+ encryption: options['encryption'],
filter: omniauth_user_filter,
- name_proc: name_proc
+ name_proc: name_proc,
+ disable_verify_certificates: !options['verify_certificates']
)
if has_auth?
@@ -62,6 +69,9 @@ module Gitlab
)
end
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
opts
end
@@ -157,15 +167,37 @@ module Gitlab
base_config.servers.values.find { |server| server['provider_name'] == provider }
end
- def encryption
- case options['method'].to_s
- when 'ssl'
- :simple_tls
- when 'tls'
- :start_tls
- else
- nil
- end
+ def encryption_options
+ method = translate_method(options['encryption'])
+ return nil unless method
+
+ {
+ method: method,
+ tls_options: tls_options(method)
+ }
+ end
+
+ def translate_method(method_from_config)
+ NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym]
+ end
+
+ def tls_options(method)
+ return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method
+
+ opts = if options['verify_certificates']
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
+ else
+ # It is important to explicitly set verify_mode for two reasons:
+ # 1. The behavior of OpenSSL is undefined when verify_mode is not set.
+ # 2. The net-ldap gem implementation verifies the certificate hostname
+ # unless verify_mode is set to VERIFY_NONE.
+ { verify_mode: OpenSSL::SSL::VERIFY_NONE }
+ end
+
+ opts[:ca_file] = options['ca_file'] if options['ca_file'].present?
+ opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present?
+
+ opts
end
def auth_options
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index 5f67e97fa2a..8e57ba831c5 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -18,10 +18,10 @@ module Gitlab
end
def token
- Gitlab::Redis.with do |redis|
- token = redis.get(redis_key)
+ Gitlab::Redis::SharedState.with do |redis|
+ token = redis.get(redis_shared_state_key)
token ||= Devise.friendly_token(TOKEN_LENGTH)
- redis.set(redis_key, token, ex: EXPIRY_TIME)
+ redis.set(redis_shared_state_key, token, ex: EXPIRY_TIME)
token
end
@@ -41,7 +41,7 @@ module Gitlab
private
- def redis_key
+ def redis_shared_state_key
"gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor
end
end
diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb
index 3503fac40e8..9f432673a6e 100644
--- a/lib/gitlab/mail_room.rb
+++ b/lib/gitlab/mail_room.rb
@@ -1,6 +1,6 @@
require 'yaml'
require 'json'
-require_relative 'redis' unless defined?(Gitlab::Redis)
+require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues)
module Gitlab
module MailRoom
@@ -34,11 +34,11 @@ module Gitlab
config[:idle_timeout] = 60 if config[:idle_timeout].nil?
if config[:enabled] && config[:address]
- gitlab_redis = Gitlab::Redis.new(rails_env)
- config[:redis_url] = gitlab_redis.url
+ gitlab_redis_queues = Gitlab::Redis::Queues.new(rails_env)
+ config[:redis_url] = gitlab_redis_queues.url
- if gitlab_redis.sentinels?
- config[:sentinels] = gitlab_redis.sentinels
+ if gitlab_redis_queues.sentinels?
+ config[:sentinels] = gitlab_redis_queues.sentinels
end
end
diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb
new file mode 100644
index 00000000000..219accfc029
--- /dev/null
+++ b/lib/gitlab/metrics/base_sampler.rb
@@ -0,0 +1,94 @@
+require 'logger'
+module Gitlab
+ module Metrics
+ class BaseSampler
+ def self.initialize_instance(*args)
+ raise "#{name} singleton instance already initialized" if @instance
+ @instance = new(*args)
+ at_exit(&@instance.method(:stop))
+ @instance
+ end
+
+ def self.instance
+ @instance
+ end
+
+ attr_reader :running
+
+ # interval - The sampling interval in seconds.
+ def initialize(interval)
+ interval_half = interval.to_f / 2
+
+ @interval = interval
+ @interval_steps = (-interval_half..interval_half).step(0.1).to_a
+
+ @mutex = Mutex.new
+ end
+
+ def enabled?
+ true
+ end
+
+ def start
+ return unless enabled?
+
+ @mutex.synchronize do
+ return if running
+ @running = true
+
+ @thread = Thread.new do
+ sleep(sleep_interval)
+
+ while running
+ safe_sample
+
+ sleep(sleep_interval)
+ end
+ end
+ end
+ end
+
+ def stop
+ @mutex.synchronize do
+ return unless running
+
+ @running = false
+
+ if @thread
+ @thread.wakeup if @thread.alive?
+ @thread.join
+ @thread = nil
+ end
+ end
+ end
+
+ def safe_sample
+ sample
+ rescue => e
+ Rails.logger.warn("#{self.class}: #{e}, stopping")
+ stop
+ end
+
+ def sample
+ raise NotImplementedError
+ end
+
+ # Returns the sleep interval with a random adjustment.
+ #
+ # The random adjustment is put in place to ensure we:
+ #
+ # 1. Don't generate samples at the exact same interval every time (thus
+ # potentially missing anything that happens in between samples).
+ # 2. Don't sample data at the same interval two times in a row.
+ def sleep_interval
+ while step = @interval_steps.sample
+ if step != @last_step
+ @last_step = step
+
+ return @interval + @last_step
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/influx_sampler.rb
index 0000450d9bb..6db1dd755b7 100644
--- a/lib/gitlab/metrics/sampler.rb
+++ b/lib/gitlab/metrics/influx_sampler.rb
@@ -5,14 +5,11 @@ module Gitlab
# This class is used to gather statistics that can't be directly associated
# with a transaction such as system memory usage, garbage collection
# statistics, etc.
- class Sampler
+ class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds.
def initialize(interval = Metrics.settings[:sample_interval])
- interval_half = interval.to_f / 2
-
- @interval = interval
- @interval_steps = (-interval_half..interval_half).step(0.1).to_a
- @last_step = nil
+ super(interval)
+ @last_step = nil
@metrics = []
@@ -26,18 +23,6 @@ module Gitlab
end
end
- def start
- Thread.new do
- Thread.current.abort_on_exception = true
-
- loop do
- sleep(sleep_interval)
-
- sample
- end
- end
- end
-
def sample
sample_memory_usage
sample_file_descriptors
@@ -86,7 +71,7 @@ module Gitlab
end
def sample_gc
- time = GC::Profiler.total_time * 1000.0
+ time = GC::Profiler.total_time * 1000.0
stats = GC.stat.merge(total_time: time)
# We want the difference of GC runs compared to the last sample, not the
@@ -111,23 +96,6 @@ module Gitlab
def sidekiq?
Sidekiq.server?
end
-
- # Returns the sleep interval with a random adjustment.
- #
- # The random adjustment is put in place to ensure we:
- #
- # 1. Don't generate samples at the exact same interval every time (thus
- # potentially missing anything that happens in between samples).
- # 2. Don't sample data at the same interval two times in a row.
- def sleep_interval
- while step = @interval_steps.sample
- if step != @last_step
- @last_step = step
-
- return @interval + @last_step
- end
- end
- end
end
end
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 9d314a56e58..460dab47276 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -6,9 +6,11 @@ module Gitlab
include Gitlab::CurrentSettings
def metrics_folder_present?
- ENV.has_key?('prometheus_multiproc_dir') &&
- ::Dir.exist?(ENV['prometheus_multiproc_dir']) &&
- ::File.writable?(ENV['prometheus_multiproc_dir'])
+ multiprocess_files_dir = ::Prometheus::Client.configuration.multiprocess_files_dir
+
+ multiprocess_files_dir &&
+ ::Dir.exist?(multiprocess_files_dir) &&
+ ::File.writable?(multiprocess_files_dir)
end
def prometheus_metrics_enabled?
@@ -29,8 +31,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels)
end
- def gauge(name, docstring, base_labels = {})
- provide_metric(name) || registry.gauge(name, docstring, base_labels)
+ def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
+ provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
new file mode 100644
index 00000000000..0dc19f31d03
--- /dev/null
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -0,0 +1,40 @@
+module Gitlab
+ module Metrics
+ class RequestsRackMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def self.http_request_total
+ @http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count')
+ end
+
+ def self.rack_uncaught_errors_count
+ @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request handling uncaught errors count')
+ end
+
+ def self.http_request_duration_seconds
+ @http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time',
+ {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25])
+ end
+
+ def call(env)
+ method = env['REQUEST_METHOD'].downcase
+ started = Time.now.to_f
+ begin
+ RequestsRackMiddleware.http_request_total.increment(method: method)
+
+ status, headers, body = @app.call(env)
+
+ elapsed = Time.now.to_f - started
+ RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method, status: status }, elapsed)
+
+ [status, headers, body]
+ rescue
+ RequestsRackMiddleware.rack_uncaught_errors_count.increment
+ raise
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/unicorn_sampler.rb b/lib/gitlab/metrics/unicorn_sampler.rb
new file mode 100644
index 00000000000..f6987252039
--- /dev/null
+++ b/lib/gitlab/metrics/unicorn_sampler.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Metrics
+ class UnicornSampler < BaseSampler
+ def initialize(interval)
+ super(interval)
+ end
+
+ def unicorn_active_connections
+ @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
+ end
+
+ def unicorn_queued_connections
+ @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ end
+
+ def enabled?
+ # Raindrops::Linux.tcp_listener_stats is only present on Linux
+ unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
+ end
+
+ def sample
+ Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
+ end
+
+ Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
+ end
+ end
+
+ private
+
+ def tcp_listeners
+ @tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
+ end
+
+ def unix_listeners
+ @unix_listeners ||= Unicorn.listener_names - tcp_listeners
+ end
+
+ def unicorn_with_listeners?
+ defined?(Unicorn) && Unicorn.listener_names.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index b3f453e506d..3f2bbd9f6a6 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -101,14 +101,18 @@ module Gitlab
# Look for a corresponding person with same uid in any of the configured LDAP providers
Gitlab::LDAP::Config.providers.each do |provider|
adapter = Gitlab::LDAP::Adapter.new(provider)
- @ldap_person = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
- # The `uid` might actually be a DN. Try it next.
- @ldap_person ||= Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
+ @ldap_person = find_ldap_person(auth_hash, adapter)
break if @ldap_person
end
@ldap_person
end
+ def find_ldap_person(auth_hash, adapter)
+ by_uid = Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
+ # The `uid` might actually be a DN. Try it next.
+ by_uid || Gitlab::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
+ end
+
def ldap_config
Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
end
diff --git a/lib/gitlab/otp_key_rotator.rb b/lib/gitlab/otp_key_rotator.rb
index 0d541935bc6..22332474945 100644
--- a/lib/gitlab/otp_key_rotator.rb
+++ b/lib/gitlab/otp_key_rotator.rb
@@ -34,7 +34,7 @@ module Gitlab
write_csv do |csv|
ActiveRecord::Base.transaction do
- User.with_two_factor.in_batches do |relation|
+ User.with_two_factor.in_batches do |relation| # rubocop: disable Cop/InBatches
rows = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt)
rows.each do |row|
user = %i[id ciphertext iv salt].zip(row).to_h
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 10eb99fb461..894bd5efae5 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -14,43 +14,42 @@ module Gitlab
TOP_LEVEL_ROUTES = %w[
-
.well-known
+ 404.html
+ 422.html
+ 500.html
+ 502.html
+ 503.html
abuse_reports
admin
- all
api
+ apple-touch-icon-precomposed.png
+ apple-touch-icon.png
assets
autocomplete
ci
dashboard
+ deploy.html
explore
+ favicon.ico
files
groups
health_check
help
- hooks
import
invites
- issues
jwt
koding
- member
- merge_requests
- new
- notes
notification_settings
oauth
profile
projects
public
- repository
robots.txt
s
search
sent_notifications
- services
+ slash-command-logo.png
snippets
- system
- teams
u
unicorn_test
unsubscribes
@@ -112,6 +111,7 @@ module Gitlab
# this group would not be accessible through `/groups/parent/activity` since
# this would map to the activity-page of its parent.
GROUP_ROUTES = %w[
+ -
activity
analytics
audit_events
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index 163a40ad306..56112ec2301 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -1,7 +1,34 @@
module Gitlab
module PerformanceBar
- def self.enabled?
- Feature.enabled?('gitlab_performance_bar')
+ include Gitlab::CurrentSettings
+
+ ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
+ EXPIRY_TIME = 5.minutes
+
+ def self.enabled?(user = nil)
+ return false unless user && allowed_group_id
+
+ allowed_user_ids.include?(user.id)
+ end
+
+ def self.allowed_group_id
+ current_application_settings.performance_bar_allowed_group_id
+ end
+
+ def self.allowed_user_ids
+ Rails.cache.fetch(ALLOWED_USER_IDS_KEY, expires_in: EXPIRY_TIME) do
+ group = Group.find_by_id(allowed_group_id)
+
+ if group
+ GroupMembersFinder.new(group).execute.pluck(:user_id)
+ else
+ []
+ end
+ end
+ end
+
+ def self.expire_allowed_user_ids_cache
+ Rails.cache.delete(ALLOWED_USER_IDS_KEY)
end
end
end
diff --git a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb b/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb
deleted file mode 100644
index d939a6ea18d..00000000000
--- a/lib/gitlab/performance_bar/peek_performance_bar_with_rack_body.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# This solves a bug with a X-Senfile header that wouldn't be set properly, see
-# https://github.com/peek/peek-performance_bar/pull/27
-module Gitlab
- module PerformanceBar
- module PeekPerformanceBarWithRackBody
- def call(env)
- @env = env
- reset_stats
-
- @total_requests += 1
- first_request if @total_requests == 1
-
- env['process.request_start'] = @start.to_f
- env['process.total_requests'] = total_requests
-
- status, headers, body = @app.call(env)
- body = Rack::BodyProxy.new(body) { record_request }
- [status, headers, body]
- end
- end
- end
-end
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
index 574ae8731a5..67fee8c227d 100644
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -1,4 +1,5 @@
# Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb
+# PEEK_DB_CLIENT is a constant set in config/initializers/peek.rb
module Gitlab
module PerformanceBar
module PeekQueryTracker
@@ -23,14 +24,20 @@ module Gitlab
subscribe('sql.active_record') do |_, start, finish, _, data|
if RequestStore.active? && RequestStore.store[:peek_enabled]
- track_query(data[:sql].strip, data[:binds], start, finish)
+ # data[:cached] is only available starting from Rails 5.1.0
+ # https://github.com/rails/rails/blob/v5.1.0/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L113
+ # Before that, data[:name] was set to 'CACHE'
+ # https://github.com/rails/rails/blob/v4.2.9/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb#L80
+ unless data.fetch(:cached, data[:name] == 'CACHE')
+ track_query(data[:sql].strip, data[:binds], start, finish)
+ end
end
end
end
def track_query(raw_query, bindings, start, finish)
query = Gitlab::Sherlock::Query.new(raw_query, start, finish)
- query_info = { duration: '%.3f' % query.duration, sql: query.formatted_query }
+ query_info = { duration: query.duration.round(3), sql: query.formatted_query }
PEEK_DB_CLIENT.query_details << query_info
end
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
deleted file mode 100644
index bc5370de32a..00000000000
--- a/lib/gitlab/redis.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-# This file should not have any direct dependency on Rails environment
-# please require all dependencies below:
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/module/delegation'
-
-module Gitlab
- class Redis
- CACHE_NAMESPACE = 'cache:gitlab'.freeze
- SESSION_NAMESPACE = 'session:gitlab'.freeze
- SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze
- MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
- DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
-
- class << self
- delegate :params, :url, to: :new
-
- def with
- @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) }
- @pool.with { |redis| yield redis }
- end
-
- def pool_size
- if Sidekiq.server?
- # the pool will be used in a multi-threaded context
- Sidekiq.options[:concurrency] + 5
- else
- # probably this is a Unicorn process, so single threaded
- 5
- end
- end
-
- def _raw_config
- return @_raw_config if defined?(@_raw_config)
-
- begin
- @_raw_config = ERB.new(File.read(config_file)).result.freeze
- rescue Errno::ENOENT
- @_raw_config = false
- end
-
- @_raw_config
- end
-
- def config_file
- ENV['GITLAB_REDIS_CONFIG_FILE'] || File.expand_path('../../config/resque.yml', __dir__)
- end
- end
-
- def initialize(rails_env = nil)
- @rails_env = rails_env || ::Rails.env
- end
-
- def params
- redis_store_options
- end
-
- def url
- raw_config_hash[:url]
- end
-
- def sentinels
- raw_config_hash[:sentinels]
- end
-
- def sentinels?
- sentinels && !sentinels.empty?
- end
-
- private
-
- def redis_store_options
- config = raw_config_hash
- redis_url = config.delete(:url)
- redis_uri = URI.parse(redis_url)
-
- if redis_uri.scheme == 'unix'
- # Redis::Store does not handle Unix sockets well, so let's do it for them
- config[:path] = redis_uri.path
- config
- else
- redis_hash = ::Redis::Store::Factory.extract_host_options_from_uri(redis_url)
- # order is important here, sentinels must be after the connection keys.
- # {url: ..., port: ..., sentinels: [...]}
- redis_hash.merge(config)
- end
- end
-
- def raw_config_hash
- config_data = fetch_config
-
- if config_data
- config_data.is_a?(String) ? { url: config_data } : config_data.deep_symbolize_keys
- else
- { url: DEFAULT_REDIS_URL }
- end
- end
-
- def fetch_config
- self.class._raw_config ? YAML.load(self.class._raw_config)[@rails_env] : false
- end
- end
-end
diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb
new file mode 100644
index 00000000000..b0da516ff83
--- /dev/null
+++ b/lib/gitlab/redis/cache.rb
@@ -0,0 +1,34 @@
+# please require all dependencies below:
+require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
+
+module Gitlab
+ module Redis
+ class Cache < ::Gitlab::Redis::Wrapper
+ CACHE_NAMESPACE = 'cache:gitlab'.freeze
+ DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'.freeze
+ REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'.freeze
+ if defined?(::Rails) && ::Rails.root.present?
+ DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.cache.yml').freeze
+ end
+
+ class << self
+ def default_url
+ DEFAULT_REDIS_CACHE_URL
+ end
+
+ def config_file_name
+ # if ENV set for this class, use it even if it points to a file does not exist
+ file_name = ENV[REDIS_CACHE_CONFIG_ENV_VAR_NAME]
+ return file_name unless file_name.nil?
+
+ # otherwise, if config files exists for this class, use it
+ file_name = File.expand_path(DEFAULT_REDIS_CACHE_CONFIG_FILE_NAME, __dir__)
+ return file_name if File.file?(file_name)
+
+ # this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb
new file mode 100644
index 00000000000..f9249d05565
--- /dev/null
+++ b/lib/gitlab/redis/queues.rb
@@ -0,0 +1,35 @@
+# please require all dependencies below:
+require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
+
+module Gitlab
+ module Redis
+ class Queues < ::Gitlab::Redis::Wrapper
+ SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze
+ MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
+ DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'.freeze
+ REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'.freeze
+ if defined?(::Rails) && ::Rails.root.present?
+ DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.queues.yml').freeze
+ end
+
+ class << self
+ def default_url
+ DEFAULT_REDIS_QUEUES_URL
+ end
+
+ def config_file_name
+ # if ENV set for this class, use it even if it points to a file does not exist
+ file_name = ENV[REDIS_QUEUES_CONFIG_ENV_VAR_NAME]
+ return file_name if file_name
+
+ # otherwise, if config files exists for this class, use it
+ file_name = File.expand_path(DEFAULT_REDIS_QUEUES_CONFIG_FILE_NAME, __dir__)
+ return file_name if File.file?(file_name)
+
+ # this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
new file mode 100644
index 00000000000..395dcf082da
--- /dev/null
+++ b/lib/gitlab/redis/shared_state.rb
@@ -0,0 +1,34 @@
+# please require all dependencies below:
+require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
+
+module Gitlab
+ module Redis
+ class SharedState < ::Gitlab::Redis::Wrapper
+ SESSION_NAMESPACE = 'session:gitlab'.freeze
+ DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
+ REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
+ if defined?(::Rails) && ::Rails.root.present?
+ DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME = ::Rails.root.join('config', 'redis.shared_state.yml').freeze
+ end
+
+ class << self
+ def default_url
+ DEFAULT_REDIS_SHARED_STATE_URL
+ end
+
+ def config_file_name
+ # if ENV set for this class, use it even if it points to a file does not exist
+ file_name = ENV[REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME]
+ return file_name if file_name
+
+ # otherwise, if config files exists for this class, use it
+ file_name = File.expand_path(DEFAULT_REDIS_SHARED_STATE_CONFIG_FILE_NAME, __dir__)
+ return file_name if File.file?(file_name)
+
+ # this will force use of DEFAULT_REDIS_SHARED_STATE_URL when config file is absent
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
new file mode 100644
index 00000000000..c43b37dde74
--- /dev/null
+++ b/lib/gitlab/redis/wrapper.rb
@@ -0,0 +1,135 @@
+# This file should only be used by sub-classes, not directly by any clients of the sub-classes
+# please require all dependencies below:
+require 'active_support/core_ext/hash/keys'
+require 'active_support/core_ext/module/delegation'
+
+module Gitlab
+ module Redis
+ class Wrapper
+ DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
+ REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'.freeze
+ if defined?(::Rails) && ::Rails.root.present?
+ DEFAULT_REDIS_CONFIG_FILE_NAME = ::Rails.root.join('config', 'resque.yml').freeze
+ end
+
+ class << self
+ delegate :params, :url, to: :new
+
+ def with
+ @pool ||= ConnectionPool.new(size: pool_size) { ::Redis.new(params) }
+ @pool.with { |redis| yield redis }
+ end
+
+ def pool_size
+ # heuristic constant 5 should be a config setting somewhere -- related to CPU count?
+ size = 5
+ if Sidekiq.server?
+ # the pool will be used in a multi-threaded context
+ size += Sidekiq.options[:concurrency]
+ end
+ size
+ end
+
+ def _raw_config
+ return @_raw_config if defined?(@_raw_config)
+
+ @_raw_config =
+ begin
+ if filename = config_file_name
+ ERB.new(File.read(filename)).result.freeze
+ else
+ false
+ end
+ rescue Errno::ENOENT
+ false
+ end
+ end
+
+ def default_url
+ DEFAULT_REDIS_URL
+ end
+
+ def config_file_name
+ # if ENV set for wrapper class, use it even if it points to a file does not exist
+ file_name = ENV[REDIS_CONFIG_ENV_VAR_NAME]
+ return file_name unless file_name.nil?
+
+ # otherwise, if config files exists for wrapper class, use it
+ file_name = File.expand_path(DEFAULT_REDIS_CONFIG_FILE_NAME, __dir__)
+ return file_name if File.file?(file_name)
+
+ # nil will force use of DEFAULT_REDIS_URL when config file is absent
+ nil
+ end
+ end
+
+ def initialize(rails_env = nil)
+ @rails_env = rails_env || ::Rails.env
+ end
+
+ def params
+ redis_store_options
+ end
+
+ def url
+ raw_config_hash[:url]
+ end
+
+ def sentinels
+ raw_config_hash[:sentinels]
+ end
+
+ def sentinels?
+ sentinels && !sentinels.empty?
+ end
+
+ private
+
+ def redis_store_options
+ config = raw_config_hash
+ redis_url = config.delete(:url)
+ redis_uri = URI.parse(redis_url)
+
+ if redis_uri.scheme == 'unix'
+ # Redis::Store does not handle Unix sockets well, so let's do it for them
+ config[:path] = redis_uri.path
+ query = redis_uri.query
+ unless query.nil?
+ queries = CGI.parse(redis_uri.query)
+ db_numbers = queries["db"] if queries.key?("db")
+ config[:db] = db_numbers[0].to_i if db_numbers.any?
+ end
+ config
+ else
+ redis_hash = ::Redis::Store::Factory.extract_host_options_from_uri(redis_url)
+ # order is important here, sentinels must be after the connection keys.
+ # {url: ..., port: ..., sentinels: [...]}
+ redis_hash.merge(config)
+ end
+ end
+
+ def raw_config_hash
+ config_data = fetch_config
+
+ if config_data
+ config_data.is_a?(String) ? { url: config_data } : config_data.deep_symbolize_keys
+ else
+ { url: self.class.default_url }
+ end
+ end
+
+ def fetch_config
+ return false unless self.class._raw_config
+
+ yaml = YAML.load(self.class._raw_config)
+
+ # If the file has content but it's invalid YAML, `load` returns false
+ if yaml
+ yaml.fetch(@rails_env, false)
+ else
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 7668ecacc4b..f5b757ace77 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -33,7 +33,12 @@ module Gitlab
def issues
if project && project.jira_tracker?
- @references[:external_issue] ||= references(:external_issue)
+ if project.issues_enabled?
+ @references[:all_issues] ||= references(:external_issue) + references(:issue)
+ else
+ @references[:external_issue] ||= references(:external_issue) +
+ references(:issue).select { |i| i.project_id != project.id }
+ end
else
@references[:issue] ||= references(:issue)
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index b706434217d..1adc5ec952a 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -19,27 +19,29 @@ module Gitlab
"It must start with letter, digit, emoji or '_'."
end
- def file_name_regex
- @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
- end
-
- def file_name_regex_message
- "can contain only letters, digits, '_', '-', '@', '+' and '.'."
- end
-
- def container_registry_reference_regex
- Gitlab::PathRegex.git_reference_regex
- end
-
##
- # Docker Distribution Registry 2.4.1 repository name rules
+ # Docker Distribution Registry repository / tag name rules
+ #
+ # See https://github.com/docker/distribution/blob/master/reference/regexp.go.
#
def container_repository_name_regex
@container_repository_regex ||= %r{\A[a-z0-9]+(?:[-._/][a-z0-9]+)*\Z}
end
+ ##
+ # We do not use regexp anchors here because these are not allowed when
+ # used as a routing constraint.
+ #
+ def container_registry_tag_regex
+ @container_registry_tag_regex ||= /[\w][\w.-]{0,127}/
+ end
+
+ def environment_name_regex_chars
+ 'a-zA-Z0-9_/\\$\\{\\}\\. -'
+ end
+
def environment_name_regex
- @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze
+ @environment_name_regex ||= /\A[#{environment_name_regex_chars}]+\z/.freeze
end
def environment_name_regex_message
diff --git a/lib/omni_auth/request_forgery_protection.rb b/lib/gitlab/request_forgery_protection.rb
index 69155131d8d..48dd0487790 100644
--- a/lib/omni_auth/request_forgery_protection.rb
+++ b/lib/gitlab/request_forgery_protection.rb
@@ -1,6 +1,8 @@
-# Protects OmniAuth request phase against CSRF.
+# A module to check CSRF tokens in requests.
+# It's used in API helpers and OmniAuth.
+# Usage: GitLab::RequestForgeryProtection.call(env)
-module OmniAuth
+module Gitlab
module RequestForgeryProtection
class Controller < ActionController::Base
protect_from_forgery with: :exception
@@ -17,5 +19,13 @@ module OmniAuth
def self.call(env)
app.call(env)
end
+
+ def self.verified?(env)
+ call(env)
+
+ true
+ rescue ActionController::InvalidAuthenticityToken
+ false
+ end
end
end
diff --git a/lib/gitlab/route_map.rb b/lib/gitlab/route_map.rb
index 877aa6e6a28..f3952657983 100644
--- a/lib/gitlab/route_map.rb
+++ b/lib/gitlab/route_map.rb
@@ -18,7 +18,11 @@ module Gitlab
mapping = @map.find { |mapping| mapping[:source] === path }
return unless mapping
- path.sub(mapping[:source], mapping[:public])
+ if mapping[:source].is_a?(String)
+ path.sub(mapping[:source], mapping[:public])
+ else
+ mapping[:source].replace(path, mapping[:public])
+ end
end
private
@@ -35,7 +39,7 @@ module Gitlab
source_pattern = source_pattern[1...-1].gsub('\/', '/')
begin
- source_pattern = /\A#{source_pattern}\z/
+ source_pattern = Gitlab::UntrustedRegexp.new('\A' + source_pattern + '\z')
rescue RegexpError => e
raise FormatError, "Route map entry source is not a valid regular expression: #{e}"
end
diff --git a/lib/gitlab/routes/legacy_builds.rb b/lib/gitlab/routes/legacy_builds.rb
deleted file mode 100644
index 36d1a8a6f64..00000000000
--- a/lib/gitlab/routes/legacy_builds.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module Gitlab
- module Routes
- class LegacyBuilds
- def initialize(map)
- @map = map
- end
-
- def draw
- @map.instance_eval do
- resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
- collection do
- resources :artifacts, only: [], controller: 'build_artifacts' do
- collection do
- get :latest_succeeded,
- path: '*ref_name_and_path',
- format: false
- end
- end
- end
-
- member do
- get :raw
- end
-
- resource :artifacts, only: [], controller: 'build_artifacts' do
- get :download
- get :browse, path: 'browse(/*path)', format: false
- get :file, path: 'file/*path', format: false
- get :raw, path: 'raw/*path', format: false
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb
index 632e2d87500..e57890f1143 100644
--- a/lib/gitlab/routing.rb
+++ b/lib/gitlab/routing.rb
@@ -2,10 +2,35 @@ module Gitlab
module Routing
extend ActiveSupport::Concern
+ mattr_accessor :_includers
+ self._includers = []
+
included do
+ Gitlab::Routing.includes_helpers(self)
+
include Gitlab::Routing.url_helpers
end
+ def self.includes_helpers(klass)
+ self._includers << klass
+ end
+
+ def self.add_helpers(mod)
+ url_helpers.include mod
+ url_helpers.extend mod
+
+ GitlabRoutingHelper.include mod
+ GitlabRoutingHelper.extend mod
+
+ app_url_helpers = Gitlab::Application.routes.named_routes.url_helpers_module
+ app_url_helpers.include mod
+ app_url_helpers.extend mod
+
+ _includers.each do |klass|
+ klass.include mod
+ end
+ end
+
# Returns the URL helpers Module.
#
# This method caches the output as Rails' "url_helpers" method creates an
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 0baea092e6a..4366ff336ef 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -1,3 +1,6 @@
+# Gitaly note: JV: two sets of straightforward RPC's. 1 Hard RPC: fork_repository.
+# SSH key operations are not part of Gitaly so will never be migrated.
+
require 'securerandom'
module Gitlab
@@ -68,6 +71,7 @@ module Gitlab
# Ex.
# add_repository("/path/to/storage", "gitlab/gitlab-ci")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def add_repository(storage, name)
gitlab_shell_fast_execute([gitlab_shell_projects_path,
'add-project', storage, "#{name}.git"])
@@ -81,6 +85,7 @@ module Gitlab
# Ex.
# import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def import_repository(storage, name, url)
# Timeout should be less than 900 ideally, to prevent the memory killer
# to silently kill the process without knowing we are timing out here.
@@ -99,6 +104,7 @@ module Gitlab
# Ex.
# fetch_remote("gitlab/gitlab-ci", "upstream")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def fetch_remote(storage, name, remote, forced: false, no_tags: false)
args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, "#{Gitlab.config.gitlab_shell.git_timeout}"]
args << '--force' if forced
@@ -115,6 +121,7 @@ module Gitlab
# Ex.
# mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def mv_repository(storage, path, new_path)
gitlab_shell_fast_execute([gitlab_shell_projects_path, 'mv-project',
storage, "#{path}.git", "#{new_path}.git"])
@@ -129,6 +136,7 @@ module Gitlab
# Ex.
# fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
#
+ # Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one.
def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
gitlab_shell_fast_execute([gitlab_shell_projects_path, 'fork-project',
forked_from_storage, "#{path}.git", forked_to_storage,
@@ -143,6 +151,7 @@ module Gitlab
# Ex.
# remove_repository("/path/to/storage", "gitlab/gitlab-ci")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def remove_repository(storage, name)
gitlab_shell_fast_execute([gitlab_shell_projects_path,
'rm-project', storage, "#{name}.git"])
@@ -194,6 +203,7 @@ module Gitlab
# Ex.
# add_namespace("/path/to/storage", "gitlab")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def add_namespace(storage, name)
path = full_path(storage, name)
FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name)
@@ -207,6 +217,7 @@ module Gitlab
# Ex.
# rm_namespace("/path/to/storage", "gitlab")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def rm_namespace(storage, name)
FileUtils.rm_r(full_path(storage, name), force: true)
end
@@ -216,6 +227,7 @@ module Gitlab
# Ex.
# mv_namespace("/path/to/storage", "gitlab", "gitlabhq")
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def mv_namespace(storage, old_name, new_name)
return false if exists?(storage, new_name) || !exists?(storage, old_name)
@@ -241,6 +253,7 @@ module Gitlab
# exists?(storage, 'gitlab')
# exists?(storage, 'gitlab/cookies.git')
#
+ # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def exists?(storage, dir_name)
File.exist?(full_path(storage, dir_name))
end
diff --git a/lib/gitlab/slash_commands/issue_command.rb b/lib/gitlab/slash_commands/issue_command.rb
index 87ea19b8806..3d96982b820 100644
--- a/lib/gitlab/slash_commands/issue_command.rb
+++ b/lib/gitlab/slash_commands/issue_command.rb
@@ -2,7 +2,7 @@ module Gitlab
module SlashCommands
class IssueCommand < BaseCommand
def self.available?(project)
- project.issues_enabled? && project.default_issues_tracker?
+ project.issues_enabled?
end
def collection
diff --git a/lib/gitlab/slash_commands/presenters/base.rb b/lib/gitlab/slash_commands/presenters/base.rb
index 27696436574..e13808a2720 100644
--- a/lib/gitlab/slash_commands/presenters/base.rb
+++ b/lib/gitlab/slash_commands/presenters/base.rb
@@ -2,7 +2,7 @@ module Gitlab
module SlashCommands
module Presenters
class Base
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
def initialize(resource = nil)
@resource = resource
diff --git a/lib/gitlab/sql/glob.rb b/lib/gitlab/sql/glob.rb
new file mode 100644
index 00000000000..5e89e12b2b1
--- /dev/null
+++ b/lib/gitlab/sql/glob.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module SQL
+ module Glob
+ extend self
+
+ # Convert a simple glob pattern with wildcard (*) to SQL LIKE pattern
+ # with SQL expression
+ def to_like(pattern)
+ <<~SQL
+ REPLACE(REPLACE(REPLACE(#{pattern},
+ #{q('%')}, #{q('\\%')}),
+ #{q('_')}, #{q('\\_')}),
+ #{q('*')}, #{q('%')})
+ SQL
+ end
+
+ def q(string)
+ ActiveRecord::Base.connection.quote(string)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
new file mode 100644
index 00000000000..7ce2e9d636e
--- /dev/null
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ # An untrusted regular expression is any regexp containing patterns sourced
+ # from user input.
+ #
+ # Ruby's built-in regular expression library allows patterns which complete in
+ # exponential time, permitting denial-of-service attacks.
+ #
+ # Not all regular expression features are available in untrusted regexes, and
+ # there is a strict limit on total execution time. See the RE2 documentation
+ # at https://github.com/google/re2/wiki/Syntax for more details.
+ class UntrustedRegexp
+ delegate :===, to: :regexp
+
+ def initialize(pattern)
+ @regexp = RE2::Regexp.new(pattern, log_errors: false)
+
+ raise RegexpError.new(regexp.error) unless regexp.ok?
+ end
+
+ def replace_all(text, rewrite)
+ RE2.GlobalReplace(text, regexp, rewrite)
+ end
+
+ def scan(text)
+ matches = scan_regexp.scan(text).to_a
+ matches.map!(&:first) if regexp.number_of_capturing_groups.zero?
+ matches
+ end
+
+ def replace(text, rewrite)
+ RE2.Replace(text, regexp, rewrite)
+ end
+
+ private
+
+ attr_reader :regexp
+
+ # RE2 scan operates differently to Ruby scan when there are no capture
+ # groups, so work around it
+ def scan_regexp
+ @scan_regexp ||=
+ if regexp.number_of_capturing_groups.zero?
+ RE2::Regexp.new('(' + regexp.source + ')')
+ else
+ regexp
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 23af9318d1a..824e2d7251f 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -1,6 +1,6 @@
module Gitlab
class UrlBuilder
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
include GitlabRoutingHelper
include ActionView::RecordIdentifier
@@ -23,9 +23,9 @@ module Gitlab
when WikiPage
wiki_page_url
when ProjectSnippet
- project_snippet_url(object)
+ project_snippet_url(object.project, object)
when Snippet
- personal_snippet_url(object)
+ snippet_url(object)
else
raise NotImplementedError.new("No URL builder defined for #{object.class}")
end
@@ -52,26 +52,24 @@ module Gitlab
commit_url(id: object.commit_id, anchor: dom_id(object))
elsif object.for_issue?
- issue = Issue.find(object.noteable_id)
- issue_url(issue, anchor: dom_id(object))
+ issue_url(object.noteable, anchor: dom_id(object))
elsif object.for_merge_request?
- merge_request = MergeRequest.find(object.noteable_id)
- merge_request_url(merge_request, anchor: dom_id(object))
+ merge_request_url(object.noteable, anchor: dom_id(object))
elsif object.for_snippet?
- snippet = Snippet.find(object.noteable_id)
+ snippet = object.noteable
if snippet.is_a?(PersonalSnippet)
snippet_url(snippet, anchor: dom_id(object))
else
- project_snippet_url(snippet, anchor: dom_id(object))
+ project_snippet_url(snippet.project, snippet, anchor: dom_id(object))
end
end
end
def wiki_page_url
- namespace_project_wiki_url(object.wiki.project.namespace, object.wiki.project, object.slug)
+ project_wiki_url(object.wiki.project, object.slug)
end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index f19b325a126..e0ac21305a5 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -39,14 +39,14 @@ module Gitlab
notes: Note.count,
pages_domains: PagesDomain.count,
projects: Project.count,
- projects_prometheus_active: PrometheusService.active.count,
+ projects_imported_from_github: Project.where(import_type: 'github').count,
protected_branches: ProtectedBranch.count,
releases: Release.count,
snippets: Snippet.count,
todos: Todo.count,
uploads: Upload.count,
web_hooks: WebHook.count
- }
+ }.merge(services_usage)
}
end
@@ -63,6 +63,18 @@ module Gitlab
usage_data
end
+
+ def services_usage
+ types = {
+ JiraService: :projects_jira_active,
+ SlackService: :projects_slack_notifications_active,
+ SlackSlashCommandsService: :projects_slack_slash_active,
+ PrometheusService: :projects_prometheus_active
+ }
+
+ results = Service.unscoped.where(type: types.keys, active: true).group(:type).count
+ results.each_with_object({}) { |(key, value), response| response[types[key.to_sym]] = value }
+ end
end
end
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 3b922da7ced..d9a5af09f08 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -1,5 +1,11 @@
module Gitlab
class UserAccess
+ extend Gitlab::Cache::RequestCache
+
+ request_cache_key do
+ [user&.id, project&.id]
+ end
+
attr_reader :user, :project
def initialize(user, project: nil)
@@ -28,43 +34,47 @@ module Gitlab
true
end
- def can_create_tag?(ref)
+ request_cache def can_create_tag?(ref)
return false unless can_access_git?
- if ProtectedTag.protected?(project, ref)
- project.protected_tags.protected_ref_accessible_to?(ref, user, action: :create)
+ if protected?(ProtectedTag, project, ref)
+ protected_tag_accessible_to?(ref, action: :create)
else
user.can?(:push_code, project)
end
end
- def can_delete_branch?(ref)
+ request_cache def can_delete_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
+ if protected?(ProtectedBranch, project, ref)
user.can?(:delete_protected_branch, project)
else
user.can?(:push_code, project)
end
end
- def can_push_to_branch?(ref)
+ def can_update_branch?(ref)
+ can_push_to_branch?(ref) || can_merge_to_branch?(ref)
+ end
+
+ request_cache def can_push_to_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
+ if protected?(ProtectedBranch, project, ref)
return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
- project.protected_branches.protected_ref_accessible_to?(ref, user, action: :push)
+ protected_branch_accessible_to?(ref, action: :push)
else
user.can?(:push_code, project)
end
end
- def can_merge_to_branch?(ref)
+ request_cache def can_merge_to_branch?(ref)
return false unless can_access_git?
- if ProtectedBranch.protected?(project, ref)
- project.protected_branches.protected_ref_accessible_to?(ref, user, action: :merge)
+ if protected?(ProtectedBranch, project, ref)
+ protected_branch_accessible_to?(ref, action: :merge)
else
user.can?(:push_code, project)
end
@@ -81,5 +91,23 @@ module Gitlab
def can_access_git?
user && user.can?(:access_git)
end
+
+ def protected_branch_accessible_to?(ref, action:)
+ ProtectedBranch.protected_ref_accessible_to?(
+ ref, user,
+ action: action,
+ protected_refs: project.protected_branches)
+ end
+
+ def protected_tag_accessible_to?(ref, action:)
+ ProtectedTag.protected_ref_accessible_to?(
+ ref, user,
+ action: action,
+ protected_refs: project.protected_tags)
+ end
+
+ request_cache def protected?(kind, project, ref)
+ kind.protected?(project, ref)
+ end
end
end
diff --git a/lib/gitlab/user_activities.rb b/lib/gitlab/user_activities.rb
index eb36ab9fded..125488536e1 100644
--- a/lib/gitlab/user_activities.rb
+++ b/lib/gitlab/user_activities.rb
@@ -6,13 +6,13 @@ module Gitlab
BATCH_SIZE = 500
def self.record(key, time = Time.now)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.hset(KEY, key, time.to_i)
end
end
def delete(*keys)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.hdel(KEY, keys)
end
end
@@ -21,7 +21,7 @@ module Gitlab
cursor = 0
loop do
cursor, pairs =
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.hscan(KEY, cursor, count: BATCH_SIZE)
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 48f3d950779..c60bd91ea6e 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -89,12 +89,12 @@ module Gitlab
end
def level_name(level)
- level_name = 'Unknown'
+ level_name = N_('VisibilityLevel|Unknown')
options.each do |name, lvl|
level_name = name if lvl == level.to_i
end
- level_name
+ s_(level_name)
end
def level_value(level)
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index f96ee69096d..3f25e463412 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -25,27 +25,28 @@ module Gitlab
RepoPath: repo_path
}
- if Gitlab.config.gitaly.enabled
- server = {
- address: Gitlab::GitalyClient.address(project.repository_storage),
- token: Gitlab::GitalyClient.token(project.repository_storage)
- }
- params[:Repository] = repository.gitaly_repository.to_h
-
- feature_enabled = case action.to_s
- when 'git_receive_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
- when 'git_upload_pack'
- Gitlab::GitalyClient.feature_enabled?(:post_upload_pack)
- when 'info_refs'
- true
- else
- raise "Unsupported action: #{action}"
- end
- if feature_enabled
- params[:GitalyAddress] = server[:address] # This field will be deprecated
- params[:GitalyServer] = server
- end
+ server = {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ }
+ params[:Repository] = repository.gitaly_repository.to_h
+
+ feature_enabled = case action.to_s
+ when 'git_receive_pack'
+ Gitlab::GitalyClient.feature_enabled?(:post_receive_pack)
+ when 'git_upload_pack'
+ Gitlab::GitalyClient.feature_enabled?(
+ :post_upload_pack,
+ status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ )
+ when 'info_refs'
+ true
+ else
+ raise "Unsupported action: #{action}"
+ end
+ if feature_enabled
+ params[:GitalyAddress] = server[:address] # This field will be deprecated
+ params[:GitalyServer] = server
end
params
@@ -64,10 +65,21 @@ module Gitlab
end
def send_git_blob(repository, blob)
- params = {
- 'RepoPath' => repository.path_to_repo,
- 'BlobId' => blob.id
- }
+ params = if Gitlab::GitalyClient.feature_enabled?(:workhorse_raw_show)
+ {
+ 'GitalyServer' => gitaly_server_hash(repository),
+ 'GetBlobRequest' => {
+ repository: repository.gitaly_repository.to_h,
+ oid: blob.id,
+ limit: -1
+ }
+ }
+ else
+ {
+ 'RepoPath' => repository.path_to_repo,
+ 'BlobId' => blob.id
+ }
+ end
[
SEND_DATA_HEADER,
@@ -178,7 +190,7 @@ module Gitlab
end
def set_key_and_notify(key, value, expire: nil, overwrite: true)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Queues.with do |redis|
result = redis.set(key, value, ex: expire, nx: !overwrite)
if result
redis.publish(NOTIFICATION_CHANNEL, "#{key}=#{value}")
@@ -194,6 +206,13 @@ module Gitlab
def encode(hash)
Base64.urlsafe_encode64(JSON.dump(hash))
end
+
+ def gitaly_server_hash(repository)
+ {
+ address: Gitlab::GitalyClient.address(repository.project.repository_storage),
+ token: Gitlab::GitalyClient.token(repository.project.repository_storage)
+ }
+ end
end
end
end
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
index 99f9c2c9b04..7cfe76b7b71 100644
--- a/lib/peek/rblineprof/custom_controller_helpers.rb
+++ b/lib/peek/rblineprof/custom_controller_helpers.rb
@@ -41,9 +41,14 @@ module Peek
]
end.sort_by{ |a,b,c,d,e,f| -f }
- output = ''
- per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
+ output = "<div class='modal-dialog modal-full'><div class='modal-content'>"
+ output << "<div class='modal-header'>"
+ output << "<button class='close btn btn-link btn-sm' type='button' data-dismiss='modal'>X</button>"
+ output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
+ output << "</div>"
+ output << "<div class='modal-body'>"
+ per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
output << "<div class='peek-rblineprof-file'><div class='heading'>"
show_src = file_sort > min
@@ -86,11 +91,32 @@ module Peek
output << "</div></div>" # .data then .peek-rblineprof-file
end
- response.body += "<div class='peek-rblineprof-modal' id='line-profile'>#{output}</div>".html_safe
+ output << "</div></div></div>"
+
+ response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output}</div>".html_safe
end
ret
end
+
+ private
+
+ def human_description(lineprofiler_param)
+ case lineprofiler_param
+ when 'app'
+ 'app/ & lib/'
+ when 'views'
+ 'app/view/'
+ when 'gems'
+ 'vendor/gems'
+ when 'all'
+ 'everything in Rails.root'
+ when 'stdlib'
+ 'everything in the Ruby standard library'
+ else
+ 'app/, config/, lib/, vendor/ & plugin/'
+ end
+ end
end
end
end
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 125a3d560d6..564aa141952 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -5,12 +5,12 @@ namespace :cache do
desc "GitLab | Clear redis cache"
task redis: :environment do
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
cursor = REDIS_SCAN_START_STOP
loop do
cursor, keys = redis.scan(
cursor,
- match: "#{Gitlab::Redis::CACHE_NAMESPACE}*",
+ match: "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}*",
count: REDIS_CLEAR_BATCH_SIZE
)
diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake
index b27f7475115..b48e4dce445 100644
--- a/lib/tasks/gettext.rake
+++ b/lib/tasks/gettext.rake
@@ -5,7 +5,7 @@ namespace :gettext do
# See: https://github.com/grosser/gettext_i18n_rails#customizing-list-of-translatable-files
def files_to_translate
folders = %W(app lib config #{locale_path}).join(',')
- exts = %w(rb erb haml slim rhtml js jsx vue coffee handlebars hbs mustache).join(',')
+ exts = %w(rb erb haml slim rhtml js jsx vue handlebars hbs mustache).join(',')
Dir.glob(
"{#{folders}}/**/*.{#{exts}}"
diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake
index 003d57adbbd..259a755d724 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -4,6 +4,7 @@ namespace :gitlab do
task compile: [
'yarn:check',
'rake:assets:precompile',
+ 'gettext:po_to_json',
'webpack:compile',
'fix_urls'
]
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index e3883278886..e9fb6a008b0 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -42,8 +42,7 @@ namespace :gitlab do
http_clone_url = project.http_url_to_repo
ssh_clone_url = project.ssh_url_to_repo
- omniauth_providers = Gitlab.config.omniauth.providers
- omniauth_providers.map! { |provider| provider['name'] }
+ omniauth_providers = Gitlab.config.omniauth.providers.map { |provider| provider['name'] }
puts ""
puts "GitLab information".color(:yellow)
diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb
index 964aa0fe1bc..28b2d86eed2 100644
--- a/lib/tasks/gitlab/task_helpers.rb
+++ b/lib/tasks/gitlab/task_helpers.rb
@@ -153,7 +153,6 @@ module Gitlab
clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
checkout_version(version, target_dir)
- reset_to_version(version, target_dir)
end
def clone_repo(repo, target_dir)
@@ -161,12 +160,8 @@ module Gitlab
end
def checkout_version(version, target_dir)
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet])
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}])
- end
-
- def reset_to_version(version, target_dir)
- run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}])
+ run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --])
end
end
end
diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake
index 4108cee08b4..9cc986535e1 100644
--- a/lib/tasks/migrate/setup_postgresql.rake
+++ b/lib/tasks/migrate/setup_postgresql.rake
@@ -4,6 +4,7 @@ require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lowe
require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes')
require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like')
+require Rails.root.join('db/migrate/20170724214302_add_lower_path_index_to_redirect_routes')
require Rails.root.join('db/migrate/20170503185032_index_redirect_routes_path_for_like')
desc 'GitLab | Sets up PostgreSQL'
@@ -12,5 +13,6 @@ task setup_postgresql: :environment do
AddUsersLowerUsernameEmailIndexes.new.up
AddLowerPathIndexToRoutes.new.up
IndexRoutesPathForLike.new.up
+ AddLowerPathIndexToRedirectRoutes.new.up
IndexRedirectRoutesPathForLike.new.up
end
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index dd1430700f8..1774c911d71 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -4,20 +4,39 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-20 06:26-0400\n"
+"PO-Revision-Date: 2017-07-13 08:13-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Bulgarian (https://translate.zanata.org/project/view/GitLab)\n"
"Language: bg\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "%s подаване беше пропуÑнато, за да не Ñе натоварва ÑиÑтемата."
+msgstr[1] "%s Ð¿Ð¾Ð´Ð°Ð²Ð°Ð½Ð¸Ñ Ð±Ñха пропуÑнати, за да не Ñе натоварва ÑиÑтемата."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d подаване"
+msgstr[1] "%d подаваниÑ"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} подаде %{commit_timeago}"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 Ñхема"
+msgstr[1] "%d Ñхеми"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Ðабор от графики отноÑно непрекъÑнатата интеграциÑ"
+
msgid "About auto deploy"
msgstr "ОтноÑно автоматичното внедрÑване"
@@ -67,9 +86,24 @@ msgstr ""
"автоматичното внедрÑване, изберете Yaml шаблон за GitLab CI и подайте "
"промените Ñи. %{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "ТърÑете в клоновете"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Превключване на клона"
+
msgid "Branches"
msgstr "Клонове"
+msgid "Browse Directory"
+msgstr "Преглед на папката"
+
+msgid "Browse File"
+msgstr "Преглед на файла"
+
+msgid "Browse Files"
+msgstr "Преглед на файловете"
+
msgid "Browse files"
msgstr "Разглеждане на файловете"
@@ -165,6 +199,9 @@ msgid_plural "Commits"
msgstr[0] "Подаване"
msgstr[1] "ПодаваниÑ"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Времетраене на подаваниÑта в минути за поÑледните 30 подаваниÑ"
+
msgid "Commit message"
msgstr "Съобщение за подаването"
@@ -177,6 +214,9 @@ msgstr "ДобавÑне на „%{file_name}“"
msgid "Commits"
msgstr "ПодаваниÑ"
+msgid "Commits feed"
+msgstr "Поток от подаваниÑ"
+
msgid "Commits|History"
msgstr "ИÑториÑ"
@@ -201,6 +241,13 @@ msgstr "Копиране на идентификатора на подаване
msgid "Create New Directory"
msgstr "Създаване на нова папка"
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Създайте Ñи личен жетон за доÑтъп в профила Ñи, за да можете да изтеглÑте и "
+"изпращате промени чрез %{protocol}."
+
msgid "Create directory"
msgstr "Създаване на папка"
@@ -219,6 +266,9 @@ msgstr "РазклонÑване"
msgid "CreateTag|Tag"
msgstr "Етикет"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "Ñи Ñъздадете личен жетон за доÑтъп"
+
msgid "Cron Timezone"
msgstr "ЧаÑова зона за „Cron“"
@@ -339,6 +389,9 @@ msgstr "Планът за Ñхема не може да бъде премахнÐ
msgid "Files"
msgstr "Файлове"
+msgid "Filter by commit message"
+msgstr "Филтриране по Ñъобщение"
+
msgid "Find by path"
msgstr "ТърÑене по път"
@@ -387,6 +440,15 @@ msgstr "Шаблон за интервала"
msgid "Introducing Cycle Analytics"
msgstr "ПредÑтавÑме Ви анализа на циклите"
+msgid "Jobs for last month"
+msgstr "Задачи за поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¼ÐµÑец"
+
+msgid "Jobs for last week"
+msgstr "Задачи за поÑледната Ñедмица"
+
+msgid "Jobs for last year"
+msgstr "Задачи за поÑледната година"
+
msgid "LFSStatus|Disabled"
msgstr "Изключено"
@@ -552,6 +614,21 @@ msgstr "План за Ñхема"
msgid "Pipeline Schedules"
msgstr "Планове за Ñхема"
+msgid "PipelineCharts|Failed:"
+msgstr "ÐеуÑпешни:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Обща ÑтатиÑтика"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Коефициент на уÑпех:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "УÑпешни:"
+
+msgid "PipelineCharts|Total:"
+msgstr "Общо:"
+
msgid "PipelineSchedules|Activated"
msgstr "Включено"
@@ -564,6 +641,12 @@ msgstr "Ð’Ñички"
msgid "PipelineSchedules|Inactive"
msgstr "Ðеактивно"
+msgid "PipelineSchedules|Input variable key"
+msgstr "Въведете ключ за променливата"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Въведете ÑтойноÑтта на променливата"
+
msgid "PipelineSchedules|Next Run"
msgstr "Следващо изпълнение"
@@ -573,15 +656,33 @@ msgstr "Ðищо"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Въведете кратко опиÑание за тази Ñхема"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Премахване на реда за променлива"
+
msgid "PipelineSchedules|Take ownership"
msgstr "Поемане на ÑобÑтвеноÑтта"
msgid "PipelineSchedules|Target"
msgstr "Цел"
+msgid "PipelineSchedules|Variables"
+msgstr "Променливи"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "ÑобÑтвен"
+msgid "Pipelines"
+msgstr "Схеми"
+
+msgid "Pipelines charts"
+msgstr "Графики за Ñхемите"
+
+msgid "Pipeline|all"
+msgstr "вÑички"
+
+msgid "Pipeline|success"
+msgstr "уÑпешни"
+
msgid "Pipeline|with stage"
msgstr "Ñ ÐµÑ‚Ð°Ð¿"
@@ -711,10 +812,10 @@ msgstr "Изберете чаÑова зона"
msgid "Select target branch"
msgstr "Изберете целеви клон"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
"Задайте парола на профила Ñи, за да можете да изтеглÑте и изпращате промени "
-"чрез %{protocol}"
+"чрез %{protocol}."
msgid "Set up CI"
msgstr "ÐаÑтройка на ÐИ"
@@ -1032,9 +1133,15 @@ msgstr "Качване на нов файл"
msgid "Upload file"
msgstr "Качване на файл"
+msgid "UploadLink|click to upload"
+msgstr "щракнете за качване"
+
msgid "Use your global notification setting"
msgstr "Използване на глобалната Ви наÑтройка за извеÑтиÑта"
+msgid "View open merge request"
+msgstr "Преглед на отворената заÑвка за Ñливане"
+
msgid "VisibilityLevel|Internal"
msgstr "Вътрешен"
@@ -1054,6 +1161,15 @@ msgid "Withdraw Access Request"
msgstr "ОттеглÑне на заÑвката за доÑтъп"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ðа път Ñте да премахнете „%{group_name}“.\n"
+"Ðко Ñ Ð¿Ñ€ÐµÐ¼Ð°Ñ…Ð½ÐµÑ‚Ðµ, групата ÐЕ може да бъде възÑтановена!\n"
+"ÐÐИСТИÐРли иÑкате това?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index bda3fc09e85..46bf4e33997 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -17,8 +17,8 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n"
-msgid "%d additional commit has been omitted to prevent performance issues."
-msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
@@ -619,6 +619,12 @@ msgstr ""
msgid "PipelineSchedules|Inactive"
msgstr ""
+msgid "PipelineSchedules|Input variable key"
+msgstr ""
+
+msgid "PipelineSchedules|Input variable value"
+msgstr ""
+
msgid "PipelineSchedules|Next Run"
msgstr ""
@@ -628,12 +634,18 @@ msgstr ""
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
+msgid "PipelineSchedules|Remove variable row"
+msgstr ""
+
msgid "PipelineSchedules|Take ownership"
msgstr ""
msgid "PipelineSchedules|Target"
msgstr ""
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
@@ -1057,6 +1069,12 @@ msgid "Withdraw Access Request"
msgstr ""
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 3ef9e19067d..62dbc2621f4 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -4,20 +4,39 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-20 06:24-0400\n"
+"PO-Revision-Date: 2017-07-13 08:46-0400\n"
"Last-Translator: Lyubomir Vasilev <lyubomirv@abv.bg>\n"
"Language-Team: Esperanto (https://translate.zanata.org/project/view/GitLab)\n"
"Language: eo\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "%s enmetado estis transsaltita, por ne troÅarÄi la sistemon."
+msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troÅarÄi la sistemon."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d enmetado"
+msgstr[1] "%d enmetadoj"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} enmetis %{commit_timeago}"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 ĉenstablo"
+msgstr[1] "%d ĉenstabloj"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Aro da diagramoj pri la seninterrompa integrado"
+
msgid "About auto deploy"
msgstr "Pri la aÅ­tomata disponigado"
@@ -67,9 +86,24 @@ msgstr ""
"disponigadon, bonvolu elekti Yaml-Åablonon por GitLab CI kaj enmeti viajn "
"ÅanÄojn. %{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Serĉu branĉon"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Iri al branĉo"
+
msgid "Branches"
msgstr "Branĉoj"
+msgid "Browse Directory"
+msgstr "Foliumi dosierujon"
+
+msgid "Browse File"
+msgstr "Foliumi dosieron"
+
+msgid "Browse Files"
+msgstr "Foliumi dosierojn"
+
msgid "Browse files"
msgstr "Elekti dosierojn"
@@ -165,6 +199,9 @@ msgid_plural "Commits"
msgstr[0] "Enmetado"
msgstr[1] "Enmetadoj"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "DaÅ­ro de la enmetadoj por la lastaj 30 enmetadoj"
+
msgid "Commit message"
msgstr "MesaÄo pri la enmetado"
@@ -177,6 +214,9 @@ msgstr "Aldoni „%{file_name}“"
msgid "Commits"
msgstr "Enmetadoj"
+msgid "Commits feed"
+msgstr "Fluo de enmetadoj"
+
msgid "Commits|History"
msgstr "Historio"
@@ -201,6 +241,13 @@ msgstr "Kopii la identigilon de la enmetado"
msgid "Create New Directory"
msgstr "Krei novan dosierujon"
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Kreu propran atingoĵetonon en via konto por ebligi al vi eltiri kaj alpuÅi "
+"per %{protocol}."
+
msgid "Create directory"
msgstr "Krei dosierujon"
@@ -219,6 +266,9 @@ msgstr "Disbranĉigi"
msgid "CreateTag|Tag"
msgstr "Etikedo"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "kreos propran atingoĵetonon"
+
msgid "Cron Timezone"
msgstr "Horzono por Cron"
@@ -339,6 +389,9 @@ msgstr "Ne eblas forigi la ĉenstablan planon"
msgid "Files"
msgstr "Dosieroj"
+msgid "Filter by commit message"
+msgstr "Filtri per mesaÄo"
+
msgid "Find by path"
msgstr "Trovi per dosierindiko"
@@ -388,6 +441,15 @@ msgstr "Intervala Åablono"
msgid "Introducing Cycle Analytics"
msgstr "Ni prezentas al vi la ciklan analizon"
+msgid "Jobs for last month"
+msgstr "Taskoj po la lasta monato"
+
+msgid "Jobs for last week"
+msgstr "Taskoj po la lasta semajno"
+
+msgid "Jobs for last year"
+msgstr "Taskoj po la lasta jaro"
+
msgid "LFSStatus|Disabled"
msgstr "MalÅaltita"
@@ -553,6 +615,21 @@ msgstr "Ĉenstabla plano"
msgid "Pipeline Schedules"
msgstr "Ĉenstablaj planoj"
+msgid "PipelineCharts|Failed:"
+msgstr "Malsukcesaj:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Äœenerala statistiko"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Proporcio de sukceso:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "Sukcesaj:"
+
+msgid "PipelineCharts|Total:"
+msgstr "Totalo:"
+
msgid "PipelineSchedules|Activated"
msgstr "Åœaltita"
@@ -565,6 +642,12 @@ msgstr "Ĉiuj"
msgid "PipelineSchedules|Inactive"
msgstr "MalÅaltitaj"
+msgid "PipelineSchedules|Input variable key"
+msgstr "Entajpu Ålosilon por la variablo"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Entajpu la valoron de la variablo"
+
msgid "PipelineSchedules|Next Run"
msgstr "Sekvanta plenumo"
@@ -574,15 +657,33 @@ msgstr "Nenio"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "Entajpu mallongan priskribon pri ĉi tiu ĉenstablo"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Forigi la variablan linion"
+
msgid "PipelineSchedules|Take ownership"
msgstr "Akiri posedon"
msgid "PipelineSchedules|Target"
msgstr "Celo"
+msgid "PipelineSchedules|Variables"
+msgstr "Variabloj"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Propra"
+msgid "Pipelines"
+msgstr "Ĉenstabloj"
+
+msgid "Pipelines charts"
+msgstr "Ĉenstablaj diagramoj"
+
+msgid "Pipeline|all"
+msgstr "ĉiuj"
+
+msgid "Pipeline|success"
+msgstr "sukcesaj"
+
msgid "Pipeline|with stage"
msgstr "kun etapo"
@@ -712,10 +813,10 @@ msgstr "Elektu horzonon"
msgid "Select target branch"
msgstr "Elektu celan branĉon"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr ""
"Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuÅi per "
-"%{protocol}"
+"%{protocol}."
msgid "Set up CI"
msgstr "Agordi SI"
@@ -1033,9 +1134,15 @@ msgstr "AlÅuti novan dosieron"
msgid "Upload file"
msgstr "AlÅuti dosieron"
+msgid "UploadLink|click to upload"
+msgstr "alklaku por alÅuti"
+
msgid "Use your global notification setting"
msgstr "Uzi vian Äeneralan agordon pri la sciigoj"
+msgid "View open merge request"
+msgstr "Vidi la malfermitan peton pri kunfando"
+
msgid "VisibilityLevel|Internal"
msgstr "Interna"
@@ -1056,6 +1163,15 @@ msgid "Withdraw Access Request"
msgstr "Nuligi la peton pri atingeblo"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vi forigos „%{group_name}“.\n"
+"Oni NE POVAS malfari la forigon de grupo!\n"
+"Ĉu vi estas ABSOLUTE certa?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index cc44a06cbc5..5c669d51a68 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2017-06-21 12:09-0500\n"
+"PO-Revision-Date: 2017-07-13 12:10-0500\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
@@ -17,19 +17,27 @@ msgstr ""
"Last-Translator: Bob Van Landuyt <bob@gitlab.com>\n"
"X-Generator: Poedit 2.0.2\n"
-msgid "%d additional commit has been omitted to prevent performance issues."
-msgid_plural "%d additional commits have been omitted to prevent performance issues."
-msgstr[0] "%d cambio adicional ha sido omitido para evitar problemas de rendimiento."
-msgstr[1] "%d cambios adicionales han sido omitidos para evitar problemas de rendimiento."
-
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] "%d cambio"
msgstr[1] "%d cambios"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "%s cambio adicional ha sido omitido para evitar problemas de rendimiento."
+msgstr[1] "%s cambios adicionales han sido omitidos para evitar problemas de rendimiento."
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} cambió %{commit_timeago}"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 pipeline"
+msgstr[1] "%d pipelines"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Una colección de gráficos sobre Integración Continua"
+
msgid "About auto deploy"
msgstr "Acerca del auto despliegue"
@@ -184,6 +192,9 @@ msgid_plural "Commits"
msgstr[0] "Cambio"
msgstr[1] "Cambios"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Duración de los cambios en minutos para los últimos 30"
+
msgid "Commit message"
msgstr "Mensaje del cambio"
@@ -223,6 +234,9 @@ msgstr "Copiar SHA del cambio al portapapeles"
msgid "Create New Directory"
msgstr "Crear Nuevo Directorio"
+msgid "Create a personal access token on your account to pull or push via %{protocol}."
+msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a través de %{protocol}."
+
msgid "Create directory"
msgstr "Crear directorio"
@@ -241,6 +255,9 @@ msgstr "Bifurcar"
msgid "CreateTag|Tag"
msgstr "Etiqueta"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "crear un token de acceso personal"
+
msgid "Cron Timezone"
msgstr "Zona horaria del Cron"
@@ -401,6 +418,15 @@ msgstr "Patrón de intervalo"
msgid "Introducing Cycle Analytics"
msgstr "Introducción a Cycle Analytics"
+msgid "Jobs for last month"
+msgstr "Trabajos del mes pasado"
+
+msgid "Jobs for last week"
+msgstr "Trabajos de la semana pasada"
+
+msgid "Jobs for last year"
+msgstr "Trabajos del año pasado"
+
msgid "LFSStatus|Disabled"
msgstr "Deshabilitado"
@@ -566,6 +592,21 @@ msgstr "Programación del Pipeline"
msgid "Pipeline Schedules"
msgstr "Programaciones de los Pipelines"
+msgid "PipelineCharts|Failed:"
+msgstr "Fallidos:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Estadísticas generales"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Ratio de éxito"
+
+msgid "PipelineCharts|Successful:"
+msgstr "Exitosos:"
+
+msgid "PipelineCharts|Total:"
+msgstr "Total:"
+
msgid "PipelineSchedules|Activated"
msgstr "Activado"
@@ -578,6 +619,12 @@ msgstr "Todos"
msgid "PipelineSchedules|Inactive"
msgstr "Inactivos"
+msgid "PipelineSchedules|Input variable key"
+msgstr "Ingrese nombre de clave"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Ingrese el valor de la variable"
+
msgid "PipelineSchedules|Next Run"
msgstr "Próxima Ejecución"
@@ -585,7 +632,10 @@ msgid "PipelineSchedules|None"
msgstr "Ninguno"
msgid "PipelineSchedules|Provide a short description for this pipeline"
-msgstr "Proporcione una breve descripción para este pipeline"
+msgstr "Proporcione una descripción breve para este pipeline"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Eliminar fila de variable"
msgid "PipelineSchedules|Take ownership"
msgstr "Tomar posesión"
@@ -593,9 +643,24 @@ msgstr "Tomar posesión"
msgid "PipelineSchedules|Target"
msgstr "Destino"
+msgid "PipelineSchedules|Variables"
+msgstr "Variables"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Personalizado"
+msgid "Pipelines"
+msgstr "Pipelines"
+
+msgid "Pipelines charts"
+msgstr "Gráficos de los pipelines"
+
+msgid "Pipeline|all"
+msgstr "todos"
+
+msgid "Pipeline|success"
+msgstr "exitósos"
+
msgid "Pipeline|with stage"
msgstr "con etapa"
@@ -719,8 +784,8 @@ msgstr "Selecciona una zona horaria"
msgid "Select target branch"
msgstr "Selecciona una rama de destino"
-msgid "Set a password on your account to pull or push via %{protocol}"
-msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}."
msgid "Set up CI"
msgstr "Configurar CI"
@@ -994,6 +1059,9 @@ msgstr "Privado"
msgid "VisibilityLevel|Public"
msgstr "Público"
+msgid "VisibilityLevel|Unknown"
+msgstr "Desconocido"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "¿Quieres ver los datos? Por favor pide acceso al administrador."
@@ -1004,6 +1072,15 @@ msgid "Withdraw Access Request"
msgstr "Retirar Solicitud de Acceso"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Va a eliminar %{group_name}.\n"
+"¡El grupo eliminado NO puede ser restaurado!\n"
+"¿Estás TOTALMENTE seguro?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 2000fa433b4..959654c7849 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -1,32 +1,308 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the gitlab package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# Dremor <egeorget@opmbx.org>, 2017. #zanata
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Rémy Coutable <remy@rymai.me>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-14 04:21-0400\n"
+"PO-Revision-Date: 2017-07-19 09:45-0400\n"
"Last-Translator: Dremor <egeorget@opmbx.org>\n"
-"Language-Team: French (https://www.transifex.com/gitlab-fr/teams/75145/fr/)\n"
+"Language-Team: French (https://translate.zanata.org/project/view/GitLab)\n"
"Language: fr\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s validation supplémentaire a été masquée afin d'éviter de créer de "
+"problèmes de performances."
+msgstr[1] ""
+"%s validations supplémentaires ont été masquées afin d'éviter de créer de "
+"problèmes de performances."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d validation"
+msgstr[1] "%d validations"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} a validé %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 pipeline"
+msgstr[1] "%d pipelines"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Un ensemble de graphiques concernant l’Intégration Continue (CI)"
+
+msgid "About auto deploy"
+msgstr "A propos de l'auto-déploiement"
+
+msgid "Active"
+msgstr "Actif"
+
+msgid "Activity"
+msgstr "Activité"
+
+msgid "Add Changelog"
+msgstr "Ajouter un journal des modifications"
+
+msgid "Add Contribution guide"
+msgstr "Ajouter un guide de contribution"
+
+msgid "Add License"
+msgstr "Ajouter une licence"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Ajoutez une clef SSH à votre profil pour pouvoir récupérer et pousser par "
+"SSH."
+
+msgid "Add new directory"
+msgstr "Ajouter un nouveau dossier"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Projet archivé ! Le dépôt est en lecture seule"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Êtes-vous sûr de vouloir supprimer ce pipeline programmé"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Attachez un fichier par glisser &amp; déposer ou %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branche"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branche <strong>%{branch_name}</strong> a été crée. Pour mettre en place "
+"le déploiement automatisé, sélectionnez un modèle de fichier Yaml pour "
+"l'intégration continue (CI) de GitLab, et validez les modifications. "
+"%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Rechercher la branche"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Changer de branche"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Parcourir le dossier"
+
+msgid "Browse File"
+msgstr "Parcourir le fichier"
+
+msgid "Browse Files"
+msgstr "Parcourir les fichiers"
+
+msgid "Browse files"
+msgstr "Parcourir les fichiers"
msgid "ByAuthor|by"
msgstr "par"
+msgid "CI configuration"
+msgstr "Configuration de l'intégration continue (CI)"
+
+msgid "Cancel"
+msgstr "Annuler"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Sélectionner dans la branche"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Annuler dans la branche"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Sélectionner"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Annuler"
+
+msgid "Changelog"
+msgstr "Journal des modifications"
+
+msgid "Charts"
+msgstr "Graphiques"
+
+msgid "Cherry-pick this commit"
+msgstr "Sélectionner cette validation"
+
+msgid "Cherry-pick this merge request"
+msgstr "Sélectionner cette demande de fusion"
+
+msgid "CiStatusLabel|canceled"
+msgstr "annulé"
+
+msgid "CiStatusLabel|created"
+msgstr "créé"
+
+msgid "CiStatusLabel|failed"
+msgstr "échoué"
+
+msgid "CiStatusLabel|manual action"
+msgstr "action manuelle"
+
+msgid "CiStatusLabel|passed"
+msgstr "passé"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "passé avec des avertissements"
+
+msgid "CiStatusLabel|pending"
+msgstr "en attente"
+
+msgid "CiStatusLabel|skipped"
+msgstr "ignoré"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "en attente d'action manuelle"
+
+msgid "CiStatusText|blocked"
+msgstr "bloqué"
+
+msgid "CiStatusText|canceled"
+msgstr "annulé "
+
+msgid "CiStatusText|created"
+msgstr "créé"
+
+msgid "CiStatusText|failed"
+msgstr "échoué"
+
+msgid "CiStatusText|manual"
+msgstr "manuel"
+
+msgid "CiStatusText|passed"
+msgstr "passé"
+
+msgid "CiStatusText|pending"
+msgstr "en attente"
+
+msgid "CiStatusText|skipped"
+msgstr "ignoré"
+
+msgid "CiStatus|running"
+msgstr "en cours"
+
msgid "Commit"
msgid_plural "Commits"
msgstr[0] "Validation"
msgstr[1] "Validations"
-msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
-msgstr "L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire pour aller d’une idée à sa mise en production pour votre projet."
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Durée des 30 derniers pipelines en minutes"
+
+msgid "Commit message"
+msgstr "Message de validation"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Validation"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Ajout de %{file_name}"
+
+msgid "Commits"
+msgstr "Validations"
+
+msgid "Commits feed"
+msgstr "Flux de validations"
+
+msgid "Commits|History"
+msgstr "Historique"
+
+msgid "Committed by"
+msgstr "Validé par"
+
+msgid "Compare"
+msgstr "Comparer"
+
+msgid "Contribution guide"
+msgstr "Guilde de contribution"
+
+msgid "Contributors"
+msgstr "Contributeurs"
+
+msgid "Copy URL to clipboard"
+msgstr "Copier l'URL dans le presse-papier"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copier le SHA de la validation"
+
+msgid "Create New Directory"
+msgstr "Créer un nouveau dossier"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Créer un jeton d’accès personnel pour votre compte afin de récupérer ou "
+"pousser par %{protocol}."
+
+msgid "Create directory"
+msgstr "Créer un dossier"
+
+msgid "Create empty bare repository"
+msgstr "Créer un dépôt vide"
+
+msgid "Create merge request"
+msgstr "Créer une demande de fusion"
+
+msgid "Create new..."
+msgstr "Créer nouveau..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fourcher"
+
+msgid "CreateTag|Tag"
+msgstr "Étiquette"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "Créer un jeton d'accès personnel"
+
+msgid "Cron Timezone"
+msgstr "Fuseau horaire de Cron"
+
+msgid "Cron syntax"
+msgstr "Syntaxe Cron"
+
+msgid "Custom notification events"
+msgstr "Événements de notification personnalisés"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"Le niveau de notification Personnalisé est similaire au niveau Participation."
+" Cependant, il permet également de recevoir des notifications pour des "
+"événements sélectionnés. Pour plus d’information, vous pouvez consulter "
+"%{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Analyseur de cycle"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"L’analyseur de cycle permet d’avoir une vue d’ensemble du temps nécessaire "
+"pour aller d’une idée à sa mise en production pour votre projet."
msgid "CycleAnalyticsStage|Code"
msgstr "Code"
@@ -49,31 +325,169 @@ msgstr "Pré-production"
msgid "CycleAnalyticsStage|Test"
msgstr "Test"
+msgid "Define a custom pattern with cron syntax"
+msgstr "Définir un schéma personnalisé avec une syntaxe Cron"
+
+msgid "Delete"
+msgstr "Supprimer"
+
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "Déploiement"
msgstr[1] "Déploiements"
+msgid "Description"
+msgstr "Description"
+
+msgid "Directory name"
+msgstr "Nom du dossier"
+
+msgid "Don't show again"
+msgstr "Ne plus montrer"
+
+msgid "Download"
+msgstr "Télécharger"
+
+msgid "Download tar"
+msgstr "Télécharger tar"
+
+msgid "Download tar.bz2"
+msgstr "Télécharger tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Télécharger tar.gz"
+
+msgid "Download zip"
+msgstr "Télécharger zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Télécharger"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Patch email"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Diff simple"
+
+msgid "DownloadSource|Download"
+msgstr "Télécharger"
+
+msgid "Edit"
+msgstr "Éditer"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Éditer le pipeline programmé %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Chaque jour (à 4:00 du matin)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Chaque mois (le 1er à 4:00 du matin)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Chaque semaine (dimanche à 4:00 du matin)"
+
+msgid "Failed to change the owner"
+msgstr "Échec du changement de propriétaire"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Échec de la suppression du pipeline programmé"
+
+msgid "Files"
+msgstr "Fichiers"
+
+msgid "Filter by commit message"
+msgstr "Filtrer par message de validation"
+
+msgid "Find by path"
+msgstr "Rechercher par chemin"
+
+msgid "Find file"
+msgstr "Rechercher un fichier"
+
msgid "FirstPushedBy|First"
msgstr "En premier"
msgid "FirstPushedBy|pushed by"
msgstr "poussé par"
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fourche"
+msgstr[1] "Fourches"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Fouché depuis"
+
msgid "From issue creation until deploy to production"
msgstr "Depuis la création de l'incident jusqu'au déploiement en production"
msgid "From merge request merge until deploy to production"
-msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
+msgstr ""
+"Depuis la fusion de la demande de fusion jusqu'au déploiement en production"
+
+msgid "Go to your fork"
+msgstr "Aller à votre fourche"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fourche"
+
+msgid "Home"
+msgstr "Accueil"
+
+msgid "Housekeeping successfully started"
+msgstr "Maintenance démarrée avec succès"
+
+msgid "Import repository"
+msgstr "Importer un dépôt"
+
+msgid "Interval Pattern"
+msgstr "Schéma d’intervalle"
msgid "Introducing Cycle Analytics"
msgstr "Introduction à l'analyseur de cycle"
+msgid "Jobs for last month"
+msgstr "Tâches pour le mois dernier"
+
+msgid "Jobs for last week"
+msgstr "Tâches pour la semaine dernière"
+
+msgid "Jobs for last year"
+msgstr "Tâches pour l'année dernière"
+
+msgid "LFSStatus|Disabled"
+msgstr "Désactivé"
+
+msgid "LFSStatus|Enabled"
+msgstr "Activé"
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Le dernier %d jour"
msgstr[1] "Les derniers %d jours"
+msgid "Last Pipeline"
+msgstr "Dernier pipeline"
+
+msgid "Last Update"
+msgstr "Dernière mise à jour"
+
+msgid "Last commit"
+msgstr "Dernière validation"
+
+msgid "Learn more in the"
+msgstr "En apprendre plus dans le"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentation concernant la programmation de pipeline"
+
+msgid "Leave group"
+msgstr "Quitter le groupe"
+
+msgid "Leave project"
+msgstr "Quitter le projet"
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] "Limiter l'affichage au plus à %d évènement"
@@ -82,29 +496,276 @@ msgstr[1] "Limiter l'affichage au plus à %d évènements"
msgid "Median"
msgstr "Médian"
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "ajouter une clef SSH"
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] "Nouvel incident"
msgstr[1] "Nouveaux incidents"
+msgid "New Pipeline Schedule"
+msgstr "Nouveau pipeline programmé"
+
+msgid "New branch"
+msgstr "Nouvelle branche"
+
+msgid "New directory"
+msgstr "Nouveau dossier"
+
+msgid "New file"
+msgstr "Nouveau Fichier"
+
+msgid "New issue"
+msgstr "Nouvel incident"
+
+msgid "New merge request"
+msgstr "Nouvelle demande de fusion"
+
+msgid "New schedule"
+msgstr "Nouveau programme"
+
+msgid "New snippet"
+msgstr "Nouvel extrait de code"
+
+msgid "New tag"
+msgstr "Nouvelle étiquette"
+
+msgid "No repository"
+msgstr "Pas de dépôt"
+
+msgid "No schedules"
+msgstr "Aucun programme"
+
msgid "Not available"
msgstr "Indisponible"
msgid "Not enough data"
msgstr "Données insuffisantes"
+msgid "Notification events"
+msgstr "Événement de notifications"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Clore l'incident"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Clore la demande de fusion"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Pipeline échoué"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Fusionner le demande de fusion"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nouvel incident"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nouvelle demande de fusion"
+
+msgid "NotificationEvent|New note"
+msgstr "Nouvelle note"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Réassigner l'incident"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Réassigner la demande de fusion"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Ré-ouvrir l'incident"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline réussi"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personnalisé"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Désactivé"
+
+msgid "NotificationLevel|Global"
+msgstr "Global"
+
+msgid "NotificationLevel|On mention"
+msgstr "En cas de mention"
+
+msgid "NotificationLevel|Participate"
+msgstr "Participation"
+
+msgid "NotificationLevel|Watch"
+msgstr "Surveillé"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtre"
+
msgid "OpenedNDaysAgo|Opened"
msgstr "Ouvert"
+msgid "Options"
+msgstr "Options"
+
+msgid "Owner"
+msgstr "Propriétaire"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
msgid "Pipeline Health"
msgstr "Santé du Pipeline"
+msgid "Pipeline Schedule"
+msgstr "Programmation de pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Programmations de pipeline"
+
+msgid "PipelineCharts|Failed:"
+msgstr "Échecs : "
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Statistiques générales"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Ratio de succès : "
+
+msgid "PipelineCharts|Successful:"
+msgstr "Succès :"
+
+msgid "PipelineCharts|Total:"
+msgstr "Total :"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Activé"
+
+msgid "PipelineSchedules|Active"
+msgstr "Actif"
+
+msgid "PipelineSchedules|All"
+msgstr "Tous"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inactif"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "Nom de la variable"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Valeur de la variable"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Prochaine exécution"
+
+msgid "PipelineSchedules|None"
+msgstr "Aucune"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Indiquez une courte description"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Supprimer la variable"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "S’approprier"
+
+msgid "PipelineSchedules|Target"
+msgstr "Cible"
+
+msgid "PipelineSchedules|Variables"
+msgstr "Variables"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personnalisé"
+
+msgid "Pipelines"
+msgstr "Pipelines"
+
+msgid "Pipelines charts"
+msgstr "Graphique des pipelines"
+
+msgid "Pipeline|all"
+msgstr "Tous"
+
+msgid "Pipeline|success"
+msgstr "Succès"
+
+msgid "Pipeline|with stage"
+msgstr "avec l'étape"
+
+msgid "Pipeline|with stages"
+msgstr "avec les étapes"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Projet '%{project_name}' en attente de suppression."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Projet '%{project_name}' créé avec succès."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Projet '%{project_name}' mis à jour avec succès."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Le projet '%{project_name}' sera supprimé."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+"L’accès au projet doit être explicitement accordé à chaque utilisateur."
+
+msgid "Project export could not be deleted."
+msgstr "L'export du projet n'a pas pu être supprimé."
+
+msgid "Project export has been deleted."
+msgstr "L'export du projet a été supprimé."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Le lien de l’export du projet a expiré. Merci de générer un nouvel export "
+"depuis les paramètres du projet."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"L'export du projet a débuté. Un lien de téléchargement sera envoyé par "
+"courriel."
+
+msgid "Project home"
+msgstr "Accueil du projet"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Désactivé"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Toute personne ayant accès"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Seulement les membres de l'équipe"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nom"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Jamais"
+
msgid "ProjectLifecycle|Stage"
msgstr "Étape"
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Graphique "
+
msgid "Read more"
msgstr "Lire plus"
+msgid "Readme"
+msgstr "LisezMoi"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Étiquettes"
+
msgid "Related Commits"
msgstr "Validations liés"
@@ -123,43 +784,201 @@ msgstr "Demandes de fusion liées"
msgid "Related Merged Requests"
msgstr "Demandes fusionnées liées"
+msgid "Remind later"
+msgstr "Me le rappeler ultérieurement"
+
+msgid "Remove project"
+msgstr "Supprimer le projet"
+
+msgid "Request Access"
+msgstr "Demander l'accès"
+
+msgid "Revert this commit"
+msgstr "Annuler cette validation"
+
+msgid "Revert this merge request"
+msgstr "Annuler cette demande de fusion"
+
+msgid "Save pipeline schedule"
+msgstr "Sauvegarder le pipeline programmé"
+
+msgid "Schedule a new pipeline"
+msgstr "Programmer un nouveau pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Programmer des pipelines"
+
+msgid "Search branches and tags"
+msgstr "Rechercher dans les branches et les étiquettes"
+
+msgid "Select Archive Format"
+msgstr "Sélectionnez le format de l'archive"
+
+msgid "Select a timezone"
+msgstr "Sélectionnez un fuseau horaire"
+
+msgid "Select target branch"
+msgstr "Sélectionnez une branche cible"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser "
+"par %{protocol}."
+
+msgid "Set up CI"
+msgstr "Mettre en place l'intégration continue (CI)"
+
+msgid "Set up Koding"
+msgstr "Mettre en place Koding"
+
+msgid "Set up auto deploy"
+msgstr "Mettre en place l’auto-déploiement"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "définir un mot de passe"
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Affichage de %d évènement"
msgstr[1] "Affichage de %d évènements"
-msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
-msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion."
+msgid "Source code"
+msgstr "Code source"
+
+msgid "StarProject|Star"
+msgstr "S'abonner"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Créer une %{new_merge_request} avec ces changements"
+
+msgid "Switch branch/tag"
+msgstr "Changer de branche / d'étiquette"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Étiquette"
+msgstr[1] "Étiquettes"
+
+msgid "Tags"
+msgstr "Étiquettes"
+
+msgid "Target Branch"
+msgstr "Branche cible"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"L’étape de développement montre le temps entre la première validation et la "
+"création de la demande de fusion. Les données seront automatiquement "
+"ajoutées ici une fois que vous aurez créé votre première demande de fusion."
msgid "The collection of events added to the data gathered for that stage."
-msgstr "L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
+msgstr ""
+"L’ensemble d’évènements ajoutés aux données récupérées pour cette étape."
-msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
-msgstr "L'étape des incidents montre le temps nécessaire entre la création d'un incident et son assignation à un jalon, ou son ajout à une liste d'un tableau d'incident. Débutez à créer des incidents pour voir des données pour cette étape."
+msgid "The fork relationship has been removed."
+msgstr "La relation de fourche a été supprimée."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"L'étape des incidents montre le temps nécessaire entre la création d'un "
+"incident et son assignation à un jalon, ou son ajout à une liste d'un "
+"tableau d'incidents. Débutez à créer des incidents pour voir des données "
+"pour cette étape."
msgid "The phase of the development lifecycle."
msgstr "Les étapes du cycle de développement."
-msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit."
-msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation."
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Les pipelines programmés exécutent des pipelines dans le futur, de façon "
+"répétée, pour les branches et étiquettes spécifiées. Ces pipelines "
+"programmés héritent d’un accès partiel au projet basé sur l’utilisateur qui "
+"leurs est associé."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"L’étape de planification montre le temps entre l’étape précédente et l’envoi "
+"de votre première validation. Ce temps sera automatiquement ajouté quand "
+"vous pousserez votre première validation."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"L’étape de mise en production montre le temps nécessaire entre la création "
+"d’un incident et le déploiement du code en production. Les données seront "
+"automatiquement ajoutées une fois que vous aurez complété le cycle complet, "
+"depuis l’idée jusqu’à la mise en production."
+
+msgid "The project can be accessed by any logged in user."
+msgstr ""
+"Votre projet peut être accédé par n’importe quel utilisateur authentifié"
-msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle."
-msgstr "L’étape de mise en production montre le temps nécessaire entre la création d’un incident et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production."
+msgid "The project can be accessed without any authentication."
+msgstr "Votre projet peut être accédé sans aucune authentification."
-msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
-msgstr "L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion."
+msgid "The repository for this project does not exist."
+msgstr "Le dépôt pour ce projet n'existe pas."
-msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
-msgstr "L’étape de pré-production indique le temps entre la fusion de la RF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois."
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"L’étape d’évaluation montre le temps entre la création de la demande de "
+"fusion et la fusion effective de celle-ci. Ces données seront "
+"automatiquement ajoutées après que vous ayez fusionné votre première demande "
+"de fusion."
-msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
-msgstr "L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque pipeline liés à la demande de fusion. Les données seront automatiquement ajoutées après que votre premier pipeline s’achèvera."
+msgid ""
+"The staging stage shows the time between merging the MR and deploying code "
+"to the production environment. The data will be automatically added once you "
+"deploy to production for the first time."
+msgstr ""
+"L’étape de pré-production indique le temps entre la fusion de la DF et le "
+"déploiement du code dans l’environnent de production. Les données seront "
+"automatiquement ajoutées une fois que vous déploierez en production pour la "
+"première fois."
+
+msgid ""
+"The testing stage shows the time GitLab CI takes to run every pipeline for "
+"the related merge request. The data will automatically be added after your "
+"first pipeline finishes running."
+msgstr ""
+"L’étape de test montre le temps que le CI de GitLab met pour exécuter chaque "
+"pipeline liés à la demande de fusion. Les données seront automatiquement "
+"ajoutées après que votre premier pipeline s’achèvera."
msgid "The time taken by each data entry gathered by that stage."
msgstr "Le temps pris par chaque entrée récoltée durant cette étape."
-msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
-msgstr "La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"La valeur située au point médian d’une série de valeur observée. C.à.d., "
+"entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Cela signifie que vous ne pouvez pas pousser du code tant que vous ne créez "
+"pas un dépôt vide, ou importez une dépôt existant."
msgid "Time before an issue gets scheduled"
msgstr "Temps avant qu’un incident ne soit planifié"
@@ -173,6 +992,129 @@ msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture"
msgid "Time until first merge request"
msgstr "Temps jusqu’à la première demande de fusion"
+msgid "Timeago|%s days ago"
+msgstr "Il y a %s jours"
+
+msgid "Timeago|%s days remaining"
+msgstr "Il reste %s jours"
+
+msgid "Timeago|%s hours remaining"
+msgstr "Il reste %s heures"
+
+msgid "Timeago|%s minutes ago"
+msgstr "Il y a %s minutes"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "Il reste %s minutes"
+
+msgid "Timeago|%s months ago"
+msgstr "Il y a %s mois"
+
+msgid "Timeago|%s months remaining"
+msgstr "Il reste %s mois"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "Il reste %s secondes"
+
+msgid "Timeago|%s weeks ago"
+msgstr "Il y a %s semaines"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "Il reste %s semaines"
+
+msgid "Timeago|%s years ago"
+msgstr "Il y a %s ans"
+
+msgid "Timeago|%s years remaining"
+msgstr "Il reste %s ans"
+
+msgid "Timeago|1 day remaining"
+msgstr "Il reste un jour"
+
+msgid "Timeago|1 hour remaining"
+msgstr "Il reste une heure"
+
+msgid "Timeago|1 minute remaining"
+msgstr "Il reste une minute"
+
+msgid "Timeago|1 month remaining"
+msgstr "Il reste un mois"
+
+msgid "Timeago|1 week remaining"
+msgstr "Il reste une semaine"
+
+msgid "Timeago|1 year remaining"
+msgstr "Il reste un an"
+
+msgid "Timeago|Past due"
+msgstr "En retard"
+
+msgid "Timeago|a day ago"
+msgstr "Il y a un jour"
+
+msgid "Timeago|a month ago"
+msgstr "Il y a un mois"
+
+msgid "Timeago|a week ago"
+msgstr "Il y a une semaine"
+
+msgid "Timeago|a while"
+msgstr "Il y a un moment"
+
+msgid "Timeago|a year ago"
+msgstr "Il y a un an"
+
+msgid "Timeago|about %s hours ago"
+msgstr "Il y a environ %s heures"
+
+msgid "Timeago|about a minute ago"
+msgstr "Il y a environ une minute"
+
+msgid "Timeago|about an hour ago"
+msgstr "Il y a environ une heure"
+
+msgid "Timeago|in %s days"
+msgstr "Dans %s jours"
+
+msgid "Timeago|in %s hours"
+msgstr "Dans %s heures"
+
+msgid "Timeago|in %s minutes"
+msgstr "Dans %s minutes"
+
+msgid "Timeago|in %s months"
+msgstr "Dans %s mois"
+
+msgid "Timeago|in %s seconds"
+msgstr "Dans %s secondes"
+
+msgid "Timeago|in %s weeks"
+msgstr "Dans %s semaines"
+
+msgid "Timeago|in %s years"
+msgstr "Dans %s années"
+
+msgid "Timeago|in 1 day"
+msgstr "Dans 1 jour"
+
+msgid "Timeago|in 1 hour"
+msgstr "Dans 1 heure"
+
+msgid "Timeago|in 1 minute"
+msgstr "Dans 1 minute"
+
+msgid "Timeago|in 1 month"
+msgstr "Dans 1 mois"
+
+msgid "Timeago|in 1 week"
+msgstr "Dans 1 semaine"
+
+msgid "Timeago|in 1 year"
+msgstr "Dans 1 an"
+
+msgid "Timeago|less than a minute ago"
+msgstr "il y a moins d'une minute"
+
msgid "Time|hr"
msgid_plural "Time|hrs"
msgstr[0] "hr"
@@ -192,16 +1134,141 @@ msgstr "Temps total"
msgid "Total test time for all commits/merges"
msgstr "Temps total de test pour toutes les validations/fusions"
+msgid "Unstar"
+msgstr "Se désabonner"
+
+msgid "Upload New File"
+msgstr "Téléverser un nouveau fichier"
+
+msgid "Upload file"
+msgstr "Téléverser un fichier"
+
+msgid "UploadLink|click to upload"
+msgstr "Cliquez pour envoyer"
+
+msgid "Use your global notification setting"
+msgstr "Utiliser vos paramètres de notification globaux"
+
+msgid "View open merge request"
+msgstr "Afficher la demande de fusion"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interne"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privé"
+
+msgid "VisibilityLevel|Public"
+msgstr "Public"
+
msgid "Want to see the data? Please ask an administrator for access."
-msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès."
+msgstr ""
+"Vous voulez voir les données ? Merci de contacter un administrateur pour en "
+"obtenir l’accès."
msgid "We don't have enough data to show this stage."
msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape."
+msgid "Withdraw Access Request"
+msgstr "Retirer la demande d'accès"
+
+msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE "
+"PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous êtes sur le point de supprimer %{project_name_with_namespace}.\n"
+"Les projets supprimés NE PEUVENT PAS être restaurés !\n"
+"Êtes vous ABSOLUMENT sûr ? "
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous allez supprimer la relation de fourche avec le projet source "
+"%{forked_from_project}. Êtes-vous VRAIMENT sûr."
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Vous allez transférer %{project_name_with_namespace} à un nouveau "
+"propriétaire. Êtes vous VRAIMENT sûr ?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Vous ne pouvez ajouter de fichier que dans une branche"
+
+msgid "You have reached your project limit"
+msgstr "Vous avez atteint votre limite de projet"
+
+msgid "You must sign in to star a project"
+msgstr "Vous devez vous identifier pour vous abonner à un projet"
+
msgid "You need permission."
msgstr "Vous avez besoin d’une autorisation."
+msgid "You will not get any notifications via email"
+msgstr "Vous ne recevrez aucune notification par courriel"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr ""
+"Vous ne recevrez de notification que pour les évènements que vous aurez "
+"choisis"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr ""
+"Vous ne recevrez de notification que pour les sujets auxquels vous avez "
+"participé"
+
+msgid "You will receive notifications for any activity"
+msgstr "Vous recevrez des notifications pour n’importe quelles activités"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr ""
+"Vous ne recevrez de notifications que pour les commentaires où vous êtes "
+"@mentionné"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Vous ne pourrez pas récupérer ou pousser de code par %{protocol} tant que "
+"vous n'aurez pas %{set_password_link} pour votre compte"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous "
+"n’aurez pas %{add_ssh_key_link} dans votre profil"
+
+msgid "Your name"
+msgstr "Votre nom"
+
msgid "day"
msgid_plural "days"
msgstr[0] "jour"
msgstr[1] "jours"
+
+msgid "new merge request"
+msgstr "nouvelle demande de fusion"
+
+msgid "notification emails"
+msgstr "courriels de notification"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "parent"
+msgstr[1] "parents"
+
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9f1caeddaa7..babef3ed0af 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-28 13:32+0200\n"
-"PO-Revision-Date: 2017-06-28 13:32+0200\n"
+"POT-Creation-Date: 2017-07-13 12:07-0500\n"
+"PO-Revision-Date: 2017-07-13 12:07-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -18,13 +18,13 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
-msgid "%d additional commit has been omitted to prevent performance issues."
-msgid_plural "%d additional commits have been omitted to prevent performance issues."
+msgid "%d commit"
+msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
-msgid "%d commit"
-msgid_plural "%d commits"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
@@ -620,6 +620,12 @@ msgstr ""
msgid "PipelineSchedules|Inactive"
msgstr ""
+msgid "PipelineSchedules|Input variable key"
+msgstr ""
+
+msgid "PipelineSchedules|Input variable value"
+msgstr ""
+
msgid "PipelineSchedules|Next Run"
msgstr ""
@@ -629,12 +635,18 @@ msgstr ""
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr ""
+msgid "PipelineSchedules|Remove variable row"
+msgstr ""
+
msgid "PipelineSchedules|Take ownership"
msgstr ""
msgid "PipelineSchedules|Target"
msgstr ""
+msgid "PipelineSchedules|Variables"
+msgstr ""
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
@@ -1048,6 +1060,9 @@ msgstr ""
msgid "VisibilityLevel|Public"
msgstr ""
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
@@ -1058,6 +1073,12 @@ msgid "Withdraw Access Request"
msgstr ""
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
new file mode 100644
index 00000000000..d4fac6ab34e
--- /dev/null
+++ b/locale/it/gitlab.po
@@ -0,0 +1,1242 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Paolo Falomo <info@paolofalomo.it>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-07-12 05:45-0400\n"
+"Last-Translator: Paolo Falomo <info@paolofalomo.it>\n"
+"Language-Team: Italian (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: it\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli "
+"issues."
+msgstr[1] ""
+"%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni "
+"negli issues."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d commit"
+msgstr[1] "%d commit"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} ha committato %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 pipeline"
+msgstr[1] "%d pipeline"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Un insieme di grafici riguardo la Continuous Integration"
+
+msgid "About auto deploy"
+msgstr "Riguardo il rilascio automatico"
+
+msgid "Active"
+msgstr "Attivo"
+
+msgid "Activity"
+msgstr "Attività"
+
+msgid "Add Changelog"
+msgstr "Aggiungi Changelog"
+
+msgid "Add Contribution guide"
+msgstr "Aggiungi Guida per contribuire"
+
+msgid "Add License"
+msgstr "Aggiungi Licenza"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Aggiungi una chiave SSH al tuo profilo per eseguire pull o push tramite SSH"
+
+msgid "Add new directory"
+msgstr "Aggiungi una directory (cartella)"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Progetto archiviato! La Repository è sola-lettura"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Sei sicuro di voler cancellare questa pipeline programmata?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr ""
+"Aggiungi un file tramite trascina &amp; rilascia ( drag &amp; drop) o "
+"%{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branch"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"La branch <strong>%{branch_name}</strong> è stata creata. Per impostare un "
+"rilascio automatico scegli un template CI di Gitlab e committa le tue "
+"modifiche %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Cerca branches"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Cambia branch"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Naviga direttori"
+
+msgid "Browse File"
+msgstr "Esplora File"
+
+msgid "Browse Files"
+msgstr "Esplora Files"
+
+msgid "Browse files"
+msgstr "Guarda i files"
+
+msgid "ByAuthor|by"
+msgstr "per"
+
+msgid "CI configuration"
+msgstr "Configurazione CI (Integrazione Continua)"
+
+msgid "Cancel"
+msgstr "Cancella"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Preleva nella branch"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Ripristina nella branch"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Ripristina"
+
+msgid "Changelog"
+msgstr "Changelog"
+
+msgid "Charts"
+msgstr "Grafici"
+
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick this commit"
+
+msgid "Cherry-pick this merge request"
+msgstr "Cherry-pick questa richiesta di merge"
+
+msgid "CiStatusLabel|canceled"
+msgstr "cancellato"
+
+msgid "CiStatusLabel|created"
+msgstr "creato"
+
+msgid "CiStatusLabel|failed"
+msgstr "fallito"
+
+msgid "CiStatusLabel|manual action"
+msgstr "azione manuale"
+
+msgid "CiStatusLabel|passed"
+msgstr "superata"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "superata con avvisi"
+
+msgid "CiStatusLabel|pending"
+msgstr "in coda"
+
+msgid "CiStatusLabel|skipped"
+msgstr "saltata"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "in attesa di azione manuale"
+
+msgid "CiStatusText|blocked"
+msgstr "bloccata"
+
+msgid "CiStatusText|canceled"
+msgstr "cancellata"
+
+msgid "CiStatusText|created"
+msgstr "creata"
+
+msgid "CiStatusText|failed"
+msgstr "fallita"
+
+msgid "CiStatusText|manual"
+msgstr "manuale"
+
+msgid "CiStatusText|passed"
+msgstr "superata"
+
+msgid "CiStatusText|pending"
+msgstr "in coda"
+
+msgid "CiStatusText|skipped"
+msgstr "saltata"
+
+msgid "CiStatus|running"
+msgstr "in corso"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Commit"
+msgstr[1] "Commits"
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Durata del commit (in minuti) per gli ultimi 30 commit"
+
+msgid "Commit message"
+msgstr "Messaggio del commit"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Commit"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Aggiungi %{file_name}"
+
+msgid "Commits"
+msgstr "Commits"
+
+msgid "Commits feed"
+msgstr "Feed dei Commits"
+
+msgid "Commits|History"
+msgstr "Cronologia"
+
+msgid "Committed by"
+msgstr "Committato da "
+
+msgid "Compare"
+msgstr "Confronta"
+
+msgid "Contribution guide"
+msgstr "Guida per contribuire"
+
+msgid "Contributors"
+msgstr "Collaboratori"
+
+msgid "Copy URL to clipboard"
+msgstr "Copia URL negli appunti"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copia l'SHA del commit negli appunti"
+
+msgid "Create New Directory"
+msgstr "Crea una nuova cartella"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Creare un token di accesso sul tuo account per eseguire pull o push tramite "
+"%{protocol}"
+
+msgid "Create directory"
+msgstr "Crea cartella"
+
+msgid "Create empty bare repository"
+msgstr "Crea una repository vuota"
+
+msgid "Create merge request"
+msgstr "Crea una richiesta di merge"
+
+msgid "Create new..."
+msgstr "Crea nuovo..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fork"
+
+msgid "CreateTag|Tag"
+msgstr "Tag"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "Crea token d'accesso personale"
+
+msgid "Cron Timezone"
+msgstr "Cron Timezone"
+
+msgid "Cron syntax"
+msgstr "Sintassi Cron"
+
+msgid "Custom notification events"
+msgstr "Eventi-Notifica personalizzati"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"I livelli di notifica personalizzati sono uguali a quelli di partecipazione. "
+"Con i livelli di notifica personalizzati riceverai anche notifiche per gli "
+"eventi da te scelti %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Statistiche Cicliche"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"L'Analisi Ciclica fornisce una panoramica sul tempo che trascorre tra l'idea "
+"ed il rilascio in produzione del tuo progetto"
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Codice"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Issue"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "Pianificazione"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "Produzione"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "Revisione"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "Pre-rilascio"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "Test"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Definisci un patter personalizzato mediante la sintassi cron"
+
+msgid "Delete"
+msgstr "Elimina"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "Rilascio"
+msgstr[1] "Rilasci"
+
+msgid "Description"
+msgstr "Descrizione"
+
+msgid "Directory name"
+msgstr "Nome cartella"
+
+msgid "Don't show again"
+msgstr "Non mostrare più"
+
+msgid "Download"
+msgstr "Scarica"
+
+msgid "Download tar"
+msgstr "Scarica tar"
+
+msgid "Download tar.bz2"
+msgstr "Scarica tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Scarica tar.gz"
+
+msgid "Download zip"
+msgstr "Scarica zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Scarica"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Email Patches"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Differenze"
+
+msgid "DownloadSource|Download"
+msgstr "Scarica"
+
+msgid "Edit"
+msgstr "Modifica"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Cambia programmazione della pipeline %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ogni giorno (alle 4 del mattino)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Ogni primo giorno del mese (alle 4 del mattino)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ogni settimana (Di domenica alle 4 del mattino)"
+
+msgid "Failed to change the owner"
+msgstr "Impossibile cambiare owner"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Impossibile rimuovere la pipeline pianificata"
+
+msgid "Files"
+msgstr "Files"
+
+msgid "Filter by commit message"
+msgstr "Filtra per messaggio di commit"
+
+msgid "Find by path"
+msgstr "Trova in percorso"
+
+msgid "Find file"
+msgstr "Trova file"
+
+msgid "FirstPushedBy|First"
+msgstr "Primo"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "Push di"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fork"
+msgstr[1] "Forks"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Fork da"
+
+msgid "From issue creation until deploy to production"
+msgstr "Dalla creazione di un issue fino al rilascio in produzione"
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+"Dalla richiesta di merge fino effettua il merge fino al rilascio in "
+"produzione"
+
+msgid "Go to your fork"
+msgstr "Vai il tuo fork"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fork"
+
+msgid "Home"
+msgstr "Home"
+
+msgid "Housekeeping successfully started"
+msgstr "Housekeeping iniziato con successo"
+
+msgid "Import repository"
+msgstr "Importa repository"
+
+msgid "Interval Pattern"
+msgstr "Intervallo di Pattern"
+
+msgid "Introducing Cycle Analytics"
+msgstr "Introduzione delle Analisi Cicliche"
+
+msgid "Jobs for last month"
+msgstr "Jobs dell'ultimo mese"
+
+msgid "Jobs for last week"
+msgstr "Jobs dell'ultima settimana"
+
+msgid "Jobs for last year"
+msgstr "Jobs dell'ultimo anno"
+
+msgid "LFSStatus|Disabled"
+msgstr "Disabilitato"
+
+msgid "LFSStatus|Enabled"
+msgstr "Abilitato"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "L'ultimo %d giorno"
+msgstr[1] "Gli ultimi %d giorni"
+
+msgid "Last Pipeline"
+msgstr "Ultima Pipeline"
+
+msgid "Last Update"
+msgstr "Ultimo Aggiornamento"
+
+msgid "Last commit"
+msgstr "Ultimo Commit"
+
+msgid "Learn more in the"
+msgstr "Leggi di più su"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentazione sulla pianificazione delle pipelines"
+
+msgid "Leave group"
+msgstr "Abbandona il gruppo"
+
+msgid "Leave project"
+msgstr "Abbandona il progetto"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "Limita visualizzazione %d d'evento"
+msgstr[1] "Limita visualizzazione %d di eventi"
+
+msgid "Median"
+msgstr "Mediano"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "aggiungi una chiave SSH"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Nuovo Issue"
+msgstr[1] "Nuovi Issues"
+
+msgid "New Pipeline Schedule"
+msgstr "Nuova pianificazione Pipeline"
+
+msgid "New branch"
+msgstr "Nuova Branch"
+
+msgid "New directory"
+msgstr "Nuova directory"
+
+msgid "New file"
+msgstr "Nuovo file"
+
+msgid "New issue"
+msgstr "Nuovo Issue"
+
+msgid "New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "New schedule"
+msgstr "Nuova pianficazione"
+
+msgid "New snippet"
+msgstr "Nuovo snippet"
+
+msgid "New tag"
+msgstr "Nuovo tag"
+
+msgid "No repository"
+msgstr "Nessuna Repository"
+
+msgid "No schedules"
+msgstr "Nessuna pianificazione"
+
+msgid "Not available"
+msgstr "Non disponibile"
+
+msgid "Not enough data"
+msgstr "Dati insufficienti "
+
+msgid "Notification events"
+msgstr "Notifica eventi"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Chiudi issue"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Chiudi richiesta di merge"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Pipeline fallita"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Completa la richiesta di merge"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nuovo issue"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "NotificationEvent|New note"
+msgstr "Nuova nota"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Riassegna issue"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Riassegna richiesta di Merge"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Riapri issue"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline Completata"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personalizzato"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Disabilitato"
+
+msgid "NotificationLevel|Global"
+msgstr "Globale"
+
+msgid "NotificationLevel|On mention"
+msgstr "Se menzionato"
+
+msgid "NotificationLevel|Participate"
+msgstr "Partecipa"
+
+msgid "NotificationLevel|Watch"
+msgstr "Osserva"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtra"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Aperto"
+
+msgid "Options"
+msgstr "Opzioni"
+
+msgid "Owner"
+msgstr "Owner"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
+msgid "Pipeline Health"
+msgstr "Stato della Pipeline"
+
+msgid "Pipeline Schedule"
+msgstr "Pianificazione Pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Pianificazione multipla Pipeline"
+
+msgid "PipelineCharts|Failed:"
+msgstr "Fallita:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Statistiche riassuntive"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Percentuale di successo"
+
+msgid "PipelineCharts|Successful:"
+msgstr "Completata:"
+
+msgid "PipelineCharts|Total:"
+msgstr "Totale:"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Attivata"
+
+msgid "PipelineSchedules|Active"
+msgstr "Attiva"
+
+msgid "PipelineSchedules|All"
+msgstr "Tutto"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inattiva"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Prossima esecuzione"
+
+msgid "PipelineSchedules|None"
+msgstr "Nessuna"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Fornisci una breve descrizione per questa pipeline"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Prendi possesso"
+
+msgid "PipelineSchedules|Target"
+msgstr "Target"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personalizzato"
+
+msgid "Pipelines"
+msgstr "Pipeline"
+
+msgid "Pipelines charts"
+msgstr "Grafici pipeline"
+
+msgid "Pipeline|all"
+msgstr "tutto"
+
+msgid "Pipeline|success"
+msgstr "successo"
+
+msgid "Pipeline|with stage"
+msgstr "con stadio"
+
+msgid "Pipeline|with stages"
+msgstr "con più stadi"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Il Progetto '%{project_name}' in coda di eliminazione."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Il Progetto '%{project_name}' è stato creato con successo."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Il Progetto '%{project_name}' è stato aggiornato con successo."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Il Progetto '%{project_name}' verrà eliminato"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "L'accesso al progetto dev'esser fornito esplicitamente ad ogni utente"
+
+msgid "Project export could not be deleted."
+msgstr "L'esportazione del progetto non può essere eliminata."
+
+msgid "Project export has been deleted."
+msgstr "L'esportazione del progetto è stata eliminata."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"Il link d'esportazione del progetto è scaduto. Genera una nuova esportazione "
+"dalle impostazioni del tuo progetto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Esportazione del progetto iniziata. Un link di download sarà inviato via "
+"email."
+
+msgid "Project home"
+msgstr "Home di progetto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Disabilitato"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Chiunque con accesso"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Solo i membri del team"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nome"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Mai"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Stadio"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Grafico"
+
+msgid "Read more"
+msgstr "Vedi altro"
+
+msgid "Readme"
+msgstr "Leggimi"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Tags"
+
+msgid "Related Commits"
+msgstr "Commit correlati"
+
+msgid "Related Deployed Jobs"
+msgstr "Attività di Rilascio Correlate"
+
+msgid "Related Issues"
+msgstr "Issues Correlati"
+
+msgid "Related Jobs"
+msgstr "Attività Correlate"
+
+msgid "Related Merge Requests"
+msgstr "Richieste di Merge Correlate"
+
+msgid "Related Merged Requests"
+msgstr "Richieste di Merge Completate Correlate"
+
+msgid "Remind later"
+msgstr "Ricordamelo più tardi"
+
+msgid "Remove project"
+msgstr "Rimuovi progetto"
+
+msgid "Request Access"
+msgstr "Richiedi accesso"
+
+msgid "Revert this commit"
+msgstr "Ripristina questo commit"
+
+msgid "Revert this merge request"
+msgstr "Ripristina questa richiesta di merge"
+
+msgid "Save pipeline schedule"
+msgstr "Salva pianificazione pipeline"
+
+msgid "Schedule a new pipeline"
+msgstr "Pianifica una nuova Pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Pianificazione pipelines"
+
+msgid "Search branches and tags"
+msgstr "Ricerca branches e tags"
+
+msgid "Select Archive Format"
+msgstr "Seleziona formato d'archivio"
+
+msgid "Select a timezone"
+msgstr "Seleziona una timezone"
+
+msgid "Select target branch"
+msgstr "Seleziona una branch di destinazione"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Establezca una contraseña en su cuenta para actualizar o enviar a través de "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "Configura CI"
+
+msgid "Set up Koding"
+msgstr "Configura Koding"
+
+msgid "Set up auto deploy"
+msgstr "Configura il rilascio automatico"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "imposta una password"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "Visualizza %d evento"
+msgstr[1] "Visualizza %d eventi"
+
+msgid "Source code"
+msgstr "Codice Sorgente"
+
+msgid "StarProject|Star"
+msgstr "Star"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "inizia una %{new_merge_request} con queste modifiche"
+
+msgid "Switch branch/tag"
+msgstr "Cambia branch/tag"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Tag"
+msgstr[1] "Tags"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Target Branch"
+msgstr "Branch di destinazione"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"Lo stadio di programmazione mostra il tempo trascorso dal primo commit alla "
+"creazione di una richiesta di merge (MR). I dati saranno aggiunti una volta "
+"che avrai creato la prima richiesta di merge."
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "L'insieme di eventi aggiunti ai dati raccolti per quello stadio."
+
+msgid "The fork relationship has been removed."
+msgstr "La relazione del fork è stata rimossa"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"Lo stadio di Issue mostra il tempo che impiega un issue ad esser correlato "
+"ad una Milestone, o ad esser aggiunto ad una tua Lavagna. Inizia la "
+"creazione di problemi per visualizzare i dati in questo stadio."
+
+msgid "The phase of the development lifecycle."
+msgstr "Il ciclo vitale della fase di sviluppo."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Le pipelines pianificate vengono eseguite nel futuro, ripetitivamente, per "
+"specifici tag o branch ed ereditano restrizioni di progetto basate "
+"sull'utente ad esse associato."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"Lo stadio di pianificazione mostra il tempo trascorso dal primo commit al "
+"suo step precedente. Questo periodo sarà disponibile automaticamente nel "
+"momento in cui farai il primo commit."
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"Lo stadio di produzione mostra il tempo totale che trascorre tra la "
+"creazione di un issue il suo rilascio (inteso come codice) in produzione. "
+"Questo dato sarà disponibile automaticamente nel momento in cui avrai "
+"completato l'intero processo ideale del ciclo di produzione"
+
+msgid "The project can be accessed by any logged in user."
+msgstr "Qualunque utente autenticato può accedere a questo progetto."
+
+msgid "The project can be accessed without any authentication."
+msgstr ""
+"Chiunque può accedere a questo progetto (senza alcuna autenticazione)."
+
+msgid "The repository for this project does not exist."
+msgstr "La repository di questo progetto non esiste."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo "
+"svolgimento effettivo. Questo dato sarà disponibile appena avrai completato "
+"una MR (Merger Request)"
+
+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 ""
+"Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta "
+"di Merge) completata al suo rilascio in ambiente di produzione. Questa "
+"informazione sarà disponibile dal tuo primo rilascio in produzione"
+
+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 ""
+"Lo stadio di test mostra il tempo che ogni Pipeline impiega per essere "
+"eseguita in ogni Richiesta di Merge correlata. L'informazione sarà "
+"disponibile automaticamente quando la tua prima Pipeline avrà finito d'esser "
+"eseguita."
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+"Il tempo aggregato relativo eventi/data entry raccolto in quello stadio."
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"Il valore falsato nel mezzo di una serie di dati osservati. ES: tra 3,5,9 il "
+"mediano è 5. Tra 3,5,7,8 il mediano è (5+7)/2 quindi 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Questo significa che non è possibile effettuare push di codice fino a che "
+"non crei una repository vuota o ne importi una esistente"
+
+msgid "Time before an issue gets scheduled"
+msgstr "Il tempo che impiega un issue per esser pianificato"
+
+msgid "Time before an issue starts implementation"
+msgstr "Il tempo che impiega un issue per esser implementato"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Il tempo tra la creazione di una richiesta di merge ed il merge/close"
+
+msgid "Time until first merge request"
+msgstr "Il tempo fino alla prima richiesta di merge"
+
+msgid "Timeago|%s days ago"
+msgstr "%s giorni fa"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s giorni rimanenti"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s ore rimanenti"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s minuti rimanenti"
+
+msgid "Timeago|%s months ago"
+msgstr "%s minuti fa"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s mesi rimanenti"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s secondi rimanenti"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s settimane fa"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s settimane rimanenti"
+
+msgid "Timeago|%s years ago"
+msgstr "%s anni fa"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s anni rimanenti"
+
+msgid "Timeago|1 day remaining"
+msgstr "1 giorno rimanente"
+
+msgid "Timeago|1 hour remaining"
+msgstr "1 ora rimanente"
+
+msgid "Timeago|1 minute remaining"
+msgstr "1 minuto rimanente"
+
+msgid "Timeago|1 month remaining"
+msgstr "1 mese rimanente"
+
+msgid "Timeago|1 week remaining"
+msgstr "1 settimana rimanente"
+
+msgid "Timeago|1 year remaining"
+msgstr "1 anno rimanente"
+
+msgid "Timeago|Past due"
+msgstr "Entro"
+
+msgid "Timeago|a day ago"
+msgstr "un giorno fa"
+
+msgid "Timeago|a month ago"
+msgstr "un mese fa"
+
+msgid "Timeago|a week ago"
+msgstr "una settimana fa"
+
+msgid "Timeago|a while"
+msgstr "poco fa"
+
+msgid "Timeago|a year ago"
+msgstr "un anno fa"
+
+msgid "Timeago|about %s hours ago"
+msgstr "circa %s ore fa"
+
+msgid "Timeago|about a minute ago"
+msgstr "circa un minuto fa"
+
+msgid "Timeago|about an hour ago"
+msgstr "circa un ora fa"
+
+msgid "Timeago|in %s days"
+msgstr "in %s giorni"
+
+msgid "Timeago|in %s hours"
+msgstr "in %s ore"
+
+msgid "Timeago|in %s minutes"
+msgstr "in %s minuti"
+
+msgid "Timeago|in %s months"
+msgstr "in %s mesi"
+
+msgid "Timeago|in %s seconds"
+msgstr "in %s secondi"
+
+msgid "Timeago|in %s weeks"
+msgstr "in %s settimane"
+
+msgid "Timeago|in %s years"
+msgstr "in %s anni"
+
+msgid "Timeago|in 1 day"
+msgstr "in 1 giorno"
+
+msgid "Timeago|in 1 hour"
+msgstr "in 1 ora"
+
+msgid "Timeago|in 1 minute"
+msgstr "in 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "in 1 mese"
+
+msgid "Timeago|in 1 week"
+msgstr "in 1 settimana"
+
+msgid "Timeago|in 1 year"
+msgstr "in 1 anno"
+
+msgid "Timeago|less than a minute ago"
+msgstr "meno di un minuto fa"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "hr"
+msgstr[1] "hr"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "min"
+msgstr[1] "mins"
+
+msgid "Time|s"
+msgstr "s"
+
+msgid "Total Time"
+msgstr "Tempo Totale"
+
+msgid "Total test time for all commits/merges"
+msgstr "Tempo totale di test per tutti i commits/merges"
+
+msgid "Unstar"
+msgstr "Unstar"
+
+msgid "Upload New File"
+msgstr "Carica un nuovo file"
+
+msgid "Upload file"
+msgstr "Carica file"
+
+msgid "UploadLink|click to upload"
+msgstr "clicca per caricare"
+
+msgid "Use your global notification setting"
+msgstr "Usa le tue impostazioni globali "
+
+msgid "View open merge request"
+msgstr "Mostra la richieste di merge aperte"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interno"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privato"
+
+msgid "VisibilityLevel|Public"
+msgstr "Pubblico"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+"Vuoi visualizzare i dati? Richiedi l'accesso ad un amministratore, grazie."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Non ci sono sufficienti dati da mostrare su questo stadio"
+
+msgid "Withdraw Access Request"
+msgstr "Ritira richiesta d'accesso"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere %{project_name_with_namespace}.\n"
+"I progetti rimossi NON POSSONO essere ripristinati\n"
+"Sei assolutamente sicuro?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per rimuovere la relazione con il progetto sorgente "
+"%{forked_from_project}. Sei ASSOLUTAMENTE sicuro?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei "
+"ASSOLUTAMENTE sicuro?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Puoi aggiungere files solo quando sei in una branch"
+
+msgid "You have reached your project limit"
+msgstr "Hai raggiunto il tuo limite di progetto"
+
+msgid "You must sign in to star a project"
+msgstr "Devi accedere per porre una star al progetto"
+
+msgid "You need permission."
+msgstr "Necessiti del permesso."
+
+msgid "You will not get any notifications via email"
+msgstr "Non riceverai alcuna notifica via email"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Riceverai notifiche solo per gli eventi che hai scelto"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Riceverai notifiche solo per i threads a cui hai partecipato"
+
+msgid "You will receive notifications for any activity"
+msgstr "Riceverai notifiche per ogni attività"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Riceverai notifiche solo per i commenti ai quale sei stato menzionato"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Non sarai in grado di eseguire pull o push di codice tramite %{protocol} "
+"fino a che %{set_password_link} nel tuo account."
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Non sarai in grado di effettuare push o pull tramite SSH fino a che "
+"%{add_ssh_key_link} al tuo profilo"
+
+msgid "Your name"
+msgstr "Il tuo nome"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "giorno"
+msgstr[1] "giorni"
+
+msgid "new merge request"
+msgstr "Nuova richiesta di merge"
+
+msgid "notification emails"
+msgstr "Notifiche via email"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "parent"
+msgstr[1] "parents"
+
diff --git a/locale/it/gitlab.po.time_stamp b/locale/it/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/it/gitlab.po.time_stamp
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
new file mode 100644
index 00000000000..04c61906c73
--- /dev/null
+++ b/locale/ja/gitlab.po
@@ -0,0 +1,1203 @@
+# Arthur Charron <arthur.charron@hotmail.fr>, 2017. #zanata
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Kohei Ota <inductor@kela.jp>, 2017. #zanata
+# Taisuke Inoue <taisuke.inoue.jp@gmail.com>, 2017. #zanata
+# Takuya Noguchi <takninnovationresearch@gmail.com>, 2017. #zanata
+# YANO Tethurou <tetuyano+zana@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Last-Translator: YANO TETTER <tetuyano+zana@gmail.com>\n"
+"PO-Revision-Date: 2017-07-19 09:45-0400\n"
+"Last-Translator: YANO Tethurou <tetuyano+zana@gmail.com>\n"
+"Language: ja\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "パフォーマンス低下をé¿ã‘ã‚‹ãŸã‚ %s 個ã®ã‚³ãƒŸãƒƒãƒˆã‚’çœç•¥ã—ã¾ã—ãŸã€‚"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d個ã®ã‚³ãƒŸãƒƒãƒˆ"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link}ã¯%{commit_timeago}å‰ã€ã‚³ãƒŸãƒƒãƒˆã—ã¾ã—ãŸã€‚"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 個ã®ãƒ‘イプライン"
+msgstr[1] "%d 個ã®ãƒ‘イプライン"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "CIã«ã¤ã„ã¦ã®ã‚°ãƒ©ãƒ•"
+
+msgid "About auto deploy"
+msgstr "自動デプロイã«ã¤ã„ã¦"
+
+msgid "Active"
+msgstr "有効"
+
+msgid "Activity"
+msgstr "アクティビティー"
+
+msgid "Add Changelog"
+msgstr "変更履歴を追加"
+
+msgid "Add Contribution guide"
+msgstr "貢献者å‘ã‘ガイドを追加"
+
+msgid "Add License"
+msgstr "ライセンスを追加"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "SSHã§ãƒ—ルやプッシュã™ã‚‹å ´åˆã¯ã€ãƒ—ロフィールã«SSHéµã‚’追加ã—ã¦ãã ã•ã„。"
+
+msgid "Add new directory"
+msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’追加"
+
+msgid "Archived project! Repository is read-only"
+msgstr "アーカイブ済ã¿ãƒ—ロジェクトï¼ï¼ˆãƒ¬ãƒã‚¸ãƒˆãƒªãƒ¼ã¯èª­ã¿å–り専用ã§ã™ï¼‰"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "ã“ã®ãƒ‘イプラインスケジュールを削除ã—ã¾ã™ã‹ï¼Ÿ"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "ドラッグ&ドロップã¾ãŸã¯ %{upload_link} ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’添付"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "ブランãƒ"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"<strong>%{branch_name}</strong> ブランãƒãŒä½œæˆã•ã‚Œã¾ã—ãŸã€‚自動デプロイを設定ã™ã‚‹ã«ã¯ã€GitLab CI Yaml "
+"テンプレートをé¸æŠžã—ã¦ã€å¤‰æ›´ã‚’コミットã—ã¦ãã ã•ã„。 %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "ブランãƒã‚’検索"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "ブランãƒã‚’切替"
+
+msgid "Branches"
+msgstr "ブランãƒ"
+
+msgid "Browse Directory"
+msgstr "ディレクトリを表示"
+
+msgid "Browse File"
+msgstr "ファイルを表示"
+
+msgid "Browse Files"
+msgstr "ファイルを表示"
+
+msgid "Browse files"
+msgstr "ファイルを表示"
+
+msgid "ByAuthor|by"
+msgstr "作者"
+
+msgid "CI configuration"
+msgstr "CI 設定"
+
+msgid "Cancel"
+msgstr "キャンセル"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "ピック先ブランãƒ:"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "リãƒãƒ¼ãƒˆå…ˆãƒ–ランãƒ:"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "ãƒã‚§ãƒªãƒ¼ãƒ”ック"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "リãƒãƒ¼ãƒˆ"
+
+msgid "Changelog"
+msgstr "変更履歴"
+
+msgid "Charts"
+msgstr "ãƒãƒ£ãƒ¼ãƒˆ"
+
+msgid "Cherry-pick this commit"
+msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’ãƒã‚§ãƒªãƒ¼ãƒ”ック"
+
+msgid "Cherry-pick this merge request"
+msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’ãƒã‚§ãƒªãƒ¼ãƒ”ック"
+
+msgid "CiStatusLabel|canceled"
+msgstr "キャンセル"
+
+msgid "CiStatusLabel|created"
+msgstr "作æˆæ¸ˆã¿"
+
+msgid "CiStatusLabel|failed"
+msgstr "失敗"
+
+msgid "CiStatusLabel|manual action"
+msgstr "手動実行"
+
+msgid "CiStatusLabel|passed"
+msgstr "æˆåŠŸ"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "æˆåŠŸï¼ˆè­¦å‘Šã‚り)"
+
+msgid "CiStatusLabel|pending"
+msgstr "開始待ã¡"
+
+msgid "CiStatusLabel|skipped"
+msgstr "スキップ済ã¿"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "手動実行待ã¡"
+
+msgid "CiStatusText|blocked"
+msgstr "ブロック"
+
+msgid "CiStatusText|canceled"
+msgstr "キャンセル"
+
+msgid "CiStatusText|created"
+msgstr "作æˆæ¸ˆã¿"
+
+msgid "CiStatusText|failed"
+msgstr "失敗"
+
+msgid "CiStatusText|manual"
+msgstr "手動"
+
+msgid "CiStatusText|passed"
+msgstr "æˆåŠŸ"
+
+msgid "CiStatusText|pending"
+msgstr "実行待ã¡"
+
+msgid "CiStatusText|skipped"
+msgstr "スキップ済ã¿"
+
+msgid "CiStatus|running"
+msgstr "実行中"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "コミット"
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "ç›´è¿‘30コミットã®æ‰€è¦æ™‚é–“(分)"
+
+msgid "Commit message"
+msgstr "コミットメッセージ"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "コミット"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "%{file_name} を追加"
+
+msgid "Commits"
+msgstr "コミット"
+
+msgid "Commits feed"
+msgstr "コミットフィード"
+
+msgid "Commits|History"
+msgstr "履歴"
+
+msgid "Committed by"
+msgstr "コミット担当者: "
+
+msgid "Compare"
+msgstr "比較"
+
+msgid "Contribution guide"
+msgstr "貢献者å‘ã‘ガイド"
+
+msgid "Contributors"
+msgstr "貢献者"
+
+msgid "Copy URL to clipboard"
+msgstr "クリップボードã«URLをコピー"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "コミットã®SHAをクリップボードã«ã‚³ãƒ”ー"
+
+msgid "Create New Directory"
+msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’作æˆ"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr "%{protocol} ã§ãƒ—ッシュやプルã™ã‚‹ãŸã‚ã®ã‚ãªãŸå€‹äººç”¨ã‚¢ã‚¯ã‚»ã‚¹ãƒˆãƒ¼ã‚¯ãƒ³ã‚’作æˆ"
+
+msgid "Create directory"
+msgstr "ディレクトリを作æˆ"
+
+msgid "Create empty bare repository"
+msgstr "空ã®bareレãƒã‚¸ãƒˆãƒªãƒ¼ã‚’作æˆ"
+
+msgid "Create merge request"
+msgstr "マージリクエストを作æˆ"
+
+msgid "Create new..."
+msgstr "æ–°è¦ä½œæˆ"
+
+msgid "CreateNewFork|Fork"
+msgstr "フォーク"
+
+msgid "CreateTag|Tag"
+msgstr "ã‚¿ã‚°"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "個人用アクセストークンを作æˆ"
+
+msgid "Cron Timezone"
+msgstr "Cron ã®ã‚¿ã‚¤ãƒ ã‚¾ãƒ¼ãƒ³"
+
+msgid "Cron syntax"
+msgstr "Cron ã®æ§‹æ–‡"
+
+msgid "Custom notification events"
+msgstr "カスタム通知設定"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"\"カスタム\" ã®é€šçŸ¥ãƒ¬ãƒ™ãƒ«ã®åŸºæœ¬ã¯ \"å‚加\" "
+"ã¨åŒã˜ã§ã™ã€‚ã¾ãŸã€ã‚«ã‚¹ã‚¿ãƒ é€šçŸ¥ã«è¨­å®šã™ã‚‹ã“ã¨ã§é¸æŠžã—ãŸã‚«ã‚¹ã‚¿ãƒ ã‚¤ãƒ™ãƒ³ãƒˆã®é€šçŸ¥ã‚’å—ã‘å–ã‚‹ã“ã¨ã‚‚ã§ãã¾ã™ã€‚ã‚‚ã£ã¨è©³ã—ã知りãŸã„å ´åˆã¯ "
+"%{notification_link} を見ã¦ãã ã•ã„。"
+
+msgid "Cycle Analytics"
+msgstr "サイクル分æž"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"サイクル分æžã«ã‚ˆã‚Šã€ã‚ãªãŸã®ãƒ—ロジェクトãŒã‚¢ã‚¤ãƒ‡ã‚£ã‚¢ã®æ®µéšŽã‹ã‚‰ãƒ—ロダクション環境ã«ãƒªãƒªãƒ¼ã‚¹ã•ã‚Œã‚‹ã¾ã§ã©ã‚Œãらã„時間ãŒã‹ã‹ã£ãŸã‹ä¿¯çž°ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "コード"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "課題"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "計画"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "プロダクション"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "レビュー"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "ステージング"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "テスト"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Cron 構文ã§ã‚«ã‚¹ã‚¿ãƒ ãªãƒ‘ターンを指定ã™ã‚‹"
+
+msgid "Delete"
+msgstr "削除"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "デプロイ"
+
+msgid "Description"
+msgstr "説明"
+
+msgid "Directory name"
+msgstr "ディレクトリå"
+
+msgid "Don't show again"
+msgstr "次回ã‹ã‚‰è¡¨ç¤ºã—ãªã„"
+
+msgid "Download"
+msgstr "ダウンロード"
+
+msgid "Download tar"
+msgstr "tarå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+
+msgid "Download tar.bz2"
+msgstr "tar.bz2å½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+
+msgid "Download tar.gz"
+msgstr "tar.gzå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+
+msgid "Download zip"
+msgstr "zipå½¢å¼ã§ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰"
+
+msgid "DownloadArtifacts|Download"
+msgstr "ダウンロード"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "パッãƒã‚’メールã§é€ä¿¡"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "プレーン差分"
+
+msgid "DownloadSource|Download"
+msgstr "ダウンロード"
+
+msgid "Edit"
+msgstr "編集"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "パイプラインスケジュール %{id} を編集"
+
+msgid "Every day (at 4:00am)"
+msgstr "毎日 (åˆå‰4:00)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "毎月 (1æ—¥ã®åˆå‰4:00)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "毎週 (日曜日ã®åˆå‰4:00)"
+
+msgid "Failed to change the owner"
+msgstr "オーナーを変更ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "パイプラインスケジュールを削除ã§ãã¾ã›ã‚“ã§ã—ãŸ"
+
+msgid "Files"
+msgstr "ファイル"
+
+msgid "Filter by commit message"
+msgstr "コミットメッセージã§çµžã‚Šè¾¼ã¿"
+
+msgid "Find by path"
+msgstr "パスã§æ¤œç´¢"
+
+msgid "Find file"
+msgstr "ファイルを検索"
+
+msgid "FirstPushedBy|First"
+msgstr "åˆå›ž"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "プッシュã—ãŸäºº"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "フォーク"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "フォーク元"
+
+msgid "From issue creation until deploy to production"
+msgstr "課題ãŒç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒ—ロダクションã«ãƒ‡ãƒ—ロイã•ã‚Œã‚‹ã¾ã§"
+
+msgid "From merge request merge until deploy to production"
+msgstr "マージリクエストãŒãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã‹ã‚‰ãƒ—ロダクションã«ãƒ‡ãƒ—ロイã•ã‚Œã‚‹ã¾ã§"
+
+msgid "Go to your fork"
+msgstr "自分ã®ãƒ•ã‚©ãƒ¼ã‚¯ã¸ç§»å‹•"
+
+msgid "GoToYourFork|Fork"
+msgstr "フォーク"
+
+msgid "Home"
+msgstr "ホーム"
+
+msgid "Housekeeping successfully started"
+msgstr "ãƒã‚¦ã‚¹ã‚­ãƒ¼ãƒ”ングã¯æ­£å¸¸ã«èµ·å‹•ã—ã¾ã—ãŸã€‚"
+
+msgid "Import repository"
+msgstr "レãƒã‚¸ãƒˆãƒªãƒ¼ã‚’インãƒãƒ¼ãƒˆ"
+
+msgid "Interval Pattern"
+msgstr "é–“éš”ã®ãƒ‘ターン"
+
+msgid "Introducing Cycle Analytics"
+msgstr "サイクル分æžã®ã”紹介"
+
+msgid "Jobs for last month"
+msgstr "先月ã®ã‚¸ãƒ§ãƒ–"
+
+msgid "Jobs for last week"
+msgstr "先週ã®ã‚¸ãƒ§ãƒ–"
+
+msgid "Jobs for last year"
+msgstr "昨年ã®ã‚¸ãƒ§ãƒ–"
+
+msgid "LFSStatus|Disabled"
+msgstr "無効"
+
+msgid "LFSStatus|Enabled"
+msgstr "有効"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "éŽåŽ»%d日間"
+
+msgid "Last Pipeline"
+msgstr "最新パイプライン"
+
+msgid "Last Update"
+msgstr "最新アップデート"
+
+msgid "Last commit"
+msgstr "最新コミット"
+
+msgid "Learn more in the"
+msgstr "詳ã—ã見る:"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "詳ã—ãã¯ãƒ‘イプラインスケジュールã®ãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã‚’å‚ç…§"
+
+msgid "Leave group"
+msgstr "グループを離脱"
+
+msgid "Leave project"
+msgstr "プロジェクトを離脱"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] "イベント表示数を最大 %d 個ã«åˆ¶é™"
+
+msgid "Median"
+msgstr "中央値"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "SSH éµã‚’追加"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "æ–°è¦èª²é¡Œ"
+
+msgid "New Pipeline Schedule"
+msgstr "æ–°è¦ãƒ‘イプラインスケジュール"
+
+msgid "New branch"
+msgstr "æ–°è¦ãƒ–ランãƒ"
+
+msgid "New directory"
+msgstr "æ–°è¦ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒª"
+
+msgid "New file"
+msgstr "æ–°è¦ãƒ•ã‚¡ã‚¤ãƒ«"
+
+msgid "New issue"
+msgstr "æ–°è¦èª²é¡Œ"
+
+msgid "New merge request"
+msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+msgid "New schedule"
+msgstr "æ–°è¦ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«"
+
+msgid "New snippet"
+msgstr "æ–°è¦ã‚¹ãƒ‹ãƒšãƒƒãƒˆ"
+
+msgid "New tag"
+msgstr "æ–°è¦ã‚¿ã‚°"
+
+msgid "No repository"
+msgstr "レãƒã‚¸ãƒˆãƒªãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“"
+
+msgid "No schedules"
+msgstr "スケジュールãªã—"
+
+msgid "Not available"
+msgstr "利用ã§ãã¾ã›ã‚“"
+
+msgid "Not enough data"
+msgstr "データä¸è¶³"
+
+msgid "Notification events"
+msgstr "イベント通知"
+
+msgid "NotificationEvent|Close issue"
+msgstr "課題をクローズ"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "マージリクエストをクローズ"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "パイプラインã«å¤±æ•—"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "マージリクエストをマージ"
+
+msgid "NotificationEvent|New issue"
+msgstr "æ–°è¦èª²é¡Œ"
+
+msgid "NotificationEvent|New merge request"
+msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+msgid "NotificationEvent|New note"
+msgstr "æ–°è¦ãƒŽãƒ¼ãƒˆ"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "課題ã®æ‹…当者を変更"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "マージリクエスト担当者を変更"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "課題をå†ã‚ªãƒ¼ãƒ—ン"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "パイプラインæˆåŠŸ"
+
+msgid "NotificationLevel|Custom"
+msgstr "カスタム"
+
+msgid "NotificationLevel|Disabled"
+msgstr "無効"
+
+msgid "NotificationLevel|Global"
+msgstr "全体設定"
+
+msgid "NotificationLevel|On mention"
+msgstr "メンション時"
+
+msgid "NotificationLevel|Participate"
+msgstr "å‚加"
+
+msgid "NotificationLevel|Watch"
+msgstr "ã™ã¹ã¦é€šçŸ¥"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "フィルター"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "オープンã•ã‚ŒãŸã®ã¯"
+
+msgid "Options"
+msgstr "オプション"
+
+msgid "Owner"
+msgstr "オーナー"
+
+msgid "Pipeline"
+msgstr "パイプライン"
+
+msgid "Pipeline Health"
+msgstr "パイプラインã®é€²æ—状æ³"
+
+msgid "Pipeline Schedule"
+msgstr "パイプラインスケジュール"
+
+msgid "Pipeline Schedules"
+msgstr "パイプラインスケジュール"
+
+msgid "PipelineCharts|Failed:"
+msgstr "失敗:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "全体統計"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "æˆåŠŸæ¯”率:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "æˆåŠŸ:"
+
+msgid "PipelineCharts|Total:"
+msgstr "åˆè¨ˆ:"
+
+msgid "PipelineSchedules|Activated"
+msgstr "アクティブ"
+
+msgid "PipelineSchedules|Active"
+msgstr "アクティブ"
+
+msgid "PipelineSchedules|All"
+msgstr "全件"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "無効"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "変数ã®åå‰ã‚’入力"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "変数ã®å€¤ã‚’入力"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "次ã®å®Ÿè¡Œ"
+
+msgid "PipelineSchedules|None"
+msgstr "ãªã—"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "ã“ã®ãƒ‘イプラインã«ã¤ã„ã¦ç°¡å˜ã«è¨˜è¿°ã—ã¦ãã ã•ã„。"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "変数を削除"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "権é™ã‚’å–å¾—ã™ã‚‹"
+
+msgid "PipelineSchedules|Target"
+msgstr "ターゲット"
+
+msgid "PipelineSchedules|Variables"
+msgstr "変数"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "カスタム"
+
+msgid "Pipelines"
+msgstr "パイプライン"
+
+msgid "Pipelines charts"
+msgstr "パイプラインãƒãƒ£ãƒ¼ãƒˆ"
+
+msgid "Pipeline|all"
+msgstr "全件"
+
+msgid "Pipeline|success"
+msgstr "æˆåŠŸ"
+
+msgid "Pipeline|with stage"
+msgstr "ステージã‚ã‚Š"
+
+msgid "Pipeline|with stages"
+msgstr "ステージã‚ã‚Š"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "'%{project_name}' プロジェクトã¯å‰Šé™¤å‡¦ç†å¾…ã¡ã§ã™ã€‚"
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "'%{project_name}' プロジェクトã¯æ­£å¸¸ã«ä½œæˆã•ã‚Œã¾ã—ãŸã€‚"
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "'%{project_name}' プロジェクトã¯æ­£å¸¸ã«æ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚"
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "'%{project_name}' プロジェクトã¯å‰Šé™¤ã•ã‚Œã¾ã™ã€‚"
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "ユーザーã”ã¨ã«ãƒ—ロジェクトアクセスã®æ¨©é™ã‚’指定ã—ãªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。"
+
+msgid "Project export could not be deleted."
+msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚"
+
+msgid "Project export has been deleted."
+msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’削除ã—ã¾ã—ãŸã€‚"
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãƒªãƒ³ã‚¯ã¯æœŸé™åˆ‡ã‚Œã«ãªã‚Šã¾ã—ãŸã€‚プロジェクト設定ã«ã¦æ–°ã—ãエクスãƒãƒ¼ãƒˆãƒªãƒ³ã‚¯ã‚’作æˆã—ã¦ãã ã•ã„。"
+
+msgid "Project export started. A download link will be sent by email."
+msgstr "プロジェクトã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆã‚’開始ã—ã¾ã—ãŸã€‚ダウンロードã®ãƒªãƒ³ã‚¯ã¯ãƒ¡ãƒ¼ãƒ«ã§é€ä¿¡ã—ã¾ã™"
+
+msgid "Project home"
+msgstr "プロジェクトホーム"
+
+msgid "ProjectFeature|Disabled"
+msgstr "無効"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "アクセス権é™ã‚’æŒã£ã¦ã„る人"
+
+msgid "ProjectFeature|Only team members"
+msgstr "ãƒãƒ¼ãƒ ãƒ¡ãƒ³ãƒãƒ¼ã®ã¿"
+
+msgid "ProjectFileTree|Name"
+msgstr "åå‰"
+
+msgid "ProjectLastActivity|Never"
+msgstr "記録ãªã—"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "ステージ"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚°ãƒ©ãƒ•"
+
+msgid "Read more"
+msgstr "続ãを読む"
+
+msgid "Readme"
+msgstr "Readme"
+
+msgid "RefSwitcher|Branches"
+msgstr "ブランãƒ"
+
+msgid "RefSwitcher|Tags"
+msgstr "ã‚¿ã‚°"
+
+msgid "Related Commits"
+msgstr "関連ã™ã‚‹ã‚³ãƒŸãƒƒãƒˆ"
+
+msgid "Related Deployed Jobs"
+msgstr "関連ã™ã‚‹ãƒ‡ãƒ—ロイ済ジョブ"
+
+msgid "Related Issues"
+msgstr "関連ã™ã‚‹èª²é¡Œ"
+
+msgid "Related Jobs"
+msgstr "関連ã™ã‚‹ã‚¸ãƒ§ãƒ–"
+
+msgid "Related Merge Requests"
+msgstr "関連ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+msgid "Related Merged Requests"
+msgstr "関連ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+msgid "Remind later"
+msgstr "後ã§é€šçŸ¥"
+
+msgid "Remove project"
+msgstr "プロジェクトを削除"
+
+msgid "Request Access"
+msgstr "アクセス権é™ã‚’リクエストã™ã‚‹"
+
+msgid "Revert this commit"
+msgstr "ã“ã®ã‚³ãƒŸãƒƒãƒˆã‚’リãƒãƒ¼ãƒˆ"
+
+msgid "Revert this merge request"
+msgstr "ã“ã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’リãƒãƒ¼ãƒˆ"
+
+msgid "Save pipeline schedule"
+msgstr "パイプラインスケジュールをä¿å­˜"
+
+msgid "Schedule a new pipeline"
+msgstr "æ–°ã—ã„パイプラインã®ã‚¹ã‚±ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’作æˆ"
+
+msgid "Scheduling Pipelines"
+msgstr "パイプラインスケジューリング"
+
+msgid "Search branches and tags"
+msgstr "ブランãƒã¾ãŸã¯ã‚¿ã‚°ã‚’検索"
+
+msgid "Select Archive Format"
+msgstr "アーカイブã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆã‚’é¸æŠž"
+
+msgid "Select a timezone"
+msgstr "タイムゾーンをé¸æŠž"
+
+msgid "Select target branch"
+msgstr "ターゲットブランãƒã‚’é¸æŠž"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr "%{protocol} プロコトル経由ã§ãƒ—ルã€ãƒ—ッシュã™ã‚‹ãŸã‚ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードを設定。"
+
+msgid "Set up CI"
+msgstr "CI を設定"
+
+msgid "Set up Koding"
+msgstr "Koding を設定"
+
+msgid "Set up auto deploy"
+msgstr "自動デプロイを設定"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "パスワードを設定"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] "%d ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’表示中"
+
+msgid "Source code"
+msgstr "ソースコード"
+
+msgid "StarProject|Star"
+msgstr "スターを付ã‘ã‚‹"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "ã“ã®å¤‰æ›´ã§ %{new_merge_request} を作æˆã™ã‚‹"
+
+msgid "Switch branch/tag"
+msgstr "ブランãƒãƒ»ã‚¿ã‚°åˆ‡ã‚Šæ›¿ãˆ"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "ã‚¿ã‚°"
+
+msgid "Tags"
+msgstr "ã‚¿ã‚°"
+
+msgid "Target Branch"
+msgstr "ターゲットブランãƒ"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+"コーディングステージã§ã¯ã€æœ€åˆã®ã‚³ãƒŸãƒƒãƒˆã‹ã‚‰ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒä½œæˆã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒ‡ãƒ¼ã‚¿ã¯æœ€åˆã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒä½œæˆã•ã‚ŒãŸã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã§è¨ˆæ¸¬ãƒ‡ãƒ¼ã‚¿ã«è¿½åŠ ã•ã‚ŒãŸã‚¤ãƒ™ãƒ³ãƒˆãƒªã‚¹ãƒˆ"
+
+msgid "The fork relationship has been removed."
+msgstr "フォークã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãŒå‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚"
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"課題ステージã§ã¯ã€èª²é¡ŒãŒç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒžã‚¤ãƒ«ã‚¹ãƒˆãƒ¼ãƒ³ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã‚‹ã‹ã€èª²é¡Œãƒœãƒ¼ãƒ‰ã®ãƒªã‚¹ãƒˆã«è¿½åŠ ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒªã‚¹ãƒˆã«è¡¨ç¤ºã™ã‚‹ã«ã¯èª²é¡Œã‚’最åˆã«ä½œæˆã—ã¦ãã ã•ã„。"
+
+msgid "The phase of the development lifecycle."
+msgstr "開発ライフサイクルã®æ®µéšŽ"
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"パイプラインスケジュールã¯æŒ‡å®šã®ãƒ–ランãƒã¾ãŸã¯ã‚¿ã‚°ã«å¯¾ã—ã¦è‡ªå‹•çš„ã«ãƒ‘イプラインを実行ã—ã¾ã™ã€‚計画済ã¿ãƒ‘イプラインã¯ãれらã®ç´ä»˜ã‘られãŸãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ—ロジェクトã¨åŒã˜æ¨©é™ã‚’継承ã—ã¾ã™ã€‚"
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+"計画ステージã§ã¯ã€èª²é¡Œã‚¹ãƒ†ãƒ¼ã‚¸ã«ç™»éŒ²ã•ã‚Œã¦ã‹ã‚‰ãƒ—ッシュã•ã‚ŒãŸæœ€åˆã®ã‚³ãƒŸãƒƒãƒˆæ™‚刻ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚最åˆã®ã‚³ãƒŸãƒƒãƒˆãŒãƒ—ッシュã•ã‚Œã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+"プロダクションステージã§ã¯ã€èª²é¡ŒãŒä½œæˆã•ã‚Œã¦ã‹ã‚‰ãƒ—ロダクションã¸ãƒ‡ãƒ—ロイã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚アイディアã®æ™‚点ã‹ã‚‰ãƒ—ロダクションã¾ã§ã®å…¨ã‚¹ãƒ†ãƒ¼ã‚¸ãŒå®Œäº†ã—ãŸã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
+
+msgid "The project can be accessed by any logged in user."
+msgstr "プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ãƒ¦ãƒ¼ã‚¶ãƒ¼ã§ã‚ã‚Œã°èª°ã§ã‚‚アクセスã§ãã¾ã™ã€‚"
+
+msgid "The project can be accessed without any authentication."
+msgstr "プロジェクトã¯ã€ãƒ­ã‚°ã‚¤ãƒ³ãªã—ã«èª°ã§ã‚‚アクセスã§ãã¾ã™ã€‚"
+
+msgid "The repository for this project does not exist."
+msgstr "ã“ã®ãƒ—ロジェクトã«ãƒ¬ãƒã‚¸ãƒˆãƒªãƒ¼ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+"レビューステージã¨ã¯ã€ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’作æˆã—ã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã™ã‚‹ã¾ã§ã®æ™‚é–“ã§ã™ã€‚ã“ã®ãƒ‡ãƒ¼ã‚¿ã¯æœ€åˆã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒãƒžãƒ¼ã‚¸ã•ã‚ŒãŸã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
+
+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 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 ""
+"テスティングステージã§ã¯ã€GitLab CI "
+"ãŒé–¢é€£ã™ã‚‹ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã®å„パイプラインを実行ã™ã‚‹æ™‚é–“ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ã“ã®ãƒ‡ãƒ¼ã‚¿ã¯æœ€åˆã®ãƒ‘イプラインãŒå®Œäº†ã—ãŸã¨ãã«è‡ªå‹•çš„ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚"
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr "ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã«åŽé›†ã•ã‚ŒãŸãƒ‡ãƒ¼ã‚¿æ¯Žã®æ™‚é–“"
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+"得られãŸä¸€é€£ã®ãƒ‡ãƒ¼ã‚¿ã‚’å°ã•ã„é †ã«ä¸¦ã¹ãŸã¨ãã«ä¸­å¤®ã«ä½ç½®ã™ã‚‹å€¤ã€‚例ãˆã°ã€3, 5, 9ã®ä¸­å¤®å€¤ã¯5。3, 5, 7, 8ã®ä¸­å¤®å€¤ã¯ (5+7)/2 = "
+"6。"
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr "空レãƒã‚¸ãƒˆãƒªãƒ¼ã‚’作æˆã¾ãŸã¯æ—¢å­˜ãƒ¬ãƒã‚¸ãƒˆãƒªãƒ¼ã‚’インãƒãƒ¼ãƒˆã‚’ã—ãªã‘ã‚Œã°ã€ã‚³ãƒ¼ãƒ‰ã®ãƒ—ッシュã¯ã§ãã¾ã›ã‚“。"
+
+msgid "Time before an issue gets scheduled"
+msgstr "課題ãŒè¨ˆç”»ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“"
+
+msgid "Time before an issue starts implementation"
+msgstr "課題ã®å®Ÿè£…ãŒé–‹å§‹ã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“"
+
+msgid "Time between merge request creation and merge/close"
+msgstr "マージリクエストãŒä½œæˆã•ã‚Œã¦ã‹ã‚‰ãƒžãƒ¼ã‚¸ã¾ãŸã¯ã‚¯ãƒ­ãƒ¼ã‚ºã•ã‚Œã‚‹ã¾ã§ã®æ™‚é–“"
+
+msgid "Time until first merge request"
+msgstr "最åˆã®ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã¾ã§ã®æ™‚é–“"
+
+msgid "Timeago|%s days ago"
+msgstr "%sæ—¥å‰"
+
+msgid "Timeago|%s days remaining"
+msgstr "残り %s日間"
+
+msgid "Timeago|%s hours remaining"
+msgstr "残り %s時間"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s分å‰"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "残り %s分間"
+
+msgid "Timeago|%s months ago"
+msgstr "%sヶ月å‰"
+
+msgid "Timeago|%s months remaining"
+msgstr "残り %sヶ月"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "残り %s 秒"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s週間å‰"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "残り %s週間"
+
+msgid "Timeago|%s years ago"
+msgstr "%så¹´å‰"
+
+msgid "Timeago|%s years remaining"
+msgstr "残り %s年間"
+
+msgid "Timeago|1 day remaining"
+msgstr "残り 1日間"
+
+msgid "Timeago|1 hour remaining"
+msgstr "残り 1時間"
+
+msgid "Timeago|1 minute remaining"
+msgstr "残り 1分間"
+
+msgid "Timeago|1 month remaining"
+msgstr "残り 1ヶ月"
+
+msgid "Timeago|1 week remaining"
+msgstr "残り 1週間"
+
+msgid "Timeago|1 year remaining"
+msgstr "残り 1年間"
+
+msgid "Timeago|Past due"
+msgstr "期é™ã‚ªãƒ¼ãƒãƒ¼"
+
+msgid "Timeago|a day ago"
+msgstr "1æ—¥å‰"
+
+msgid "Timeago|a month ago"
+msgstr "1ヶ月å‰"
+
+msgid "Timeago|a week ago"
+msgstr "1週間å‰"
+
+msgid "Timeago|a while"
+msgstr "ã—ã°ã‚‰ãå‰"
+
+msgid "Timeago|a year ago"
+msgstr "1å¹´å‰"
+
+msgid "Timeago|about %s hours ago"
+msgstr "ç´„%s時間å‰"
+
+msgid "Timeago|about a minute ago"
+msgstr "ç´„1分間å‰"
+
+msgid "Timeago|about an hour ago"
+msgstr "ç´„1時間å‰"
+
+msgid "Timeago|in %s days"
+msgstr "%s日間以内"
+
+msgid "Timeago|in %s hours"
+msgstr "%s時間以内"
+
+msgid "Timeago|in %s minutes"
+msgstr "%s分間以内"
+
+msgid "Timeago|in %s months"
+msgstr "%sヶ月以内"
+
+msgid "Timeago|in %s seconds"
+msgstr "%s秒以内"
+
+msgid "Timeago|in %s weeks"
+msgstr "%s週間以内"
+
+msgid "Timeago|in %s years"
+msgstr "%s年間以内"
+
+msgid "Timeago|in 1 day"
+msgstr "1日以内"
+
+msgid "Timeago|in 1 hour"
+msgstr "1時間以内"
+
+msgid "Timeago|in 1 minute"
+msgstr "1分以内"
+
+msgid "Timeago|in 1 month"
+msgstr "1ヶ月以内"
+
+msgid "Timeago|in 1 week"
+msgstr "1週間以内"
+
+msgid "Timeago|in 1 year"
+msgstr "1年以内"
+
+msgid "Timeago|less than a minute ago"
+msgstr "1分未満"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "時間"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "分"
+
+msgid "Time|s"
+msgstr "秒"
+
+msgid "Total Time"
+msgstr "åˆè¨ˆæ™‚é–“"
+
+msgid "Total test time for all commits/merges"
+msgstr "ã™ã¹ã¦ã®ã‚³ãƒŸãƒƒãƒˆ/マージã®åˆè¨ˆãƒ†ã‚¹ãƒˆæ™‚é–“"
+
+msgid "Unstar"
+msgstr "スターを外ã™"
+
+msgid "Upload New File"
+msgstr "æ–°è¦ãƒ•ã‚¡ã‚¤ãƒ«ã‚’アップロード"
+
+msgid "Upload file"
+msgstr "ファイルをアップロード"
+
+msgid "UploadLink|click to upload"
+msgstr "クリックã—ã¦ã‚¢ãƒƒãƒ—ロード"
+
+msgid "Use your global notification setting"
+msgstr "全体通知設定を利用"
+
+msgid "View open merge request"
+msgstr "オープンãªãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’表示"
+
+msgid "VisibilityLevel|Internal"
+msgstr "内部"
+
+msgid "VisibilityLevel|Private"
+msgstr "プライベート"
+
+msgid "VisibilityLevel|Public"
+msgstr "パブリック"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr "ã“ã®ãƒ‡ãƒ¼ã‚¿ã‚’å‚ç…§ã—ãŸã„ã§ã™ã‹ï¼Ÿã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã«ã¯ç®¡ç†è€…ã«å•ã„åˆã‚ã›ã¦ãã ã•ã„。"
+
+msgid "We don't have enough data to show this stage."
+msgstr "データä¸è¶³ã®ãŸã‚ã€ã“ã®ã‚¹ãƒ†ãƒ¼ã‚¸ã®è¡¨ç¤ºã¯ã§ãã¾ã›ã‚“。"
+
+msgid "Withdraw Access Request"
+msgstr "アクセスリクエストをå–り消ã™"
+
+msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "%{group_name} グループを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
+"削除ã•ã‚ŒãŸã‚°ãƒ«ãƒ¼ãƒ—ã¯çµ¶å¯¾ã«å…ƒã«æˆ»ã›ã¾ã›ã‚“ï¼\n"
+"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"%{project_name_with_namespace} プロジェクトを削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚\n"
+"削除ã•ã‚ŒãŸãƒ—ロジェクトã¯çµ¶å¯¾ã«å…ƒã«ã¯æˆ»ã›ã¾ã›ã‚“ï¼\n"
+"本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "å…ƒã®ãƒ—ロジェクト (%{forked_from_project}) ã¨ã®ãƒªãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ã‚’削除ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace} プロジェクトを別ã®ã‚ªãƒ¼ãƒŠãƒ¼ã«ç§»è­²ã—よã†ã¨ã—ã¦ã„ã¾ã™ã€‚本当ã«ã‚ˆã‚ã—ã„ã§ã™ã‹ï¼Ÿ"
+
+msgid "You can only add files when you are on a branch"
+msgstr "ファイルを追加ã™ã‚‹ã«ã¯ã€ã©ã“ã‹ã®ãƒ–ランãƒã«ã„ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“"
+
+msgid "You have reached your project limit"
+msgstr "プロジェクト数ã®ä¸Šé™ã«é”ã—ã¦ã„ã¾ã™"
+
+msgid "You must sign in to star a project"
+msgstr "プロジェクトã«ã‚¹ã‚¿ãƒ¼ã‚’ã¤ã‘ãŸã„å ´åˆã¯ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ãã ã•ã„"
+
+msgid "You need permission."
+msgstr "権é™ãŒå¿…è¦ã§ã™"
+
+msgid "You will not get any notifications via email"
+msgstr "通知メールをé€ä¿¡ã—ã¾ã›ã‚“"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "é¸æŠžã—ãŸã‚¤ãƒ™ãƒ³ãƒˆã®ã¿é€šçŸ¥ã—ã¾ã™"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "å‚加ã—ãŸã‚¹ãƒ¬ãƒƒãƒ‰ã®ã¿é€šçŸ¥ã—ã¾ã™"
+
+msgid "You will receive notifications for any activity"
+msgstr "å…¨ã¦ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティーを通知ã—ã¾ã™"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "ã‚ãªãŸãŒ @mentioned ã§ã‚³ãƒ¡ãƒ³ãƒˆã•ã‚ŒãŸæ™‚ã®ã¿é€šçŸ¥ã—ã¾ã™"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"%{set_password_link} ã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ãƒ‘スワードãŒã‚»ãƒƒãƒˆã•ã‚Œã¦ã„ãªã„ã®ã§ã€ãƒ—ロジェクト㫠%{protocol} "
+"ã§ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã‚’プッシュã€ãƒ—ルã§ãã¾ã›ã‚“"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr "%{add_ssh_key_link} をプロファイルã«è¿½åŠ ã—ã¦ã„ãªã„ã®ã§ã€ãƒ—ロジェクトã«ã‚½ãƒ¼ã‚¹ã‚³ãƒ¼ãƒ‰ã‚’プッシュã€ãƒ—ルã§ãã¾ã›ã‚“"
+
+msgid "Your name"
+msgstr "åå‰"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "æ—¥"
+
+msgid "new merge request"
+msgstr "æ–°è¦ãƒžãƒ¼ã‚¸ãƒªã‚¯ã‚¨ã‚¹ãƒˆ"
+
+msgid "notification emails"
+msgstr "メール通知"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "親"
diff --git a/locale/ja/gitlab.po.time_stamp b/locale/ja/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/ja/gitlab.po.time_stamp
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index fe6d51c36ac..78cf6d2dacc 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -1,29 +1,298 @@
# Alexandre Alencar <alexandre.alencar@gmail.com>, 2017. #zanata
# Fabio Beneditto <fabiobeneditto@gmail.com>, 2017. #zanata
# Leandro Nunes dos Santos <leandronunes@gmail.com>, 2017. #zanata
+# Huang Tao <htve@outlook.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-05-04 19:24-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-05 03:29-0400\n"
-"Last-Translator: Alexandre Alencar <alexandre.alencar@gmail.com>\n"
-"Language-Team: Portuguese (Brazil)\n"
+"Language-Team: Portuguese (Brazil) (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-14 01:17-0400\n"
+"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language: pt-BR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s commit adicional foi omitido para prevenir problemas de performance."
+msgstr[1] ""
+"%s commits adicionais foram omitidos para prevenir problemas de performance."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d commit"
+msgstr[1] "%d commits"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} fez commit %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 pipeline"
+msgstr[1] "%d pipelines"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Uma coleção de gráficos sobre Integração Contínua"
+
+msgid "About auto deploy"
+msgstr "Sobre a implantação automática"
+
+msgid "Active"
+msgstr "Ativo"
+
+msgid "Activity"
+msgstr "Atividade"
+
+msgid "Add Changelog"
+msgstr "Adicionar registro de mudanças"
+
+msgid "Add Contribution guide"
+msgstr "Adicionar Guia de contribuição"
+
+msgid "Add License"
+msgstr "Adicionar Licença"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr "Adicionar chave SSH ao seu perfil para fazer pull ou push via SSH."
+
+msgid "Add new directory"
+msgstr "Adicionar novo diretório"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Projeto arquivado! O repositório é somente leitura"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Tem certeza que deseja excluir este agendamento de pipeline?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Para anexar arquivo, arraste e solte ou %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Branch"
+msgstr[1] "Branches"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"O branch <strong>%{branch_name}</strong> foi criado. Para configurar a "
+"implantação automática, selecione um modelo de Yaml do GitLab CI e registre "
+"suas mudanças. %{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "BranchSwitcherPlaceholder|Procurar por branches"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "BranchSwitcherTitle|Mudar de branch"
+
+msgid "Branches"
+msgstr "Branches"
+
+msgid "Browse Directory"
+msgstr "Navegar no Diretório"
+
+msgid "Browse File"
+msgstr "Pesquisar Arquivo"
+
+msgid "Browse Files"
+msgstr "Pesquisar Arquivos"
+
+msgid "Browse files"
+msgstr "Navegar pelos arquivos"
+
msgid "ByAuthor|by"
msgstr "por"
+msgid "CI configuration"
+msgstr "Configuração da Integração Contínua"
+
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Pick para um branch"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "Reverter no branch"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "Reverter"
+
+msgid "Changelog"
+msgstr "Registro de mudanças"
+
+msgid "Charts"
+msgstr "Gráficos"
+
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick esse commit"
+
+msgid "Cherry-pick this merge request"
+msgstr "Cherry-pick esse merge request"
+
+msgid "CiStatusLabel|canceled"
+msgstr "cancelado"
+
+msgid "CiStatusLabel|created"
+msgstr "criado"
+
+msgid "CiStatusLabel|failed"
+msgstr "falhou"
+
+msgid "CiStatusLabel|manual action"
+msgstr "ação manual"
+
+msgid "CiStatusLabel|passed"
+msgstr "passou"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "passou com avisos"
+
+msgid "CiStatusLabel|pending"
+msgstr "pendente"
+
+msgid "CiStatusLabel|skipped"
+msgstr "ignorado"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "aguardando ação manual"
+
+msgid "CiStatusText|blocked"
+msgstr "bloqueado"
+
+msgid "CiStatusText|canceled"
+msgstr "cancelado"
+
+msgid "CiStatusText|created"
+msgstr "criado"
+
+msgid "CiStatusText|failed"
+msgstr "falhou"
+
+msgid "CiStatusText|manual"
+msgstr "manual"
+
+msgid "CiStatusText|passed"
+msgstr "passou"
+
+msgid "CiStatusText|pending"
+msgstr "pendente"
+
+msgid "CiStatusText|skipped"
+msgstr "ignorado"
+
+msgid "CiStatus|running"
+msgstr "executando"
+
msgid "Commit"
msgid_plural "Commits"
msgstr[0] "Commit"
msgstr[1] "Commits"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "Duração do commit em minutos para os últimos 30 commits"
+
+msgid "Commit message"
+msgstr "Mensagem de commit"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Commit"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Adicionar %{file_name}"
+
+msgid "Commits"
+msgstr "Commits"
+
+msgid "Commits feed"
+msgstr "Feed de commits"
+
+msgid "Commits|History"
+msgstr "Histórico"
+
+msgid "Committed by"
+msgstr "Commit feito por"
+
+msgid "Compare"
+msgstr "Comparar"
+
+msgid "Contribution guide"
+msgstr "Guia de contribuição"
+
+msgid "Contributors"
+msgstr "Contribuidores"
+
+msgid "Copy URL to clipboard"
+msgstr "Copiar URL para área de transferência"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Copiar SHA do commit para a área de transferência"
+
+msgid "Create New Directory"
+msgstr "Criar Novo Diretório"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Crie um token de acesso pessoal na sua conta para dar pull ou push via "
+"%{protocol}."
+
+msgid "Create directory"
+msgstr "Criar diretório"
+
+msgid "Create empty bare repository"
+msgstr "Criar repositório bruto vazio"
+
+msgid "Create merge request"
+msgstr "Criar merge request"
+
+msgid "Create new..."
+msgstr "Criar novo..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Fork"
+
+msgid "CreateTag|Tag"
+msgstr "Tag"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "CreateTokenToCloneLink|criar um token de acesso pessoal"
+
+msgid "Cron Timezone"
+msgstr "Fuso horário do cron"
+
+msgid "Cron syntax"
+msgstr "Sintaxe do cron"
+
+msgid "Custom notification events"
+msgstr "Eventos de notificação personalizados"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"Níveis de notificação personalizados são equivalentes a níveis de "
+"participação. Com níveis de notificação personalizados você também será "
+"notificado sobre eventos selecionados. Para mais informações, visite "
+"%{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Análise de Ciclo"
+
msgid ""
"Cycle Analytics gives an overview of how much time it takes to go from idea "
"to production in your project."
@@ -35,7 +304,7 @@ msgid "CycleAnalyticsStage|Code"
msgstr "Código"
msgid "CycleAnalyticsStage|Issue"
-msgstr "Tarefa"
+msgstr "Issue"
msgid "CycleAnalyticsStage|Plan"
msgstr "Plano"
@@ -52,43 +321,217 @@ msgstr "Homologação"
msgid "CycleAnalyticsStage|Test"
msgstr "Teste"
+msgid "Define a custom pattern with cron syntax"
+msgstr "Defina um padrão personalizado utilizando a sintaxe do cron"
+
+msgid "Delete"
+msgstr "Excluir"
+
msgid "Deploy"
msgid_plural "Deploys"
msgstr[0] "Implantação"
msgstr[1] "Implantações"
+msgid "Description"
+msgstr "Descrição"
+
+msgid "Directory name"
+msgstr "Nome do diretório"
+
+msgid "Don't show again"
+msgstr "Não exibir novamente"
+
+msgid "Download"
+msgstr "Baixar"
+
+msgid "Download tar"
+msgstr "Baixar tar"
+
+msgid "Download tar.bz2"
+msgstr "Baixar tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Baixar tar.gz"
+
+msgid "Download zip"
+msgstr "Baixar zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Baixar"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Email com as mudanças"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Arquivo de texto com as mudanças"
+
+msgid "DownloadSource|Download"
+msgstr "Baixar"
+
+msgid "Edit"
+msgstr "Alterar"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Alterar Agendamento do Pipeline %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Todos os dias (às 4:00)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Todos os meses (no dia primeiro às 4:00)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Toda semana (domingos às 4:00)"
+
+msgid "Failed to change the owner"
+msgstr "Erro ao alterar o proprietário"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Erro ao excluir o agendamento do pipeline"
+
+msgid "Files"
+msgstr "Arquivos"
+
+msgid "Filter by commit message"
+msgstr "Filtrar por mensagem de commit"
+
+msgid "Find by path"
+msgstr "Localizar por caminho"
+
+msgid "Find file"
+msgstr "Localizar arquivo"
+
msgid "FirstPushedBy|First"
msgstr "Primeiro"
msgid "FirstPushedBy|pushed by"
msgstr "publicado por"
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Fork"
+msgstr[1] "Forks"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Forked de"
+
msgid "From issue creation until deploy to production"
-msgstr "Da criação de tarefas até a implantação para a produção"
+msgstr "Da abertura de tarefas até a implantação para a produção"
msgid "From merge request merge until deploy to production"
-msgstr "Da incorporação do merge request até a implantação em produção"
+msgstr ""
+"Da aceitação da solicitação de incorporação até a implantação em produção"
+
+msgid "Go to your fork"
+msgstr "Ir para seu fork"
+
+msgid "GoToYourFork|Fork"
+msgstr "Fork"
+
+msgid "Home"
+msgstr "Início"
+
+msgid "Housekeeping successfully started"
+msgstr "Manutenção iniciada com sucesso"
+
+msgid "Import repository"
+msgstr "Importar repositório"
+
+msgid "Interval Pattern"
+msgstr "Padrão de intervalo"
msgid "Introducing Cycle Analytics"
msgstr "Apresentando a Análise de Ciclo"
+msgid "Jobs for last month"
+msgstr "Jobs no último mês"
+
+msgid "Jobs for last week"
+msgstr "Jobs na última semana"
+
+msgid "Jobs for last year"
+msgstr "Jobs no último ano"
+
+msgid "LFSStatus|Disabled"
+msgstr "Desabilitado"
+
+msgid "LFSStatus|Enabled"
+msgstr "Habilitado"
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] "Último %d dia"
msgstr[1] "Últimos %d dias"
+msgid "Last Pipeline"
+msgstr "Último Pipeline"
+
+msgid "Last Update"
+msgstr "Última Atualização"
+
+msgid "Last commit"
+msgstr "Último commit"
+
+msgid "Learn more in the"
+msgstr "Saiba mais em"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "documentação de agendamento de pipeline"
+
+msgid "Leave group"
+msgstr "Sair do grupo"
+
+msgid "Leave project"
+msgstr "Sair do projeto"
+
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
-msgstr[0] "Limitado a mostrar %d evento no máximo"
-msgstr[1] "Limitado a mostrar %d eventos no máximo"
+msgstr[0] "Limitado a mostrar %d evento, no máximo"
+msgstr[1] "Limitado a mostrar %d eventos, no máximo"
msgid "Median"
msgstr "Mediana"
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "adicione uma chave SSH"
+
msgid "New Issue"
msgid_plural "New Issues"
-msgstr[0] "Nova Tarefa"
-msgstr[1] "Novas Tarefas"
+msgstr[0] "Nova Issue"
+msgstr[1] "Novas Issues"
+
+msgid "New Pipeline Schedule"
+msgstr "Novo Agendamento de Pipeline"
+
+msgid "New branch"
+msgstr "Novo branch"
+
+msgid "New directory"
+msgstr "Novo diretório"
+
+msgid "New file"
+msgstr "Novo arquivo"
+
+msgid "New issue"
+msgstr "Nova issue"
+
+msgid "New merge request"
+msgstr "Novo merge request"
+
+msgid "New schedule"
+msgstr "Novo agendamento"
+
+msgid "New snippet"
+msgstr "Novo snippet"
+
+msgid "New tag"
+msgstr "Nova tag"
+
+msgid "No repository"
+msgstr "Nenhum repositório"
+
+msgid "No schedules"
+msgstr "Nenhum agendamento"
msgid "Not available"
msgstr "Não disponível"
@@ -96,29 +539,240 @@ msgstr "Não disponível"
msgid "Not enough data"
msgstr "Dados insuficientes"
+msgid "Notification events"
+msgstr "Eventos de notificação"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Fechar issue"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Fechar merge request"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Falha no pipeline"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Aceitar merge request"
+
+msgid "NotificationEvent|New issue"
+msgstr "Nova issue"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Novo merge request"
+
+msgid "NotificationEvent|New note"
+msgstr "Novo comentário"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Reatribuir issue"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Reatribuir merge request"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Reabrir issue"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "Pipeline bem sucedido"
+
+msgid "NotificationLevel|Custom"
+msgstr "Personalizar"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Desabilitado"
+
+msgid "NotificationLevel|Global"
+msgstr "Global"
+
+msgid "NotificationLevel|On mention"
+msgstr "Quando mencionado"
+
+msgid "NotificationLevel|Participate"
+msgstr "Participar"
+
+msgid "NotificationLevel|Watch"
+msgstr "Observar"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Filtrar"
+
msgid "OpenedNDaysAgo|Opened"
msgstr "Aberto"
+msgid "Options"
+msgstr "Opções"
+
+msgid "Owner"
+msgstr "Proprietário"
+
+msgid "Pipeline"
+msgstr "Pipeline"
+
msgid "Pipeline Health"
msgstr "Saúde da Pipeline"
+msgid "Pipeline Schedule"
+msgstr "Agendamento da Pipeline"
+
+msgid "Pipeline Schedules"
+msgstr "Agendamentos da Pipeline"
+
+msgid "PipelineCharts|Failed:"
+msgstr "PipelineCharts|Falhou:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "PipelineCharts|Estatísticas gerais"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "PipelineCharts|Taxa de sucesso:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "PipelineCharts|Sucesso:"
+
+msgid "PipelineCharts|Total:"
+msgstr "PipelineCharts|Total:"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Ativado"
+
+msgid "PipelineSchedules|Active"
+msgstr "Ativo"
+
+msgid "PipelineSchedules|All"
+msgstr "Todos"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Inativo"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "PipelineSchedules|Chave da variável de entrada"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "PipelineSchedules|Valor da variável de entrada"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "Próxima Execução"
+
+msgid "PipelineSchedules|None"
+msgstr "Nenhum"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Digite uma descrição curta para esta pipeline"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "PipelineSchedules|Remova a linha da variável"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Tornar-se proprietário"
+
+msgid "PipelineSchedules|Target"
+msgstr "Destino"
+
+msgid "PipelineSchedules|Variables"
+msgstr "PipelineSchedules|Variáveis"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "Personalizado"
+
+msgid "Pipelines"
+msgstr "Pipelines"
+
+msgid "Pipelines charts"
+msgstr "Gráficos de pipelines"
+
+msgid "Pipeline|all"
+msgstr "Pipeline|todos"
+
+msgid "Pipeline|success"
+msgstr "Pipeline|sucesso"
+
+msgid "Pipeline|with stage"
+msgstr "com etapa"
+
+msgid "Pipeline|with stages"
+msgstr "com etapas"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Projeto'%{project_name}' marcado para exclusão."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Projeto '%{project_name}' criado com sucesso."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Projeto '%{project_name}' atualizado com sucesso."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Projeto '%{project_name}' será excluído."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr ""
+"Acesso ao projeto deve ser concedido explicitamente para cada usuário."
+
+msgid "Project export could not be deleted."
+msgstr "A exportação do projeto não pôde ser excluída."
+
+msgid "Project export has been deleted."
+msgstr "Exportação do projeto excluída."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"O link para a exportação do projeto expirou. Favor gerar uma nova exportação "
+"a partir das configurações do projeto."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Exportação do projeto iniciada. Um link para baixá-la será enviado por email."
+""
+
+msgid "Project home"
+msgstr "Página inicial do projeto"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Desabilitado"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Todos que possuem acesso"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Apenas membros do time"
+
+msgid "ProjectFileTree|Name"
+msgstr "Nome"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Nunca"
+
msgid "ProjectLifecycle|Stage"
msgstr "Etapa"
+msgid "ProjectNetworkGraph|Graph"
+msgstr "Ãrvore"
+
msgid "Read more"
-msgstr "Ler mais"
+msgstr "Leia mais"
+
+msgid "Readme"
+msgstr "Leia-me"
+
+msgid "RefSwitcher|Branches"
+msgstr "Branches"
+
+msgid "RefSwitcher|Tags"
+msgstr "Tags"
msgid "Related Commits"
msgstr "Commits Relacionados"
msgid "Related Deployed Jobs"
-msgstr "Jobs Relacionados Incorporados"
+msgstr "Tarefas Implantadas Relacionadas"
msgid "Related Issues"
-msgstr "Tarefas Relacionadas"
+msgstr "Issues Relacionadas"
msgid "Related Jobs"
-msgstr "Jobs Relacionados"
+msgstr "Tarefas Relacionadas"
msgid "Related Merge Requests"
msgstr "Merge Requests Relacionados"
@@ -126,84 +780,180 @@ msgstr "Merge Requests Relacionados"
msgid "Related Merged Requests"
msgstr "Merge Requests Relacionados"
+msgid "Remind later"
+msgstr "Lembrar mais tarde"
+
+msgid "Remove project"
+msgstr "Remover projeto"
+
+msgid "Request Access"
+msgstr "Solicitar acesso"
+
+msgid "Revert this commit"
+msgstr "Reverter este commit"
+
+msgid "Revert this merge request"
+msgstr "Reverter esse merge request"
+
+msgid "Save pipeline schedule"
+msgstr "Salvar agendamento da pipeline"
+
+msgid "Schedule a new pipeline"
+msgstr "Agendar nova pipeline"
+
+msgid "Scheduling Pipelines"
+msgstr "Agendando pipelines"
+
+msgid "Search branches and tags"
+msgstr "Procurar branch e tags"
+
+msgid "Select Archive Format"
+msgstr "Selecionar Formato do Arquivo"
+
+msgid "Select a timezone"
+msgstr "Selecionar fuso horário"
+
+msgid "Select target branch"
+msgstr "Selecionar branch de destino"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Defina uma senha para sua conta para aceitar ou entregar código via "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "Configurar CI"
+
+msgid "Set up Koding"
+msgstr "Configurar Koding"
+
+msgid "Set up auto deploy"
+msgstr "Configurar implantação automática"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "defina uma senha"
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] "Mostrando %d evento"
msgstr[1] "Mostrando %d eventos"
+msgid "Source code"
+msgstr "Código-fonte"
+
+msgid "StarProject|Star"
+msgstr "Marcar"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Iniciar um %{new_merge_request} a partir dessas alterações"
+
+msgid "Switch branch/tag"
+msgstr "Trocar branch/tag"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Tag"
+msgstr[1] "Tags"
+
+msgid "Tags"
+msgstr "Tags"
+
+msgid "Target Branch"
+msgstr "Branch de destino"
+
msgid ""
"The coding stage shows the time from the first commit to creating the merge "
"request. The data will automatically be added here once you create your "
"first merge request."
msgstr ""
-"O estágio de codificação mostra o tempo desde o primeiro commit até a "
-"criação do merge request. \n"
-"Os dados serão automaticamente adicionados aqui uma vez que você tenha "
-"criado seu primeiro merge request."
+"A etapa de codificação mostra o tempo desde a entrega do primeiro commit até "
+"a criação do merge request. Os dados serão automaticamente adicionados aqui "
+"desde o momento de criação do merge request."
msgid "The collection of events added to the data gathered for that stage."
-msgstr ""
-"A coleção de eventos adicionados aos dados coletados para esse estágio."
+msgstr "A coleção de eventos adicionados aos dados coletados para essa etapa."
+
+msgid "The fork relationship has been removed."
+msgstr "O relacionamento como fork foi removido."
msgid ""
"The issue stage shows the time it takes from creating an issue to assigning "
"the issue to a milestone, or add the issue to a list on your Issue Board. "
"Begin creating issues to see data for this stage."
msgstr ""
-"O estágio em questão mostra o tempo que leva desde a criação de uma tarefa "
-"até a sua assinatura para um milestone, ou a sua adição para a lista no seu "
-"Painel de Tarefas. Comece a criar tarefas para ver dados para esta etapa."
+"A etapa de relatos mostra o tempo que leva desde a criação de uma issue até "
+"sua atribuição a um marco, ou sua adição a uma lista no seu Issue Board. "
+"Comece a criar issues para ver dados para esta etapa."
msgid "The phase of the development lifecycle."
msgstr "A fase do ciclo de vida do desenvolvimento."
msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"O agendamento de pipeline executa pipelines no futuro, repetidamente, para "
+"branches ou tags específicas. Essas pipelines agendadas terão acesso "
+"limitado ao projeto baseado no seu usuário associado."
+
+msgid ""
"The planning stage shows the time from the previous step to pushing your "
"first commit. This time will be added automatically once you push your first "
"commit."
msgstr ""
-"A fase de planejamento mostra o tempo do passo anterior até empurrar o seu "
-"primeiro commit. Este tempo será adicionado automaticamente assim que você "
-"realizar seu primeiro commit."
+"A etapa de planejamento mostra o tempo do passo anterior até a publicação de "
+"seu primeiro conjunto de mudanças. Este tempo será adicionado "
+"automaticamente assim que você enviar seu primeiro conjunto de mudanças."
msgid ""
"The production stage shows the total time it takes between creating an issue "
"and deploying the code to production. The data will be automatically added "
"once you have completed the full idea to production cycle."
msgstr ""
-"O estágio de produção mostra o tempo total que leva entre criar uma tarefa e "
-"implantar o código na produção. Os dados serão adicionados automaticamente "
-"até que você complete todo o ciclo de produção."
+"A etapa de produção mostra o tempo total que leva entre criar uma issue e "
+"implantar o código em produção. Os dados serão adicionados automaticamente "
+"assim que você completar todo o ciclo de produção."
+
+msgid "The project can be accessed by any logged in user."
+msgstr "O projeto pode ser acessado por qualquer usuário autenticado."
+
+msgid "The project can be accessed without any authentication."
+msgstr "O projeto pode ser acessado sem a necessidade de autenticação."
+
+msgid "The repository for this project does not exist."
+msgstr "Não existe repositório para este projeto."
msgid ""
"The review stage shows the time from creating the merge request to merging "
"it. The data will automatically be added after you merge your first merge "
"request."
msgstr ""
-"A etapa de revisão mostra o tempo de criação de um merge request até que o "
-"merge seja feito. Os dados serão automaticamente adicionados depois que você "
-"fizer seu primeiro merge request."
+"A etapa de revisão mostra o tempo de criação de uma solicitação de "
+"incorporação até sua aceitação. Os dados serão automaticamente adicionados "
+"depois que sua primeira solicitação de incorporação for aceita."
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 ""
-"O estágio de estágio mostra o tempo entre a fusão do MR e o código de "
-"implantação para o ambiente de produção. Os dados serão automaticamente "
-"adicionados depois de implantar na produção pela primeira vez."
+"A etapa de homologação mostra o tempo entre o aceite da solicitação de "
+"incorporação e a implantação do código no ambiente de produção. Os dados "
+"serão automaticamente adicionados depois que você implantar em produção pela "
+"primeira vez."
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 ""
-"A fase de teste mostra o tempo que o GitLab CI leva para executar cada "
-"pipeline para o merge request relacionado. Os dados serão automaticamente "
-"adicionados após a conclusão do primeiro pipeline."
+"A etapa de testes mostra o tempo que o GitLab CI leva para executar cada "
+"pipeline para a solicitação de incorporação associada. Os dados serão "
+"automaticamente adicionados após a conclusão do primeiro pipeline."
msgid "The time taken by each data entry gathered by that stage."
-msgstr "O tempo necessário para cada entrada de dados reunida por essa etapa."
+msgstr "O tempo necessário por cada entrada de dados reunida por essa etapa."
msgid ""
"The value lying at the midpoint of a series of observed values. E.g., "
@@ -211,19 +961,151 @@ msgid ""
" 6."
msgstr ""
"O valor situado no ponto médio de uma série de valores observados. Ex., "
-"entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5 + 7) / 2 = 6."
+"entre 3, 5, 9, a mediana é 5. Entre 3, 5, 7, 8, a mediana é (5+7)/2 = 6."
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Isto significa que você não pode entregar código até que crie um repositório "
+"vazio ou importe um existente."
msgid "Time before an issue gets scheduled"
-msgstr "Tempo até que uma tarefa seja planejada"
+msgstr "Tempo até que uma issue seja agendada"
msgid "Time before an issue starts implementation"
-msgstr "Tempo até que uma tarefa comece a ser implementada"
+msgstr "Tempo até que uma issue comece a ser implementado"
msgid "Time between merge request creation and merge/close"
-msgstr "Tempo entre a criação do merge request e o merge/fechamento"
+msgstr ""
+"Tempo entre a criação da solicitação de incorporação e a aceitação/"
+"fechamento"
msgid "Time until first merge request"
-msgstr "Tempo até o primeiro merge request"
+msgstr "Tempo até a primeira solicitação de incorporação"
+
+msgid "Timeago|%s days ago"
+msgstr "há %s dias"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s dias restantes"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s horas restantes"
+
+msgid "Timeago|%s minutes ago"
+msgstr "há %s minutos"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s minutos restantes"
+
+msgid "Timeago|%s months ago"
+msgstr "há %s meses"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s meses restantes"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s segundos restantes"
+
+msgid "Timeago|%s weeks ago"
+msgstr "há %s semanas"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s semanas restantes"
+
+msgid "Timeago|%s years ago"
+msgstr "há %s anos"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s anos restantes"
+
+msgid "Timeago|1 day remaining"
+msgstr "1 dia restante"
+
+msgid "Timeago|1 hour remaining"
+msgstr "1 hora restante"
+
+msgid "Timeago|1 minute remaining"
+msgstr "1 minuto restante"
+
+msgid "Timeago|1 month remaining"
+msgstr "1 mês restante"
+
+msgid "Timeago|1 week remaining"
+msgstr "1 semana restante"
+
+msgid "Timeago|1 year remaining"
+msgstr "1 ano restante"
+
+msgid "Timeago|Past due"
+msgstr "Venceu"
+
+msgid "Timeago|a day ago"
+msgstr "há um dia"
+
+msgid "Timeago|a month ago"
+msgstr "há um mês"
+
+msgid "Timeago|a week ago"
+msgstr "há uma semana"
+
+msgid "Timeago|a while"
+msgstr "há algum tempo"
+
+msgid "Timeago|a year ago"
+msgstr "há um ano"
+
+msgid "Timeago|about %s hours ago"
+msgstr "há cerca de %s horas"
+
+msgid "Timeago|about a minute ago"
+msgstr "há cerca de um minuto"
+
+msgid "Timeago|about an hour ago"
+msgstr "há cerca de uma hora"
+
+msgid "Timeago|in %s days"
+msgstr "em %s dias"
+
+msgid "Timeago|in %s hours"
+msgstr "em %s horas"
+
+msgid "Timeago|in %s minutes"
+msgstr "em %s minutos"
+
+msgid "Timeago|in %s months"
+msgstr "em %s meses"
+
+msgid "Timeago|in %s seconds"
+msgstr "em %s segundos"
+
+msgid "Timeago|in %s weeks"
+msgstr "em %s semanas"
+
+msgid "Timeago|in %s years"
+msgstr "em %s anos"
+
+msgid "Timeago|in 1 day"
+msgstr "em 1 dia"
+
+msgid "Timeago|in 1 hour"
+msgstr "em 1 hora"
+
+msgid "Timeago|in 1 minute"
+msgstr "em 1 minuto"
+
+msgid "Timeago|in 1 month"
+msgstr "em 1 mês"
+
+msgid "Timeago|in 1 week"
+msgstr "em 1 semana"
+
+msgid "Timeago|in 1 year"
+msgstr "em 1 ano"
+
+msgid "Timeago|less than a minute ago"
+msgstr "há menos de um minuto"
msgid "Time|hr"
msgid_plural "Time|hrs"
@@ -244,20 +1126,134 @@ msgstr "Tempo Total"
msgid "Total test time for all commits/merges"
msgstr "Tempo de teste total para todos os commits/merges"
+msgid "Unstar"
+msgstr "Desmarcar"
+
+msgid "Upload New File"
+msgstr "Enviar Novo Arquivo"
+
+msgid "Upload file"
+msgstr "Enviar arquivo"
+
+msgid "UploadLink|click to upload"
+msgstr "UploadLink|clique para fazer upload"
+
+msgid "Use your global notification setting"
+msgstr "Utilizar configuração de notificação global"
+
+msgid "View open merge request"
+msgstr "Ver merge request aberto"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Interno"
+
+msgid "VisibilityLevel|Private"
+msgstr "Privado"
+
+msgid "VisibilityLevel|Public"
+msgstr "Público"
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr "Precisa visualizar os dados? Solicite acesso ao administrador."
msgid "We don't have enough data to show this stage."
-msgstr "Não temos dados suficientes para mostrar esta fase."
+msgstr "Esta etapa não possui dados suficientes para exibição."
-msgid "You have reached your project limit"
+msgid "Withdraw Access Request"
+msgstr "Remover Requisição de Acesso"
+
+msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Você vai remover %{group_name}.\n"
+"Grupos removidos NÃO PODEM ser restaurados!\n"
+"Você está ABSOLUTAMENTE certo?"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Você irá remover %{project_name_with_namespace}.\n"
+"O projeto removido NÃO PODE ser restaurado!\n"
+"Tem certeza ABSOLUTA?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Você ira remover o relacionamento de fork com o projeto original "
+"%{forked_from_project}. Tem certeza ABSOLUTA?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
msgstr ""
+"Você irá transferir %{project_name_with_namespace} para outro proprietário. "
+"Tem certeza ABSOLUTA?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Você somente pode adicionar arquivos quando estiver em um branch"
+
+msgid "You have reached your project limit"
+msgstr "Você atingiu o limite de seu projeto"
+
+msgid "You must sign in to star a project"
+msgstr "Você deve estar autenticado para marcar um projeto"
msgid "You need permission."
msgstr "Você precisa de permissão."
+msgid "You will not get any notifications via email"
+msgstr "Você não será notificado por email"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Você será notificado apenas sobre eventos selecionados"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr "Você será notificado apenas sobre tópicos nos quais participou"
+
+msgid "You will receive notifications for any activity"
+msgstr "Você será notificado sobre qualquer atividade"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr "Você será notificado apenas sobre comentários que te @mencionam"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Você não poderá fazer pull ou push via %{protocol} até que "
+"%{set_password_link} para sua conta"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Você não conseguirá fazer pull ou push no projeto via SSH até que adicione "
+"%{add_ssh_key_link} ao seu perfil"
+
+msgid "Your name"
+msgstr "Seu nome"
+
msgid "day"
msgid_plural "days"
msgstr[0] "dia"
msgstr[1] "dias"
+msgid "new merge request"
+msgstr "novo merge request"
+
+msgid "notification emails"
+msgstr "emails de notificação"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "pai"
+msgstr[1] "pais"
+
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
new file mode 100644
index 00000000000..4643bed98e2
--- /dev/null
+++ b/locale/ru/gitlab.po
@@ -0,0 +1,1233 @@
+# SAS <Stepanov.sa@bashkortostan.ru>, 2017. #zanata
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-06-28 13:32+0200\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-07-11 05:13-0400\n"
+"Last-Translator: SAS <Stepanov.sa@bashkortostan.ru>\n"
+"Language-Team: Russian (https://translate.zanata.org/project/view/GitLab)\n"
+"Language: ru\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s добавленный коммит был иÑключен Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ "
+"производительноÑтью."
+msgstr[1] ""
+"%s добавленные коммиты были иÑключены Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ "
+"производительноÑтью."
+msgstr[2] ""
+"%s добавленные коммиты были иÑключены Ð´Ð»Ñ Ð¿Ñ€ÐµÐ´Ð¾Ñ‚Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ Ñ "
+"производительноÑтью."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d коммит"
+msgstr[1] "%d коммит(а|ов)"
+msgstr[2] "%d коммит(а|ов)"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} закоммичено %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr ""
+
+msgid "About auto deploy"
+msgstr "ÐвтоматичеÑкое развертывание"
+
+msgid "Active"
+msgstr "Ðктивный"
+
+msgid "Activity"
+msgstr "ÐктивноÑÑ‚ÑŒ"
+
+msgid "Add Changelog"
+msgstr "Добавить в журнал изменений"
+
+msgid "Add Contribution guide"
+msgstr "Добавить руководÑтво Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¸Ð±ÑŒÑŽÑ‚ÐµÑ€Ð¾Ð²"
+
+msgid "Add License"
+msgstr "Добавить лицензию"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Добавьте ключ SSH в Ñвой профиль, чтобы отправлÑÑ‚ÑŒ или получать код через "
+"SSH."
+
+msgid "Add new directory"
+msgstr "Добавить новую директорию"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Ðрхивный проект! Репозиторий доÑтупен только Ð´Ð»Ñ Ñ‡Ñ‚ÐµÐ½Ð¸Ñ"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Ð’Ñ‹ дейÑтвительно хотите удалить Ñто раÑпиÑание конвейера?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Приложить файл через drag &amp; drop или %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Ветка"
+msgstr[1] "Ветки"
+msgstr[2] "Ветки"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"Ветка <strong>%{branch_name}</strong> Ñоздана. Ð”Ð»Ñ Ð½Ð°Ñтройки автоматичеÑкого "
+"Ñ€Ð°Ð·Ð²ÐµÑ€Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð²Ñ‹Ð±ÐµÑ€ÐµÑ‚Ðµ GitLab CI Yaml-шаблон и зафикÑируйте изменениÑ. "
+"%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "BranchSwitcherPlaceholder|ПоиÑк веток"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "BranchSwitcherTitle|Переключить ветку"
+
+msgid "Branches"
+msgstr "Ветки"
+
+msgid "Browse Directory"
+msgstr "ПроÑмотр директории"
+
+msgid "Browse File"
+msgstr "ПроÑмотр файла"
+
+msgid "Browse Files"
+msgstr "ПроÑмотр файлов"
+
+msgid "Browse files"
+msgstr "ПроÑмотр файлов"
+
+msgid "ByAuthor|by"
+msgstr "ByAuthor|по автору"
+
+msgid "CI configuration"
+msgstr "ÐаÑтройка CI"
+
+msgid "Cancel"
+msgstr "Отмена"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "ChangeTypeActionLabel|Выбрать в ветке"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "ChangeTypeActionLabel|Отменить в ветке"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "ChangeTypeAction|Подобрать"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "ChangeTypeAction|Отменить"
+
+msgid "Changelog"
+msgstr "Журнал изменений"
+
+msgid "Charts"
+msgstr "Графики"
+
+msgid "Cherry-pick this commit"
+msgstr "Подобрать в Ñтом коммите"
+
+msgid "Cherry-pick this merge request"
+msgstr "Побрать в Ñтом запроÑе на ÑлиÑние"
+
+msgid "CiStatusLabel|canceled"
+msgstr "CiStatusLabel|отменено"
+
+msgid "CiStatusLabel|created"
+msgstr "CiStatusLabel|Ñоздано"
+
+msgid "CiStatusLabel|failed"
+msgstr "CiStatusLabel|неудачно"
+
+msgid "CiStatusLabel|manual action"
+msgstr "CiStatusLabel|ручное дейÑтвие"
+
+msgid "CiStatusLabel|passed"
+msgstr "CiStatusLabel|пройдено"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "CiStatusLabel|пройдено Ñ Ð¿Ñ€ÐµÐ´ÑƒÐ¿Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñми"
+
+msgid "CiStatusLabel|pending"
+msgstr "CiStatusLabel|в ожидании"
+
+msgid "CiStatusLabel|skipped"
+msgstr "CiStatusLabel|пропущено"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "CiStatusLabel|ожидание ручных дейÑтвий"
+
+msgid "CiStatusText|blocked"
+msgstr "CiStatusText|блокировано"
+
+msgid "CiStatusText|canceled"
+msgstr "CiStatusText|отменено"
+
+msgid "CiStatusText|created"
+msgstr "CiStatusText|Ñоздано"
+
+msgid "CiStatusText|failed"
+msgstr "CiStatusText|неудачно"
+
+msgid "CiStatusText|manual"
+msgstr "CiStatusText|ручное"
+
+msgid "CiStatusText|passed"
+msgstr "CiStatusText|пройдено"
+
+msgid "CiStatusText|pending"
+msgstr "CiStatusText|в ожидании"
+
+msgid "CiStatusText|skipped"
+msgstr "CiStatusText|пропущено"
+
+msgid "CiStatus|running"
+msgstr "CiStatus|выполнÑетÑÑ"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Коммит"
+msgstr[1] "Коммиты"
+msgstr[2] "Коммиты"
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+
+msgid "Commit message"
+msgstr "ОпиÑание коммита"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "CommitBoxTitle|Коммит"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "CommitMessage|Добавить %{file_name}"
+
+msgid "Commits"
+msgstr "Коммиты"
+
+msgid "Commits feed"
+msgstr ""
+
+msgid "Commits|History"
+msgstr "Commits|ИÑториÑ"
+
+msgid "Committed by"
+msgstr "Коммит"
+
+msgid "Compare"
+msgstr "Сравнение"
+
+msgid "Contribution guide"
+msgstr "РуководÑтво контрибьютора"
+
+msgid "Contributors"
+msgstr "Контрибьюторы"
+
+msgid "Copy URL to clipboard"
+msgstr "Копировать URL в буфер обмена"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Копировать SHA коммита в буфер обмена"
+
+msgid "Create New Directory"
+msgstr "Создать новую директорию"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+
+msgid "Create directory"
+msgstr "Создать директорию"
+
+msgid "Create empty bare repository"
+msgstr "Создать пуÑтой пуÑтой репозиторий"
+
+msgid "Create merge request"
+msgstr "Создать Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° объединение"
+
+msgid "Create new..."
+msgstr "Ðовый"
+
+msgid "CreateNewFork|Fork"
+msgstr "CreateNewFork|Форк"
+
+msgid "CreateTag|Tag"
+msgstr "CreateTag|ТÑг"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr ""
+
+msgid "Cron Timezone"
+msgstr "Ð’Ñ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð·Ð¾Ð½Ð° Cron"
+
+msgid "Cron syntax"
+msgstr "СинтакÑÐ¸Ñ Cron"
+
+msgid "Custom notification events"
+msgstr " ÐаÑтраиваемые ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ ÑобытиÑÑ…"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"ÐаÑтраиваемые уровни уведомлений аналогичны уровню уведомлений в "
+"ÑоответÑтвии Ñ ÑƒÑ‡Ð°Ñтием. С наÑтраиваемыми уровнÑми уведомлений вы также "
+"будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ выбранных ÑобытиÑÑ…. Чтобы узнать больше, "
+"поÑмотрите %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Ðналитика цикла разработки"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "CycleAnalyticsStage|Код"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "CycleAnalyticsStage|Обращение"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Production"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "CycleAnalyticsStage|Ревьюв"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr ""
+
+msgid "CycleAnalyticsStage|Test"
+msgstr ""
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Определить наÑтраиваемый шаблон Ñ ÑинтакÑиÑом cron"
+
+msgid "Delete"
+msgstr "Удалить"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Description"
+msgstr "ОпиÑание"
+
+msgid "Directory name"
+msgstr "Ðаименование директории"
+
+msgid "Don't show again"
+msgstr "Ðе показывать Ñнова"
+
+msgid "Download"
+msgstr "Загрузить"
+
+msgid "Download tar"
+msgstr "Загрузить tar"
+
+msgid "Download tar.bz2"
+msgstr "Загрузить tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Загрузить tar.gz"
+
+msgid "Download zip"
+msgstr "Загрузить zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "DownloadArtifacts|Загрузка"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "DownloadCommit|Email-патчи"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "DownloadCommit|Plain Diff"
+
+msgid "DownloadSource|Download"
+msgstr "DownloadSource|Загрузка"
+
+msgid "Edit"
+msgstr "Редактировать"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Изменить раÑпиÑание конвейера %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Ежедневно (в 4:00)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "ЕжемеÑÑчно (каждое 1-е чиÑло в 4:00)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Еженедельно (по воÑкреÑениÑми в 4:00)"
+
+msgid "Failed to change the owner"
+msgstr "Ðе удалоÑÑŒ изменить владельца"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Ðе удалоÑÑŒ удалить раÑпиÑание конвейера"
+
+msgid "Files"
+msgstr "Файлы"
+
+msgid "Filter by commit message"
+msgstr "Фильтр по комментариÑми к коммитам"
+
+msgid "Find by path"
+msgstr "ПоиÑк по пути"
+
+msgid "Find file"
+msgstr "Ðайти файл"
+
+msgid "FirstPushedBy|First"
+msgstr ""
+
+msgid "FirstPushedBy|pushed by"
+msgstr ""
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Форк"
+msgstr[1] "Форки"
+msgstr[2] "Форки"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "ForkedFromProjectPath|Форк от "
+
+msgid "From issue creation until deploy to production"
+msgstr ""
+
+msgid "From merge request merge until deploy to production"
+msgstr ""
+
+msgid "Go to your fork"
+msgstr "Перейти к вашему форку"
+
+msgid "GoToYourFork|Fork"
+msgstr "GoToYourFork|Форк"
+
+msgid "Home"
+msgstr "ДомашнÑÑ"
+
+msgid "Housekeeping successfully started"
+msgstr "ОчиÑтка уÑпешно запущена"
+
+msgid "Import repository"
+msgstr "Импорт репозиториÑ"
+
+msgid "Interval Pattern"
+msgstr "Шаблон интервала"
+
+msgid "Introducing Cycle Analytics"
+msgstr ""
+
+msgid "Jobs for last month"
+msgstr ""
+
+msgid "Jobs for last week"
+msgstr ""
+
+msgid "Jobs for last year"
+msgstr ""
+
+msgid "LFSStatus|Disabled"
+msgstr "LFSStatus|Отключено"
+
+msgid "LFSStatus|Enabled"
+msgstr "LFSStatus|Включено"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Last Pipeline"
+msgstr "ПоÑледний конвейер"
+
+msgid "Last Update"
+msgstr "ПоÑледнее обновление"
+
+msgid "Last commit"
+msgstr "ПоÑледний коммит"
+
+msgid "Learn more in the"
+msgstr "Узнайте больше в"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "Подробнее в|документации по раÑпиÑаниÑм конвейеров"
+
+msgid "Leave group"
+msgstr "Покинуть группу"
+
+msgid "Leave project"
+msgstr "Покинуть проект"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Median"
+msgstr "Медиана"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "MissingSSHKeyWarningLink|добавить ключ SSH"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Ðовое обращение"
+msgstr[1] "Ðовые обращениÑ"
+msgstr[2] "Ðовые обращениÑ"
+
+msgid "New Pipeline Schedule"
+msgstr "Ðовое раÑпиÑание конвейера"
+
+msgid "New branch"
+msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°"
+
+msgid "New directory"
+msgstr "ÐÐ¾Ð²Ð°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ"
+
+msgid "New file"
+msgstr "Ðовый файл"
+
+msgid "New issue"
+msgstr "Ðовое обращение"
+
+msgid "New merge request"
+msgstr "Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° объединение"
+
+msgid "New schedule"
+msgstr "Ðовое раÑпиÑание"
+
+msgid "New snippet"
+msgstr "Ðовый Ñниппет"
+
+msgid "New tag"
+msgstr "Ðовый Ñ‚Ñг"
+
+msgid "No repository"
+msgstr "Ðет репозиториÑ"
+
+msgid "No schedules"
+msgstr "Ðет раÑпиÑаниÑ"
+
+msgid "Not available"
+msgstr ""
+
+msgid "Not enough data"
+msgstr ""
+
+msgid "Notification events"
+msgstr "Ð£Ð²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ ÑобытиÑÑ…"
+
+msgid "NotificationEvent|Close issue"
+msgstr "NotificationEvent|Обращение закрыто"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° объединение закрыт"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "NotificationEvent|Ðеудача в конвейере"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "NotificationEvent|Объединить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "NotificationEvent|New issue"
+msgstr "NotificationEvent|Ðовое обращение"
+
+msgid "NotificationEvent|New merge request"
+msgstr "NotificationEvent|Ðовый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "NotificationEvent|New note"
+msgstr "NotificationEvent|ÐÐ¾Ð²Ð°Ñ Ð·Ð°Ð¼ÐµÑ‚ÐºÐ°"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "NotificationEvent|Переназначить обращение"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "NotificationEvent|Переназначить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "NotificationEvent|Переоткрыть обращение"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "NotificationEvent|УÑпешно в конвейере"
+
+msgid "NotificationLevel|Custom"
+msgstr "NotificationLevel|ÐаÑтраиваемый"
+
+msgid "NotificationLevel|Disabled"
+msgstr "NotificationLevel|Отключено"
+
+msgid "NotificationLevel|Global"
+msgstr "NotificationLevel|Глобальный"
+
+msgid "NotificationLevel|On mention"
+msgstr "NotificationLevel|С упоминанием"
+
+msgid "NotificationLevel|Participate"
+msgstr "NotificationLevel|По учаÑтию"
+
+msgid "NotificationLevel|Watch"
+msgstr "NotificationLevel|ОтÑлеживать"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "OfSearchInADropdown|Фильтр"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "OpenedNDaysAgo|Открыто"
+
+msgid "Options"
+msgstr "ÐаÑтройки"
+
+msgid "Owner"
+msgstr "Владелец"
+
+msgid "Pipeline"
+msgstr "Конвейер"
+
+msgid "Pipeline Health"
+msgstr ""
+
+msgid "Pipeline Schedule"
+msgstr "РаÑпиÑание конвейера"
+
+msgid "Pipeline Schedules"
+msgstr "РаÑпиÑÐ°Ð½Ð¸Ñ ÐºÐ¾Ð½Ð²ÐµÐ¹ÐµÑ€Ð¾Ð²"
+
+msgid "PipelineCharts|Failed:"
+msgstr ""
+
+msgid "PipelineCharts|Overall statistics"
+msgstr ""
+
+msgid "PipelineCharts|Success ratio:"
+msgstr ""
+
+msgid "PipelineCharts|Successful:"
+msgstr ""
+
+msgid "PipelineCharts|Total:"
+msgstr ""
+
+msgid "PipelineSchedules|Activated"
+msgstr "PipelineSchedules|Ðктивировано"
+
+msgid "PipelineSchedules|Active"
+msgstr "PipelineSchedules|Ðктивно"
+
+msgid "PipelineSchedules|All"
+msgstr "PipelineSchedules|Ð’Ñе"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "PipelineSchedules|Ðеактивно"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "PipelineSchedules|Следующий запуÑк"
+
+msgid "PipelineSchedules|None"
+msgstr "PipelineSchedules|None"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "PipelineSchedules|ПредоÑтавьте краткое опиÑание Ñтого конвейера"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "PipelineSchedules|Стать владельцем"
+
+msgid "PipelineSchedules|Target"
+msgstr "PipelineSchedules|Цель"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "PipelineSheduleIntervalPattern|ÐаÑтраиваемый"
+
+msgid "Pipelines"
+msgstr ""
+
+msgid "Pipelines charts"
+msgstr ""
+
+msgid "Pipeline|all"
+msgstr ""
+
+msgid "Pipeline|success"
+msgstr ""
+
+msgid "Pipeline|with stage"
+msgstr "Pipeline|Ñо Ñтадией"
+
+msgid "Pipeline|with stages"
+msgstr "Pipeline|Ñо ÑтадиÑми"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Проект '%{project_name}' добавлен в очередь на удаление."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Проект '%{project_name}' уÑпешно Ñоздан."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Проект '%{project_name}' уÑпешно обновлен."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Проект '%{project_name}' удален."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "ДоÑтуп к проекту должен предоÑтавлÑÑ‚ÑŒÑÑ Ñвно каждому пользователю."
+
+msgid "Project export could not be deleted."
+msgstr "Ðевозможно удалить ÑкÑпорт проекта."
+
+msgid "Project export has been deleted."
+msgstr "ЭкÑпорт проекта удален."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"ИÑтек Ñрок дейÑÑ‚Ð²Ð¸Ñ ÑÑылки на проект. Создайте новый ÑкÑпорт в ваших "
+"наÑтройках проекта."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Ðачат ÑкÑпорт проекта. СÑылка Ð´Ð»Ñ ÑÐºÐ°Ñ‡Ð¸Ð²Ð°Ð½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ отправлена по Ñлектронной "
+"почте."
+
+msgid "Project home"
+msgstr "ДомашнÑÑ Ñтраница проекта"
+
+msgid "ProjectFeature|Disabled"
+msgstr "ProjectFeature|Отключено"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "ProjectFeature|Ð’Ñе Ñ Ð´Ð¾Ñтупом"
+
+msgid "ProjectFeature|Only team members"
+msgstr "ProjectFeature|Только члены команды"
+
+msgid "ProjectFileTree|Name"
+msgstr "ProjectFileTree|ИмÑ"
+
+msgid "ProjectLastActivity|Never"
+msgstr "ProjectLastActivity|Ðикогда"
+
+msgid "ProjectLifecycle|Stage"
+msgstr ""
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "ProjectNetworkGraph|Граф"
+
+msgid "Read more"
+msgstr ""
+
+msgid "Readme"
+msgstr "Readme"
+
+msgid "RefSwitcher|Branches"
+msgstr "RefSwitcher|Ветки"
+
+msgid "RefSwitcher|Tags"
+msgstr "RefSwitcher|ТÑги"
+
+msgid "Related Commits"
+msgstr ""
+
+msgid "Related Deployed Jobs"
+msgstr ""
+
+msgid "Related Issues"
+msgstr ""
+
+msgid "Related Jobs"
+msgstr ""
+
+msgid "Related Merge Requests"
+msgstr "СвÑзанные запроÑÑ‹ на ÑлиÑние"
+
+msgid "Related Merged Requests"
+msgstr "СвÑзанные объединенные запроÑÑ‹"
+
+msgid "Remind later"
+msgstr "Ðапомнить позже"
+
+msgid "Remove project"
+msgstr "Удалить проект"
+
+msgid "Request Access"
+msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
+
+msgid "Revert this commit"
+msgstr "Отменить Ñто изменение"
+
+msgid "Revert this merge request"
+msgstr "Отменить Ñтот Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "Save pipeline schedule"
+msgstr "Сохранить раÑпиÑание конвейра"
+
+msgid "Schedule a new pipeline"
+msgstr "РаÑпиÑание нового конвейера"
+
+msgid "Scheduling Pipelines"
+msgstr "Планирование конвейеров"
+
+msgid "Search branches and tags"
+msgstr "Ðайти ветки и Ñ‚Ñги"
+
+msgid "Select Archive Format"
+msgstr "Выбрать формат архива"
+
+msgid "Select a timezone"
+msgstr "Выбор временной зоны"
+
+msgid "Select target branch"
+msgstr "Выбор целевой ветки"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"УÑтановите пароль в Ñвоем аккаунте, чтобы отправлÑÑ‚ÑŒ или получать код через "
+"%{protocol}."
+
+msgid "Set up CI"
+msgstr "ÐаÑтройка CI"
+
+msgid "Set up Koding"
+msgstr "ÐаÑтройка Koding"
+
+msgid "Set up auto deploy"
+msgstr "ÐаÑтройка автоматичеÑкого развертываниÑ"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "SetPasswordToCloneLink|уÑтановить пароль"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Source code"
+msgstr "ИÑходный код"
+
+msgid "StarProject|Star"
+msgstr "StarProject|Отметить"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Ðачать %{new_merge_request} Ñ Ñтих изменений"
+
+msgid "Switch branch/tag"
+msgstr "Переключить ветка/Ñ‚Ñг"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "ТÑг"
+msgstr[1] "ТÑги"
+msgstr[2] "ТÑги"
+
+msgid "Tags"
+msgstr "ТÑги"
+
+msgid "Target Branch"
+msgstr "Ð¦ÐµÐ»ÐµÐ²Ð°Ñ Ð²ÐµÑ‚ÐºÐ°"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr "СвÑзь форка удалена."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"Ð¡Ñ‚Ð°Ð´Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð²Ñ€ÐµÐ¼Ñ, которое потребуетÑÑ Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ "
+"Ð½Ð°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸ÑŽ вехи, или Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð² вашу доÑку обращений. "
+"Ðачните Ñоздавать обращениÑ, чтобы увидеть ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтой Ñтадии. "
+
+msgid "The phase of the development lifecycle."
+msgstr "Фаза жизненного цикла разработки."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"РаÑпиÑание конвейеров запуÑкает в будущем неоднократно конвейеры, Ð´Ð»Ñ "
+"определенных ветвей или Ñ‚Ñгов. Запланированные конвейеры наÑледуют "
+"Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° доÑтуп к проекту на оÑнове ÑвÑзанного Ñ Ð½Ð¸Ð¼Ð¸ пользователÑ."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr "ДоÑтуп к проекту возможен любым зарегиÑтрированным пользователем."
+
+msgid "The project can be accessed without any authentication."
+msgstr "ДоÑтуп к проекту возможен без какой-либо проверки подлинноÑти."
+
+msgid "The repository for this project does not exist."
+msgstr "Репозиторий Ð´Ð»Ñ Ñтого проекта не ÑущеÑтвует."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+
+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 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 ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Это означает, что вы не можете пушить код, пока не Ñоздадите пуÑтой "
+"репозиторий или не импортируете ÑущеÑтвующий."
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Ð’Ñ€ÐµÐ¼Ñ Ð¼ÐµÐ¶Ð´Ñƒ Ñозданием запроÑа ÑлиÑÐ½Ð¸Ñ Ð¸ ÑлиÑнием / закрытием"
+
+msgid "Time until first merge request"
+msgstr ""
+
+msgid "Timeago|%s days ago"
+msgstr "Timeago|%s дн(Ñ|ей) назад"
+
+msgid "Timeago|%s days remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s дн(Ñ|ей)"
+
+msgid "Timeago|%s hours remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s чаÑов"
+
+msgid "Timeago|%s minutes ago"
+msgstr "Timeago|%s минут назад"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s минут(а|Ñ‹)"
+
+msgid "Timeago|%s months ago"
+msgstr "Timeago|%s минут(а|ы) назад"
+
+msgid "Timeago|%s months remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s меÑÑцев(а)"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s Ñекунд(Ñ‹)"
+
+msgid "Timeago|%s weeks ago"
+msgstr "Timeago|%s недель(и) назад"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s недель(и)"
+
+msgid "Timeago|%s years ago"
+msgstr "Timeago|%s лет/года назад"
+
+msgid "Timeago|%s years remaining"
+msgstr "Timeago|ОÑталоÑÑŒ %s лет/года"
+
+msgid "Timeago|1 day remaining"
+msgstr "Timeago|ОÑталÑÑ Ð´ÐµÐ½ÑŒ"
+
+msgid "Timeago|1 hour remaining"
+msgstr "Timeago|ОÑталÑÑ Ñ‡Ð°Ñ"
+
+msgid "Timeago|1 minute remaining"
+msgstr "Timeago|ОÑталаÑÑŒ одна минута"
+
+msgid "Timeago|1 month remaining"
+msgstr "Timeago|ОÑталÑÑ Ð¼ÐµÑÑц"
+
+msgid "Timeago|1 week remaining"
+msgstr "Timeago|ОÑталаÑÑŒ неделÑ"
+
+msgid "Timeago|1 year remaining"
+msgstr "Timeago|ОÑталÑÑ Ð³Ð¾Ð´"
+
+msgid "Timeago|Past due"
+msgstr "Timeago|ПроÑрочено"
+
+msgid "Timeago|a day ago"
+msgstr "Timeago|день назад"
+
+msgid "Timeago|a month ago"
+msgstr "Timeago|меÑÑц назад"
+
+msgid "Timeago|a week ago"
+msgstr "Timeago|неделю назад"
+
+msgid "Timeago|a while"
+msgstr "Timeago|какое-то времÑ"
+
+msgid "Timeago|a year ago"
+msgstr "Timeago|год назад"
+
+msgid "Timeago|about %s hours ago"
+msgstr "Timeago|около %s чаÑов назад"
+
+msgid "Timeago|about a minute ago"
+msgstr "Timeago|около минуты назад"
+
+msgid "Timeago|about an hour ago"
+msgstr "Timeago|около чаÑа назад"
+
+msgid "Timeago|in %s days"
+msgstr "Timeago|через %s днÑ(ей)"
+
+msgid "Timeago|in %s hours"
+msgstr "Timeago|через %s чаÑа(ов)"
+
+msgid "Timeago|in %s minutes"
+msgstr "Timeago|через %s минут(ы)"
+
+msgid "Timeago|in %s months"
+msgstr "Timeago|через %s меÑÑц(а|ев)"
+
+msgid "Timeago|in %s seconds"
+msgstr "Timeago|через %s Ñекунд(Ñ‹)"
+
+msgid "Timeago|in %s weeks"
+msgstr "Timeago|через %s недели"
+
+msgid "Timeago|in %s years"
+msgstr "Timeago|через %s лет/года"
+
+msgid "Timeago|in 1 day"
+msgstr "Timeago|через день"
+
+msgid "Timeago|in 1 hour"
+msgstr "Timeago|через чаÑ"
+
+msgid "Timeago|in 1 minute"
+msgstr "Timeago|через минуту"
+
+msgid "Timeago|in 1 month"
+msgstr "Timeago|через меÑÑц"
+
+msgid "Timeago|in 1 week"
+msgstr "Timeago|через неделю"
+
+msgid "Timeago|in 1 year"
+msgstr "Timeago|через год"
+
+msgid "Timeago|less than a minute ago"
+msgstr "Timeago|менее чем минуту назад"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "ч"
+msgstr[1] "ч"
+msgstr[2] "ч"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "мин"
+msgstr[1] "мин"
+msgstr[2] "мин"
+
+msgid "Time|s"
+msgstr "Ñ"
+
+msgid "Total Time"
+msgstr "Общее времÑ"
+
+msgid "Total test time for all commits/merges"
+msgstr ""
+
+msgid "Unstar"
+msgstr "СнÑÑ‚ÑŒ отметку"
+
+msgid "Upload New File"
+msgstr "Выгрузить новый файл"
+
+msgid "Upload file"
+msgstr "Выгрузить файл"
+
+msgid "UploadLink|click to upload"
+msgstr "UploadLink|кликните Ð´Ð»Ñ Ð²Ñ‹Ð³Ñ€ÑƒÐ·ÐºÐ¸"
+
+msgid "Use your global notification setting"
+msgstr "ИÑпользуютÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ‹Ð¹ наÑтройки уведомлений"
+
+msgid "View open merge request"
+msgstr "ПроÑмотреть открытый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "VisibilityLevel|Internal"
+msgstr "VisibilityLevel|Ограниченный"
+
+msgid "VisibilityLevel|Private"
+msgstr "VisibilityLevel|Приватный"
+
+msgid "VisibilityLevel|Public"
+msgstr "VisibilityLevel|Публичный"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr ""
+
+msgid "We don't have enough data to show this stage."
+msgstr ""
+
+msgid "Withdraw Access Request"
+msgstr "Отменить Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Вы хотите удалить %{project_name_with_namespace}.\n"
+"Удаленный проект ÐЕ МОЖЕТ быть воÑÑтановлен!\n"
+"Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Ð’Ñ‹ ÑобираетеÑÑŒ удалить ÑвÑзь форка Ñ Ð¸Ñходным проектом "
+"%{forked_from_project}. Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ð’Ñ‹ ÑобираетеÑÑŒ передать проект %{project_name_with_namespace} другому "
+"владельцу. Ð’Ñ‹ ÐБСОЛЮТÐО уверены?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Ð’Ñ‹ можете добавлÑÑ‚ÑŒ только файлы, когда находитеÑÑŒ в ветке"
+
+msgid "You have reached your project limit"
+msgstr "Ð’Ñ‹ доÑтигли Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð² вашем проекте"
+
+msgid "You must sign in to star a project"
+msgstr "Ðеобходимо войти, чтобы оценить проект"
+
+msgid "You need permission."
+msgstr "Вам нужно разрешение."
+
+msgid "You will not get any notifications via email"
+msgstr "Ð’Ñ‹ не получите никаких уведомлений по Ñлектронной почте"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ о выбранных вами ÑобытиÑÑ…"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr ""
+"Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ о тех тредах, в которых вы учаÑтвовали"
+
+msgid "You will receive notifications for any activity"
+msgstr "Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð¾ любых дейÑтвиÑÑ…"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr ""
+"Ð’Ñ‹ будете получать ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸ÐµÐ², в которых вы были "
+"@упомÑнуты"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Ð’Ñ‹ не Ñможете получать и отправлÑÑ‚ÑŒ код проекта через %{protocol} пока "
+"%{set_password_link} в ваш аккаунт"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Ð’Ñ‹ не Ñможете получать и отправлÑÑ‚ÑŒ код проекта через SSH пока "
+"%{add_ssh_key_link} в ваш профиль."
+
+msgid "Your name"
+msgstr "Ваше имÑ"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "день"
+msgstr[1] "дни"
+msgstr[2] "дни"
+
+msgid "new merge request"
+msgstr "новый Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° ÑлиÑние"
+
+msgid "notification emails"
+msgstr "email Ð´Ð»Ñ ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ð¹"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "иÑточник"
+msgstr[1] "иÑточники"
+msgstr[2] "иÑточники"
+
diff --git a/locale/ru/gitlab.po.time_stamp b/locale/ru/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/ru/gitlab.po.time_stamp
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
new file mode 100644
index 00000000000..2ded61f40d7
--- /dev/null
+++ b/locale/uk/gitlab.po
@@ -0,0 +1,1256 @@
+# Huang Tao <htve@outlook.com>, 2017. #zanata
+# Ðндрей Витюк <andruwa13@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: gitlab 1.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language-Team: Ukrainian (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-25 03:27-0400\n"
+"Last-Translator: Ðндрей Витюк <andruwa13@gmail.com>\n"
+"Language: uk\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] ""
+"%s доданий Комміт був виключений Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ з продуктивніÑÑ‚ÑŽ."
+msgstr[1] ""
+"%s доданих коммітів були виключені Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ з продуктивніÑÑ‚ÑŽ."
+msgstr[2] ""
+"%s доданих коммітів були виключені Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð±Ñ–Ð³Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ з продуктивніÑÑ‚ÑŽ."
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d комміт"
+msgstr[1] "%d комміта"
+msgstr[2] "%d коммітів"
+
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr "%{commit_author_link} комміт %{commit_timeago}"
+
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 конвеєр"
+msgstr[1] "%d конвеєра"
+msgstr[2] "%d конвеєрів"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "Це набір графічних елементів Ð´Ð»Ñ Ð±ÐµÐ·Ð¿ÐµÑ€ÐµÑ€Ð²Ð½Ð¾Ñ— інтеграції"
+
+msgid "About auto deploy"
+msgstr "Про авто розгортаннÑ"
+
+msgid "Active"
+msgstr "Ðктивний"
+
+msgid "Activity"
+msgstr "ÐктивніÑÑ‚ÑŒ"
+
+msgid "Add Changelog"
+msgstr "Додати ÑпиÑок змін (Changelog)"
+
+msgid "Add Contribution guide"
+msgstr "Додати керівництво Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¸Ð±â€™ÑŽÑ‚Ð¾Ñ€Ñ–Ð²"
+
+msgid "Add License"
+msgstr "Додати ліцензію"
+
+msgid "Add an SSH key to your profile to pull or push via SSH."
+msgstr ""
+"Додати SSH ключа в Ñвій профіль, щоб мати можливіÑÑ‚ÑŒ завантажити чи "
+"надіÑлати зміни через SSH."
+
+msgid "Add new directory"
+msgstr "Додати новий каталог"
+
+msgid "Archived project! Repository is read-only"
+msgstr "Заархівований проект! Репозиторій доÑтупний лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ"
+
+msgid "Are you sure you want to delete this pipeline schedule?"
+msgstr "Ви впевнені, що хочете видалити цей розклад Ð´Ð»Ñ ÐšÐ¾Ð½Ð²ÐµÑ”Ñ€Ð°?"
+
+msgid "Attach a file by drag &amp; drop or %{upload_link}"
+msgstr "Прикріпити файл за допомогою перетÑÐ³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ %{upload_link}"
+
+msgid "Branch"
+msgid_plural "Branches"
+msgstr[0] "Гілка"
+msgstr[1] "Гілки"
+msgstr[2] "Гілок"
+
+msgid ""
+"Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, "
+"choose a GitLab CI Yaml template and commit your changes. "
+"%{link_to_autodeploy_doc}"
+msgstr ""
+"Гілка <strong>%{branch_name}</strong> Ñтворена. Ð”Ð»Ñ Ð½Ð°Ñтройки автоматичного "
+"Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð²Ð¸Ð±ÐµÑ€Ñ–Ñ‚ÑŒ GitLab CI Yaml-шаблон Ñ– закоммітьте зміни. "
+"%{link_to_autodeploy_doc}"
+
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "Пошук гілок"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "Переключити гілку"
+
+msgid "Branches"
+msgstr "Гілки"
+
+msgid "Browse Directory"
+msgstr "ПереглÑнути каталог"
+
+msgid "Browse File"
+msgstr "ПереглÑнути файл"
+
+msgid "Browse Files"
+msgstr "ПереглÑд файлів"
+
+msgid "Browse files"
+msgstr "ПереглÑд файлів"
+
+msgid "ByAuthor|by"
+msgstr "від"
+
+msgid "CI configuration"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI"
+
+msgid "Cancel"
+msgstr "СкаÑувати"
+
+msgid "ChangeTypeActionLabel|Pick into branch"
+msgstr "Вибрати в гілці"
+
+msgid "ChangeTypeActionLabel|Revert in branch"
+msgstr "СкаÑувати у гілці"
+
+msgid "ChangeTypeAction|Cherry-pick"
+msgstr "Cherry-pick"
+
+msgid "ChangeTypeAction|Revert"
+msgstr "СкаÑувати"
+
+msgid "Changelog"
+msgstr "СпиÑок змін (Changelog)"
+
+msgid "Charts"
+msgstr "Графіки"
+
+msgid "Cherry-pick this commit"
+msgstr "Cherry-pick в цьому комміті"
+
+msgid "Cherry-pick this merge request"
+msgstr "Cherry-pick в цьому запиті на злиттÑ"
+
+msgid "CiStatusLabel|canceled"
+msgstr "ÑкаÑовано"
+
+msgid "CiStatusLabel|created"
+msgstr "Ñтворено"
+
+msgid "CiStatusLabel|failed"
+msgstr "невдало"
+
+msgid "CiStatusLabel|manual action"
+msgstr "вручну"
+
+msgid "CiStatusLabel|passed"
+msgstr "виконано"
+
+msgid "CiStatusLabel|passed with warnings"
+msgstr "виконано з попередженнÑми"
+
+msgid "CiStatusLabel|pending"
+msgstr "в очікуванні"
+
+msgid "CiStatusLabel|skipped"
+msgstr "пропущено"
+
+msgid "CiStatusLabel|waiting for manual action"
+msgstr "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¸Ñ… дій"
+
+msgid "CiStatusText|blocked"
+msgstr "заблоковано"
+
+msgid "CiStatusText|canceled"
+msgstr "ÑкаÑовано"
+
+msgid "CiStatusText|created"
+msgstr "Ñтворено"
+
+msgid "CiStatusText|failed"
+msgstr "невдало"
+
+msgid "CiStatusText|manual"
+msgstr "вручну"
+
+msgid "CiStatusText|passed"
+msgstr "виконано"
+
+msgid "CiStatusText|pending"
+msgstr "в очікуванні"
+
+msgid "CiStatusText|skipped"
+msgstr "пропущено"
+
+msgid "CiStatus|running"
+msgstr "виконуєтьÑÑ"
+
+msgid "Commit"
+msgid_plural "Commits"
+msgstr[0] "Комміт"
+msgstr[1] "Комміта"
+msgstr[2] "Коммітів"
+
+msgid "Commit duration in minutes for last 30 commits"
+msgstr ""
+"ТриваліÑÑ‚ÑŒ коммітів протÑгом декількох хвилин на протÑзі 30 оÑтанніх "
+"коммітів"
+
+msgid "Commit message"
+msgstr "Комміт повідомленнÑ"
+
+msgid "CommitBoxTitle|Commit"
+msgstr "Комміт"
+
+msgid "CommitMessage|Add %{file_name}"
+msgstr "Додати %{file_name}"
+
+msgid "Commits"
+msgstr "Комміти"
+
+msgid "Commits feed"
+msgstr "Канал коммітів"
+
+msgid "Commits|History"
+msgstr "ІÑторіÑ"
+
+msgid "Committed by"
+msgstr "Комміт від"
+
+msgid "Compare"
+msgstr "ПорівнÑти"
+
+msgid "Contribution guide"
+msgstr "Керівництво контриб’юторів"
+
+msgid "Contributors"
+msgstr "Контриб’ютори"
+
+msgid "Copy URL to clipboard"
+msgstr "Скопіювати URL в буфер обміну"
+
+msgid "Copy commit SHA to clipboard"
+msgstr "Скопіювати ідентифікатор в буфер обміну"
+
+msgid "Create New Directory"
+msgstr "Створити новий каталог"
+
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr ""
+"Створити токен доÑтупу Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ аккауета, щоб відправлÑти або отримувати "
+"через %{protocol}."
+
+msgid "Create directory"
+msgstr "Створити каталог"
+
+msgid "Create empty bare repository"
+msgstr "Створити порожній репозиторій"
+
+msgid "Create merge request"
+msgstr "Створити запит на злиттÑ"
+
+msgid "Create new..."
+msgstr "Створити..."
+
+msgid "CreateNewFork|Fork"
+msgstr "Форк"
+
+msgid "CreateTag|Tag"
+msgstr "Тег"
+
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "Створити токен Ð´Ð»Ñ Ð¾ÑобиÑтого доÑтупу"
+
+msgid "Cron Timezone"
+msgstr "ЧаÑовий поÑÑ Cron"
+
+msgid "Cron syntax"
+msgstr "СинтакÑÐ¸Ñ Cron"
+
+msgid "Custom notification events"
+msgstr "КориÑтувацькі Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ про події"
+
+msgid ""
+"Custom notification levels are the same as participating levels. With custom "
+"notification levels you will also receive notifications for select events. "
+"To find out more, check out %{notification_link}."
+msgstr ""
+"Спеціальні рівні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñпівпадають з рівнем учаÑÑ‚Ñ–. За допомогою "
+"Ñпеціальних рівнів Ñповіщень ви також отримуватимете ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ вибрані "
+"події. Щоб дізнатиÑÑŒ більше, переглÑньте %{notification_link}."
+
+msgid "Cycle Analytics"
+msgstr "Ðналіз циклу"
+
+msgid ""
+"Cycle Analytics gives an overview of how much time it takes to go from idea "
+"to production in your project."
+msgstr ""
+"Ðналітика циклу дає оглÑд того, Ñкільки чаÑу потрібно, щоб перейти від ідеї "
+"до виробництва у вашому проекті."
+
+msgid "CycleAnalyticsStage|Code"
+msgstr "Код"
+
+msgid "CycleAnalyticsStage|Issue"
+msgstr "Проблема"
+
+msgid "CycleAnalyticsStage|Plan"
+msgstr "ПлануваннÑ"
+
+msgid "CycleAnalyticsStage|Production"
+msgstr "ПРОД"
+
+msgid "CycleAnalyticsStage|Review"
+msgstr "ЗатвердженнÑ"
+
+msgid "CycleAnalyticsStage|Staging"
+msgstr "ДЕВ"
+
+msgid "CycleAnalyticsStage|Test"
+msgstr "ТеÑтуваннÑ"
+
+msgid "Define a custom pattern with cron syntax"
+msgstr "Визначте влаÑний шаблон за допомогою ÑинтакÑиÑу cron"
+
+msgid "Delete"
+msgstr "Видалити"
+
+msgid "Deploy"
+msgid_plural "Deploys"
+msgstr[0] "РозгортаннÑ"
+msgstr[1] "РозгортаннÑ"
+msgstr[2] "Розгортань"
+
+msgid "Description"
+msgstr "ОпиÑ"
+
+msgid "Directory name"
+msgstr "Ім'Ñ ÐºÐ°Ñ‚Ð°Ð»Ð¾Ð³Ñƒ"
+
+msgid "Don't show again"
+msgstr "Ðе показувати знову"
+
+msgid "Download"
+msgstr "Завантажити"
+
+msgid "Download tar"
+msgstr "Завантажити tar"
+
+msgid "Download tar.bz2"
+msgstr "Завантажити tar.bz2"
+
+msgid "Download tar.gz"
+msgstr "Завантажити tar.gz"
+
+msgid "Download zip"
+msgstr "Завантажити zip"
+
+msgid "DownloadArtifacts|Download"
+msgstr "Завантажити"
+
+msgid "DownloadCommit|Email Patches"
+msgstr "Email-патчи"
+
+msgid "DownloadCommit|Plain Diff"
+msgstr "Plain Diff"
+
+msgid "DownloadSource|Download"
+msgstr "Завантажити"
+
+msgid "Edit"
+msgstr "Редагувати"
+
+msgid "Edit Pipeline Schedule %{id}"
+msgstr "Редагувати Розклад Конвеєра %{id}"
+
+msgid "Every day (at 4:00am)"
+msgstr "Кожен день (в 4:00 ранку)"
+
+msgid "Every month (on the 1st at 4:00am)"
+msgstr "Кожен міÑÑць (1-го чиÑла о 4:00 ранку)"
+
+msgid "Every week (Sundays at 4:00am)"
+msgstr "Ð©Ð¾Ñ‚Ð¸Ð¶Ð½Ñ (в неділю о 4:00 ранку)"
+
+msgid "Failed to change the owner"
+msgstr "Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ влаÑника"
+
+msgid "Failed to remove the pipeline schedule"
+msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ розклад Конвеєра"
+
+msgid "Files"
+msgstr "Файлів"
+
+msgid "Filter by commit message"
+msgstr "Фільтрувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð¼Ñ–Ñ‚Ñ–Ð²"
+
+msgid "Find by path"
+msgstr "Пошук по шлÑху"
+
+msgid "Find file"
+msgstr "Знайти файл"
+
+msgid "FirstPushedBy|First"
+msgstr "Перший"
+
+msgid "FirstPushedBy|pushed by"
+msgstr "ÐадіÑлані зміни від"
+
+msgid "Fork"
+msgid_plural "Forks"
+msgstr[0] "Форк"
+msgstr[1] "Форки"
+msgstr[2] "Форків"
+
+msgid "ForkedFromProjectPath|Forked from"
+msgstr "Форк від"
+
+msgid "From issue creation until deploy to production"
+msgstr "З моменту ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ до Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° ПРОД"
+
+msgid "From merge request merge until deploy to production"
+msgstr "З об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñƒ Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð½Ð° ПРОД"
+
+msgid "Go to your fork"
+msgstr "Перейти до вашого форку"
+
+msgid "GoToYourFork|Fork"
+msgstr "Форк"
+
+msgid "Home"
+msgstr "Головна"
+
+msgid "Housekeeping successfully started"
+msgstr "ÐžÑ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ ÑƒÑпішно розпочато"
+
+msgid "Import repository"
+msgstr "Імпорт репозеторіÑ"
+
+msgid "Interval Pattern"
+msgstr "Шаблон інтервалу"
+
+msgid "Introducing Cycle Analytics"
+msgstr "ПредÑтавлÑємо аналітику циклу"
+
+msgid "Jobs for last month"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній міÑÑць"
+
+msgid "Jobs for last week"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній тиждень"
+
+msgid "Jobs for last year"
+msgstr "КількіÑÑ‚ÑŒ завдань за оÑтанній рік"
+
+msgid "LFSStatus|Disabled"
+msgstr "Вимкнено"
+
+msgid "LFSStatus|Enabled"
+msgstr "Увімкнено"
+
+msgid "Last %d day"
+msgid_plural "Last %d days"
+msgstr[0] "ОÑтанній %d день"
+msgstr[1] "ОÑтанніх %d дні"
+msgstr[2] "ОÑтанніх %d днів"
+
+msgid "Last Pipeline"
+msgstr "ОÑтанній Конвеєр"
+
+msgid "Last Update"
+msgstr "ОÑтаннє оновленнÑ"
+
+msgid "Last commit"
+msgstr "ОÑтанній комміт"
+
+msgid "Learn more in the"
+msgstr "ДізнайтеÑÑŒ більше"
+
+msgid "Learn more in the|pipeline schedules documentation"
+msgstr "Детальніше в документації по розкладами конвеєрів"
+
+msgid "Leave group"
+msgstr "Залишити групу"
+
+msgid "Leave project"
+msgstr "Залишити проект"
+
+msgid "Limited to showing %d event at most"
+msgid_plural "Limited to showing %d events at most"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Median"
+msgstr "Медіана"
+
+msgid "MissingSSHKeyWarningLink|add an SSH key"
+msgstr "додати SSH ключ"
+
+msgid "New Issue"
+msgid_plural "New Issues"
+msgstr[0] "Ðова проблема"
+msgstr[1] "Ðові проблеми"
+msgstr[2] "Ðових проблем"
+
+msgid "New Pipeline Schedule"
+msgstr "Ðовий розклад Конвеєра"
+
+msgid "New branch"
+msgstr "Ðова гілка"
+
+msgid "New directory"
+msgstr "Ðовий каталог"
+
+msgid "New file"
+msgstr "Ðовий файл"
+
+msgid "New issue"
+msgstr "Ðова проблема"
+
+msgid "New merge request"
+msgstr "Ðовий запит на злиттÑ"
+
+msgid "New schedule"
+msgstr "Ðовий Розклад"
+
+msgid "New snippet"
+msgstr "Ðовий Ñніппет"
+
+msgid "New tag"
+msgstr "Ðовий тег"
+
+msgid "No repository"
+msgstr "Ðемає репозеторіÑ"
+
+msgid "No schedules"
+msgstr "немає Розкладів"
+
+msgid "Not available"
+msgstr "ÐедоÑтупний"
+
+msgid "Not enough data"
+msgstr "ÐедоÑтатньо даних"
+
+msgid "Notification events"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ події"
+
+msgid "NotificationEvent|Close issue"
+msgstr "Проблема закрита"
+
+msgid "NotificationEvent|Close merge request"
+msgstr "Запит на об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð·Ð°ÐºÑ€Ð¸Ñ‚Ð¸Ð¹"
+
+msgid "NotificationEvent|Failed pipeline"
+msgstr "Ðевдача в конвеєрі"
+
+msgid "NotificationEvent|Merge merge request"
+msgstr "Об'єднати запит на злиттÑ"
+
+msgid "NotificationEvent|New issue"
+msgstr "Ðова проблема"
+
+msgid "NotificationEvent|New merge request"
+msgstr "Ðовий запит на злиттÑ"
+
+msgid "NotificationEvent|New note"
+msgstr "Ðова нотатка"
+
+msgid "NotificationEvent|Reassign issue"
+msgstr "Перепризначити проблему"
+
+msgid "NotificationEvent|Reassign merge request"
+msgstr "Перепризначити запит на злиттÑ"
+
+msgid "NotificationEvent|Reopen issue"
+msgstr "Повторне Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñƒ"
+
+msgid "NotificationEvent|Successful pipeline"
+msgstr "УÑпішно в Конвеєрі"
+
+msgid "NotificationLevel|Custom"
+msgstr "ВлаÑні"
+
+msgid "NotificationLevel|Disabled"
+msgstr "Вимкнено"
+
+msgid "NotificationLevel|Global"
+msgstr "Загальні"
+
+msgid "NotificationLevel|On mention"
+msgstr "Коли Ð²Ð°Ñ Ð·Ð³Ð°Ð´ÑƒÑŽÑ‚ÑŒ"
+
+msgid "NotificationLevel|Participate"
+msgstr "Берете учаÑÑ‚ÑŒ"
+
+msgid "NotificationLevel|Watch"
+msgstr "ВідÑтежувати"
+
+msgid "OfSearchInADropdown|Filter"
+msgstr "Фільтр"
+
+msgid "OpenedNDaysAgo|Opened"
+msgstr "Відкрито"
+
+msgid "Options"
+msgstr "Параметри"
+
+msgid "Owner"
+msgstr "ВлаÑник"
+
+msgid "Pipeline"
+msgstr "Конвеєр"
+
+msgid "Pipeline Health"
+msgstr "Стан Конвеєра"
+
+msgid "Pipeline Schedule"
+msgstr "Розклад Конвеєра"
+
+msgid "Pipeline Schedules"
+msgstr "Розклади Конвеєрів"
+
+msgid "PipelineCharts|Failed:"
+msgstr "Ðе вдалоÑÑ:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "Загальна ÑтатиÑтика"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "Коефіцієнт уÑпіху:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "УÑпішні:"
+
+msgid "PipelineCharts|Total:"
+msgstr "Ð’Ñього:"
+
+msgid "PipelineSchedules|Activated"
+msgstr "Ðктивовано"
+
+msgid "PipelineSchedules|Active"
+msgstr "Ðктивні"
+
+msgid "PipelineSchedules|All"
+msgstr "Ð’ÑÑ–"
+
+msgid "PipelineSchedules|Inactive"
+msgstr "Ðеактивні"
+
+msgid "PipelineSchedules|Input variable key"
+msgstr "Введіть ім'Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¾Ñ—"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "Вхідні Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½Ð½Ð¸Ñ…"
+
+msgid "PipelineSchedules|Next Run"
+msgstr "ÐаÑтупний запуÑк"
+
+msgid "PipelineSchedules|None"
+msgstr "Ðемає"
+
+msgid "PipelineSchedules|Provide a short description for this pipeline"
+msgstr "Задайте короткий Ð¾Ð¿Ð¸Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Конвеєру"
+
+msgid "PipelineSchedules|Remove variable row"
+msgstr "Видалити змінні"
+
+msgid "PipelineSchedules|Take ownership"
+msgstr "Стати влаÑником"
+
+msgid "PipelineSchedules|Target"
+msgstr "Ціль"
+
+msgid "PipelineSchedules|Variables"
+msgstr "Змінні"
+
+msgid "PipelineSheduleIntervalPattern|Custom"
+msgstr "ВлаÑні"
+
+msgid "Pipelines"
+msgstr "Конвеєри"
+
+msgid "Pipelines charts"
+msgstr "Чарти Конвеєрів"
+
+msgid "Pipeline|all"
+msgstr "вÑÑ–"
+
+msgid "Pipeline|success"
+msgstr "уÑпіх"
+
+msgid "Pipeline|with stage"
+msgstr "зі Ñтадією"
+
+msgid "Pipeline|with stages"
+msgstr "зі ÑтадіÑми"
+
+msgid "Project '%{project_name}' queued for deletion."
+msgstr "Проект '%{project_name}' доданий в чергу на видаленнÑ."
+
+msgid "Project '%{project_name}' was successfully created."
+msgstr "Проект '%{project_name}' уÑпішно Ñтворений."
+
+msgid "Project '%{project_name}' was successfully updated."
+msgstr "Проект '%{project_name}' уÑпішно оновлено."
+
+msgid "Project '%{project_name}' will be deleted."
+msgstr "Проект '%{project_name}' видалений."
+
+msgid "Project access must be granted explicitly to each user."
+msgstr "ДоÑтуп до проекту повинен надаватиÑÑ ÐºÐ¾Ð¶Ð½Ð¾Ð¼Ñƒ кориÑтувачеві."
+
+msgid "Project export could not be deleted."
+msgstr "Ðеможливо видалити екÑпорт проекту."
+
+msgid "Project export has been deleted."
+msgstr "ЕкÑпорт проекту видалений."
+
+msgid ""
+"Project export link has expired. Please generate a new export from your "
+"project settings."
+msgstr ""
+"ЗакінчивÑÑ Ñ‚ÐµÑ€Ð¼Ñ–Ð½ дії поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° проект. Створіть новий екÑпорт в ваших "
+"наÑтройках проекту."
+
+msgid "Project export started. A download link will be sent by email."
+msgstr ""
+"Розпочато екÑпорт проекту. ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ ÑÐºÐ°Ñ‡ÑƒÐ²Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ надіÑлана "
+"електронною поштою."
+
+msgid "Project home"
+msgstr "Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка проекту"
+
+msgid "ProjectFeature|Disabled"
+msgstr "Вимкнено"
+
+msgid "ProjectFeature|Everyone with access"
+msgstr "Ð’Ñе з доÑтупом"
+
+msgid "ProjectFeature|Only team members"
+msgstr "Тільки члени команди"
+
+msgid "ProjectFileTree|Name"
+msgstr "Ім'Ñ"
+
+msgid "ProjectLastActivity|Never"
+msgstr "Ðіколи"
+
+msgid "ProjectLifecycle|Stage"
+msgstr "Етап"
+
+msgid "ProjectNetworkGraph|Graph"
+msgstr "ІÑторіÑ"
+
+msgid "Read more"
+msgstr "Докладніше"
+
+msgid "Readme"
+msgstr "Прочитай Мене"
+
+msgid "RefSwitcher|Branches"
+msgstr "Гілки"
+
+msgid "RefSwitcher|Tags"
+msgstr "Теги"
+
+msgid "Related Commits"
+msgstr "Пов'Ñзані Комміти"
+
+msgid "Related Deployed Jobs"
+msgstr "Пов’Ñзані розгорнуті задачі (Jobs)"
+
+msgid "Related Issues"
+msgstr "Пов’Ñзані Проблеми (Issues)"
+
+msgid "Related Jobs"
+msgstr "Пов’Ñзані Задачі (Jobs)"
+
+msgid "Related Merge Requests"
+msgstr "Пов'Ñзані запити на злиттÑ"
+
+msgid "Related Merged Requests"
+msgstr "Пов'Ñзані об'єднані запити"
+
+msgid "Remind later"
+msgstr "Ðагадати пізніше"
+
+msgid "Remove project"
+msgstr "Видалити проект"
+
+msgid "Request Access"
+msgstr "Запит доÑтупу"
+
+msgid "Revert this commit"
+msgstr "СкаÑувати цей комміт"
+
+msgid "Revert this merge request"
+msgstr "СкаÑувати цей запит на злиттÑ"
+
+msgid "Save pipeline schedule"
+msgstr "Зберегти Розклад Конвеєра"
+
+msgid "Schedule a new pipeline"
+msgstr "Розклад нового конвеєра"
+
+msgid "Scheduling Pipelines"
+msgstr "ÐŸÐ»Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½Ð²ÐµÑ”Ñ€Ñ–Ð²"
+
+msgid "Search branches and tags"
+msgstr "Пошук гілок та тегів"
+
+msgid "Select Archive Format"
+msgstr "Виберіть формат архіву"
+
+msgid "Select a timezone"
+msgstr "Вибрати чаÑовий поÑÑ"
+
+msgid "Select target branch"
+msgstr "Вибір цільової гілки"
+
+msgid "Set a password on your account to pull or push via %{protocol}."
+msgstr ""
+"Ð’Ñтановіть пароль Ñвого облікового запиÑу, щоб відправлÑти або отримувати "
+"код через %{protocol}."
+
+msgid "Set up CI"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI"
+
+msgid "Set up Koding"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Koding"
+
+msgid "Set up auto deploy"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ðµ розгортаннÑ"
+
+msgid "SetPasswordToCloneLink|set a password"
+msgstr "вÑтановити пароль"
+
+msgid "Showing %d event"
+msgid_plural "Showing %d events"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+msgid "Source code"
+msgstr "Код"
+
+msgid "StarProject|Star"
+msgstr "ПідпиÑатиÑÑ"
+
+msgid "Start a %{new_merge_request} with these changes"
+msgstr "Почати %{new_merge_request} з цих змін"
+
+msgid "Switch branch/tag"
+msgstr "тег"
+
+msgid "Tag"
+msgid_plural "Tags"
+msgstr[0] "Тег"
+msgstr[1] "Теги"
+msgstr[2] "Тегів"
+
+msgid "Tags"
+msgstr "Теги"
+
+msgid "Target Branch"
+msgstr "Цільова гілка"
+
+msgid ""
+"The coding stage shows the time from the first commit to creating the merge "
+"request. The data will automatically be added here once you create your "
+"first merge request."
+msgstr ""
+
+msgid "The collection of events added to the data gathered for that stage."
+msgstr ""
+
+msgid "The fork relationship has been removed."
+msgstr "Зв'Ñзок форка видалена."
+
+msgid ""
+"The issue stage shows the time it takes from creating an issue to assigning "
+"the issue to a milestone, or add the issue to a list on your Issue Board. "
+"Begin creating issues to see data for this stage."
+msgstr ""
+"Етап випуÑку показує, Ñкільки чаÑу потрібно від ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ до "
+"приÑÐ²Ð¾Ñ”Ð½Ð½Ñ Ð²Ð¸Ð¿ÑƒÑку, або Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ в вашу дошку проблем. Почніть "
+"Ñтворювати проблеми, щоб переглÑдати дані Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ етапу."
+
+msgid "The phase of the development lifecycle."
+msgstr "Фаза життєвого циклу розробки."
+
+msgid ""
+"The pipelines schedule runs pipelines in the future, repeatedly, for "
+"specific branches or tags. Those scheduled pipelines will inherit limited "
+"project access based on their associated user."
+msgstr ""
+"Розклад конвеєрів запуÑкає в майбутньому конвеєри, Ð´Ð»Ñ Ð¿ÐµÐ²Ð½Ð¸Ñ… гілок або "
+"тегів. Заплановані конвеєри уÑпадковують Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð° доÑтуп до проекту на "
+"оÑнові пов'Ñзаного з ними кориÑтувача."
+
+msgid ""
+"The planning stage shows the time from the previous step to pushing your "
+"first commit. This time will be added automatically once you push your first "
+"commit."
+msgstr ""
+
+msgid ""
+"The production stage shows the total time it takes between creating an issue "
+"and deploying the code to production. The data will be automatically added "
+"once you have completed the full idea to production cycle."
+msgstr ""
+
+msgid "The project can be accessed by any logged in user."
+msgstr "ДоÑтуп до проекту можливий будь-Ñким зареєÑтрованим кориÑтувачем."
+
+msgid "The project can be accessed without any authentication."
+msgstr "ДоÑтуп до проекту можливий без будь-Ñкої перевірки автентичноÑÑ‚Ñ–."
+
+msgid "The repository for this project does not exist."
+msgstr "Репозиторій Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ проекту не Ñ–Ñнує."
+
+msgid ""
+"The review stage shows the time from creating the merge request to merging "
+"it. The data will automatically be added after you merge your first merge "
+"request."
+msgstr ""
+
+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 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 ""
+
+msgid "The time taken by each data entry gathered by that stage."
+msgstr ""
+
+msgid ""
+"The value lying at the midpoint of a series of observed values. E.g., "
+"between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 ="
+" 6."
+msgstr ""
+
+msgid ""
+"This means you can not push code until you create an empty repository or "
+"import existing one."
+msgstr ""
+"Це означає, що ви не можете відправлÑти код, поки не Ñтворите порожній "
+"репозиторій або ÐЕ імпортуєте Ñ–Ñнуючий."
+
+msgid "Time before an issue gets scheduled"
+msgstr ""
+
+msgid "Time before an issue starts implementation"
+msgstr ""
+
+msgid "Time between merge request creation and merge/close"
+msgstr "Ð§Ð°Ñ Ð¼Ñ–Ð¶ ÑтвореннÑм запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– злиттÑм або закриттÑм"
+
+msgid "Time until first merge request"
+msgstr "Ð§Ð°Ñ Ð´Ð¾ першого запиту на злиттÑ"
+
+msgid "Timeago|%s days ago"
+msgstr "%s днів тому"
+
+msgid "Timeago|%s days remaining"
+msgstr "%s днів, що залишилиÑÑ"
+
+msgid "Timeago|%s hours remaining"
+msgstr "%s годин, що залишилиÑÑ"
+
+msgid "Timeago|%s minutes ago"
+msgstr "%s хвилин тому"
+
+msgid "Timeago|%s minutes remaining"
+msgstr "%s хвилини залишитиÑÑ"
+
+msgid "Timeago|%s months ago"
+msgstr "%s міÑÑці(в) тому"
+
+msgid "Timeago|%s months remaining"
+msgstr "%s міÑÑці(в), що залишилиÑÑ"
+
+msgid "Timeago|%s seconds remaining"
+msgstr "%s Ñекунд, що залишаютьÑÑ"
+
+msgid "Timeago|%s weeks ago"
+msgstr "%s тижні(в) тому"
+
+msgid "Timeago|%s weeks remaining"
+msgstr "%s тижні(в) залишилиÑÑ"
+
+msgid "Timeago|%s years ago"
+msgstr "%s років тому"
+
+msgid "Timeago|%s years remaining"
+msgstr "%s роки, що залишилиÑÑ"
+
+msgid "Timeago|1 day remaining"
+msgstr "ЗалишивÑÑ 1 день"
+
+msgid "Timeago|1 hour remaining"
+msgstr "ЗалишилаÑÑŒ 1 година"
+
+msgid "Timeago|1 minute remaining"
+msgstr "ЗалишилаÑÑŒ 1 хвилина"
+
+msgid "Timeago|1 month remaining"
+msgstr "ЗалишивÑÑ 1 міÑÑць"
+
+msgid "Timeago|1 week remaining"
+msgstr "ЗалишивÑÑ 1 тиждень"
+
+msgid "Timeago|1 year remaining"
+msgstr "ЗалишивÑÑ 1 рік"
+
+msgid "Timeago|Past due"
+msgstr "ПроÑтрочені"
+
+msgid "Timeago|a day ago"
+msgstr "День тому"
+
+msgid "Timeago|a month ago"
+msgstr "міÑÑць тому"
+
+msgid "Timeago|a week ago"
+msgstr "тиждень тому"
+
+msgid "Timeago|a while"
+msgstr "деÑкий Ñ‡Ð°Ñ Ð½Ð°Ð·Ð°Ð´"
+
+msgid "Timeago|a year ago"
+msgstr "рік тому"
+
+msgid "Timeago|about %s hours ago"
+msgstr "Близько %s годин тому"
+
+msgid "Timeago|about a minute ago"
+msgstr "Близько хвилини тому"
+
+msgid "Timeago|about an hour ago"
+msgstr "Близько години тому"
+
+msgid "Timeago|in %s days"
+msgstr "через %s дні(в)"
+
+msgid "Timeago|in %s hours"
+msgstr "через %s годин(и)"
+
+msgid "Timeago|in %s minutes"
+msgstr "через %s хвилин(и)"
+
+msgid "Timeago|in %s months"
+msgstr "через %s міÑÑці(в)"
+
+msgid "Timeago|in %s seconds"
+msgstr "через %s Ñекунд(и)"
+
+msgid "Timeago|in %s weeks"
+msgstr "через %s тижні(в)"
+
+msgid "Timeago|in %s years"
+msgstr "через %s роки(ів)"
+
+msgid "Timeago|in 1 day"
+msgstr "через 1 день"
+
+msgid "Timeago|in 1 hour"
+msgstr "через годину"
+
+msgid "Timeago|in 1 minute"
+msgstr "через хвилину"
+
+msgid "Timeago|in 1 month"
+msgstr "через міÑÑць"
+
+msgid "Timeago|in 1 week"
+msgstr "через тиждень"
+
+msgid "Timeago|in 1 year"
+msgstr "через рік"
+
+msgid "Timeago|less than a minute ago"
+msgstr "менше хвилини тому"
+
+msgid "Time|hr"
+msgid_plural "Time|hrs"
+msgstr[0] "година"
+msgstr[1] "години"
+msgstr[2] "годин"
+
+msgid "Time|min"
+msgid_plural "Time|mins"
+msgstr[0] "хвилина"
+msgstr[1] "хвилини"
+msgstr[2] "хвилин"
+
+msgid "Time|s"
+msgstr "Ñекунд(а)"
+
+msgid "Total Time"
+msgstr "Загальний чаÑ"
+
+msgid "Total test time for all commits/merges"
+msgstr "Загальний чаÑ, щоб перевірити вÑÑ– фікÑації/злиттÑ"
+
+msgid "Unstar"
+msgstr "ВідпиÑатиÑÑŒ"
+
+msgid "Upload New File"
+msgstr "Завантажити новий файл"
+
+msgid "Upload file"
+msgstr "Завантажити файл"
+
+msgid "UploadLink|click to upload"
+msgstr "ÐатиÑніть, щоб завантажити"
+
+msgid "Use your global notification setting"
+msgstr "ВикориÑтовуютьÑÑ Ð³Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ñ– Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ"
+
+msgid "View open merge request"
+msgstr "ПереглÑд відкритих запитів на злиттÑ"
+
+msgid "VisibilityLevel|Internal"
+msgstr "Внутрішній"
+
+msgid "VisibilityLevel|Private"
+msgstr "Приватний"
+
+msgid "VisibilityLevel|Public"
+msgstr "Публічний"
+
+msgid "Want to see the data? Please ask an administrator for access."
+msgstr "Хочете побачити дані? Будь лаÑка, попроÑить у адмініÑтратора доÑтуп."
+
+msgid "We don't have enough data to show this stage."
+msgstr "Ми не маємо доÑтатньо даних Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ цього етапу."
+
+msgid "Withdraw Access Request"
+msgstr "СкаÑувати запит доÑтупу"
+
+msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ви хочете видалити %{group_name}.\n"
+"Видалені групи ÐЕ МОЖÐРбуду відновити!\n"
+"Ви ÐБСОЛЮТÐО впевнені?"
+
+msgid ""
+"You are going to remove %{project_name_with_namespace}.\n"
+"Removed project CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ви хочете видалити %{project_name_with_namespace}.\n"
+"Видалений проект ÐЕ МОЖЕ бути відновлений!\n"
+"Ви ÐБСОЛЮТÐО впевнені?"
+
+msgid ""
+"You are going to remove the fork relationship to source project "
+"%{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr ""
+"Ви збираєтеÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ зв'Ñзок з форка з вихідним проектом "
+"%{forked_from_project}. Ви ÐБСОЛЮТÐО впевнені?"
+
+msgid ""
+"You are going to transfer %{project_name_with_namespace} to another owner. "
+"Are you ABSOLUTELY sure?"
+msgstr ""
+"Ви збираєтеÑÑ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‚Ð¸ проект %{project_name_with_namespace} іншому влаÑнику."
+" Ви ÐБСОЛЮТÐО впевнені?"
+
+msgid "You can only add files when you are on a branch"
+msgstr "Ви можете додавати тільки файли, коли перебуваєте в гілці"
+
+msgid "You have reached your project limit"
+msgstr "Ви доÑÑгли Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð² вашому проекті"
+
+msgid "You must sign in to star a project"
+msgstr "Ðеобхідно увійти, щоб оцінити проект"
+
+msgid "You need permission."
+msgstr "Вам потрібен дозвіл"
+
+msgid "You will not get any notifications via email"
+msgstr "Ви не отримаєте ніÑких повідомлень по електронній пошті"
+
+msgid "You will only receive notifications for the events you choose"
+msgstr "Ви будете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ про обрані вами події"
+
+msgid ""
+"You will only receive notifications for threads you have participated in"
+msgstr ""
+"Ви будете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ про тих темах, в Ñких ви брали "
+"учаÑÑ‚ÑŒ"
+
+msgid "You will receive notifications for any activity"
+msgstr "Ви будете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ будь-Ñкі дії"
+
+msgid ""
+"You will receive notifications only for comments in which you were "
+"@mentioned"
+msgstr ""
+"Ви будете отримувати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ Ð´Ð»Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ–Ð², в Ñких ви були "
+"@згадані"
+
+msgid ""
+"You won't be able to pull or push project code via %{protocol} until you "
+"%{set_password_link} on your account"
+msgstr ""
+"Ви не зможете отримувати Ñ– відправлÑти код проекту через %{protocol} поки "
+"%{set_password_link} в ваш аккаунт"
+
+msgid ""
+"You won't be able to pull or push project code via SSH until you "
+"%{add_ssh_key_link} to your profile"
+msgstr ""
+"Ви не зможете отримувати Ñ– відправлÑти код проекту через SSH поки "
+"%{add_ssh_key_link} в ваш профіль."
+
+msgid "Your name"
+msgstr "Ваше ім'Ñ"
+
+msgid "day"
+msgid_plural "days"
+msgstr[0] "день"
+msgstr[1] "дні"
+msgstr[2] "днів"
+
+msgid "new merge request"
+msgstr "Ðовий запит на злиттÑ"
+
+msgid "notification emails"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾ÑŽ поштою"
+
+msgid "parent"
+msgid_plural "parents"
+msgstr[0] "джерело"
+msgstr[1] "джерела"
+msgstr[2] "джерел"
diff --git a/locale/uk/gitlab.po.time_stamp b/locale/uk/gitlab.po.time_stamp
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/locale/uk/gitlab.po.time_stamp
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 8ba95093b82..f471e7def25 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -4,20 +4,37 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-19 09:57-0400\n"
+"PO-Revision-Date: 2017-07-12 06:23-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (China) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "为æ高页é¢åŠ è½½é€Ÿåº¦åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %s 次æ交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 次æ交"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交于 %{commit_timeago}"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 æ¡æµæ°´çº¿"
+msgstr[1] "%d æ¡æµæ°´çº¿"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "æŒç»­é›†æˆæ•°æ®å›¾"
+
msgid "About auto deploy"
msgstr "关于自动部署"
@@ -63,9 +80,24 @@ msgstr ""
"已创建分支 <strong>%{branch_name}</strong> 。如需设置自动部署, 请选择åˆé€‚çš„ GitLab CI Yaml "
"模æ¿å¹¶æ交更改。%{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "æœç´¢åˆ†æ”¯"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切æ¢åˆ†æ”¯"
+
msgid "Branches"
msgstr "分支"
+msgid "Browse Directory"
+msgstr "æµè§ˆç›®å½•"
+
+msgid "Browse File"
+msgstr "æµè§ˆæ–‡ä»¶"
+
+msgid "Browse Files"
+msgstr "æµè§ˆæ–‡ä»¶"
+
msgid "Browse files"
msgstr "æµè§ˆæ–‡ä»¶"
@@ -160,6 +192,9 @@ msgid "Commit"
msgid_plural "Commits"
msgstr[0] "æ交"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "最近30次æ交相应æŒç»­é›†æˆèŠ±è´¹çš„时间(分钟)"
+
msgid "Commit message"
msgstr "æ交信æ¯"
@@ -172,6 +207,9 @@ msgstr "添加 %{file_name}"
msgid "Commits"
msgstr "æ交"
+msgid "Commits feed"
+msgstr "æ交动æ€"
+
msgid "Commits|History"
msgstr "历å²"
@@ -196,6 +234,11 @@ msgstr "å¤åˆ¶æ交 SHA 的值到剪贴æ¿"
msgid "Create New Directory"
msgstr "创建新目录"
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr "在å¸æˆ·ä¸Šåˆ›å»ºä¸ªäººè®¿é—®ä»¤ç‰Œï¼Œä»¥é€šè¿‡ %{protocol} æ¥æ‹‰å–或推é€ã€‚"
+
msgid "Create directory"
msgstr "创建目录"
@@ -214,6 +257,9 @@ msgstr "派生"
msgid "CreateTag|Tag"
msgstr "标签"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "创建个人访问令牌"
+
msgid "Cron Timezone"
msgstr "Cron 时区"
@@ -329,6 +375,9 @@ msgstr "无法删除æµæ°´çº¿è®¡åˆ’"
msgid "Files"
msgstr "文件"
+msgid "Filter by commit message"
+msgstr "按æ交消æ¯è¿‡æ»¤"
+
msgid "Find by path"
msgstr "按路径查找"
@@ -375,6 +424,15 @@ msgstr "循环周期"
msgid "Introducing Cycle Analytics"
msgstr "周期分æžç®€ä»‹"
+msgid "Jobs for last month"
+msgstr "上个月的作业"
+
+msgid "Jobs for last week"
+msgstr "上个星期的作业"
+
+msgid "Jobs for last year"
+msgstr "去年的作业"
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -537,6 +595,21 @@ msgstr "æµæ°´çº¿è®¡åˆ’"
msgid "Pipeline Schedules"
msgstr "æµæ°´çº¿è®¡åˆ’"
+msgid "PipelineCharts|Failed:"
+msgstr "失败:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "总体统计数æ®"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "æˆåŠŸçŽ‡ï¼š"
+
+msgid "PipelineCharts|Successful:"
+msgstr "æˆåŠŸï¼š"
+
+msgid "PipelineCharts|Total:"
+msgstr "总计:"
+
msgid "PipelineSchedules|Activated"
msgstr "是å¦å¯ç”¨"
@@ -549,6 +622,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未å¯ç”¨"
+msgid "PipelineSchedules|Input variable key"
+msgstr "输入å˜é‡å"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "输入å˜é‡å€¼"
+
msgid "PipelineSchedules|Next Run"
msgstr "下次è¿è¡Œæ—¶é—´"
@@ -558,15 +637,33 @@ msgstr "æ— "
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "为此æµæ°´çº¿æ供简短æè¿°"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "删除å˜é‡"
+
msgid "PipelineSchedules|Take ownership"
-msgstr "å–得所有者"
+msgstr "å–得所有æƒ"
msgid "PipelineSchedules|Target"
msgstr "目标"
+msgid "PipelineSchedules|Variables"
+msgstr "å˜é‡"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定义"
+msgid "Pipelines"
+msgstr "æµæ°´çº¿"
+
+msgid "Pipelines charts"
+msgstr "æµæ°´çº¿ç»Ÿè®¡å›¾"
+
+msgid "Pipeline|all"
+msgstr "所有"
+
+msgid "Pipeline|success"
+msgstr "æˆåŠŸ"
+
msgid "Pipeline|with stage"
msgstr "于阶段"
@@ -692,7 +789,7 @@ msgstr "选择时区"
msgid "Select target branch"
msgstr "选择目标分支"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "为账å·åˆ›å»ºä¸€ä¸ªç”¨äºŽæŽ¨é€æˆ–拉å–çš„ %{protocol} 密ç ã€‚"
msgid "Set up CI"
@@ -974,9 +1071,15 @@ msgstr "上传新文件"
msgid "Upload file"
msgstr "上传文件"
+msgid "UploadLink|click to upload"
+msgstr "点击上传"
+
msgid "Use your global notification setting"
msgstr "使用全局通知设置"
+msgid "View open merge request"
+msgstr "查看待处ç†çš„åˆå¹¶è¯·æ±‚"
+
msgid "VisibilityLevel|Internal"
msgstr "内部"
@@ -996,12 +1099,18 @@ msgid "Withdraw Access Request"
msgstr "å–消æƒé™ç”³è¯·"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "å³å°†åˆ é™¤ %{group_name}。\n"
+"已删除的群组无法æ¢å¤ï¼\n"
+"确定继续å—?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
-msgstr "å³å°†è¦åˆ é™¤ %{project_name_with_namespace}。\n"
-"已删除的项目无法æ¢å¤ï¼\n"
-"确定继续å—?"
+msgstr "å³å°†è¦åˆ é™¤ %{project_name_with_namespace}。已删除的项目无法æ¢å¤ï¼ç¡®å®šç»§ç»­å—?"
msgid ""
"You are going to remove the fork relationship to source project "
@@ -1069,4 +1178,3 @@ msgstr "通知邮件"
msgid "parent"
msgid_plural "parents"
msgstr[0] "父级"
-
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 4d545d27185..1b7c39f8f62 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -1,24 +1,39 @@
# Huang Tao <htve@outlook.com>, 2017. #zanata
-# Victor Wu <anonymous@domain.com>, 2017.
-# Hazel Yang <anonymous@domain.com>, 2017.
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-19 09:57-0400\n"
+"PO-Revision-Date: 2017-07-12 06:32-0400\n"
"Last-Translator: Huang Tao <htve@outlook.com>\n"
-"Language-Team: Chinese (Hong Kong) (https://translate.zanata.org/project/view/GitLab)\n"
+"Language-Team: Chinese (Hong Kong SAR China) (https://translate.zanata.org/project/view/GitLab)\n"
"Language: zh-HK\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "為æ高é é¢åŠ è¼‰é€Ÿåº¦åŠæ€§èƒ½ï¼Œå·²çœç•¥äº† %s 次æ交。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] " %d 次æ交"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "ç”± %{commit_author_link} æ交於 %{commit_timeago}"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 æ¢æµæ°´ç·š"
+msgstr[1] "%d æ¢æµæ°´ç·š"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "相關æŒçºŒé›†æˆçš„圖åƒé›†åˆ"
+
msgid "About auto deploy"
msgstr "關於自動部署"
@@ -64,9 +79,24 @@ msgstr ""
"分支 <strong>%{branch_name}</strong> 已創建。如需設置自動部署, è«‹é¸æ“‡åˆé©çš„ GitLab CI Yaml "
"模æ¿ä½µæ交更改。%{link_to_autodeploy_doc}"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "æœç´¢åˆ†æ”¯"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切æ›åˆ†æ”¯"
+
msgid "Branches"
msgstr "分支"
+msgid "Browse Directory"
+msgstr "ç€è¦½ç›®éŒ„"
+
+msgid "Browse File"
+msgstr "ç€è¦½æ–‡ä»¶"
+
+msgid "Browse Files"
+msgstr "ç€è¦½æ–‡ä»¶"
+
msgid "Browse files"
msgstr "ç€è¦½æ–‡ä»¶"
@@ -161,6 +191,9 @@ msgid "Commit"
msgid_plural "Commits"
msgstr[0] "æ交"
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "最近30次æ交花費的時間(分é˜ï¼‰"
+
msgid "Commit message"
msgstr "æ交信æ¯"
@@ -173,6 +206,9 @@ msgstr "添加 %{file_name}"
msgid "Commits"
msgstr "æ交"
+msgid "Commits feed"
+msgstr "æ交動態"
+
msgid "Commits|History"
msgstr "æ­·å²"
@@ -197,6 +233,11 @@ msgstr "複製æ交 SHA 到剪貼æ¿"
msgid "Create New Directory"
msgstr "創建新目錄"
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr "在帳戶上創建個人訪å•ä»¤ç‰Œï¼Œä»¥é€šéŽ %{protocol} 來拉å–或推é€ã€‚"
+
msgid "Create directory"
msgstr "創建目錄"
@@ -215,6 +256,9 @@ msgstr "派生"
msgid "CreateTag|Tag"
msgstr "標籤"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "創建個人訪å•ä»¤ç‰Œ"
+
msgid "Cron Timezone"
msgstr "Cron 時å€"
@@ -330,6 +374,9 @@ msgstr "無法刪除æµæ°´ç·šè¨ˆåŠƒ"
msgid "Files"
msgstr "文件"
+msgid "Filter by commit message"
+msgstr "按æ交消æ¯éŽæ¿¾"
+
msgid "Find by path"
msgstr "按路徑查找"
@@ -376,6 +423,15 @@ msgstr "循環週期"
msgid "Introducing Cycle Analytics"
msgstr "週期分æžç°¡ä»‹"
+msgid "Jobs for last month"
+msgstr "上個月的作業"
+
+msgid "Jobs for last week"
+msgstr "上個星期的作業"
+
+msgid "Jobs for last year"
+msgstr "去年的作業"
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -538,6 +594,21 @@ msgstr "æµæ°´ç·šè¨ˆåŠƒ"
msgid "Pipeline Schedules"
msgstr "æµæ°´ç·šè¨ˆåŠƒ"
+msgid "PipelineCharts|Failed:"
+msgstr "失敗:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "總體統計"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "æˆåŠŸçŽ‡ï¼š"
+
+msgid "PipelineCharts|Successful:"
+msgstr "æˆåŠŸï¼š"
+
+msgid "PipelineCharts|Total:"
+msgstr "總計:"
+
msgid "PipelineSchedules|Activated"
msgstr "是å¦å•Ÿç”¨"
@@ -550,6 +621,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未啟用"
+msgid "PipelineSchedules|Input variable key"
+msgstr "輸入變é‡å"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "輸入變é‡å€¼"
+
msgid "PipelineSchedules|Next Run"
msgstr "下次é‹è¡Œæ™‚é–“"
@@ -559,15 +636,33 @@ msgstr "ç„¡"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "為此æµæ°´ç·šæ供簡短æè¿°"
+msgid "PipelineSchedules|Remove variable row"
+msgstr "刪除變é‡"
+
msgid "PipelineSchedules|Take ownership"
-msgstr "å–得所有者"
+msgstr "å–得所有權"
msgid "PipelineSchedules|Target"
msgstr "目標"
+msgid "PipelineSchedules|Variables"
+msgstr "變é‡"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自定義"
+msgid "Pipelines"
+msgstr "æµæ°´ç·š"
+
+msgid "Pipelines charts"
+msgstr "æµæ°´ç·šåœ–表"
+
+msgid "Pipeline|all"
+msgstr "所有"
+
+msgid "Pipeline|success"
+msgstr "æˆåŠŸ"
+
msgid "Pipeline|with stage"
msgstr "於階段"
@@ -693,7 +788,7 @@ msgstr "é¸æ“‡æ™‚å€"
msgid "Select target branch"
msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯"
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "為賬號添加壹個用於推é€æˆ–拉å–çš„ %{protocol} 密碼。"
msgid "Set up CI"
@@ -975,9 +1070,15 @@ msgstr "上傳新文件"
msgid "Upload file"
msgstr "上傳文件"
+msgid "UploadLink|click to upload"
+msgstr "點擊上傳"
+
msgid "Use your global notification setting"
msgstr "使用全局通知設置"
+msgid "View open merge request"
+msgstr "查看開啟的åˆä¸¦è«‹æ±‚"
+
msgid "VisibilityLevel|Internal"
msgstr "內部"
@@ -997,12 +1098,18 @@ msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è¯·"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "å³å°‡åˆªé™¤ %{group_name}。\n"
+"已刪除的群組無法æ¢å¾©ï¼\n"
+"確定繼續嗎?"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
-msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。\n"
-"已刪除的項目無法æ¢è¤‡ï¼\n"
-"確定繼續嗎?"
+msgstr "å³å°‡è¦åˆªé™¤ %{project_name_with_namespace}。已刪除的項目無法æ¢è¤‡ï¼ç¢ºå®šç¹¼çºŒå—Žï¼Ÿ"
msgid ""
"You are going to remove the fork relationship to source project "
@@ -1070,4 +1177,3 @@ msgstr "通知郵件"
msgid "parent"
msgid_plural "parents"
msgstr[0] "父級"
-
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index bb2b84c67b0..8d30a78145d 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -1,26 +1,43 @@
# Huang Tao <htve@outlook.com>, 2017. #zanata
-# Lin Jen-Shin <anonymous@domain.com>, 2017.
# Hazel Yang <anonymous@domain.com>, 2017.
# TzeKei Lee <anonymous@domain.com>, 2017.
# Jerry Ho <a29988122@gmail.com>, 2017.
+# Lin Jen-Shin <godfat@godfat.org>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-06-15 21:59-0500\n"
+"POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"PO-Revision-Date: 2017-06-28 11:13-0400\n"
-"Last-Translator: Huang Tao <htve@outlook.com>\n"
"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
+"PO-Revision-Date: 2017-07-20 09:50-0400\n"
+"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language: zh-TW\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
+msgid "%s additional commit has been omitted to prevent performance issues."
+msgid_plural ""
+"%s additional commits have been omitted to prevent performance issues."
+msgstr[0] "因效能考é‡ï¼Œä¸é¡¯ç¤º %s 個更動 (commit)。"
+
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] "%d 個更動 (commit)"
+
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "%{commit_author_link} 在 %{commit_timeago} é€äº¤"
+msgid "1 pipeline"
+msgid_plural "%d pipelines"
+msgstr[0] "1 æ¢æµæ°´ç·š"
+msgstr[1] "%d æ¢æµæ°´ç·š"
+
+msgid "A collection of graphs regarding Continuous Integration"
+msgstr "æŒçºŒæ•´åˆ (CI) 相關的圖表"
+
msgid "About auto deploy"
msgstr "關於自動部署"
@@ -66,9 +83,24 @@ msgstr ""
"已建立分支 (branch) <strong>%{branch_name}</strong> 。如è¦è¨­å®šè‡ªå‹•éƒ¨ç½²ï¼Œ è«‹é¸æ“‡åˆé©çš„ GitLab CI "
"Yaml 模æ¿ï¼Œç„¶å¾Œè¨˜å¾—è¦é€äº¤ (commit) 您的編輯內容。%{link_to_autodeploy_doc}\n"
+msgid "BranchSwitcherPlaceholder|Search branches"
+msgstr "æœå°‹åˆ†æ”¯ (branches)"
+
+msgid "BranchSwitcherTitle|Switch branch"
+msgstr "切æ›åˆ†æ”¯ (branch)"
+
msgid "Branches"
msgstr "分支 (branch) "
+msgid "Browse Directory"
+msgstr "ç€è¦½ç›®éŒ„"
+
+msgid "Browse File"
+msgstr "ç€è¦½æª”案"
+
+msgid "Browse Files"
+msgstr "ç€è¦½æª”案"
+
msgid "Browse files"
msgstr "ç€è¦½æª”案"
@@ -163,6 +195,9 @@ msgid "Commit"
msgid_plural "Commits"
msgstr[0] "更動記錄 (commit) "
+msgid "Commit duration in minutes for last 30 commits"
+msgstr "最近 30 次更動花費的時間(分é˜ï¼‰"
+
msgid "Commit message"
msgstr "更動說明 (commit) "
@@ -175,6 +210,9 @@ msgstr "建立 %{file_name}"
msgid "Commits"
msgstr "更動記錄 (commit) "
+msgid "Commits feed"
+msgstr "æ›´å‹•æ‘˜è¦ (commit feed)"
+
msgid "Commits|History"
msgstr "éŽåŽ»æ›´å‹• (commit) "
@@ -199,6 +237,11 @@ msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿"
msgid "Create New Directory"
msgstr "建立新目錄"
+msgid ""
+"Create a personal access token on your account to pull or push via "
+"%{protocol}."
+msgstr "建立個人存å–憑證 (access token) 以使用 %{protocol} 來上傳 (push) 或下載 (pull) 。"
+
msgid "Create directory"
msgstr "建立目錄"
@@ -217,6 +260,9 @@ msgstr "分支 (fork) "
msgid "CreateTag|Tag"
msgstr "建立標籤"
+msgid "CreateTokenToCloneLink|create a personal access token"
+msgstr "建立個人存å–憑證 (access token)"
+
msgid "Cron Timezone"
msgstr "Cron 時å€"
@@ -332,6 +378,9 @@ msgstr "無法刪除æµæ°´ç·š (pipeline) 排程"
msgid "Files"
msgstr "檔案"
+msgid "Filter by commit message"
+msgstr "以更動說明篩é¸"
+
msgid "Find by path"
msgstr "以路徑æœå°‹"
@@ -378,6 +427,15 @@ msgstr "循環週期"
msgid "Introducing Cycle Analytics"
msgstr "週期分æžç°¡ä»‹"
+msgid "Jobs for last month"
+msgstr "上個月的任務 (job) "
+
+msgid "Jobs for last week"
+msgstr "上個星期的任務 (job) "
+
+msgid "Jobs for last year"
+msgstr "去年的任務 (job) "
+
msgid "LFSStatus|Disabled"
msgstr "åœç”¨"
@@ -540,6 +598,21 @@ msgstr "æµæ°´ç·š (pipeline) 排程"
msgid "Pipeline Schedules"
msgstr "æµæ°´ç·š (pipeline) 排程"
+msgid "PipelineCharts|Failed:"
+msgstr "失敗:"
+
+msgid "PipelineCharts|Overall statistics"
+msgstr "總體統計"
+
+msgid "PipelineCharts|Success ratio:"
+msgstr "æˆåŠŸæ¯”率:"
+
+msgid "PipelineCharts|Successful:"
+msgstr "æˆåŠŸï¼š"
+
+msgid "PipelineCharts|Total:"
+msgstr "總計:"
+
msgid "PipelineSchedules|Activated"
msgstr "是å¦å•Ÿç”¨"
@@ -552,6 +625,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未啟用"
+msgid "PipelineSchedules|Input variable key"
+msgstr "變數å稱"
+
+msgid "PipelineSchedules|Input variable value"
+msgstr "變數值"
+
msgid "PipelineSchedules|Next Run"
msgstr "下次執行時間"
@@ -561,15 +640,33 @@ msgstr "ç„¡"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "請簡單說明此æµæ°´ç·š (pipeline) "
+msgid "PipelineSchedules|Remove variable row"
+msgstr "刪除變數"
+
msgid "PipelineSchedules|Take ownership"
msgstr "å–得所有權"
msgid "PipelineSchedules|Target"
msgstr "目標"
+msgid "PipelineSchedules|Variables"
+msgstr "變數"
+
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自訂"
+msgid "Pipelines"
+msgstr "æµæ°´ç·š (pipeline) "
+
+msgid "Pipelines charts"
+msgstr "æµæ°´ç·š (pipeline) 圖表"
+
+msgid "Pipeline|all"
+msgstr "所有"
+
+msgid "Pipeline|success"
+msgstr "æˆåŠŸ"
+
msgid "Pipeline|with stage"
msgstr "於階段"
@@ -675,13 +772,13 @@ msgid "Revert this merge request"
msgstr "還原此åˆä½µè«‹æ±‚ (merge request) "
msgid "Save pipeline schedule"
-msgstr "ä¿å­˜æµæ°´ç·š (pipeline) 排程"
+msgstr "儲存æµæ°´ç·š (pipeline) 排程"
msgid "Schedule a new pipeline"
msgstr "建立æµæ°´ç·š (pipeline) 排程"
msgid "Scheduling Pipelines"
-msgstr "æµæ°´ç·š (pipeline) 計劃"
+msgstr "æµæ°´ç·š (pipeline) 排程"
msgid "Search branches and tags"
msgstr "æœå°‹åˆ†æ”¯ (branch) 和標籤"
@@ -695,7 +792,7 @@ msgstr "é¸æ“‡æ™‚å€"
msgid "Select target branch"
msgstr "é¸æ“‡ç›®æ¨™åˆ†æ”¯ (branch) "
-msgid "Set a password on your account to pull or push via %{protocol}"
+msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "請先設定密碼,æ‰èƒ½ä½¿ç”¨ %{protocol} 來上傳 (push) 或下載 (pull) 。"
msgid "Set up CI"
@@ -765,8 +862,7 @@ msgid ""
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
-"在指定了特定分支 (branch) 或標籤後,此處的æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚\n"
-"æµæ°´ç·šæŽ’程的存å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚"
+"在指定了特定分支 (branch) 或標籤後,此處的æµæ°´ç·š (pipeline) 排程會ä¸æ–·åœ°é‡è¤‡åŸ·è¡Œã€‚æµæ°´ç·šæŽ’程的存å–權é™èˆ‡å°ˆæ¡ˆæœ¬èº«ç›¸åŒã€‚"
msgid ""
"The planning stage shows the time from the previous step to pushing your "
@@ -985,9 +1081,15 @@ msgstr "上傳新檔案"
msgid "Upload file"
msgstr "上傳檔案"
+msgid "UploadLink|click to upload"
+msgstr "點擊上傳"
+
msgid "Use your global notification setting"
msgstr "使用全域通知設定"
+msgid "View open merge request"
+msgstr "查看此分支的åˆä½µè«‹æ±‚ (merge request)"
+
msgid "VisibilityLevel|Internal"
msgstr "內部"
@@ -1007,6 +1109,14 @@ msgid "Withdraw Access Request"
msgstr "å–消權é™ç”³è«‹"
msgid ""
+"You are going to remove %{group_name}.\n"
+"Removed groups CANNOT be restored!\n"
+"Are you ABSOLUTELY sure?"
+msgstr "å³å°‡è¦åˆªé™¤ %{group_name}。\n"
+"被刪除的群組完全無法救回來喔ï¼\n"
+"真的「100%確定ã€è¦é€™éº¼åšå—Žï¼Ÿ"
+
+msgid ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
@@ -1084,4 +1194,3 @@ msgstr "通知信"
msgid "parent"
msgid_plural "parents"
msgstr[0] "上層"
-
diff --git a/package.json b/package.json
index 5a997e813f8..fd944531a6a 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"visibilityjs": "^1.2.4",
"vue": "^2.2.6",
"vue-loader": "^11.3.4",
- "vue-resource": "^0.9.3",
+ "vue-resource": "^1.3.4",
"vue-template-compiler": "^2.2.6",
"webpack": "^2.6.1",
"webpack-bundle-analyzer": "^2.8.2"
diff --git a/public/ci/favicon.ico b/public/ci/favicon.ico
deleted file mode 100644
index 9663d4d00b9..00000000000
--- a/public/ci/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/qa/qa.rb b/qa/qa.rb
index 58cf615cc9f..db9d8c42fde 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -48,8 +48,14 @@ module QA
module Main
autoload :Entry, 'qa/page/main/entry'
autoload :Menu, 'qa/page/main/menu'
- autoload :Groups, 'qa/page/main/groups'
- autoload :Projects, 'qa/page/main/projects'
+ end
+
+ module Dashboard
+ autoload :Groups, 'qa/page/dashboard/groups'
+ end
+
+ module Group
+ autoload :Show, 'qa/page/group/show'
end
module Project
diff --git a/qa/qa/page/main/groups.rb b/qa/qa/page/dashboard/groups.rb
index 169c5ebc967..3690f40dcfe 100644
--- a/qa/qa/page/main/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -1,9 +1,11 @@
module QA
module Page
- module Main
+ module Dashboard
class Groups < Page::Base
def prepare_test_namespace
- return if page.has_content?(Runtime::Namespace.name)
+ if page.has_content?(Runtime::Namespace.name)
+ return click_link(Runtime::Namespace.name)
+ end
click_on 'New group'
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
new file mode 100644
index 00000000000..296c311d7c6
--- /dev/null
+++ b/qa/qa/page/group/show.rb
@@ -0,0 +1,11 @@
+module QA
+ module Page
+ module Group
+ class Show < Page::Base
+ def go_to_new_project
+ click_link 'New Project'
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/projects.rb b/qa/qa/page/main/projects.rb
deleted file mode 100644
index 28d3a424022..00000000000
--- a/qa/qa/page/main/projects.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module QA
- module Page
- module Main
- class Projects < Page::Base
- def go_to_new_project
- ##
- # There are 'New Project' and 'New project' buttons on the projects
- # page, so we can't use `click_on`.
- #
- button = find('a', text: /^new project$/i)
- button.click
- end
- end
- end
- end
-end
diff --git a/qa/qa/scenario/gitlab/project/create.rb b/qa/qa/scenario/gitlab/project/create.rb
index 38522714e64..b860701c304 100644
--- a/qa/qa/scenario/gitlab/project/create.rb
+++ b/qa/qa/scenario/gitlab/project/create.rb
@@ -13,9 +13,8 @@ module QA
def perform
Page::Main::Menu.act { go_to_groups }
- Page::Main::Groups.act { prepare_test_namespace }
- Page::Main::Menu.act { go_to_projects }
- Page::Main::Projects.act { go_to_new_project }
+ Page::Dashboard::Groups.act { prepare_test_namespace }
+ Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
index b341aa3094a..4dfdd6cd93c 100644
--- a/qa/qa/specs/config.rb
+++ b/qa/qa/specs/config.rb
@@ -25,27 +25,15 @@ module QA
def configure_rspec!
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
- # This option will default to `true` in RSpec 4. It makes the `description`
- # and `failure_message` of custom matchers include text for helper methods
- # defined using `chain`.
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
- # Prevents you from mocking or stubbing a method that does not exist on
- # a real object. This is generally recommended, and will default to
- # `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
- # Run specs in random order to surface order dependencies.
config.order = :random
Kernel.srand config.seed
-
- # config.before(:all) do
- # page.current_window.resize_to(1200, 1800)
- # end
-
config.formatter = :documentation
config.color = true
end
@@ -56,7 +44,7 @@ module QA
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
'chromeOptions' => {
'binary' => '/usr/bin/google-chrome-stable',
- 'args' => %w[headless no-sandbox disable-gpu]
+ 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1024]
}
)
@@ -64,6 +52,10 @@ module QA
.new(app, browser: :chrome, desired_capabilities: capabilities)
end
+ Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+ end
+
Capybara.configure do |config|
config.app_host = @address
config.default_driver = :chrome
diff --git a/rubocop/cop/active_record_dependent.rb b/rubocop/cop/active_record_dependent.rb
new file mode 100644
index 00000000000..8d15f150885
--- /dev/null
+++ b/rubocop/cop/active_record_dependent.rb
@@ -0,0 +1,26 @@
+require_relative '../model_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that prevents the use of `dependent: ...` in ActiveRecord models.
+ class ActiveRecordDependent < RuboCop::Cop::Cop
+ include ModelHelpers
+
+ MSG = 'Do not use `dependent: to remove associated data, ' \
+ 'use foreign keys with cascading deletes instead'.freeze
+
+ METHOD_NAMES = [:has_many, :has_one, :belongs_to].freeze
+
+ def on_send(node)
+ return unless in_model?(node)
+ return unless METHOD_NAMES.include?(node.children[1])
+
+ node.children.last.each_node(:pair) do |pair|
+ key_name = pair.children[0].children[0]
+
+ add_offense(pair, :expression) if key_name == :dependent
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/activerecord_serialize.rb b/rubocop/cop/active_record_serialize.rb
index 9bdcc3b4c34..204caf37f8b 100644
--- a/rubocop/cop/activerecord_serialize.rb
+++ b/rubocop/cop/active_record_serialize.rb
@@ -3,7 +3,7 @@ require_relative '../model_helpers'
module RuboCop
module Cop
# Cop that prevents the use of `serialize` in ActiveRecord models.
- class ActiverecordSerialize < RuboCop::Cop::Cop
+ class ActiveRecordSerialize < RuboCop::Cop::Cop
include ModelHelpers
MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'.freeze
diff --git a/rubocop/cop/in_batches.rb b/rubocop/cop/in_batches.rb
new file mode 100644
index 00000000000..c0240187e66
--- /dev/null
+++ b/rubocop/cop/in_batches.rb
@@ -0,0 +1,16 @@
+require_relative '../model_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that prevents the use of `in_batches`
+ class InBatches < RuboCop::Cop::Cop
+ MSG = 'Do not use `in_batches`, use `each_batch` from the EachBatch module instead'.freeze
+
+ def on_send(node)
+ return unless node.children[1] == :in_batches
+
+ add_offense(node, :selector)
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/migration/add_column_with_default_to_large_table.rb b/rubocop/cop/migration/add_column_with_default_to_large_table.rb
index 2372e6b60ea..87788b0d9c2 100644
--- a/rubocop/cop/migration/add_column_with_default_to_large_table.rb
+++ b/rubocop/cop/migration/add_column_with_default_to_large_table.rb
@@ -20,8 +20,11 @@ module RuboCop
'necessary'.freeze
LARGE_TABLES = %i[
+ ci_builds
events
issues
+ merge_request_diff_files
+ merge_request_diffs
merge_requests
namespaces
notes
diff --git a/rubocop/cop/migration/hash_index.rb b/rubocop/cop/migration/hash_index.rb
new file mode 100644
index 00000000000..2cc59691d84
--- /dev/null
+++ b/rubocop/cop/migration/hash_index.rb
@@ -0,0 +1,51 @@
+require 'set'
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that prevents the use of hash indexes in database migrations
+ class HashIndex < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ MSG = 'hash indexes should be avoided at all costs since they are not ' \
+ 'recorded in the PostgreSQL WAL, you should use a btree index instead'.freeze
+
+ NAMES = Set.new([:add_index, :index, :add_concurrent_index]).freeze
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ name = node.children[1]
+
+ return unless NAMES.include?(name)
+
+ opts = node.children.last
+
+ return unless opts && opts.type == :hash
+
+ opts.each_node(:pair) do |pair|
+ next unless hash_key_type(pair) == :sym &&
+ hash_key_name(pair) == :using
+
+ if hash_key_value(pair).to_s == 'hash'
+ add_offense(pair, :expression)
+ end
+ end
+ end
+
+ def hash_key_type(pair)
+ pair.children[0].type
+ end
+
+ def hash_key_name(pair)
+ pair.children[0].children[0]
+ end
+
+ def hash_key_value(pair)
+ pair.children[1].children[0]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/project_path_helper.rb b/rubocop/cop/project_path_helper.rb
new file mode 100644
index 00000000000..3e1ce71ac06
--- /dev/null
+++ b/rubocop/cop/project_path_helper.rb
@@ -0,0 +1,51 @@
+module RuboCop
+ module Cop
+ class ProjectPathHelper < RuboCop::Cop::Cop
+ MSG = 'Use short project path helpers without explicitly passing the namespace: ' \
+ '`foo_project_bar_path(project, bar)` instead of ' \
+ '`foo_namespace_project_bar_path(project.namespace, project, bar)`.'.freeze
+
+ METHOD_NAME_PATTERN = /\A([a-z_]+_)?namespace_project(?:_[a-z_]+)?_(?:url|path)\z/.freeze
+
+ def on_send(node)
+ return unless method_name(node).to_s =~ METHOD_NAME_PATTERN
+
+ namespace_expr, project_expr = arguments(node)
+ return unless namespace_expr && project_expr
+
+ return unless namespace_expr.type == :send
+ return unless method_name(namespace_expr) == :namespace
+ return unless receiver(namespace_expr) == project_expr
+
+ add_offense(node, :selector)
+ end
+
+ def autocorrect(node)
+ helper_name = method_name(node).to_s.sub('namespace_project', 'project')
+
+ arguments = arguments(node)
+ arguments.shift # Remove namespace argument
+
+ replacement = "#{helper_name}(#{arguments.map(&:source).join(', ')})"
+
+ lambda do |corrector|
+ corrector.replace(node.source_range, replacement)
+ end
+ end
+
+ private
+
+ def receiver(node)
+ node.children[0]
+ end
+
+ def method_name(node)
+ node.children[1]
+ end
+
+ def arguments(node)
+ node.children[2..-1]
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 55d7708fa8c..3fbd5b0163c 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,8 +1,11 @@
require_relative 'cop/custom_error_class'
require_relative 'cop/gem_fetcher'
-require_relative 'cop/activerecord_serialize'
+require_relative 'cop/active_record_serialize'
require_relative 'cop/redirect_with_status'
require_relative 'cop/polymorphic_associations'
+require_relative 'cop/project_path_helper'
+require_relative 'cop/active_record_dependent'
+require_relative 'cop/in_batches'
require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
@@ -10,6 +13,7 @@ require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
+require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default'
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 03de59f27ad..39806901274 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -10,10 +10,7 @@ fi
# Only install knapsack after bundle install! Otherwise oddly some native
# gems could not be found under some circumstance. No idea why, hours wasted.
-retry gem install knapsack fog-aws mime-types
-
-cp config/resque.yml.example config/resque.yml
-sed -i 's/localhost/redis/g' config/resque.yml
+retry gem install knapsack
cp config/gitlab.yml.example config/gitlab.yml
@@ -37,6 +34,18 @@ else # Assume it's mysql
sed -i 's/# host:.*/host: mysql/g' config/database.yml
fi
+cp config/resque.yml.example config/resque.yml
+sed -i 's/localhost/redis/g' config/resque.yml
+
+cp config/redis.cache.yml.example config/redis.cache.yml
+sed -i 's/localhost/redis/g' config/redis.cache.yml
+
+cp config/redis.queues.yml.example config/redis.queues.yml
+sed -i 's/localhost/redis/g' config/redis.queues.yml
+
+cp config/redis.shared_state.yml.example config/redis.shared_state.yml
+sed -i 's/localhost/redis/g' config/redis.shared_state.yml
+
if [ "$SETUP_DB" != "false" ]; then
bundle exec rake db:drop db:create db:schema:load db:migrate
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 092048a6259..a31e44fa928 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -5,12 +5,12 @@ describe 'mail_room.yml' do
let(:mailroom_config_path) { 'config/mail_room.yml' }
let(:gitlab_config_path) { 'config/mail_room.yml' }
- let(:redis_config_path) { 'config/resque.yml' }
+ let(:queues_config_path) { 'config/redis.queues.yml' }
let(:configuration) do
vars = {
'MAIL_ROOM_GITLAB_CONFIG_FILE' => absolute_path(gitlab_config_path),
- 'GITLAB_REDIS_CONFIG_FILE' => absolute_path(redis_config_path)
+ 'GITLAB_REDIS_QUEUES_CONFIG_FILE' => absolute_path(queues_config_path)
}
cmd = "puts ERB.new(File.read(#{absolute_path(mailroom_config_path).inspect})).result"
@@ -21,12 +21,12 @@ describe 'mail_room.yml' do
end
before(:each) do
- stub_env('GITLAB_REDIS_CONFIG_FILE', absolute_path(redis_config_path))
- clear_redis_raw_config
+ stub_env('GITLAB_REDIS_QUEUES_CONFIG_FILE', absolute_path(queues_config_path))
+ clear_queues_raw_config
end
after(:each) do
- clear_redis_raw_config
+ clear_queues_raw_config
end
context 'when incoming email is disabled' do
@@ -39,9 +39,9 @@ describe 'mail_room.yml' do
context 'when incoming email is enabled' do
let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' }
- let(:redis_config_path) { 'spec/fixtures/config/redis_new_format_host.yml' }
+ let(:queues_config_path) { 'spec/fixtures/config/redis_queues_new_format_host.yml' }
- let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) }
+ let(:gitlab_redis_queues) { Gitlab::Redis::Queues.new(Rails.env) }
it 'contains the intended configuration' do
expect(configuration[:mailboxes].length).to eq(1)
@@ -56,8 +56,8 @@ describe 'mail_room.yml' do
expect(mailbox[:name]).to eq('inbox')
expect(mailbox[:idle_timeout]).to eq(60)
- redis_url = gitlab_redis.url
- sentinels = gitlab_redis.sentinels
+ redis_url = gitlab_redis_queues.url
+ sentinels = gitlab_redis_queues.sentinels
expect(mailbox[:delivery_options][:redis_url]).to be_present
expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url)
@@ -73,8 +73,8 @@ describe 'mail_room.yml' do
end
end
- def clear_redis_raw_config
- Gitlab::Redis.remove_instance_variable(:@_raw_config)
+ def clear_queues_raw_config
+ Gitlab::Redis::Queues.remove_instance_variable(:@_raw_config)
rescue NameError
# raised if @_raw_config was not set; ignore
end
diff --git a/spec/controllers/admin/dashboard_controller_spec.rb b/spec/controllers/admin/dashboard_controller_spec.rb
new file mode 100644
index 00000000000..6eb9f7867d5
--- /dev/null
+++ b/spec/controllers/admin/dashboard_controller_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Admin::DashboardController do
+ describe '#index' do
+ context 'with pending_delete projects' do
+ render_views
+
+ it 'does not retrieve projects that are pending deletion' do
+ sign_in(create(:admin))
+
+ project = create(:project)
+ pending_delete_project = create(:project, pending_delete: true)
+
+ get :index
+
+ expect(response.body).to match(project.name)
+ expect(response.body).not_to match(pending_delete_project.name)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index a2720c9b81e..1641bddea11 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -30,6 +30,15 @@ describe ApplicationController do
expect(controller).not_to receive(:redirect_to)
controller.send(:check_password_expiration)
end
+
+ it 'does not redirect if the user is over their password expiry but sign-in is disabled' do
+ stub_application_setting(password_authentication_enabled: false)
+ user.password_expires_at = Time.new(2002)
+ allow(controller).to receive(:current_user).and_return(user)
+ expect(controller).not_to receive(:redirect_to)
+
+ controller.send(:check_password_expiration)
+ end
end
describe "#authenticate_user_from_token!" do
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index b40f647644d..58486f33229 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -97,6 +97,21 @@ describe AutocompleteController do
it { expect(body.size).to eq User.count }
end
+ context 'user order' do
+ it 'shows exact matches first' do
+ reported_user = create(:user, username: 'reported_user', name: 'Doug')
+ user = create(:user, username: 'user', name: 'User')
+ user1 = create(:user, username: 'user1', name: 'Ian')
+
+ sign_in(user)
+ get(:users, search: 'user')
+
+ response_usernames = JSON.parse(response.body).map { |user| user['username'] }
+
+ expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username])
+ end
+ end
+
context 'limited users per page' do
let(:per_page) { 2 }
diff --git a/spec/controllers/dashboard/labels_controller_spec.rb b/spec/controllers/dashboard/labels_controller_spec.rb
new file mode 100644
index 00000000000..2b63933008f
--- /dev/null
+++ b/spec/controllers/dashboard/labels_controller_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Dashboard::LabelsController do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+ let!(:label) { create(:label, project: project) }
+
+ before do
+ sign_in(user)
+ project.add_reporter(user)
+ end
+
+ describe "#index" do
+ let!(:unrelated_label) { create(:label, project: create(:empty_project, :public)) }
+
+ it 'returns global labels for projects the user has a relationship with' do
+ get :index, format: :json
+
+ expect(json_response).to be_kind_of(Array)
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]["id"]).to be_nil
+ expect(json_response[0]["title"]).to eq(label.title)
+ end
+ end
+end
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 085f3fd8543..4a48621abe1 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -12,6 +12,36 @@ describe Dashboard::TodosController do
end
describe 'GET #index' do
+ context 'project authorization' do
+ it 'renders 404 when user does not have read access on given project' do
+ unauthorized_project = create(:empty_project, :private)
+
+ get :index, project_id: unauthorized_project.id
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'renders 404 when given project does not exists' do
+ get :index, project_id: 999
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'renders 200 when filtering for "any project" todos' do
+ get :index, project_id: ''
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'renders 200 when user has access on given project' do
+ authorized_project = create(:empty_project, :public)
+
+ get :index, project_id: authorized_project.id
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
context 'when using pagination' do
let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignees: [user]) }
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index c6e5fb61cf9..aad67dd0164 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Groups::MilestonesController do
let(:group) { create(:group) }
- let(:project) { create(:empty_project, group: group) }
- let(:project2) { create(:empty_project, group: group) }
+ let!(:project) { create(:empty_project, group: group) }
+ let!(:project2) { create(:empty_project, group: group) }
let(:user) { create(:user) }
let(:title) { '肯定ä¸æ˜¯ä¸­æ–‡çš„问题' }
let(:milestone) do
@@ -17,24 +17,67 @@ describe Groups::MilestonesController do
end
let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
+ let(:milestone_params) do
+ {
+ title: title,
+ start_date: Date.today,
+ due_date: 1.month.from_now.to_date
+ }
+ end
+
before do
sign_in(user)
group.add_owner(user)
project.team << [user, :master]
end
- describe "#index" do
+ describe '#index' do
it 'shows group milestones page' do
get :index, group_id: group.to_param
expect(response).to have_http_status(200)
end
- it 'shows group milestones JSON' do
- get :index, group_id: group.to_param, format: :json
+ context 'as JSON' do
+ let!(:milestone) { create(:milestone, group: group, title: 'group milestone') }
+ let!(:legacy_milestone1) { create(:milestone, project: project, title: 'legacy') }
+ let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') }
- expect(response).to have_http_status(200)
- expect(response.content_type).to eq 'application/json'
+ it 'lists legacy group milestones and group milestones' do
+ get :index, group_id: group.to_param, format: :json
+
+ milestones = JSON.parse(response.body)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones.first["title"]).to eq("group milestone")
+ expect(milestones.second["title"]).to eq("legacy")
+ expect(response).to have_http_status(200)
+ expect(response.content_type).to eq 'application/json'
+ end
+ end
+ end
+
+ describe '#show' do
+ let(:milestone1) { create(:milestone, project: project, title: 'legacy') }
+ let(:milestone2) { create(:milestone, project: project, title: 'legacy') }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ context 'when there is a title parameter' do
+ it 'searchs for a legacy group milestone' do
+ expect(GlobalMilestone).to receive(:build)
+ expect(Milestone).not_to receive(:find_by_iid)
+
+ get :show, group_id: group.to_param, id: title, title: milestone1.safe_title
+ end
+ end
+
+ context 'when there is not a title parameter' do
+ it 'searchs for a group milestone' do
+ expect(GlobalMilestone).not_to receive(:build)
+ expect(Milestone).to receive(:find_by_iid)
+
+ get :show, group_id: group.to_param, id: group_milestone.id
+ end
end
end
@@ -44,16 +87,57 @@ describe Groups::MilestonesController do
it "creates group milestone with Chinese title" do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: milestone_params
- expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
- expect(Milestone.where(title: title).count).to eq(2)
+ milestone = Milestone.find_by_title(title)
+
+ expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
+ expect(milestone.group_id).to eq(group.id)
+ expect(milestone.due_date).to eq(milestone_params[:due_date])
+ expect(milestone.start_date).to eq(milestone_params[:start_date])
+ end
+ end
+
+ describe "#update" do
+ let(:milestone) { create(:milestone, group: group) }
+
+ it "updates group milestone" do
+ milestone_params[:title] = "title changed"
+
+ put :update,
+ id: milestone.iid,
+ group_id: group.to_param,
+ milestone: milestone_params
+
+ milestone.reload
+ expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
+ expect(milestone.title).to eq("title changed")
end
- it "redirects to new when there are no project ids" do
- post :create, group_id: group.to_param, milestone: { title: title, project_ids: [""] }
- expect(response).to render_template :new
- expect(assigns(:milestone).errors).not_to be_nil
+ context "legacy group milestones" do
+ let!(:milestone1) { create(:milestone, project: project, title: 'legacy milestone', description: "old description") }
+ let!(:milestone2) { create(:milestone, project: project2, title: 'legacy milestone', description: "old description") }
+
+ it "updates only group milestones state" do
+ milestone_params[:title] = "title changed"
+ milestone_params[:description] = "description changed"
+ milestone_params[:state_event] = "close"
+
+ put :update,
+ id: milestone1.title.to_slug.to_s,
+ group_id: group.to_param,
+ milestone: milestone_params,
+ title: milestone1.title
+
+ expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title))
+
+ [milestone1, milestone2].each do |milestone|
+ milestone.reload
+ expect(milestone.title).to eq("legacy milestone")
+ expect(milestone.description).to eq("old description")
+ expect(milestone.state).to eq("closed")
+ end
+ end
end
end
@@ -156,7 +240,7 @@ describe Groups::MilestonesController do
it 'does not 404' do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).not_to have_http_status(404)
end
@@ -164,7 +248,7 @@ describe Groups::MilestonesController do
it 'does not redirect to the correct casing' do
post :create,
group_id: group.to_param,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).not_to have_http_status(301)
end
@@ -176,7 +260,7 @@ describe Groups::MilestonesController do
it 'returns not found' do
post :create,
group_id: redirect_route.path,
- milestone: { project_ids: [project.id, project2.id], title: title }
+ milestone: { title: title }
expect(response).to have_http_status(404)
end
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
new file mode 100644
index 00000000000..2e0efb57c74
--- /dev/null
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Groups::Settings::CiCdController do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ group.add_master(user)
+ sign_in(user)
+ end
+
+ describe 'GET #show' do
+ it 'renders show with 200 status code' do
+ get :show, group_id: group
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+end
diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb
new file mode 100644
index 00000000000..02f2fa46047
--- /dev/null
+++ b/spec/controllers/groups/variables_controller_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Groups::VariablesController do
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ group.add_master(user)
+ end
+
+ describe 'POST #create' do
+ context 'variable is valid' do
+ it 'shows a success flash message' do
+ post :create, group_id: group, variable: { key: "one", value: "two" }
+
+ expect(flash[:notice]).to include 'Variable was successfully created.'
+ expect(response).to redirect_to(group_settings_ci_cd_path(group))
+ end
+ end
+
+ context 'variable is invalid' do
+ it 'renders show' do
+ post :create, group_id: group, variable: { key: "..one", value: "two" }
+
+ expect(response).to render_template("groups/variables/show")
+ end
+ end
+ end
+
+ describe 'POST #update' do
+ let(:variable) { create(:ci_group_variable) }
+
+ context 'updating a variable with valid characters' do
+ before do
+ group.variables << variable
+ end
+
+ it 'shows a success flash message' do
+ post :update, group_id: group,
+ id: variable.id, variable: { key: variable.key, value: 'two' }
+
+ expect(flash[:notice]).to include 'Variable was successfully updated.'
+ expect(response).to redirect_to(group_variables_path(group))
+ end
+
+ it 'renders the action #show if the variable key is invalid' do
+ post :update, group_id: group,
+ id: variable.id, variable: { key: '?', value: variable.value }
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template :show
+ end
+ end
+ end
+end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 58c16cc57e6..03da6287774 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -3,52 +3,79 @@ require 'spec_helper'
describe HealthCheckController do
include StubENV
- let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
+ let(:token) { current_application_settings.health_check_access_token }
+ let(:whitelisted_ip) { '127.0.0.1' }
+ let(:not_whitelisted_ip) { '127.0.0.2' }
before do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip])
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
describe 'GET #index' do
- context 'when services are up but NO access token' do
+ context 'when services are up but accessed from outside whitelisted ips' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip)
+ end
+
it 'returns a not found page' do
get :index
+
expect(response).to be_not_found
end
+
+ context 'when services are accessed with token' do
+ it 'supports passing the token in the header' do
+ request.headers['TOKEN'] = token
+
+ get :index
+
+ expect(response).to be_success
+ expect(response.content_type).to eq 'text/plain'
+ end
+
+ it 'supports passing the token in query params' do
+ get :index, token: token
+
+ expect(response).to be_success
+ expect(response.content_type).to eq 'text/plain'
+ end
+ end
end
- context 'when services are up and an access token is provided' do
- it 'supports passing the token in the header' do
- request.headers['TOKEN'] = token
- get :index
- expect(response).to be_success
- expect(response.content_type).to eq 'text/plain'
+ context 'when services are up and accessed from whitelisted ips' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
end
- it 'supports successful plaintest response' do
- get :index, token: token
+ it 'supports successful plaintext response' do
+ get :index
+
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful json response' do
- get :index, token: token, format: :json
+ get :index, format: :json
+
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
it 'supports successful xml response' do
- get :index, token: token, format: :xml
+ get :index, format: :xml
+
expect(response).to be_success
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
it 'supports successful responses for specific checks' do
- get :index, token: token, checks: 'email', format: :json
+ get :index, checks: 'email', format: :json
+
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
@@ -58,33 +85,29 @@ describe HealthCheckController do
context 'when a service is down but NO access token' do
it 'returns a not found page' do
get :index
+
expect(response).to be_not_found
end
end
- context 'when a service is down and an access token is provided' do
+ context 'when a service is down and an endpoint is accessed from whitelisted ip' do
before do
allow(HealthCheck::Utils).to receive(:process_checks).with(['standard']).and_return('The server is on fire')
allow(HealthCheck::Utils).to receive(:process_checks).with(['email']).and_return('Email is on fire')
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
end
- it 'supports passing the token in the header' do
- request.headers['TOKEN'] = token
+ it 'supports failure plaintext response' do
get :index
- expect(response).to have_http_status(500)
- expect(response.content_type).to eq 'text/plain'
- expect(response.body).to include('The server is on fire')
- end
- it 'supports failure plaintest response' do
- get :index, token: token
expect(response).to have_http_status(500)
expect(response.content_type).to eq 'text/plain'
expect(response.body).to include('The server is on fire')
end
it 'supports failure json response' do
- get :index, token: token, format: :json
+ get :index, format: :json
+
expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
@@ -92,7 +115,8 @@ describe HealthCheckController do
end
it 'supports failure xml response' do
- get :index, token: token, format: :xml
+ get :index, format: :xml
+
expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be false
@@ -100,7 +124,8 @@ describe HealthCheckController do
end
it 'supports failure responses for specific checks' do
- get :index, token: token, checks: 'email', format: :json
+ get :index, checks: 'email', format: :json
+
expect(response).to have_http_status(500)
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be false
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index e7c19b47a6a..cc389e554ad 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -3,55 +3,120 @@ require 'spec_helper'
describe HealthController do
include StubENV
- let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) }
+ let(:token) { current_application_settings.health_check_access_token }
+ let(:whitelisted_ip) { '127.0.0.1' }
+ let(:not_whitelisted_ip) { '127.0.0.2' }
before do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip])
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
describe '#readiness' do
- context 'authorization token provided' do
- before do
- request.headers['TOKEN'] = token
- end
+ shared_context 'endpoint responding with readiness data' do
+ let(:request_params) { {} }
+
+ subject { get :readiness, request_params }
+
+ it 'responds with readiness checks data' do
+ subject
- it 'returns proper response' do
- get :readiness
expect(json_response['db_check']['status']).to eq('ok')
- expect(json_response['redis_check']['status']).to eq('ok')
+ expect(json_response['cache_check']['status']).to eq('ok')
+ expect(json_response['queues_check']['status']).to eq('ok')
+ expect(json_response['shared_state_check']['status']).to eq('ok')
expect(json_response['fs_shards_check']['status']).to eq('ok')
expect(json_response['fs_shards_check']['labels']['shard']).to eq('default')
end
end
- context 'without authorization token' do
- it 'returns proper response' do
+ context 'accessed from whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
+ end
+
+ it_behaves_like 'endpoint responding with readiness data'
+ end
+
+ context 'accessed from not whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip)
+ end
+
+ it 'responds with resource not found' do
get :readiness
+
expect(response.status).to eq(404)
end
+
+ context 'accessed with valid token' do
+ context 'token passed in request header' do
+ before do
+ request.headers['TOKEN'] = token
+ end
+
+ it_behaves_like 'endpoint responding with readiness data'
+ end
+ end
+
+ context 'token passed as URL param' do
+ it_behaves_like 'endpoint responding with readiness data' do
+ let(:request_params) { { token: token } }
+ end
+ end
end
end
describe '#liveness' do
- context 'authorization token provided' do
- before do
- request.headers['TOKEN'] = token
- end
+ shared_context 'endpoint responding with liveness data' do
+ subject { get :liveness }
+
+ it 'responds with liveness checks data' do
+ subject
- it 'returns proper response' do
- get :liveness
expect(json_response['db_check']['status']).to eq('ok')
- expect(json_response['redis_check']['status']).to eq('ok')
+ expect(json_response['cache_check']['status']).to eq('ok')
+ expect(json_response['queues_check']['status']).to eq('ok')
+ expect(json_response['shared_state_check']['status']).to eq('ok')
expect(json_response['fs_shards_check']['status']).to eq('ok')
end
end
- context 'without authorization token' do
- it 'returns proper response' do
+ context 'accessed from whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
+ end
+
+ it_behaves_like 'endpoint responding with liveness data'
+ end
+
+ context 'accessed from not whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip)
+ end
+
+ it 'responds with resource not found' do
get :liveness
+
expect(response.status).to eq(404)
end
+
+ context 'accessed with valid token' do
+ context 'token passed in request header' do
+ before do
+ request.headers['TOKEN'] = token
+ end
+
+ it_behaves_like 'endpoint responding with liveness data'
+ end
+
+ context 'token passed as URL param' do
+ it_behaves_like 'endpoint responding with liveness data' do
+ subject { get :liveness, token: token }
+ end
+ end
+ end
end
end
end
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index 044c9f179ed..7b0976e3e67 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -3,28 +3,28 @@ require 'spec_helper'
describe MetricsController do
include StubENV
- let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) }
let(:metrics_multiproc_dir) { Dir.mktmpdir }
+ let(:whitelisted_ip) { '127.0.0.1' }
+ let(:whitelisted_ip_range) { '10.0.0.0/24' }
+ let(:ip_in_whitelisted_range) { '10.0.0.1' }
+ let(:not_whitelisted_ip) { '10.0.1.1' }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- stub_env('prometheus_multiproc_dir', metrics_multiproc_dir)
+ allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir)
allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(true)
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([whitelisted_ip, whitelisted_ip_range])
end
describe '#index' do
- context 'authorization token provided' do
- before do
- request.headers['TOKEN'] = token
- end
-
+ shared_examples_for 'endpoint providing metrics' do
it 'returns DB ping metrics' do
get :index
expect(response.body).to match(/^db_ping_timeout 0$/)
expect(response.body).to match(/^db_ping_success 1$/)
- expect(response.body).to match(/^db_ping_latency [0-9\.]+$/)
+ expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/)
end
it 'returns Redis ping metrics' do
@@ -32,17 +32,41 @@ describe MetricsController do
expect(response.body).to match(/^redis_ping_timeout 0$/)
expect(response.body).to match(/^redis_ping_success 1$/)
- expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/)
+ expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/)
+ end
+
+ it 'returns Caching ping metrics' do
+ get :index
+
+ expect(response.body).to match(/^redis_cache_ping_timeout 0$/)
+ expect(response.body).to match(/^redis_cache_ping_success 1$/)
+ expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/)
+ end
+
+ it 'returns Queues ping metrics' do
+ get :index
+
+ expect(response.body).to match(/^redis_queues_ping_timeout 0$/)
+ expect(response.body).to match(/^redis_queues_ping_success 1$/)
+ expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/)
+ end
+
+ it 'returns SharedState ping metrics' do
+ get :index
+
+ expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/)
+ expect(response.body).to match(/^redis_shared_state_ping_success 1$/)
+ expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/)
end
it 'returns file system check metrics' do
get :index
- expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/)
+ expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
end
@@ -59,7 +83,27 @@ describe MetricsController do
end
end
- context 'without authorization token' do
+ context 'accessed from whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
+ end
+
+ it_behaves_like 'endpoint providing metrics'
+ end
+
+ context 'accessed from ip in whitelisted range' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(ip_in_whitelisted_range)
+ end
+
+ it_behaves_like 'endpoint providing metrics'
+ end
+
+ context 'accessed from not whitelisted ip' do
+ before do
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(not_whitelisted_ip)
+ end
+
it 'returns proper response' do
get :index
diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb
new file mode 100644
index 00000000000..2955d01fad0
--- /dev/null
+++ b/spec/controllers/passwords_controller_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe PasswordsController do
+ describe '#check_password_authentication_available' do
+ before do
+ @request.env["devise.mapping"] = Devise.mappings[:user]
+ end
+
+ context 'when password authentication is disabled' do
+ it 'prevents a password reset' do
+ stub_application_setting(password_authentication_enabled: false)
+
+ post :create
+
+ expect(flash[:alert]).to eq 'Password authentication is unavailable.'
+ end
+ end
+
+ context 'when reset email belongs to an ldap user' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') }
+
+ it 'prevents a password reset' do
+ post :create, user: { email: user.email }
+
+ expect(flash[:alert]).to eq 'Password authentication is unavailable.'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb
index 2f9d18e3a0e..d387aba227b 100644
--- a/spec/controllers/profiles/accounts_controller_spec.rb
+++ b/spec/controllers/profiles/accounts_controller_spec.rb
@@ -29,7 +29,7 @@ describe Profiles::AccountsController do
end
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
+ [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
describe "#{provider} provider" do
let(:user) { create(:omniauth_user, provider: provider.to_s) }
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 428bc45b842..d2c613a2423 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -134,10 +134,7 @@ describe Projects::ArtifactsController do
context 'found the job and redirect' do
shared_examples 'redirect to the job' do
it 'redirects' do
- path = browse_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job)
+ path = browse_project_job_artifacts_path(project, job)
expect(response).to redirect_to(path)
end
@@ -174,11 +171,7 @@ describe Projects::ArtifactsController do
end
it 'redirects' do
- path = file_namespace_project_job_artifacts_path(
- project.namespace,
- project,
- job,
- 'README.md')
+ path = file_project_job_artifacts_path(project, job, 'README.md')
expect(response).to redirect_to(path)
end
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
new file mode 100644
index 00000000000..d68200164e4
--- /dev/null
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BadgesController do
+ let(:project) { pipeline.project }
+ let!(:pipeline) { create(:ci_empty_pipeline) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'requests the pipeline badge successfully' do
+ get_badge(:pipeline)
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'requests the coverage badge successfully' do
+ get_badge(:coverage)
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ def get_badge(badge)
+ get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg
+ end
+end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 561bc219bb4..02bbc48dc59 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -117,7 +117,7 @@ describe Projects::BlobController do
end
it 'redirects to blob show' do
- expect(response).to redirect_to(namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG'))
+ expect(response).to redirect_to(project_blob_path(project, 'master/CHANGELOG'))
end
end
@@ -164,7 +164,7 @@ describe Projects::BlobController do
end
def blob_after_edit_path
- namespace_project_blob_path(project.namespace, project, 'master/CHANGELOG')
+ project_blob_path(project, 'master/CHANGELOG')
end
before do
@@ -186,7 +186,7 @@ describe Projects::BlobController do
it 'redirects to MR diff' do
put :update, mr_params
- after_edit_path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ after_edit_path = diffs_project_merge_request_path(project, merge_request)
file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}"
expect(response).to redirect_to(after_edit_path + file_anchor)
end
@@ -223,7 +223,7 @@ describe Projects::BlobController do
it 'redirects to blob' do
put :update, default_params
- expect(response).to redirect_to(namespace_project_blob_path(forked_project.namespace, forked_project, 'master/CHANGELOG'))
+ expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG'))
end
end
@@ -235,8 +235,7 @@ describe Projects::BlobController do
put :update, default_params
expect(response).to redirect_to(
- namespace_project_new_merge_request_path(
- forked_project.namespace,
+ project_new_merge_request_path(
forked_project,
merge_request: {
source_project_id: forked_project.id,
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 14426b09c73..9cd4e9dbf84 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -110,7 +110,7 @@ describe Projects::BranchesController do
branch_name: branch,
issue_iid: issue.iid
- expect(response).to redirect_to namespace_project_tree_path(project.namespace, project, branch)
+ expect(response).to redirect_to project_tree_path(project, branch)
end
it 'redirects to autodeploy setup page' do
@@ -127,7 +127,7 @@ describe Projects::BranchesController do
branch_name: branch,
issue_iid: issue.iid
- expect(response.location).to include(namespace_project_new_blob_path(project.namespace, project, branch))
+ expect(response.location).to include(project_new_blob_path(project, branch))
expect(response).to have_http_status(302)
end
end
@@ -303,7 +303,7 @@ describe Projects::BranchesController do
it 'redirects to branches path' do
expect(response)
- .to redirect_to(namespace_project_branches_path(project.namespace, project))
+ .to redirect_to(project_branches_path(project))
end
end
end
@@ -323,7 +323,7 @@ describe Projects::BranchesController do
it 'redirects to branches' do
destroy_all_merged
- expect(response).to redirect_to namespace_project_branches_path(project.namespace, project)
+ expect(response).to redirect_to project_branches_path(project)
end
it 'starts worker to delete merged branches' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index e10da40eaab..df53863482d 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -169,7 +169,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: commit.id)
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully reverted.')
end
end
@@ -191,7 +191,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: commit.id)
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(response).to redirect_to project_commit_path(project, commit.id)
expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
end
end
@@ -218,7 +218,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: master_pickable_commit.id)
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
end
end
@@ -240,7 +240,7 @@ describe Projects::CommitController do
start_branch: 'master',
id: master_pickable_commit.id)
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id)
expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
end
end
@@ -343,7 +343,8 @@ describe Projects::CommitController do
get_pipelines(id: commit.id, format: :json)
expect(response).to be_ok
- expect(JSON.parse(response.body)).not_to be_empty
+ expect(JSON.parse(response.body)['pipelines']).not_to be_empty
+ expect(JSON.parse(response.body)['count']['all']).to eq 1
end
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 8f4694c9854..b4f9fd9b7a2 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -72,7 +72,7 @@ describe Projects::CompareController do
from: '',
to: 'master')
- expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, to: 'master'))
+ expect(response).to redirect_to(project_compare_index_path(project, to: 'master'))
end
it 'redirects back to index when params[:to] is empty and preserves params[:from]' do
@@ -82,7 +82,7 @@ describe Projects::CompareController do
from: 'master',
to: '')
- expect(response).to redirect_to(namespace_project_compare_index_path(project.namespace, project, from: 'master'))
+ expect(response).to redirect_to(project_compare_index_path(project, from: 'master'))
end
it 'redirects back to index when params[:from] and params[:to] are empty' do
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index ad0b046742d..f88f50c3cc6 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -58,11 +58,9 @@ describe Projects::EnvironmentsController do
expect(json_response['stopped_count']).to eq 1
end
- it 'does not set the polling interval header' do
- # TODO, this is a temporary fix, see follow up issue:
- # https://gitlab.com/gitlab-org/gitlab-ee/issues/2677
+ it 'sets the polling interval header' do
expect(response).to have_http_status(:ok)
- expect(response.headers['Poll-Interval']).to be_nil
+ expect(response.headers['Poll-Interval']).to eq("3000")
end
end
@@ -184,7 +182,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- namespace_project_job_url(project.namespace, project, action) })
+ project_job_url(project, action) })
end
end
@@ -198,7 +196,7 @@ describe Projects::EnvironmentsController do
expect(response).to have_http_status(200)
expect(json_response).to eq(
{ 'redirect_url' =>
- namespace_project_environment_url(project.namespace, project, environment) })
+ project_environment_url(project, environment) })
end
end
end
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index b5435357f53..019a50882ab 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -34,7 +34,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
@@ -65,7 +65,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
@@ -79,7 +79,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(flash[:alert]).to eq('Please select a group.')
end
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
new file mode 100644
index 00000000000..b93ab220f4d
--- /dev/null
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Projects::HooksController do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe '#index' do
+ it 'redirects to settings/integrations page' do
+ get(:index, namespace_id: project.namespace, project_id: project)
+
+ expect(response).to redirect_to(
+ project_settings_integrations_path(project)
+ )
+ end
+ end
+end
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index 6724b474179..9be61342616 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -59,7 +59,7 @@ describe Projects::ImportsController do
it 'redirects to new_namespace_project_import_path' do
get :show, namespace_id: project.namespace.to_param, project_id: project
- expect(response).to redirect_to new_namespace_project_import_path(project.namespace, project)
+ expect(response).to redirect_to new_project_import_path(project)
end
end
@@ -75,7 +75,7 @@ describe Projects::ImportsController do
get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully forked.'
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
@@ -84,14 +84,14 @@ describe Projects::ImportsController do
get :show, namespace_id: project.namespace.to_param, project_id: project
expect(flash[:notice]).to eq 'The project was successfully imported.'
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
context 'when continue params is present' do
let(:params) do
{
- to: namespace_project_path(project.namespace, project),
+ to: project_path(project),
notice: 'Finished'
}
end
@@ -120,7 +120,7 @@ describe Projects::ImportsController do
it 'redirects to namespace_project_path' do
get :show, namespace_id: project.namespace.to_param, project_id: project
- expect(response).to redirect_to namespace_project_path(project.namespace, project)
+ expect(response).to redirect_to project_path(project)
end
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 9f98427a86b..2c57f3bcf8d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -7,14 +7,30 @@ describe Projects::IssuesController do
describe "GET #index" do
context 'external issue tracker' do
- it 'redirects to the external issue tracker' do
- external = double(project_path: 'https://example.com/project')
- allow(project).to receive(:external_issue_tracker).and_return(external)
- controller.instance_variable_set(:@project, project)
+ before do
+ sign_in(user)
+ project.add_developer(user)
+ create(:jira_service, project: project)
+ end
- get :index, namespace_id: project.namespace, project_id: project
+ context 'when GitLab issues disabled' do
+ it 'returns 404 status' do
+ project.issues_enabled = false
+ project.save!
+
+ get :index, namespace_id: project.namespace, project_id: project
- expect(response).to redirect_to('https://example.com/project')
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when GitLab issues enabled' do
+ it 'renders the "index" template' do
+ get :index, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:index)
+ end
end
end
@@ -35,20 +51,12 @@ describe Projects::IssuesController do
it "returns 301 if request path doesn't match project path" do
get :index, namespace_id: project.namespace, project_id: project.path.upcase
- expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+ expect(response).to redirect_to(project_issues_path(project))
end
it "returns 404 when issues are disabled" do
project.issues_enabled = false
- project.save
-
- get :index, namespace_id: project.namespace, project_id: project
- expect(response).to have_http_status(404)
- end
-
- it "returns 404 when external issue tracker is enabled" do
- controller.instance_variable_set(:@project, project)
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ project.save!
get :index, namespace_id: project.namespace, project_id: project
expect(response).to have_http_status(404)
@@ -139,19 +147,36 @@ describe Projects::IssuesController do
end
context 'external issue tracker' do
+ let!(:service) do
+ create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker', new_issue_url: 'http://test.com')
+ end
+
before do
sign_in(user)
project.team << [user, :developer]
- end
- it 'redirects to the external issue tracker' do
- external = double(new_issue_path: 'https://example.com/issues/new')
+ external = double
allow(project).to receive(:external_issue_tracker).and_return(external)
- controller.instance_variable_set(:@project, project)
+ end
- get :new, namespace_id: project.namespace, project_id: project
+ context 'when GitLab issues disabled' do
+ it 'returns 404 status' do
+ project.issues_enabled = false
+ project.save!
+
+ get :new, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when GitLab issues enabled' do
+ it 'renders the "new" template' do
+ get :new, namespace_id: project.namespace, project_id: project
- expect(response).to redirect_to('https://example.com/issues/new')
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:new)
+ end
end
end
end
@@ -329,7 +354,7 @@ describe Projects::IssuesController do
update_verified_issue
expect(response)
- .to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+ .to redirect_to(project_issue_path(project, issue))
end
it 'accepts an issue after recaptcha is verified' do
@@ -512,6 +537,36 @@ describe Projects::IssuesController do
end
end
+ describe 'GET #realtime_changes' do
+ it_behaves_like 'restricted action', success: 200
+
+ def go(id:)
+ get :realtime_changes,
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ end
+
+ context 'when an issue was edited by a deleted user' do
+ let(:deleted_user) { create(:user) }
+
+ before do
+ project.team << [user, :developer]
+
+ issue.update!(last_edited_by: deleted_user, last_edited_at: Time.now)
+
+ deleted_user.destroy
+ sign_in(user)
+ end
+
+ it 'returns 200' do
+ go(id: issue.iid)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
+
describe 'GET #edit' do
it_behaves_like 'restricted action', success: 200
@@ -772,7 +827,7 @@ describe Projects::IssuesController do
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(302)
- expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
end
it 'delegates the update of the todos count cache to TodoService' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 472e5fc51a0..5a295ae47a6 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -7,6 +7,10 @@ describe Projects::JobsController do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:user) { create(:user) }
+ before do
+ stub_not_protect_default_branch
+ end
+
describe 'GET index' do
context 'when scope is pending' do
before do
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index bf1776eb320..f19ad4c2c81 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -178,7 +178,7 @@ describe Projects::LabelsController do
it 'redirects to the correct casing' do
get :index, namespace_id: project.namespace, project_id: project.to_param.upcase
- expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+ expect(response).to redirect_to(project_labels_path(project))
expect(controller).not_to set_flash[:notice]
end
end
@@ -191,7 +191,7 @@ describe Projects::LabelsController do
it 'redirects to the canonical path' do
get :index, namespace_id: project.namespace, project_id: project.to_param + 'old'
- expect(response).to redirect_to(namespace_project_labels_path(project.namespace, project))
+ expect(response).to redirect_to(project_labels_path(project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project))
end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 422a8b6fac0..12e413db902 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -38,7 +38,7 @@ describe Projects::MattermostsController do
it 'shows the error' do
allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"])
- expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project))
+ expect(subject).to redirect_to(new_project_mattermost_url(project))
end
end
@@ -51,7 +51,7 @@ describe Projects::MattermostsController do
subject
service = project.services.last
- expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service))
+ expect(subject).to redirect_to(edit_project_service_url(project, service))
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 6f9ce60cf75..2fce4b7a85f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -439,7 +439,7 @@ describe Projects::MergeRequestsController do
delete :destroy, namespace_id: project.namespace, project_id: project, id: merge_request.iid
expect(response).to have_http_status(302)
- expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
+ expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
end
it 'delegates the update of the todos count cache to TodoService' do
@@ -481,7 +481,8 @@ describe Projects::MergeRequestsController do
end
it 'responds with serialized pipelines' do
- expect(json_response).not_to be_empty
+ expect(json_response['pipelines']).not_to be_empty
+ expect(json_response['count']['all']).to eq 1
end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 84a61b2784e..bb5a340cd96 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -31,6 +31,40 @@ describe Projects::MilestonesController do
end
end
+ describe "#index" do
+ context "as html" do
+ before do
+ get :index, namespace_id: project.namespace.id, project_id: project.id
+ end
+
+ it "queries only projects milestones" do
+ milestones = assigns(:milestones)
+
+ expect(milestones.count).to eq(1)
+ expect(milestones.where(project_id: nil)).to be_empty
+ end
+ end
+
+ context "as json" do
+ let!(:group) { create(:group, :public) }
+ let!(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+
+ before do
+ project.update(namespace: group)
+ get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ end
+
+ it "queries projects milestones and groups milestones" do
+ milestones = assigns(:milestones)
+
+ expect(milestones.count).to eq(2)
+ expect(milestones.where(project_id: nil).first).to eq(group_milestone)
+ expect(milestones.where(group_id: nil).first).to eq(milestone)
+ end
+ end
+ end
+
describe "#destroy" do
it "removes milestone" do
expect(issue.milestone_id).to eq(milestone.id)
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 33853c4b9d0..920189be381 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -46,7 +46,7 @@ describe Projects::PagesDomainsController do
post(:create, request_params.merge(pages_domain: pages_domain_params))
end.to change { PagesDomain.count }.by(1)
- expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+ expect(response).to redirect_to(project_pages_path(project))
end
end
@@ -56,7 +56,7 @@ describe Projects::PagesDomainsController do
delete(:destroy, request_params.merge(id: pages_domain.domain))
end.to change { PagesDomain.count }.by(-1)
- expect(response).to redirect_to(namespace_project_pages_path(project.namespace, project))
+ expect(response).to redirect_to(project_pages_path(project))
end
end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index a8c44d5c313..41bf5580993 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::PipelineSchedulesController do
+ include AccessMatchersForController
+
set(:project) { create(:empty_project, :public) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
@@ -17,6 +19,14 @@ describe Projects::PipelineSchedulesController do
expect(response).to render_template(:index)
end
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new { visit_pipelines_schedules }.count
+
+ create_list(:ci_pipeline_schedule, 2, project: project)
+
+ expect { visit_pipelines_schedules }.not_to exceed_query_limit(control_count)
+ end
+
context 'when the scope is set to active' do
let(:scope) { 'active' }
@@ -36,104 +46,352 @@ describe Projects::PipelineSchedulesController do
end
end
- describe 'GET edit' do
- let(:user) { create(:user) }
+ describe 'GET #new' do
+ set(:user) { create(:user) }
before do
- project.add_master(user)
-
+ project.add_developer(user)
sign_in(user)
end
- it 'loads the pipeline schedule' do
- get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ it 'initializes a pipeline schedule model' do
+ get :new, namespace_id: project.namespace.to_param, project_id: project
expect(response).to have_http_status(:ok)
- expect(assigns(:schedule)).to eq(pipeline_schedule)
+ expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule)
end
end
- describe 'DELETE #destroy' do
- set(:user) { create(:user) }
+ describe 'POST #create' do
+ describe 'functionality' do
+ set(:user) { create(:user) }
- context 'when a developer makes the request' do
before do
project.add_developer(user)
sign_in(user)
+ end
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ let(:basic_param) do
+ attributes_for(:ci_pipeline_schedule)
end
- it 'does not delete the pipeline schedule' do
- expect(response).not_to have_http_status(:ok)
+ context 'when variables_attributes has one variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'creates a new schedule' do
+ expect { go }
+ .to change { Ci::PipelineSchedule.count }.by(1)
+ .and change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ expect(response).to have_http_status(:found)
+
+ Ci::PipelineScheduleVariable.last.tap do |v|
+ expect(v.key).to eq("AAA")
+ expect(v.value).to eq("AAA123")
+ end
+ end
+ end
+
+ context 'when variables_attributes has two variables and duplicted' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+ })
+ end
+
+ it 'returns an error that the keys of variable are duplicated' do
+ expect { go }
+ .to change { Ci::PipelineSchedule.count }.by(0)
+ .and change { Ci::PipelineScheduleVariable.count }.by(0)
+
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
end
end
- context 'when a master makes the request' do
+ describe 'security' do
+ let(:schedule) { attributes_for(:ci_pipeline_schedule) }
+
+ it { expect { go }.to be_allowed_for(:admin) }
+ it { expect { go }.to be_allowed_for(:owner).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project) }
+ it { expect { go }.to be_denied_for(:reporter).of(project) }
+ it { expect { go }.to be_denied_for(:guest).of(project) }
+ it { expect { go }.to be_denied_for(:user) }
+ it { expect { go }.to be_denied_for(:external) }
+ it { expect { go }.to be_denied_for(:visitor) }
+ end
+
+ def go
+ post :create, namespace_id: project.namespace.to_param, project_id: project, schedule: schedule
+ end
+ end
+
+ describe 'PUT #update' do
+ describe 'functionality' do
+ set(:user) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
+
before do
- project.add_master(user)
+ project.add_developer(user)
sign_in(user)
end
- it 'destroys the pipeline schedule' do
- expect do
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
- end.to change { project.pipeline_schedules.count }.by(-1)
+ context 'when a pipeline schedule has no variables' do
+ let(:basic_param) do
+ { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true }
+ end
- expect(response).to have_http_status(302)
+ context 'when params include one variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'inserts new variable to the pipeline schedule' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ pipeline_schedule.reload
+ expect(response).to have_http_status(:found)
+ expect(pipeline_schedule.variables.last.key).to eq('AAA')
+ expect(pipeline_schedule.variables.last.value).to eq('AAA123')
+ end
+ end
+
+ context 'when params include two duplicated variables' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }, { key: 'AAA', value: 'BBB123' }]
+ })
+ end
+
+ it 'returns an error that variables are duplciated' do
+ go
+
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
+ end
+ end
+
+ context 'when a pipeline schedule has one variable' do
+ let(:basic_param) do
+ { description: 'updated_desc', cron: '0 1 * * *', cron_timezone: 'UTC', ref: 'patch-x', active: true }
+ end
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'CCC', pipeline_schedule: pipeline_schedule)
+ end
+
+ context 'when adds a new variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'AAA', value: 'AAA123' }]
+ })
+ end
+
+ it 'adds the new variable' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(1)
+
+ pipeline_schedule.reload
+ expect(pipeline_schedule.variables.last.key).to eq('AAA')
+ end
+ end
+
+ context 'when adds a new duplicated variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ key: 'CCC', value: 'AAA123' }]
+ })
+ end
+
+ it 'returns an error' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule.reload
+ expect(assigns(:schedule).errors['variables']).not_to be_empty
+ end
+ end
+
+ context 'when updates a variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, value: 'new_value' }]
+ })
+ end
+
+ it 'updates the variable' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule_variable.reload
+ expect(pipeline_schedule_variable.value).to eq('new_value')
+ end
+ end
+
+ context 'when deletes a variable' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true }]
+ })
+ end
+
+ it 'delete the existsed variable' do
+ expect { go }.to change { Ci::PipelineScheduleVariable.count }.by(-1)
+ end
+ end
+
+ context 'when deletes and creates a same key simultaneously' do
+ let(:schedule) do
+ basic_param.merge({
+ variables_attributes: [{ id: pipeline_schedule_variable.id, _destroy: true },
+ { key: 'CCC', value: 'CCC123' }]
+ })
+ end
+
+ it 'updates the variable' do
+ expect { go }.not_to change { Ci::PipelineScheduleVariable.count }
+
+ pipeline_schedule.reload
+ expect(pipeline_schedule.variables.last.key).to eq('CCC')
+ expect(pipeline_schedule.variables.last.value).to eq('CCC123')
+ end
+ end
end
end
- end
- describe 'security' do
- include AccessMatchersForController
+ describe 'security' do
+ let(:schedule) { { description: 'updated_desc' } }
- describe 'GET edit' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:master).of(project) }
- it { expect { go }.to be_allowed_for(:developer).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
it { expect { go }.to be_denied_for(:visitor) }
- def go
+ context 'when a developer created a pipeline schedule' do
+ let(:developer_1) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: developer_1) }
+
+ before do
+ project.add_developer(developer_1)
+ end
+
+ it { expect { go }.to be_allowed_for(developer_1) }
+ it { expect { go }.to be_denied_for(:developer).of(project) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ end
+
+ context 'when a master created a pipeline schedule' do
+ let(:master_1) { create(:user) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master_1) }
+
+ before do
+ project.add_master(master_1)
+ end
+
+ it { expect { go }.to be_allowed_for(master_1) }
+ it { expect { go }.to be_allowed_for(:master).of(project) }
+ it { expect { go }.to be_denied_for(:developer).of(project) }
+ end
+ end
+
+ def go
+ put :update, namespace_id: project.namespace.to_param,
+ project_id: project, id: pipeline_schedule,
+ schedule: schedule
+ end
+ end
+
+ describe 'GET #edit' do
+ describe 'functionality' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'loads the pipeline schedule' do
get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+
+ expect(response).to have_http_status(:ok)
+ expect(assigns(:schedule)).to eq(pipeline_schedule)
end
end
- describe 'GET take_ownership' do
+ describe 'security' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:master).of(project) }
- it { expect { go }.to be_allowed_for(:developer).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
it { expect { go }.to be_denied_for(:visitor) }
+ end
- def go
- post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
- end
+ def go
+ get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
end
+ end
- describe 'PUT update' do
+ describe 'GET #take_ownership' do
+ describe 'security' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:master).of(project) }
- it { expect { go }.to be_allowed_for(:developer).of(project) }
+ it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
it { expect { go }.to be_denied_for(:visitor) }
+ end
+
+ def go
+ post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ set(:user) { create(:user) }
+
+ context 'when a developer makes the request' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
- def go
- put :update, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id,
- schedule: { description: 'a' }
+ delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end
+
+ it 'does not delete the pipeline schedule' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ context 'when a master makes the request' do
+ before do
+ project.add_master(user)
+ sign_in(user)
+ end
+
+ it 'destroys the pipeline schedule' do
+ expect do
+ delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ end.to change { project.pipeline_schedules.count }.by(-1)
+
+ expect(response).to have_http_status(302)
end
end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 734532668d3..c8de275ca3e 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -8,6 +8,7 @@ describe Projects::PipelinesController do
let(:feature) { ProjectFeature::DISABLED }
before do
+ stub_not_protect_default_branch
project.add_developer(user)
project.project_feature.update(
builds_access_level: feature)
@@ -158,7 +159,7 @@ describe Projects::PipelinesController do
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
-
+
it 'retries a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(build.reload).to be_retried
@@ -175,7 +176,7 @@ describe Projects::PipelinesController do
describe 'POST cancel.json' do
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
-
+
before do
post :cancel, namespace_id: project.namespace,
project_id: project,
@@ -185,7 +186,7 @@ describe Projects::PipelinesController do
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
-
+
it 'cancels a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(pipeline.reload).to be_canceled
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index f2b59ba82ca..8671d7a78dd 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -5,11 +5,10 @@ describe Projects::ProjectMembersController do
let(:project) { create(:empty_project, :public, :access_requestable) }
describe 'GET index' do
- it 'should have the settings/members address with a 302 status code' do
+ it 'should have the project_members address with a 200 status code' do
get :index, namespace_id: project.namespace, project_id: project
- expect(response).to have_http_status(302)
- expect(response.location).to include namespace_project_settings_members_path(project.namespace, project)
+ expect(response).to have_http_status(200)
end
end
@@ -50,7 +49,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Users were successfully added.'
- expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+ expect(response).to redirect_to(project_project_members_path(project))
end
it 'adds no user to members' do
@@ -62,7 +61,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Message'
- expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
+ expect(response).to redirect_to(project_project_members_path(project))
end
end
end
@@ -111,7 +110,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(project.members).not_to include member
end
@@ -183,7 +182,7 @@ describe Projects::ProjectMembersController do
project_id: project
expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
- expect(response).to redirect_to(namespace_project_path(project.namespace, project))
+ expect(response).to redirect_to(project_path(project))
expect(project.requesters).to be_empty
expect(project.users).not_to include user
end
@@ -202,7 +201,7 @@ describe Projects::ProjectMembersController do
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(
- namespace_project_path(project.namespace, project)
+ project_path(project)
)
expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(project.users).not_to include user
@@ -253,7 +252,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
expect(project.members).to include member
end
@@ -290,7 +289,7 @@ describe Projects::ProjectMembersController do
expect(project.team_members).to include member
expect(response).to set_flash.to 'Successfully imported'
expect(response).to redirect_to(
- namespace_project_settings_members_path(project.namespace, project)
+ project_project_members_path(project)
)
end
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
new file mode 100644
index 00000000000..a823516830e
--- /dev/null
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Projects::Registry::TagsController do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :private) }
+
+ before do
+ sign_in(user)
+ stub_container_registry_config(enabled: true)
+ end
+
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ describe 'POST destroy' do
+ context 'when there is matching tag present' do
+ before do
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1 test.])
+ end
+
+ let(:repository) do
+ create(:container_repository, name: 'image', project: project)
+ end
+
+ it 'makes it possible to delete regular tag' do
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
+
+ destroy_tag('rc1')
+ end
+
+ it 'makes it possible to delete a tag that ends with a dot' do
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete)
+
+ destroy_tag('test.')
+ end
+ end
+ end
+ end
+
+ def destroy_tag(name)
+ post :destroy, namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ id: name
+ end
+end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 4dc227a36d4..5a9d8a75f3e 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -79,7 +79,7 @@ describe Projects::ServicesController do
put :update,
namespace_id: project.namespace.id, project_id: project.id, id: service.id, service: { active: true }
- expect(response).to redirect_to(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(response).to redirect_to(project_settings_integrations_path(project))
expect(flash[:notice]).to eq 'HipChat activated.'
end
end
diff --git a/spec/controllers/projects/settings/members_controller_spec.rb b/spec/controllers/projects/settings/members_controller_spec.rb
deleted file mode 100644
index 076d6cd9c6e..00000000000
--- a/spec/controllers/projects/settings/members_controller_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require('spec_helper')
-
-describe Projects::Settings::MembersController do
- let(:project) { create(:empty_project, :public, :access_requestable) }
-
- describe 'GET show' do
- it 'renders show with 200 status code' do
- get :show, namespace_id: project.namespace, project_id: project
-
- expect(response).to have_http_status(200)
- expect(response).to render_template(:show)
- end
- end
-end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index ec0b7f8c967..cc444f31797 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -148,7 +148,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(Snippet.last)
+ expect(response).to redirect_to(project_snippet_path(project, Snippet.last))
end
end
end
@@ -228,7 +228,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(project_snippet_path(project, snippet))
end
end
end
@@ -273,7 +273,7 @@ describe Projects::SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(project_snippet_path(project, snippet))
end
end
end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 1ecfe48475c..da06fcb7cfb 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -15,13 +15,13 @@ describe Projects::VariablesController do
post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "one", value: "two" }
- expect(flash[:notice]).to include 'Variables were successfully updated.'
- expect(response).to redirect_to(namespace_project_settings_ci_cd_path(project.namespace, project))
+ expect(flash[:notice]).to include 'Variable was successfully created.'
+ expect(response).to redirect_to(project_settings_ci_cd_path(project))
end
end
context 'variable is invalid' do
- it 'shows an alert flash message' do
+ it 'renders show' do
post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "..one", value: "two" }
@@ -35,7 +35,6 @@ describe Projects::VariablesController do
context 'updating a variable with valid characters' do
before do
- variable.project_id = project.id
project.variables << variable
end
@@ -44,7 +43,7 @@ describe Projects::VariablesController do
id: variable.id, variable: { key: variable.key, value: 'two' }
expect(flash[:notice]).to include 'Variable was successfully updated.'
- expect(response).to redirect_to(namespace_project_variables_path(project.namespace, project))
+ expect(response).to redirect_to(project_variables_path(project))
end
it 'renders the action #show if the variable key is invalid' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 240a81367d0..192cca45d99 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -211,24 +211,43 @@ describe ProjectsController do
let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) }
- let(:new_path) { 'renamed_path' }
- let(:project_params) { { path: new_path } }
before do
sign_in(admin)
end
- it "sets the repository to the right path after a rename" do
- controller.instance_variable_set(:@project, project)
+ context 'when only renaming a project path' do
+ it "sets the repository to the right path after a rename" do
+ expect { update_project path: 'renamed_path' }
+ .to change { project.reload.path }
- put :update,
- namespace_id: project.namespace,
- id: project.id,
- project: project_params
+ expect(project.path).to include 'renamed_path'
+ expect(assigns(:repository).path).to include project.path
+ expect(response).to have_http_status(302)
+ end
+ end
- expect(project.repository.path).to include(new_path)
- expect(assigns(:repository).path).to eq(project.repository.path)
- expect(response).to have_http_status(302)
+ context 'when project has container repositories with tags' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+ end
+
+ it 'does not allow to rename the project' do
+ expect { update_project path: 'renamed_path' }
+ .not_to change { project.reload.path }
+
+ expect(controller).to set_flash[:alert].to(/container registry tags/)
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ def update_project(**parameters)
+ put :update,
+ namespace_id: project.namespace.path,
+ id: project.path,
+ project: parameters
end
end
@@ -482,7 +501,7 @@ describe ProjectsController do
it 'redirects to the canonical path (testing non-show action)' do
get :refs, namespace_id: 'foo', id: 'bar'
- expect(response).to redirect_to(refs_namespace_project_path(namespace_id: public_project.namespace, id: public_project))
+ expect(response).to redirect_to(refs_project_path(public_project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
end
end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 917bd44c91b..5d2734b8827 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe SentNotificationsController, type: :controller do
+describe SentNotificationsController do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) }
@@ -23,7 +23,7 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the login page' do
@@ -83,12 +83,12 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the issue page' do
expect(response)
- .to redirect_to(namespace_project_issue_path(project.namespace, project, issue))
+ .to redirect_to(project_issue_path(project, issue))
end
end
@@ -109,12 +109,12 @@ describe SentNotificationsController, type: :controller do
end
it 'sets the flash message' do
- expect(controller).to set_flash[:notice].to(/unsubscribed/).now
+ expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
it 'redirects to the merge request page' do
expect(response)
- .to redirect_to(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ .to redirect_to(project_merge_request_path(project, merge_request))
end
end
end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index bf922260b2f..a22fd8eaf9b 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -1,9 +1,11 @@
require 'spec_helper'
describe SessionsController do
+ include DeviseHelpers
+
describe '#new' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
context 'when auto sign-in is enabled' do
@@ -34,7 +36,7 @@ describe SessionsController do
describe '#create' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
context 'when using standard authentications' do
@@ -47,7 +49,7 @@ describe SessionsController do
end
end
- context 'when using valid password', :redis do
+ context 'when using valid password', :clean_gitlab_redis_shared_state do
include UserActivitiesHelpers
let(:user) { create(:user) }
@@ -257,7 +259,7 @@ describe SessionsController do
describe '#new' do
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
it 'redirects correctly for referer on same host with params' do
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 430d1208cd1..475ceda11fe 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -186,8 +186,8 @@ describe SnippetsController do
end
context 'when the snippet description contains a file' do
- let(:picture_file) { '/temp/secret56/picture.jpg' }
- let(:text_file) { '/temp/secret78/text.txt' }
+ let(:picture_file) { '/system/temp/secret56/picture.jpg' }
+ let(:text_file) { '/system/temp/secret78/text.txt' }
let(:description) do
"Description with picture: ![picture](/uploads#{picture_file}) and "\
"text: [text.txt](/uploads#{text_file})"
@@ -208,8 +208,8 @@ describe SnippetsController do
snippet = subject
expected_description = "Description with picture: "\
- "![picture](/uploads/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
- "text: [text.txt](/uploads/personal_snippet/#{snippet.id}/secret78/text.txt)"
+ "![picture](/uploads/system/personal_snippet/#{snippet.id}/secret56/picture.jpg) and "\
+ "text: [text.txt](/uploads/system/personal_snippet/#{snippet.id}/secret78/text.txt)"
expect(snippet.description).to eq(expected_description)
end
@@ -341,7 +341,7 @@ describe SnippetsController do
{ spam_log_id: spam_logs.last.id,
recaptcha_verification: true })
- expect(response).to redirect_to(snippet)
+ expect(response).to redirect_to(snippet_path(snippet))
end
end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 01a0659479b..96f719e2b82 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -102,7 +102,7 @@ describe UploadsController do
subject
expect(response.body).to match '\"alt\":\"rails_sample\"'
- expect(response.body).to match "\"url\":\"/uploads/temp"
+ expect(response.body).to match "\"url\":\"/uploads/system/temp"
end
it 'does not create an Upload record' do
@@ -119,7 +119,7 @@ describe UploadsController do
subject
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
- expect(response.body).to match "\"url\":\"/uploads/temp"
+ expect(response.body).to match "\"url\":\"/uploads/system/temp"
end
it 'does not create an Upload record' do
diff --git a/spec/db/production/settings_spec.rb b/spec/db/production/settings_spec.rb
index a9d015e0666..79e67330854 100644
--- a/spec/db/production/settings_spec.rb
+++ b/spec/db/production/settings_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'rainbow/ext/string'
-describe 'seed production settings', lib: true do
+describe 'seed production settings' do
include StubENV
let(:settings_file) { Rails.root.join('db/fixtures/production/010_settings.rb') }
let(:settings) { Gitlab::CurrentSettings.current_application_settings }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0cc498f0ce9..5bba1dec7db 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -84,6 +84,10 @@ FactoryGirl.define do
success
end
+ trait :retried do
+ retried true
+ end
+
trait :cancelable do
pending
end
@@ -106,7 +110,7 @@ FactoryGirl.define do
end
after(:build) do |build, evaluator|
- build.project = build.pipeline.project
+ build.project ||= build.pipeline.project
end
factory :ci_not_started_build do
@@ -207,7 +211,8 @@ FactoryGirl.define do
cache: {
key: 'cache_key',
untracked: false,
- paths: ['vendor/*']
+ paths: ['vendor/*'],
+ policy: 'pull-push'
}
}
end
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
new file mode 100644
index 00000000000..565ced9eb1a
--- /dev/null
+++ b/spec/factories/ci/group_variables.rb
@@ -0,0 +1,12 @@
+FactoryGirl.define do
+ factory :ci_group_variable, class: Ci::GroupVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ trait(:protected) do
+ protected true
+ end
+
+ group factory: :group
+ end
+end
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
new file mode 100644
index 00000000000..ca64d1aada0
--- /dev/null
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ pipeline_schedule factory: :ci_pipeline_schedule
+ end
+end
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index 6712dd5d82e..33a17cf7ed5 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,6 +1,6 @@
FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do
- runner_id 1
- project_id 1
+ runner factory: :ci_runner
+ project factory: :empty_project
end
end
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index c3a29d8bf04..40c4663c6d8 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -2,13 +2,6 @@ FactoryGirl.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
factory :ci_trigger do
sequence(:token) { |n| "token#{n}" }
-
- factory :ci_trigger_for_trigger_schedule do
- token { SecureRandom.hex(15) }
- owner factory: :user
- project factory: :project
- ref 'master'
- end
end
end
end
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 36b9645438a..89e260cf65b 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -4,14 +4,19 @@ FactoryGirl.define do
factory :commit do
git_commit RepoHelpers.sample_commit
project factory: :empty_project
- author { build(:author) }
initialize_with do
new(git_commit, project)
end
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return build(:author)
+ end
+
trait :without_author do
- author nil
+ after(:build) do |commit|
+ allow(commit).to receive(:author).and_return nil
+ end
end
end
end
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
new file mode 100644
index 00000000000..1258dce8940
--- /dev/null
+++ b/spec/factories/gpg_keys.rb
@@ -0,0 +1,8 @@
+require_relative '../support/gpg_helpers'
+
+FactoryGirl.define do
+ factory :gpg_key do
+ key GpgHelpers::User1.public_key
+ user
+ end
+end
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
new file mode 100644
index 00000000000..a5aeffbe12d
--- /dev/null
+++ b/spec/factories/gpg_signature.rb
@@ -0,0 +1,11 @@
+require_relative '../support/gpg_helpers'
+
+FactoryGirl.define do
+ factory :gpg_signature do
+ commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
+ project
+ gpg_key
+ gpg_key_primary_keyid { gpg_key.primary_keyid }
+ valid_signature true
+ end
+end
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 841ab3c73b8..113665ff11b 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,7 +1,13 @@
FactoryGirl.define do
factory :milestone do
title
- project factory: :empty_project
+
+ transient do
+ project nil
+ group nil
+ project_id nil
+ group_id nil
+ end
trait :active do
state "active"
@@ -11,6 +17,20 @@ FactoryGirl.define do
state "closed"
end
+ after(:build) do |milestone, evaluator|
+ if evaluator.group
+ milestone.group = evaluator.group
+ elsif evaluator.group_id
+ milestone.group_id = evaluator.group_id
+ elsif evaluator.project
+ milestone.project = evaluator.project
+ elsif evaluator.project_id
+ milestone.project_id = evaluator.project_id
+ else
+ milestone.project = create(:empty_project)
+ end
+ end
+
factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed]
end
diff --git a/spec/factories/personal_snippets.rb b/spec/factories/personal_snippets.rb
deleted file mode 100644
index 0f13b2c1020..00000000000
--- a/spec/factories/personal_snippets.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-FactoryGirl.define do
- factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
- end
-end
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index cd754ea235f..d754e980931 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :project_hook do
url { generate(:url) }
enable_ssl_verification false
+ project factory: :empty_project
trait :token do
token { SecureRandom.hex(10) }
diff --git a/spec/factories/project_snippets.rb b/spec/factories/project_snippets.rb
deleted file mode 100644
index e0fe1b36fd3..00000000000
--- a/spec/factories/project_snippets.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-FactoryGirl.define do
- factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
- project factory: :empty_project
- end
-end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 1bb2db11e7f..485ed48d2de 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -171,10 +171,6 @@ FactoryGirl.define do
end
after :create do |project, evaluator|
- TestEnv.copy_repo(project,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
-
if evaluator.create_template
args = evaluator.create_template
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 388f662e6e5..f6ce99d50f9 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -18,4 +18,11 @@ FactoryGirl.define do
visibility_level Snippet::PRIVATE
end
end
+
+ factory :project_snippet, parent: :snippet, class: :ProjectSnippet do
+ project factory: :empty_project
+ end
+
+ factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+ end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 1383420fb44..3222c41c3d8 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,7 +1,7 @@
FactoryGirl.define do
factory :upload do
model { build(:project) }
- path { "uploads/system/project/avatar/avatar.jpg" }
+ path { "uploads/-/system/project/avatar/avatar.jpg" }
size 100.kilobytes
uploader "AvatarUploader"
end
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index b88e801c3d7..091fdcec3db 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-feature 'Abuse reports', feature: true do
+feature 'Abuse reports' do
let(:another_user) { create(:user) }
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
scenario 'Report abuse' do
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 3a6e356b0b0..2144f6ba635 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe "Admin::AbuseReports", feature: true, js: true do
+describe "Admin::AbuseReports", js: true do
let(:user) { create(:user) }
context 'as an admin' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'if a user has been reported for abuse' do
diff --git a/spec/features/admin/admin_active_tab_spec.rb b/spec/features/admin/admin_active_tab_spec.rb
index c74336d8221..07430ecd6e0 100644
--- a/spec/features/admin/admin_active_tab_spec.rb
+++ b/spec/features/admin/admin_active_tab_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
RSpec.describe 'admin active tab' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
shared_examples 'page has active tab' do |title|
diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb
index d8fd4319328..5f3a37c1dcc 100644
--- a/spec/features/admin/admin_appearance_spec.rb
+++ b/spec/features/admin/admin_appearance_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-feature 'Admin Appearance', feature: true do
+feature 'Admin Appearance' do
let!(:appearance) { create(:appearance) }
scenario 'Create new appearance' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
fill_in 'appearance_title', with: 'MyCompany'
@@ -20,7 +20,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Preview appearance' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
click_link "Preview"
@@ -34,7 +34,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Appearance logo' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
attach_file(:appearance_logo, logo_fixture)
@@ -46,7 +46,7 @@ feature 'Admin Appearance', feature: true do
end
scenario 'Header logos' do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_appearances_path
attach_file(:appearance_header_logo, logo_fixture)
@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do
end
def logo_selector
- '//img[@src^="/uploads/system/appearance/logo"]'
+ '//img[data-src^="/uploads/-/system/appearance/logo"]'
end
def header_logo_selector
- '//img[@src^="/uploads/system/appearance/header_logo"]'
+ '//img[data-src^="/uploads/-/system/appearance/header_logo"]'
end
def logo_fixture
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index da063bf7b74..cbccf370514 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-feature 'Admin Broadcast Messages', feature: true do
+feature 'Admin Broadcast Messages' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
create(:broadcast_message, :expired, message: 'Migration to new server')
visit admin_broadcast_messages_path
end
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index d9c4fc686b1..31d4142a8e9 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -4,7 +4,7 @@ describe 'Admin browse spam logs' do
let!(:spam_log) { create(:spam_log, description: 'abcde ' * 20) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'Browse spam logs' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index c734a2ef16d..3e3404dfdac 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin browses logs' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
it 'shows available log files' do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e767081f3e5..e020579f71e 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin Builds' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'GET /admin/builds' do
diff --git a/spec/features/admin/admin_cohorts_spec.rb b/spec/features/admin/admin_cohorts_spec.rb
index 952e5475213..bca52bf674c 100644
--- a/spec/features/admin/admin_cohorts_spec.rb
+++ b/spec/features/admin/admin_cohorts_spec.rb
@@ -1,8 +1,8 @@
require 'rails_helper'
-feature 'Admin cohorts page', feature: true do
+feature 'Admin cohorts page' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'See users count per month' do
diff --git a/spec/features/admin/admin_conversational_development_index_spec.rb b/spec/features/admin/admin_conversational_development_index_spec.rb
index b484677a6df..2d2c7df5364 100644
--- a/spec/features/admin/admin_conversational_development_index_spec.rb
+++ b/spec/features/admin/admin_conversational_development_index_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin Conversational Development Index' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
context 'when usage ping is disabled' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index 81cddd03f80..241c7cbc34e 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-RSpec.describe 'admin deploy keys', type: :feature do
+RSpec.describe 'admin deploy keys' do
let!(:deploy_key) { create(:deploy_key, public: true) }
let!(:another_deploy_key) { create(:another_deploy_key, public: true) }
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
it 'show all public deploy keys' do
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 679bf63e0fd..931f4ec3d24 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Admin disables Git access protocol', feature: true do
+feature 'Admin disables Git access protocol' do
include StubENV
let(:project) { create(:empty_project, :empty_repo) }
@@ -8,7 +8,7 @@ feature 'Admin disables Git access protocol', feature: true do
background do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in(admin)
+ sign_in(admin)
end
context 'with HTTP disabled' do
@@ -51,7 +51,7 @@ feature 'Admin disables Git access protocol', feature: true do
end
def visit_project
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
def disable_http_protocol
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 5437da29979..e214ae6b78d 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -1,8 +1,8 @@
require 'rails_helper'
-feature 'Admin disables 2FA for a user', feature: true do
+feature 'Admin disables 2FA for a user' do
scenario 'successfully', js: true do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
user = create(:user, :two_factor)
edit_user(user)
@@ -17,7 +17,7 @@ feature 'Admin disables 2FA for a user', feature: true do
end
scenario 'for a user without 2FA enabled' do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
user = create(:user)
edit_user(user)
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 8b0fafc5f07..2e1bfcdcec3 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -1,14 +1,15 @@
require 'spec_helper'
-feature 'Admin Groups', feature: true do
+feature 'Admin Groups' do
include Select2Helper
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
let(:user) { create :user }
let!(:group) { create :group }
- let!(:current_user) { gitlab_sign_in :admin }
+ let!(:current_user) { create(:admin) }
before do
+ sign_in(current_user)
stub_application_setting(default_group_visibility: internal)
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 75093aa4167..106e7370a98 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature "Admin Health Check", feature: true do
+feature "Admin Health Check" do
include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe '#show' do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index ec80c420c79..710822ac042 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Admin::HookLogs', feature: true do
+feature 'Admin::HookLogs' do
let(:project) { create(:project) }
let(:system_hook) { create(:system_hook) }
let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'show list of hook logs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index c07c21bd6a1..30fcb334b60 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe 'Admin::Hooks', feature: true do
+describe 'Admin::Hooks' do
before do
@project = create(:project)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
@system_hook = create(:system_hook)
end
@@ -74,11 +74,13 @@ describe 'Admin::Hooks', feature: true do
end
end
- describe 'Test' do
+ describe 'Test', js: true do
before do
WebMock.stub_request(:post, @system_hook.url)
visit admin_hooks_path
- click_link 'Test hook'
+
+ find('.hook-test-button.dropdown').click
+ click_link 'Push events'
end
it { expect(current_path).to eq(admin_hooks_path) }
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index bb40918bd22..ae9b47299e6 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'admin issues labels' do
let!(:feature_label) { Label.create(title: 'feature', template: true) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'list' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index ae41267e5fc..c1ece123230 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-RSpec.describe 'admin manage applications', feature: true do
+RSpec.describe 'admin manage applications' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
it do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index a4ce3e1d5ee..9856d90bffe 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -1,12 +1,14 @@
require 'spec_helper'
-describe "Admin::Projects", feature: true do
+describe "Admin::Projects" do
include Select2Helper
let(:user) { create :user }
let!(:project) { create(:project) }
- let!(:current_user) do
- gitlab_sign_in :admin
+ let!(:current_user) { create(:admin) }
+
+ before do
+ sign_in(current_user)
end
describe "GET /admin/projects" do
@@ -42,7 +44,7 @@ describe "Admin::Projects", feature: true do
end
it do
- expect(current_path).to eq admin_namespace_project_path(project.namespace, project)
+ expect(current_path).to eq admin_project_path(project)
end
it "has project info" do
@@ -62,7 +64,7 @@ describe "Admin::Projects", feature: true do
end
it 'transfers project to group web', js: true do
- visit admin_namespace_project_path(project.namespace, project)
+ visit admin_project_path(project)
click_button 'Search for Namespace'
click_link 'group: web'
@@ -79,7 +81,7 @@ describe "Admin::Projects", feature: true do
end
it 'adds admin a to a project as developer', js: true do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.users-project-form' do
select2(current_user.id, from: '#user_ids', multiple: true)
@@ -102,7 +104,7 @@ describe "Admin::Projects", feature: true do
end
it 'removes admin from the project' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.content-list' do
expect(page).to have_content(current_user.name)
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index 2bfe401521b..380cd5d7703 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe 'Admin::RequestsProfilesController', feature: true do
+describe 'Admin::RequestsProfilesController' do
before do
FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
after do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 6ad2d456b93..46bab3763cc 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -5,7 +5,7 @@ describe "Admin Runners" do
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe "Runners page" do
@@ -19,7 +19,7 @@ describe "Admin Runners" do
end
it 'has all necessary texts' do
- expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 1"
end
@@ -54,7 +54,7 @@ describe "Admin Runners" do
end
it 'has all necessary texts including no runner message' do
- expect(page).to have_text "To register a new Runner"
+ expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 0"
expect(page).to have_text 'No runners found'
end
@@ -163,12 +163,11 @@ describe "Admin Runners" do
end
it 'has a registration token' do
- expect(page).to have_content("Registration token is #{token}")
- expect(page).to have_selector('#runners-token', text: token)
+ expect(page.find('#registration_token')).to have_content(token)
end
describe 'reload registration token' do
- let(:page_token) { find('#runners-token').text }
+ let(:page_token) { find('#registration_token').text }
before do
click_button 'Reset runners registration token'
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 2d6565e6d3b..c1eced417cf 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Admin updates settings', feature: true do
+feature 'Admin updates settings' do
include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
visit admin_application_settings_path
end
@@ -16,6 +16,19 @@ feature 'Admin updates settings', feature: true do
expect(page).to have_content "Application settings saved successfully"
end
+ scenario 'Uncheck all restricted visibility levels' do
+ find('#application_setting_visibility_level_0').set(false)
+ find('#application_setting_visibility_level_10').set(false)
+ find('#application_setting_visibility_level_20').set(false)
+
+ click_button 'Save'
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(find('#application_setting_visibility_level_0')).not_to be_checked
+ expect(find('#application_setting_visibility_level_10')).not_to be_checked
+ expect(find('#application_setting_visibility_level_20')).not_to be_checked
+ end
+
scenario 'Change application settings' do
uncheck 'Gravatar enabled'
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 4efc7f0eb48..1fd1cda694a 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'Admin System Info' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'GET /admin/system_info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 231c094c91d..97ffc54415c 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
+describe 'Admin > Users > Impersonation Tokens', js: true do
let(:admin) { create(:admin) }
let!(:user) { create(:user) }
@@ -8,12 +8,12 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
find(".table.active-tokens")
end
- def inactive_impersonation_tokens
- find(".table.inactive-tokens")
+ def no_personal_access_tokens_message
+ find(".settings-message")
end
before do
- gitlab_sign_in(admin)
+ sign_in(admin)
end
describe "token creation" do
@@ -60,15 +60,17 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
click_on "Revoke"
- expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
+ expect(page).to have_selector(".settings-message")
+ expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.")
end
- it "moves expired tokens to the 'inactive' section" do
+ it "removes expired tokens from 'active' section" do
impersonation_token.update(expires_at: 5.days.ago)
visit admin_user_impersonation_tokens_path(user_id: user.username)
- expect(inactive_impersonation_tokens).to have_text(impersonation_token.name)
+ expect(page).to have_selector(".settings-message")
+ expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.")
end
end
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 6dbc697642f..e2e2b13cf8a 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -1,11 +1,15 @@
require 'spec_helper'
-describe "Admin::Users", feature: true do
+describe "Admin::Users" do
let!(:user) do
create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
end
- let!(:current_user) { gitlab_sign_in :admin }
+ let!(:current_user) { create(:admin) }
+
+ before do
+ sign_in(current_user)
+ end
describe "GET /admin/users" do
before do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 91d70435db8..5b3ee6ee822 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'Admin uses repository checks', feature: true do
+feature 'Admin uses repository checks' do
include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
scenario 'to trigger a single check' do
@@ -43,6 +43,6 @@ feature 'Admin uses repository checks', feature: true do
end
def visit_admin_project_page(project)
- visit admin_namespace_project_path(project.namespace, project)
+ visit admin_project_path(project)
end
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 711c8a710f3..5aae2dbaf91 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard Issues Feed", feature: true do
+describe "Dashboard Issues Feed" do
describe "GET /issues" do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 2f4bb45d74b..321c8a2a670 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard Feed", feature: true do
+describe "Dashboard Feed" do
describe "GET /" do
let!(:user) { create(:user, name: "Jonh") }
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index b94ad973fed..3eeb4d35131 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Issues Feed', feature: true do
+describe 'Issues Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
@@ -15,8 +15,8 @@ describe 'Issues Feed', feature: true do
context 'when authenticated' do
it 'renders atom feed' do
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, :atom)
+ sign_in user
+ visit project_issues_path(project, :atom)
expect(response_headers['Content-Type'])
.to have_content('application/atom+xml')
@@ -30,7 +30,7 @@ describe 'Issues Feed', feature: true do
context 'when authenticated via private token' do
it 'renders atom feed' do
- visit namespace_project_issues_path(project.namespace, project, :atom,
+ visit project_issues_path(project, :atom,
private_token: user.private_token)
expect(response_headers['Content-Type'])
@@ -45,7 +45,7 @@ describe 'Issues Feed', feature: true do
context 'when authenticated via RSS token' do
it 'renders atom feed' do
- visit namespace_project_issues_path(project.namespace, project, :atom,
+ visit project_issues_path(project, :atom,
rss_token: user.rss_token)
expect(response_headers['Content-Type'])
@@ -59,7 +59,7 @@ describe 'Issues Feed', feature: true do
end
it "renders atom feed with url parameters for project issues" do
- visit namespace_project_issues_path(project.namespace, project,
+ visit project_issues_path(project,
:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 44ae7204bcf..052b07689f5 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "User Feed", feature: true do
+describe "User Feed" do
describe "GET /" do
let!(:user) { create(:user) }
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 74f5f70702a..dff6f96b663 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -7,7 +7,7 @@ describe 'Auto deploy' do
before do
create :kubernetes_service, project: project
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'when no deployment service is active' do
@@ -16,7 +16,7 @@ describe 'Auto deploy' do
end
it 'does not show a button to set up auto deploy' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_no_content('Set up auto deploy')
end
end
@@ -24,7 +24,7 @@ describe 'Auto deploy' do
context 'when a deployment service is active' do
before do
project.kubernetes_service.update!(active: true)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows a button to set up auto deploy' do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index ba58af22841..c87469696da 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Issue Boards add issue modal', :feature, :js do
+describe 'Issue Boards add issue modal', :js do
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
@@ -14,14 +14,14 @@ describe 'Issue Boards add issue modal', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
it 'resets filtered search state' do
- visit namespace_project_board_path(project.namespace, project, board, search: 'testing')
+ visit project_board_path(project, board, search: 'testing')
wait_for_requests
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 87fc31d414c..c3711c9b2c5 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,9 +1,10 @@
require 'rails_helper'
-describe 'Issue Boards', feature: true, js: true do
+describe 'Issue Boards', js: true do
include DragTo
- let(:project) { create(:empty_project, :public) }
+ let(:group) { create(:group, :nested) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
let!(:user2) { create(:user) }
@@ -12,12 +13,12 @@ describe 'Issue Boards', feature: true, js: true do
project.team << [user, :master]
project.team << [user2, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'no lists' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
end
@@ -81,7 +82,7 @@ describe 'Issue Boards', feature: true, js: true do
let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting], relative_position: 1) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
@@ -158,7 +159,7 @@ describe 'Issue Boards', feature: true, js: true do
create(:labeled_issue, project: project, labels: [planning])
end
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
page.within(find('.board:nth-child(2)')) do
@@ -507,7 +508,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'keyboard shortcuts' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -519,8 +520,8 @@ describe 'Issue Boards', feature: true, js: true do
context 'signed out user' do
before do
- gitlab_sign_out
- visit namespace_project_board_path(project.namespace, project, board)
+ sign_out(:user)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -542,9 +543,9 @@ describe 'Issue Boards', feature: true, js: true do
before do
project.team << [user_guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(user_guest)
- visit namespace_project_board_path(project.namespace, project, board)
+ sign_out(:user)
+ sign_in(user_guest)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 1e620061e5e..f4be56a4463 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Issue Boards', :feature, :js do
+describe 'Issue Boards', :js do
include DragTo
let(:project) { create(:empty_project, :public) }
@@ -15,14 +15,14 @@ describe 'Issue Boards', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'un-ordered issues' do
let!(:issue4) { create(:labeled_issue, project: project, labels: [label]) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -47,7 +47,7 @@ describe 'Issue Boards', :feature, :js do
context 'ordering in list' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -110,7 +110,7 @@ describe 'Issue Boards', :feature, :js do
let!(:issue6) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label2], relative_position: 1.0) }
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 4)
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index ed3b38e6a7e..415eda0e058 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-describe 'Issue Boards shortcut', feature: true, js: true do
+describe 'Issue Boards shortcut', js: true do
let(:project) { create(:empty_project) }
before do
create(:board, project: project)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'takes user to issue board index' do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 8899e1ef5e5..1c8b9c46569 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Issue Boards add issue modal filtering', :feature, :js do
+describe 'Issue Boards add issue modal filtering', :js do
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:planning) { create(:label, project: project, name: 'Planning') }
@@ -12,7 +12,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows empty state when no results found' do
@@ -202,7 +202,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
end
def visit_board
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
click_button('Add issues')
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index 77cd87d6601..1dbe3dbda11 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Issue Boards new issue', feature: true, js: true do
+describe 'Issue Boards new issue', js: true do
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, position: 0) }
@@ -10,9 +10,9 @@ describe 'Issue Boards new issue', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -83,7 +83,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
context 'unauthorized user' do
before do
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 1c9595def21..3f58fe1c32c 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Issue Boards', feature: true, js: true do
+describe 'Issue Boards', js: true do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:project) { create(:empty_project, :public) }
@@ -20,9 +20,9 @@ describe 'Issue Boards', feature: true, js: true do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
@@ -83,7 +83,7 @@ describe 'Issue Boards', feature: true, js: true do
create(:issue, project: project)
create(:issue, :closed, project: project)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
click_card(find('.board:nth-child(1)').first('.card'))
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index d57ae6a71e7..f54f2234203 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Sub-group project issue boards', :feature, :js do
+describe 'Sub-group project issue boards', :js do
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
let(:project) { create(:empty_project, group: nested_group_1) }
@@ -13,9 +13,9 @@ describe 'Sub-group project issue boards', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_board_path(project.namespace, project, board)
+ visit project_board_path(project, board)
wait_for_requests
end
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index b2e72fc7dee..1e7fd7b62bd 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Contributions Calendar', :feature, :js do
+feature 'Contributions Calendar', :js do
let(:user) { create(:user) }
let(:contributed_project) { create(:empty_project, :public) }
let(:issue_note) { create(:note, project: contributed_project) }
@@ -68,7 +68,7 @@ feature 'Contributions Calendar', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
end
describe 'calendar day selection' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index de16ee3e567..af4cc00162a 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe 'CI Lint', js: true do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
describe 'YAML parsing' do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 0373f649ee8..15ec6f20763 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -66,7 +66,7 @@ describe 'Commits' do
end
before do
- visit namespace_project_commits_path(project.namespace, project, :master)
+ visit project_commits_path(project, :master)
end
it 'shows correct build status from default branch' do
@@ -77,7 +77,7 @@ describe 'Commits' do
end
end
- describe 'Commit builds', :feature, :js do
+ describe 'Commit builds', :js do
before do
visit ci_status_path(pipeline)
end
@@ -152,7 +152,7 @@ describe 'Commits' do
visit ci_status_path(pipeline)
end
- it 'Renders header', :feature, :js do
+ it 'Renders header', :js do
expect(page).to have_content pipeline.sha[0..7]
expect(page).to have_content pipeline.git_commit_message
expect(page).to have_content pipeline.user.name
@@ -165,7 +165,7 @@ describe 'Commits' do
end
end
- context 'when accessing internal project with disallowed access', :feature, :js do
+ context 'when accessing internal project with disallowed access', :js do
before do
project.update(
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
@@ -192,7 +192,7 @@ describe 'Commits' do
before do
project.team << [user, :master]
sign_in(user)
- visit namespace_project_commits_path(project.namespace, project, branch_name)
+ visit project_commits_path(project, branch_name)
end
it 'includes the committed_date for each commit' do
@@ -203,4 +203,105 @@ describe 'Commits' do
end
end
end
+
+ describe 'GPG signed commits', :js do
+ it 'changes from unverified to verified when the user changes his email to match the gpg key' do
+ user = create :user, email: 'unrelated.user@example.org'
+ project.team << [user, :master]
+
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user changes his email which makes the gpg key verified
+ Sidekiq::Testing.inline! do
+ user.skip_reconfirmation!
+ user.update_attributes!(email: GpgHelpers::User1.emails.first)
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ end
+
+ it 'changes from unverified to verified when the user adds the missing gpg key' do
+ user = create :user, email: GpgHelpers::User1.emails.first
+ project.team << [user, :master]
+
+ sign_in(user)
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).not_to have_content 'Verified'
+ end
+
+ # user adds the gpg key which makes the signature valid
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ visit project_commits_path(project, :'signed-commits')
+
+ within '#commits-list' do
+ expect(page).to have_content 'Unverified'
+ expect(page).to have_content 'Verified'
+ end
+ end
+
+ it 'shows popover badges' do
+ gpg_user = create :user, email: GpgHelpers::User1.emails.first, username: 'nannie.bernhard', name: 'Nannie Bernhard'
+ Sidekiq::Testing.inline! do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: gpg_user
+ end
+
+ user = create :user
+ project.team << [user, :master]
+
+ sign_in(user)
+ visit project_commits_path(project, :'signed-commits')
+
+ # unverified signature
+ click_on 'Unverified', match: :first
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with an unverified signature.'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"
+ end
+
+ # verified and the gpg user has a gitlab profile
+ click_on 'Verified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with a verified signature.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content '@nannie.bernhard'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+
+ # verified and the gpg user's profile doesn't exist anymore
+ gpg_user.destroy!
+
+ visit project_commits_path(project, :'signed-commits')
+
+ click_on 'Verified'
+ within '.popover' do
+ expect(page).to have_content 'This commit was signed with a verified signature.'
+ expect(page).to have_content 'Nannie Bernhard'
+ expect(page).to have_content 'nannie.bernhard@example.com'
+ expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}"
+ end
+ end
+ end
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 80d16539d5a..8f59ce3d2e7 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -9,7 +9,7 @@ describe "Container Registry" do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_developer(user)
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: :any, tags: [])
@@ -55,7 +55,6 @@ describe "Container Registry" do
end
def visit_container_registry
- visit namespace_project_container_registry_index_path(
- project.namespace, project)
+ visit project_container_registry_index_path(project)
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index 005c88f6bab..3e6a27eafd8 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe 'Copy as GFM', feature: true, js: true do
+describe 'Copy as GFM', js: true do
include MarkupHelper
include RepoHelpers
include ActionView::Helpers::JavaScriptHelper
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe 'Copying rendered GFM' do
@@ -16,7 +16,7 @@ describe 'Copy as GFM', feature: true, js: true do
# `markdown` helper expects a `@project` variable
@project = @feat.project
- visit namespace_project_issue_path(@project.namespace, @project, @feat.issue)
+ visit project_issue_path(@project, @feat.issue)
end
# The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert GitLab Flavored Markdown (GFM) to HTML.
@@ -121,13 +121,13 @@ describe 'Copy as GFM', feature: true, js: true do
# full issue reference
@feat.issue.to_reference(full: true),
# issue URL
- namespace_project_issue_url(@project.namespace, @project, @feat.issue),
+ project_issue_url(@project, @feat.issue),
# issue URL with note anchor
- namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123'),
+ project_issue_url(@project, @feat.issue, anchor: 'note_123'),
# issue link
- "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})",
+ "[Issue](#{project_issue_url(@project, @feat.issue)})",
# issue link with note anchor
- "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})"
+ "[Issue](#{project_issue_url(@project, @feat.issue, anchor: 'note_123')})"
)
verify(
@@ -466,7 +466,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a diff' do
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
context 'selecting one word of text' do
@@ -507,7 +507,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a blob' do
before do
- visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
+ visit project_blob_path(project, File.join('master', 'files/ruby/popen.rb'))
wait_for_requests
end
@@ -549,7 +549,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a GFM code block' do
before do
- visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
+ visit project_blob_path(project, File.join('markdown', 'doc/api/users.md'))
wait_for_requests
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 5a7ea975455..5c60cca10b9 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Cycle Analytics', feature: true, js: true do
+feature 'Cycle Analytics', js: true do
let(:user) { create(:user) }
let(:guest) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -14,9 +14,9 @@ feature 'Cycle Analytics', feature: true, js: true do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
@@ -38,8 +38,8 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(user)
+ visit project_cycle_analytics_path(project)
end
it 'shows data on each stage' do
@@ -70,8 +70,8 @@ feature 'Cycle Analytics', feature: true, js: true do
user.update_attribute(:preferred_language, 'es')
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(user)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
@@ -93,8 +93,8 @@ feature 'Cycle Analytics', feature: true, js: true do
create_cycle
deploy_master
- gitlab_sign_in(guest)
- visit namespace_project_cycle_analytics_path(project.namespace, project)
+ sign_in(guest)
+ visit project_cycle_analytics_path(project)
wait_for_requests
end
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index f7ddded10c1..067e4337e6a 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Active Tab', js: true, feature: true do
+RSpec.describe 'Dashboard Active Tab', js: true do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
shared_examples 'page has active tab' do |title|
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index 1e9cabe7850..a96270c9147 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -1,11 +1,162 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Activity', feature: true do
+feature 'Dashboard > Activity' do
+ let(:user) { create(:user) }
+
before do
- gitlab_sign_in(create :user)
- visit activity_dashboard_path
+ sign_in(user)
+ end
+
+ context 'rss' do
+ before do
+ visit activity_dashboard_path
+ end
+
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
- it_behaves_like "it has an RSS button with current_user's RSS token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ context 'event filters', :js do
+ let(:project) { create(:empty_project) }
+
+ let(:merge_request) do
+ create(:merge_request, author: user, source_project: project, target_project: project)
+ end
+
+ let(:push_event_data) do
+ {
+ before: Gitlab::Git::BLANK_SHA,
+ after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
+ ref: 'refs/heads/new_design',
+ user_id: user.id,
+ user_name: user.name,
+ repository: {
+ name: project.name,
+ url: 'localhost/rubinius',
+ description: '',
+ homepage: 'localhost/rubinius',
+ private: true
+ }
+ }
+ end
+
+ let(:note) { create(:note, project: project, noteable: merge_request) }
+
+ let!(:push_event) do
+ create(:event, :pushed, data: push_event_data, project: project, author: user)
+ end
+
+ let!(:merged_event) do
+ create(:event, :merged, project: project, target: merge_request, author: user)
+ end
+
+ let!(:joined_event) do
+ create(:event, :joined, project: project, author: user)
+ end
+
+ let!(:closed_event) do
+ create(:event, :closed, project: project, target: merge_request, author: user)
+ end
+
+ let!(:comments_event) do
+ create(:event, :commented, project: project, target: note, author: user)
+ end
+
+ before do
+ project.add_master(user)
+
+ visit activity_dashboard_path
+ wait_for_requests
+ end
+
+ scenario 'user should see all events' do
+ within '.content_list' do
+ expect(page).to have_content('pushed new branch')
+ expect(page).to have_content('joined')
+ expect(page).to have_content('accepted')
+ expect(page).to have_content('closed')
+ expect(page).to have_content('commented on')
+ end
+ end
+
+ scenario 'user should see only pushed events' do
+ click_link('Push events')
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).to have_content('pushed new branch')
+ expect(page).not_to have_content('joined')
+ expect(page).not_to have_content('accepted')
+ expect(page).not_to have_content('closed')
+ expect(page).not_to have_content('commented on')
+ end
+ end
+
+ scenario 'user should see only merged events' do
+ click_link('Merge events')
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).not_to have_content('pushed new branch')
+ expect(page).not_to have_content('joined')
+ expect(page).to have_content('accepted')
+ expect(page).not_to have_content('closed')
+ expect(page).not_to have_content('commented on')
+ end
+ end
+
+ scenario 'user should see only issues events' do
+ click_link('Issue events')
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).not_to have_content('pushed new branch')
+ expect(page).not_to have_content('joined')
+ expect(page).not_to have_content('accepted')
+ expect(page).to have_content('closed')
+ expect(page).not_to have_content('commented on')
+ end
+ end
+
+ scenario 'user should see only comments events' do
+ click_link('Comments')
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).not_to have_content('pushed new branch')
+ expect(page).not_to have_content('joined')
+ expect(page).not_to have_content('accepted')
+ expect(page).not_to have_content('closed')
+ expect(page).to have_content('commented on')
+ end
+ end
+
+ scenario 'user should see only joined events' do
+ click_link('Team')
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).not_to have_content('pushed new branch')
+ expect(page).to have_content('joined')
+ expect(page).not_to have_content('accepted')
+ expect(page).not_to have_content('closed')
+ expect(page).not_to have_content('commented on')
+ end
+ end
+
+ scenario 'user see selected event after page reloading' do
+ click_link('Push events')
+ wait_for_requests
+ visit activity_dashboard_path
+ wait_for_requests
+
+ within '.content_list' do
+ expect(page).to have_content('pushed new branch')
+ expect(page).not_to have_content('joined')
+ expect(page).not_to have_content('accepted')
+ expect(page).not_to have_content('closed')
+ expect(page).not_to have_content('commented on')
+ end
+ end
+ end
end
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index a5ba3e7e3cf..814ec0e59c7 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Archived Project', feature: true do
+RSpec.describe 'Dashboard Archived Project' do
let(:user) { create :user }
let(:project) { create :project}
let(:archived_project) { create(:project, :archived) }
@@ -9,7 +9,7 @@ RSpec.describe 'Dashboard Archived Project', feature: true do
project.team << [user, :master]
archived_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_projects_path
end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 6931d0a840e..b6dce1b8ec4 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Tooltips on .timeago dates', feature: true, js: true do
+feature 'Tooltips on .timeago dates', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:created_date) { Date.yesterday.to_time }
@@ -13,7 +13,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
Event.create( project: project, author_id: user.id, action: Event::JOINED,
updated_at: created_date, created_at: created_date)
- gitlab_sign_in user
+ sign_in user
visit user_path(user)
wait_for_requests()
@@ -30,7 +30,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
project.team << [user, :master]
create(:snippet, author: user, updated_at: created_date, created_at: created_date)
- gitlab_sign_in user
+ sign_in user
visit user_snippets_path(user)
wait_for_requests()
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 2f7245950ec..60a16830cdc 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Group', feature: true do
+RSpec.describe 'Dashboard Group' do
before do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
end
it 'creates new group', js: true do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index e520027bc38..533df7a325c 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard Groups page', js: true, feature: true do
+feature 'Dashboard Groups page', :js do
let!(:user) { create :user }
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, :nested) }
@@ -10,7 +10,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
nested_group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
expect(page).to have_content(group.full_name)
@@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
nested_group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
@@ -41,7 +41,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
fill_in 'filter_groups', with: group.name
wait_for_requests
- fill_in 'filter_groups', with: ""
+ fill_in 'filter_groups', with: ''
wait_for_requests
expect(page).to have_content(group.full_name)
@@ -58,7 +58,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
group.add_owner(user)
subgroup.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
@@ -98,7 +98,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_groups_path
end
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 25b0f40c9cd..68bfbf22736 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Help', feature: true do
+RSpec.describe 'Dashboard Help' do
before do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
end
it 'renders correctly markdown' do
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 8a8a20fd5b1..ae68b0f65d5 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Navigation bar counter', feature: true, caching: true do
+describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do
let(:user) { create(:user) }
let(:project) { create(:empty_project, namespace: user.namespace) }
let(:issue) { create(:issue, project: project) }
@@ -9,7 +9,7 @@ describe 'Navigation bar counter', feature: true, caching: true do
before do
issue.assignees = [user]
merge_request.update(assignee: user)
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'reflects dashboard issues count' do
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index c4dbaad2895..0ce642f32f2 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -1,21 +1,23 @@
require 'spec_helper'
-describe "Dashboard Issues filtering", feature: true, js: true do
+feature 'Dashboard Issues filtering', :js do
+ include SortingHelper
+
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project) }
- context 'filtering by milestone' do
- before do
- project.team << [user, :master]
- gitlab_sign_in(user)
+ let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
+ let!(:issue2) { create(:issue, project: project, author: user, assignees: [user], milestone: milestone) }
- create(:issue, project: project, author: user, assignees: [user])
- create(:issue, project: project, author: user, assignees: [user], milestone: milestone)
+ before do
+ project.add_master(user)
+ sign_in(user)
- visit_issues
- end
+ visit_issues
+ end
+ context 'filtering by milestone' do
it 'shows all issues with no milestone' do
show_milestone_dropdown
@@ -62,6 +64,46 @@ describe "Dashboard Issues filtering", feature: true, js: true do
end
end
+ context 'filtering by label' do
+ let(:label) { create(:label, project: project) }
+ let!(:label_link) { create(:label_link, label: label, target: issue) }
+
+ it 'shows all issues without filter' do
+ page.within 'ul.content-list' do
+ expect(page).to have_content issue.title
+ expect(page).to have_content issue2.title
+ end
+ end
+
+ it 'shows all issues with the selected label' do
+ page.within '.labels-filter' do
+ find('.dropdown').click
+ click_link label.title
+ end
+
+ page.within 'ul.content-list' do
+ expect(page).to have_content issue.title
+ expect(page).not_to have_content issue2.title
+ end
+ end
+ end
+
+ context 'sorting' do
+ it 'shows sorted issues' do
+ sorting_by('Oldest updated')
+ visit_issues
+
+ expect(find('.issues-filters')).to have_content('Oldest updated')
+ end
+
+ it 'keeps sorting issues after visiting Projects Issues page' do
+ sorting_by('Oldest updated')
+ visit project_issues_path(project)
+
+ expect(find('.issues-filters')).to have_content('Oldest updated')
+ end
+ end
+
def show_milestone_dropdown
click_button 'Milestone'
expect(page).to have_selector('.dropdown-content', visible: true)
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index a57962abbda..7c0bf8de14c 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
-RSpec.describe 'Dashboard Issues', feature: true do
+RSpec.describe 'Dashboard Issues' do
let(:current_user) { create :user }
+ let(:user) { current_user } # Shared examples depend on this being available
let!(:public_project) { create(:empty_project, :public) }
let(:project) { create(:empty_project) }
let(:project_with_issues_disabled) { create(:empty_project, :issues_disabled) }
@@ -12,7 +13,7 @@ RSpec.describe 'Dashboard Issues', feature: true do
before do
[project, project_with_issues_disabled].each { |project| project.team << [current_user, :master] }
- gitlab_sign_in(current_user)
+ sign_in(current_user)
visit issues_dashboard_path(assignee_id: current_user.id)
end
@@ -61,7 +62,7 @@ RSpec.describe 'Dashboard Issues', feature: true do
it 'state filter tabs work' do
find('#state-closed').click
- expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, scope: 'all', state: 'closed'), url: true)
+ expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true)
end
it_behaves_like "it has an RSS button with current_user's RSS token"
@@ -77,5 +78,27 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
end
end
+
+ it 'shows the new issue page', js: true do
+ original_defaults = Gitlab::Application.routes.default_url_options
+
+ Gitlab::Application.routes.default_url_options = {
+ host: Capybara.current_session.server.host,
+ port: Capybara.current_session.server.port,
+ protocol: 'http'
+ }
+
+ find('.new-project-item-select-button').trigger('click')
+ wait_for_requests
+ find('.select2-results li').click
+
+ expect(page).to have_current_path("/#{project.path_with_namespace}/issues/new")
+
+ page.within('#content-body') do
+ expect(page).to have_selector('.issue-form')
+ end
+
+ Gitlab::Application.routes.default_url_options = original_defaults
+ end
end
end
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 88bbb9e75b9..b1a207682c3 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard > label filter', feature: true, js: true do
+describe 'Dashboard > label filter', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:project2) { create(:project, name: 'test2', path: 'test2', namespace: user.namespace) }
@@ -11,7 +11,7 @@ describe 'Dashboard > label filter', feature: true, js: true do
project.labels << label
project2.labels << label2
- gitlab_sign_in(user)
+ sign_in(user)
visit issues_dashboard_path
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index bb1fb5b3feb..42d6fadc0c1 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
feature 'Dashboard Merge Requests' do
include FilterItemSelectHelper
+ include SortingHelper
let(:current_user) { create :user }
let(:project) { create(:empty_project) }
@@ -109,5 +110,21 @@ feature 'Dashboard Merge Requests' do
expect(page).to have_content(assigned_merge_request_from_fork.title)
expect(page).to have_content(other_merge_request.title)
end
+
+ it 'shows sorted merge requests' do
+ sorting_by('Oldest updated')
+
+ visit merge_requests_dashboard_path(assignee_id: current_user.id)
+
+ expect(find('.issues-filters')).to have_content('Oldest updated')
+ end
+
+ it 'keeps sorting merge requests after visiting Projects MR page' do
+ sorting_by('Oldest updated')
+
+ visit project_merge_requests_path(project)
+
+ expect(find('.issues-filters')).to have_content('Oldest updated')
+ end
end
end
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index b0e4036f27c..c965b565ca3 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Dashboard > milestone filter', :feature, :js do
+feature 'Dashboard > milestone filter', :js do
include FilterItemSelectHelper
let(:user) { create(:user) }
@@ -11,7 +11,7 @@ feature 'Dashboard > milestone filter', :feature, :js do
let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit issues_dashboard_path(author_id: user.id)
end
diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb
index cc4193b180f..cf32d705365 100644
--- a/spec/features/dashboard/milestone_tabs_spec.rb
+++ b/spec/features/dashboard/milestone_tabs_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard milestone tabs', :js, :feature do
+describe 'Dashboard milestone tabs', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let!(:label) { create(:label, project: project) }
@@ -15,7 +15,7 @@ describe 'Dashboard milestone tabs', :js, :feature do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end
diff --git a/spec/features/dashboard_milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index b308a2297b9..488f7397c69 100644
--- a/spec/features/dashboard_milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Dashboard > Milestones', feature: true do
+feature 'Dashboard > Milestones' do
describe 'as anonymous user' do
before do
visit dashboard_milestones_path
@@ -17,7 +17,7 @@ feature 'Dashboard > Milestones', feature: true do
let!(:milestone) { create(:milestone, project: project) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_milestones_path
end
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 0ba87d921d0..f3b538e490e 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Project member activity', feature: true, js: true do
+feature 'Project member activity', js: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public, name: 'x', namespace: user.namespace) }
@@ -10,7 +10,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
wait_for_requests
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index e9ef5d7983a..abb9e5eef96 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -7,7 +7,13 @@ feature 'Dashboard Projects' do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
+ end
+
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do
+ before do
+ visit dashboard_projects_path
+ end
end
it 'shows the project the user in a member of in the list' do
@@ -55,7 +61,7 @@ feature 'Dashboard Projects' do
end
end
- describe 'with a pipeline', redis: true do
+ describe 'with a pipeline', clean_gitlab_redis_shared_state: true do
let(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.sha) }
before do
@@ -68,9 +74,50 @@ feature 'Dashboard Projects' do
it 'shows that the last pipeline passed' do
visit dashboard_projects_path
- expect(page).to have_xpath("//a[@href='#{pipelines_namespace_project_commit_path(project.namespace, project, project.commit)}']")
+ page.within('.controls') do
+ expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit)}']")
+ expect(page).to have_css('.ci-status-link')
+ expect(page).to have_css('.ci-status-icon-success')
+ expect(page).to have_link('Commit: passed')
+ end
end
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
+ context 'last push widget' do
+ let(:push_event_data) do
+ {
+ before: Gitlab::Git::BLANK_SHA,
+ after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e',
+ ref: 'refs/heads/feature',
+ user_id: user.id,
+ user_name: user.name,
+ repository: {
+ name: project.name,
+ url: 'localhost/rubinius',
+ description: '',
+ homepage: 'localhost/rubinius',
+ private: true
+ }
+ }
+ end
+ let!(:push_event) { create(:event, :pushed, data: push_event_data, project: project, author: user) }
+
+ before do
+ visit dashboard_projects_path
+ end
+
+ scenario 'shows "Create merge request" button' do
+ expect(page).to have_content 'You pushed to feature'
+
+ within('#content-body') do
+ find_link('Create merge request', visible: false).click
+ end
+
+ expect(page).to have_selector('.merge-request-form')
+ expect(current_path).to eq project_new_merge_request_path(project)
+ expect(find('#merge_request_target_project_id').value).to eq project.id.to_s
+ expect(find('input#merge_request_source_branch').value).to eq 'feature'
+ expect(find('input#merge_request_target_branch').value).to eq 'master'
+ end
+ end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 525b0e1b210..5f1f0c10339 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-feature 'Dashboard shortcuts', :feature, :js do
+feature 'Dashboard shortcuts', :js do
context 'logged in' do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
visit root_dashboard_path
end
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index 0c069ae5cf0..c29bcc7c9e9 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe 'Dashboard snippets', feature: true do
+describe 'Dashboard snippets' do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(project.owner)
+ sign_in(project.owner)
visit dashboard_snippets_path
end
@@ -25,7 +25,7 @@ describe 'Dashboard snippets', feature: true do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_snippets_path
end
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index 5858f4aa101..d49a78b290f 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -83,7 +83,7 @@ feature 'Dashboard > User sorts todos' do
create(:todo, user: user, project: project, target: issue_2)
create(:todo, user: user, project: project, target: merge_request_1)
- gitlab_sign_in(user)
+ sign_in(user)
visit dashboard_todos_path
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 7fa4d198e00..30bab7eeaa7 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -330,7 +330,7 @@ feature 'Dashboard Todos' do
end
it 'links to the pipelines for the merge request' do
- href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
+ href = pipelines_project_merge_request_path(project, todo.target)
expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
end
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index e9f34760143..c352b6ded14 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dashboard > User filters projects', :feature do
+describe 'Dashboard > User filters projects' do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'Victorialand', namespace: user.namespace) }
let(:user2) { create(:user) }
@@ -9,7 +9,7 @@ describe 'Dashboard > User filters projects', :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'filtering personal projects' do
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 96128061e4d..fa83ad5d17c 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Discussion Comments Merge Request', :feature, :js do
+describe 'Discussion Comments Merge Request', :js do
include RepoHelpers
let(:user) { create(:user) }
@@ -9,9 +9,9 @@ describe 'Discussion Comments Merge Request', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'discussion comments', 'commit'
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index d7c1cd12fb5..f52ba9c4d09 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe 'Discussion Comments Issue', :feature, :js do
+describe 'Discussion Comments Issue', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like 'discussion comments', 'issue'
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index 31fb9c72d25..042f39f47e0 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe 'Discussion Comments Merge Request', :feature, :js do
+describe 'Discussion Comments Merge Request', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'discussion comments', 'merge request'
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index 998d633c83d..50ba13499d9 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe 'Discussion Comments Issue', :feature, :js do
+describe 'Discussion Comments Issue', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
end
it_behaves_like 'discussion comments', 'snippet'
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index d492a15ea17..357d86497d9 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Expand and collapse diffs', js: true, feature: true do
+feature 'Expand and collapse diffs', js: true do
let(:branch) { 'expand-collapse-diffs' }
let(:project) { create(:project, :repository) }
@@ -10,11 +10,11 @@ feature 'Expand and collapse diffs', js: true, feature: true do
allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
- gitlab_sign_in :admin
+ sign_in(create(:admin))
# Ensure that undiffable.md is in .gitattributes
project.repository.copy_gitattributes(branch)
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+ visit project_commit_path(project, project.commit(branch))
execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
@@ -38,7 +38,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
+ visit project_commit_path(project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
execute_script('window.location.reload()')
wait_for_requests
@@ -52,7 +52,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(large_diff).not_to have_selector('.code')
expect(large_diff).to have_selector('.nothing-here-block')
- visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
+ visit project_commit_path(project, project.commit(branch), anchor: large_diff[:id])
execute_script('window.location.reload()')
wait_for_requests
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 6be5dee0c3c..84f41eca999 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Explore Groups page', :js, :feature do
+describe 'Explore Groups page', :js do
let!(:user) { create :user }
let!(:group) { create(:group) }
let!(:public_group) { create(:group, :public) }
@@ -10,7 +10,7 @@ describe 'Explore Groups page', :js, :feature do
before do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit explore_groups_path
end
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index 2d7e703688f..2cd06258e22 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -1,22 +1,18 @@
require 'spec_helper'
-feature 'Top Plus Menu', feature: true, js: true do
- let(:user) { create :user }
- let(:guest_user) { create :user}
+feature 'Top Plus Menu', :js do
+ let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let(:public_project) { create(:project, :public) }
before do
group.add_owner(user)
- group.add_guest(guest_user)
-
- project.add_guest(guest_user)
end
context 'used by full user' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'click on New project shows new project page' do
@@ -39,7 +35,7 @@ feature 'Top Plus Menu', feature: true, js: true do
scenario 'click on New snippet shows new snippet page' do
visit root_dashboard_path
-
+
click_topmenuitem("New snippet")
expect(page).to have_content('New Snippet')
@@ -47,7 +43,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New issue shows new issue page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New issue")
@@ -56,7 +52,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New merge request shows new merge request page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New merge request")
@@ -66,7 +62,7 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'click on New project snippet shows new snippet page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.header-content' do
find('.header-new-dropdown-toggle').trigger('click')
@@ -102,12 +98,17 @@ feature 'Top Plus Menu', feature: true, js: true do
end
context 'used by guest user' do
+ let(:guest_user) { create(:user) }
+
before do
- gitlab_sign_in(guest_user)
+ group.add_guest(guest_user)
+ project.add_guest(guest_user)
+
+ sign_in(guest_user)
end
scenario 'click on New issue shows new issue page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_topmenuitem("New issue")
@@ -116,31 +117,31 @@ feature 'Top Plus Menu', feature: true, js: true do
end
scenario 'has no New merge request menu item' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
hasnot_topmenuitem("New merge request")
end
scenario 'has no New project snippet menu item' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
end
scenario 'public project has no New Issue Button' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
hasnot_topmenuitem("New issue")
end
scenario 'public project has no New merge request menu item' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
hasnot_topmenuitem("New merge request")
end
scenario 'public project has no New project snippet menu item' do
- visit namespace_project_path(public_project.namespace, public_project)
+ visit project_path(public_project)
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-project-snippet')
end
@@ -153,7 +154,7 @@ feature 'Top Plus Menu', feature: true, js: true do
scenario 'has no New project for group menu item' do
visit group_path(group)
-
+
expect(find('.header-new.dropdown')).not_to have_selector('.header-new-group-project')
end
end
@@ -168,5 +169,5 @@ feature 'Top Plus Menu', feature: true, js: true do
def hasnot_topmenuitem(item_name)
expect(find('.header-new.dropdown')).not_to have_content(item_name)
- end
+ end
end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 2d13af2a52a..300296a2b94 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "GitLab Flavored Markdown", feature: true do
+describe "GitLab Flavored Markdown" do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
@@ -25,31 +25,31 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in commits#index" do
- visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
+ visit project_commits_path(project, 'master', limit: 1)
expect(page).to have_link(issue.to_reference)
end
it "renders title in commits#show" do
- visit namespace_project_commit_path(project.namespace, project, commit)
+ visit project_commit_path(project, commit)
expect(page).to have_link(issue.to_reference)
end
it "renders description in commits#show" do
- visit namespace_project_commit_path(project.namespace, project, commit)
+ visit project_commit_path(project, commit)
expect(page).to have_link(fred.to_reference)
end
it "renders title in repositories#branches" do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
expect(page).to have_link(issue.to_reference)
end
end
- describe "for issues", feature: true, js: true do
+ describe "for issues", js: true do
before do
@other_issue = create(:issue,
author: user,
@@ -66,19 +66,19 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders subject in issues#index" do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_link(@other_issue.to_reference)
end
it "renders subject in issues#show" do
- visit namespace_project_issue_path(project.namespace, project, @issue)
+ visit project_issue_path(project, @issue)
expect(page).to have_link(@other_issue.to_reference)
end
it "renders details in issues#show" do
- visit namespace_project_issue_path(project.namespace, project, @issue)
+ visit project_issue_path(project, @issue)
expect(page).to have_link(fred.to_reference)
end
@@ -92,13 +92,13 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in merge_requests#index" do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_link(issue.to_reference)
end
it "renders title in merge_requests#show" do
- visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
+ visit project_merge_request_path(project, @merge_request)
expect(page).to have_link(issue.to_reference)
end
@@ -113,19 +113,19 @@ describe "GitLab Flavored Markdown", feature: true do
end
it "renders title in milestones#index" do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
expect(page).to have_link(issue.to_reference)
end
it "renders title in milestones#show" do
- visit namespace_project_milestone_path(project.namespace, project, @milestone)
+ visit project_milestone_path(project, @milestone)
expect(page).to have_link(issue.to_reference)
end
it "renders description in milestones#show" do
- visit namespace_project_milestone_path(project.namespace, project, @milestone)
+ visit project_milestone_path(project, @milestone)
expect(page).to have_link(fred.to_reference)
end
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 54ebfe6cf77..627a930c997 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Global search', feature: true do
+feature 'Global search' do
let(:user) { create(:user) }
let(:project) { create(:empty_project, namespace: user.namespace) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'I search through the issues and I see pagination' do
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
new file mode 100644
index 00000000000..37814ba6238
--- /dev/null
+++ b/spec/features/group_variables_spec.rb
@@ -0,0 +1,78 @@
+require 'spec_helper'
+
+feature 'Group variables', js: true do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ background do
+ group.add_master(user)
+ gitlab_sign_in(user)
+ end
+
+ context 'when user creates a new variable' do
+ background do
+ visit group_settings_ci_cd_path(group)
+ fill_in 'variable_key', with: 'AAA'
+ fill_in 'variable_value', with: 'AAA123'
+ find(:css, "#variable_protected").set(true)
+ click_on 'Add new variable'
+ end
+
+ scenario 'user sees the created variable' do
+ page.within('.variables-table') do
+ expect(find(".variable-key")).to have_content('AAA')
+ expect(find(".variable-value")).to have_content('******')
+ expect(find(".variable-protected")).to have_content('Yes')
+ end
+ click_on 'Reveal Values'
+ page.within('.variables-table') do
+ expect(find(".variable-value")).to have_content('AAA123')
+ end
+ end
+ end
+
+ context 'when user edits a variable' do
+ background do
+ create(:ci_group_variable, key: 'AAA', value: 'AAA123', protected: true,
+ group: group)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within('.variable-menu') do
+ click_on 'Update'
+ end
+
+ fill_in 'variable_key', with: 'BBB'
+ fill_in 'variable_value', with: 'BBB123'
+ find(:css, "#variable_protected").set(false)
+ click_on 'Save variable'
+ end
+
+ scenario 'user sees the updated variable' do
+ page.within('.variables-table') do
+ expect(find(".variable-key")).to have_content('BBB')
+ expect(find(".variable-value")).to have_content('******')
+ expect(find(".variable-protected")).to have_content('No')
+ end
+ end
+ end
+
+ context 'when user deletes a variable' do
+ background do
+ create(:ci_group_variable, key: 'BBB', value: 'BBB123', protected: false,
+ group: group)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within('.variable-menu') do
+ page.accept_alert 'Are you sure?' do
+ click_on 'Remove'
+ end
+ end
+ end
+
+ scenario 'user does not see the deleted variable' do
+ expect(page).to have_no_css('.variables-table')
+ end
+ end
+end
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 9f66a3d8c72..d3b25ec3d6c 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Group activity page', feature: true do
+feature 'Group activity page' do
+ let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
let(:group) { create(:group) }
let(:path) { activity_group_path(group) }
context 'when signed in' do
before do
- user = create(:group_member, :developer, user: create(:user), group: group ).user
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index b1c7151dfa8..e2c7907528b 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -5,7 +5,7 @@ feature 'Groups Merge Requests Empty States' do
let(:user) { create(:group_member, :developer, user: create(:user), group: group ).user }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'group has a project' do
diff --git a/spec/features/groups/group_name_toggle_spec.rb b/spec/features/groups/group_name_toggle_spec.rb
index f450626c370..a7b8b702ab7 100644
--- a/spec/features/groups/group_name_toggle_spec.rb
+++ b/spec/features/groups/group_name_toggle_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Group name toggle', feature: true, js: true do
+feature 'Group name toggle', js: true do
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
let(:nested_group_2) { create(:group, parent: nested_group_1) }
@@ -9,7 +9,7 @@ feature 'Group name toggle', feature: true, js: true do
SMALL_SCREEN = 300
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
it 'is not present if enough horizontal space' do
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 56e163ec4d0..47553922ede 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Edit group settings', feature: true do
+feature 'Edit group settings' do
given(:user) { create(:user) }
given(:group) { create(:group, path: 'foo') }
background do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'when the group path is changed' do
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index d6b88542ef7..449a99a2c7b 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Group issues page', feature: true do
+feature 'Group issues page' do
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb
index b33040ef843..fb338127861 100644
--- a/spec/features/groups/labels/edit_spec.rb
+++ b/spec/features/groups/labels/edit_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Edit group label', feature: true do
+feature 'Edit group label' do
given(:user) { create(:user) }
given(:group) { create(:group) }
given(:label) { create(:group_label, group: group) }
background do
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_group_label_path(group, label)
end
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
index 8b891c52d08..1dd09d4f203 100644
--- a/spec/features/groups/labels/subscription_spec.rb
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Labels subscription', feature: true do
+feature 'Labels subscription' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let!(:feature) { create(:group_label, group: group, title: 'feature') }
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
index b438f57753c..067a2dc850f 100644
--- a/spec/features/groups/members/leave_group_spec.rb
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > Leave group', feature: true do
+feature 'Groups > Members > Leave group' do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:group) { create(:group) }
diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb
index f6493c4c50e..5c5d48c3623 100644
--- a/spec/features/groups/members/list_members_spec.rb
+++ b/spec/features/groups/members/list_members_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > List members', feature: true do
+feature 'Groups > Members > List members' do
include Select2Helper
let(:user1) { create(:user, name: 'John Doe') }
diff --git a/spec/features/groups/members/manage_access_requests_spec.rb b/spec/features/groups/members/manage_access_requests_spec.rb
index f84d8594c65..b83cd657ef7 100644
--- a/spec/features/groups/members/manage_access_requests_spec.rb
+++ b/spec/features/groups/members/manage_access_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > Manage access requests', feature: true do
+feature 'Groups > Members > Manage access requests' do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
@@ -8,7 +8,7 @@ feature 'Groups > Members > Manage access requests', feature: true do
background do
group.request_access(user)
group.add_owner(owner)
- gitlab_sign_in(owner)
+ sign_in(owner)
end
scenario 'owner can see access requests' do
diff --git a/spec/features/groups/members/manage_members.rb b/spec/features/groups/members/manage_members.rb
index a9a654b20e2..9039b283393 100644
--- a/spec/features/groups/members/manage_members.rb
+++ b/spec/features/groups/members/manage_members.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > Manage members', feature: true do
+feature 'Groups > Members > Manage members' do
include Select2Helper
let(:user1) { create(:user, name: 'John Doe') }
@@ -8,7 +8,7 @@ feature 'Groups > Members > Manage members', feature: true do
let(:group) { create(:group) }
background do
- gitlab_sign_in(user1)
+ sign_in(user1)
end
scenario 'update user to owner level', :js do
diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb
index 41c31b62e18..1f3c7fd3859 100644
--- a/spec/features/groups/members/request_access_spec.rb
+++ b/spec/features/groups/members/request_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > Request access', feature: true do
+feature 'Groups > Members > Request access' do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
@@ -8,7 +8,7 @@ feature 'Groups > Members > Request access', feature: true do
background do
group.add_owner(owner)
- gitlab_sign_in(user)
+ sign_in(user)
visit group_path(group)
end
diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb
index 8ee61953844..e175ad04f86 100644
--- a/spec/features/groups/members/sort_members_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Groups > Members > Sort members', feature: true do
+feature 'Groups > Members > Sort members' do
let(:owner) { create(:user, name: 'John Doe') }
let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
let(:group) { create(:group) }
@@ -9,7 +9,7 @@ feature 'Groups > Members > Sort members', feature: true do
create(:group_member, :owner, user: owner, group: group, created_at: 5.days.ago)
create(:group_member, :developer, user: developer, group: group, created_at: 3.days.ago)
- gitlab_sign_in(owner)
+ sign_in(owner)
end
scenario 'sorts alphabetically by default' do
@@ -68,7 +68,7 @@ feature 'Groups > Members > Sort members', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
- scenario 'sorts by recent sign in', :redis do
+ scenario 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(owner.name)
@@ -76,7 +76,7 @@ feature 'Groups > Members > Sort members', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
- scenario 'sorts by oldest sign in', :redis do
+ scenario 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
diff --git a/spec/features/groups/merge_requests_spec.rb b/spec/features/groups/merge_requests_spec.rb
index b55078c3bf6..cbf147b6a5c 100644
--- a/spec/features/groups/merge_requests_spec.rb
+++ b/spec/features/groups/merge_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Group merge requests page', feature: true do
+feature 'Group merge requests page' do
let(:path) { merge_requests_group_path(group) }
let(:issuable) { create(:merge_request, source_project: project, target_project: project, title: 'this is my created issuable') }
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 330310eae6b..574bbe0e0e1 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Group milestones', :feature, :js do
+feature 'Group milestones', :js do
let(:group) { create(:group) }
let!(:project) { create(:project_empty_repo, group: group) }
let(:user) { create(:group_member, :master, user: create(:user), group: group ).user }
@@ -8,7 +8,7 @@ feature 'Group milestones', :feature, :js do
before do
Timecop.freeze
- gitlab_sign_in(user)
+ sign_in(user)
end
after do
@@ -33,4 +33,32 @@ feature 'Group milestones', :feature, :js do
expect(find('.start_date')).to have_content(Date.today.at_beginning_of_month.strftime('%b %-d, %Y'))
end
end
+
+ context 'milestones list' do
+ let!(:other_project) { create(:project_empty_repo, group: group) }
+
+ let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
+ let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') }
+ let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
+ let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
+ let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
+
+ before do
+ visit group_milestones_path(group)
+ end
+
+ it 'counts milestones correctly' do
+ expect(find('.top-area .active .badge').text).to eq("2")
+ expect(find('.top-area .closed .badge').text).to eq("2")
+ expect(find('.top-area .all .badge').text).to eq("4")
+ end
+
+ it 'lists legacy group milestones and group milestones' do
+ legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
+
+ expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
+ expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
+ end
+ end
end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 76575f61528..303013e59d5 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -1,13 +1,16 @@
require 'spec_helper'
-feature 'Group show page', feature: true do
+feature 'Group show page' do
let(:group) { create(:group) }
let(:path) { group_path(group) }
context 'when signed in' do
+ let(:user) do
+ create(:group_member, :developer, user: create(:user), group: group ).user
+ end
+
before do
- user = create(:group_member, :developer, user: create(:user), group: group ).user
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index c1dc7be7088..e59a484d992 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-feature 'Group', feature: true do
+feature 'Group' do
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
matcher :have_namespace_error_message do
@@ -108,8 +108,8 @@ feature 'Group', feature: true do
before do
group.add_owner(user)
- gitlab_sign_out
- gitlab_sign_in(user)
+ sign_out(:user)
+ sign_in(user)
visit subgroups_group_path(group)
click_link 'New Subgroup'
@@ -128,8 +128,8 @@ feature 'Group', feature: true do
it 'checks permissions to avoid exposing groups by parent_id' do
group = create(:group, :private, path: 'secret-group')
- gitlab_sign_out
- gitlab_sign_in(:user)
+ sign_out(:user)
+ sign_in(create(:user))
visit new_group_path(parent_id: group.id)
expect(page).not_to have_content('secret-group')
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index b01ee1cf491..bd4f233cba9 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Help Pages', feature: true do
+describe 'Help Pages' do
describe 'Get the main help page' do
shared_examples_for 'help page' do |prefix: ''|
it 'prefixes links correctly' do
@@ -40,7 +40,7 @@ describe 'Help Pages', feature: true do
allow_any_instance_of(ApplicationSetting).to receive(:version_check_enabled) { true }
allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' }
- gitlab_sign_in :user
+ sign_in(create(:user))
visit help_path
end
@@ -60,7 +60,7 @@ describe 'Help Pages', feature: true do
allow_any_instance_of(ApplicationSetting).to receive(:help_page_text) { "My Custom Text" }
allow_any_instance_of(ApplicationSetting).to receive(:help_page_support_url) { "http://example.com/help" }
- gitlab_sign_in(:user)
+ sign_in(create(:user))
visit help_path
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
new file mode 100644
index 00000000000..cf1f0624140
--- /dev/null
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+
+describe 'Issuables Close/Reopen/Report toggle' do
+ let(:user) { create(:user) }
+
+ shared_examples 'an issuable close/reopen/report toggle' do
+ let(:container) { find('.issuable-close-dropdown') }
+ let(:human_model_name) { issuable.model_name.human.downcase }
+
+ it 'shows toggle' do
+ expect(page).to have_link("Close #{human_model_name}")
+ expect(page).to have_selector('.issuable-close-dropdown')
+ end
+
+ it 'opens a dropdown when toggle is clicked' do
+ container.find('.dropdown-toggle').click
+
+ expect(container).to have_selector('.dropdown-menu')
+ expect(container).to have_content("Close #{human_model_name}")
+ expect(container).to have_content('Report abuse')
+ expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.")
+ expect(container).to have_selector('.close-item.droplab-item-selected')
+ expect(container).to have_selector('.report-item')
+ expect(container).not_to have_selector('.report-item.droplab-item-selected')
+ expect(container).not_to have_selector('.reopen-item')
+ end
+
+ it 'changes the button when an item is selected' do
+ button = container.find('.issuable-close-button')
+
+ container.find('.dropdown-toggle').click
+ container.find('.report-item').click
+
+ expect(container).not_to have_selector('.dropdown-menu')
+ expect(button).to have_content('Report abuse')
+
+ container.find('.dropdown-toggle').click
+ container.find('.close-item').click
+
+ expect(button).to have_content("Close #{human_model_name}")
+ end
+ end
+
+ context 'on an issue' do
+ let(:project) { create(:empty_project) }
+ let(:issuable) { create(:issue, project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ context 'when user has permission to update', :js do
+ before do
+ visit project_issue_path(project, issuable)
+ end
+
+ it_behaves_like 'an issuable close/reopen/report toggle'
+ end
+
+ context 'when user doesnt have permission to update' do
+ let(:cant_project) { create(:empty_project) }
+ let(:cant_issuable) { create(:issue, project: cant_project) }
+
+ before do
+ cant_project.add_guest(user)
+
+ visit project_issue_path(cant_project, cant_issuable)
+ end
+
+ it 'only shows the `Report abuse` and `New issue` buttons' do
+ expect(page).to have_link('Report abuse')
+ expect(page).to have_link('New issue')
+ expect(page).not_to have_link('Close issue')
+ expect(page).not_to have_link('Reopen issue')
+ expect(page).not_to have_link('Edit')
+ end
+ end
+ end
+
+ context 'on a merge request' do
+ let(:project) { create(:project) }
+ let(:issuable) { create(:merge_request, source_project: project) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ context 'when user has permission to update', :js do
+ before do
+ visit project_merge_request_path(project, issuable)
+ end
+
+ it_behaves_like 'an issuable close/reopen/report toggle'
+ end
+
+ context 'when user doesnt have permission to update' do
+ let(:cant_project) { create(:project) }
+ let(:cant_issuable) { create(:merge_request, source_project: cant_project) }
+
+ before do
+ cant_project.add_reporter(user)
+
+ visit project_merge_request_path(cant_project, cant_issuable)
+ end
+
+ it 'only shows a `Report abuse` button' do
+ expect(page).to have_link('Report abuse')
+ expect(page).not_to have_link('Close merge request')
+ expect(page).not_to have_link('Reopen merge request')
+ expect(page).not_to have_link('Edit')
+ end
+ end
+ end
+end
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
index 56c9b10e757..7c20c96528e 100644
--- a/spec/features/issuables/default_sort_order_spec.rb
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Issuables > Default sort order', feature: true do
+describe 'Projects > Issuables > Default sort order' do
let(:project) { create(:empty_project, :public) }
let(:first_created_issuable) { issuables.order_created_asc.first }
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index f3a5a8463d1..557de721222 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'issuable list', feature: true do
+describe 'issuable list' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -8,7 +8,7 @@ describe 'issuable list', feature: true do
before do
project.add_user(user, :developer)
- gitlab_sign_in(user)
+ sign_in(user)
issuable_types.each { |type| create_issuables(type) }
end
@@ -39,9 +39,9 @@ describe 'issuable list', feature: true do
def visit_issuable_list(issuable_type)
if issuable_type == :issue
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
else
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
end
diff --git a/spec/features/issuables/markdown_references_spec.rb b/spec/features/issuables/markdown_references_spec.rb
new file mode 100644
index 00000000000..169381d703a
--- /dev/null
+++ b/spec/features/issuables/markdown_references_spec.rb
@@ -0,0 +1,193 @@
+require 'rails_helper'
+
+describe 'Markdown References', :js do
+ let(:user) { create(:user) }
+ let(:actual_project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, target_project: actual_project, source_project: actual_project)}
+ let(:issue_actual_project) { create(:issue, project: actual_project) }
+ let!(:other_project) { create(:empty_project, :public) }
+ let!(:issue_other_project) { create(:issue, project: other_project) }
+ let(:issues) { [issue_actual_project, issue_other_project] }
+
+ def build_note
+ markdown = "Referencing internal issue #{issue_actual_project.to_reference}, " +
+ "cross-project #{issue_other_project.to_reference(actual_project)} external JIRA-5 " +
+ "and non existing #999"
+
+ page.within('#diff-notes-app') do
+ fill_in 'note_note', with: markdown
+ end
+ end
+
+ shared_examples 'correct references' do
+ before do
+ remotelink = double(:remotelink, all: [], build: double(save!: true))
+
+ stub_request(:get, "https://jira.example.com/rest/api/2/issue/JIRA-5")
+ stub_request(:post, "https://jira.example.com/rest/api/2/issue/JIRA-5/comment")
+ allow_any_instance_of(JIRA::Resource::Issue).to receive(:remotelink).and_return(remotelink)
+
+ sign_in(user)
+ visit merge_request_path(merge_request)
+ build_note
+ end
+
+ def links_expectations
+ issues.each do |issue|
+ if referenced_issues.include?(issue)
+ expect(page).to have_link(issue.to_reference, href: issue_path(issue))
+ else
+ expect(page).not_to have_link(issue.to_reference, href: issue_path(issue))
+ end
+ end
+
+ if jira_referenced
+ expect(page).to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5')
+ else
+ expect(page).not_to have_link('JIRA-5', href: 'https://jira.example.com/browse/JIRA-5')
+ end
+
+ expect(page).not_to have_link('#999')
+ end
+
+ it 'creates a link to the referenced issue on the preview' do
+ find('.js-md-preview-button').click
+ wait_for_requests
+
+ page.within('.md-preview-holder') do
+ links_expectations
+ end
+ end
+
+ it 'creates a link to the referenced issue after submit' do
+ click_button 'Comment'
+ wait_for_requests
+
+ page.within('#diff-notes-app') do
+ links_expectations
+ end
+ end
+
+ it 'creates a note on the referenced issues' do
+ click_button 'Comment'
+ wait_for_requests
+
+ if referenced_issues.include?(issue_actual_project)
+ visit issue_path(issue_actual_project)
+
+ page.within('#notes') do
+ expect(page).to have_content(
+ "#{user.to_reference} mentioned in merge request #{merge_request.to_reference}"
+ )
+ end
+ end
+
+ if referenced_issues.include?(issue_other_project)
+ visit issue_path(issue_other_project)
+
+ page.within('#notes') do
+ expect(page).to have_content(
+ "#{user.to_reference} mentioned in merge request #{merge_request.to_reference(other_project)}"
+ )
+ end
+ end
+ end
+ end
+
+ context 'when internal issues tracker is enabled for the other project' do
+ context 'when only internal issues tracker is enabled for the actual project' do
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project, issue_other_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+
+ context 'when both external and internal issues trackers are enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project, issue_other_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when only external issues tracker is enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_other_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when no tracker is enabled for the actual project' do
+ before do
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_other_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+ end
+
+ context 'when internal issues tracker is disabled for the other project' do
+ before do
+ other_project.issues_enabled = false
+ other_project.save!
+ end
+
+ context 'when only internal issues tracker is enabled for the actual project' do
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project] }
+ let(:jira_referenced) { false }
+ end
+ end
+
+ context 'when both external and internal issues trackers are enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [issue_actual_project] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when only external issues tracker is enabled for the actual project' do
+ before do
+ create(:jira_service, project: actual_project)
+
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [] }
+ let(:jira_referenced) { true }
+ end
+ end
+
+ context 'when no issues tracker is enabled for the actual project' do
+ before do
+ actual_project.issues_enabled = false
+ actual_project.save!
+ end
+
+ include_examples 'correct references' do
+ let(:referenced_issues) { [] }
+ let(:jira_referenced) { false }
+ end
+ end
+ end
+end
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
index 4d7a7dc1806..948d151a517 100644
--- a/spec/features/issuables/user_sees_sidebar_spec.rb
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -14,7 +14,7 @@ describe 'Issue Sidebar on Mobile' do
context 'mobile sidebar on merge requests', js: true do
before do
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
it_behaves_like "issue sidebar stays collapsed on mobile"
@@ -22,7 +22,7 @@ describe 'Issue Sidebar on Mobile' do
context 'mobile sidebar on issues', js: true do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like "issue sidebar stays collapsed on mobile"
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 6698e2c79a1..134e618feac 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Awards Emoji', feature: true do
+describe 'Awards Emoji' do
let!(:project) { create(:project, :public) }
let!(:user) { create(:user) }
let(:issue) do
@@ -12,14 +12,14 @@ describe 'Awards Emoji', feature: true do
context 'authorized user' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'visiting an issue with a legacy award emoji that is not valid anymore' do
before do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -33,7 +33,7 @@ describe 'Awards Emoji', feature: true do
let!(:note) { create(:note_on_issue, noteable: issue, project: issue.project, note: "Hello world") }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -97,7 +97,7 @@ describe 'Awards Emoji', feature: true do
context 'unauthorized user', js: true do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'has disabled emoji button' do
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index a1c97caea20..e95eb19f7d1 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-feature 'Issue awards', js: true, feature: true do
+feature 'Issue awards', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
describe 'logged in' do
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
wait_for_requests
end
@@ -17,7 +17,7 @@ feature 'Issue awards', js: true, feature: true do
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(first('.js-emoji-btn')).to have_content '1'
end
@@ -26,7 +26,7 @@ feature 'Issue awards', js: true, feature: true do
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(first('.js-emoji-btn')).to have_content '0'
end
@@ -40,7 +40,7 @@ feature 'Issue awards', js: true, feature: true do
describe 'logged out' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
wait_for_requests
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index a99c19cb787..847e3856ba5 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Issues > Labels bulk assignment', feature: true do
+feature 'Issues > Labels bulk assignment' do
let(:user) { create(:user) }
let!(:project) { create(:project) }
let!(:issue1) { create(:issue, project: project, title: "Issue 1") }
@@ -13,7 +13,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'sidebar' do
@@ -346,9 +346,9 @@ feature 'Issues > Labels bulk assignment', feature: true do
context 'as a guest' do
before do
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
context 'cannot bulk assign labels' do
@@ -410,7 +410,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
def enable_bulk_update
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index aa538803dd8..88e81d12602 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js: true do
+feature 'Create Branch/Merge Request Dropdown on issue page', js: true do
let(:user) { create(:user) }
let!(:project) { create(:project) }
let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') }
@@ -8,11 +8,11 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
context 'for team members' do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'allows creating a merge request from the issue page' do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
select_dropdown_option('create-mr')
@@ -21,21 +21,21 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
expect(page).to have_content("created branch 1-cherry-coloured-funk")
expect(page).to have_content("mentioned in merge request !1")
- visit namespace_project_merge_request_path(project.namespace, project, MergeRequest.first)
+ visit project_merge_request_path(project, MergeRequest.first)
expect(page).to have_content('WIP: Resolve "Cherry-Coloured Funk"')
- expect(current_path).to eq(namespace_project_merge_request_path(project.namespace, project, MergeRequest.first))
+ expect(current_path).to eq(project_merge_request_path(project, MergeRequest.first))
end
it 'allows creating a branch from the issue page' do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
select_dropdown_option('create-branch')
wait_for_requests
expect(page).to have_selector('.dropdown-toggle-text ', text: '1-cherry-coloured-funk')
- expect(current_path).to eq namespace_project_tree_path(project.namespace, project, '1-cherry-coloured-funk')
+ expect(current_path).to eq project_tree_path(project, '1-cherry-coloured-funk')
end
context "when there is a referenced merge request" do
@@ -52,7 +52,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
before do
referenced_mr.cache_merge_request_closes_issues!(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'disables the create branch button' do
@@ -66,7 +66,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
it 'disables the create branch button' do
issue = create(:issue, :confidential, project: project)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).not_to have_css('.create-mr-dropdown-wrap')
end
@@ -75,7 +75,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
context 'for visitors' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'shows no buttons' do
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index 5f631043e15..3c51e8053da 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Resolving all open discussions in a merge request from an issue', feature: true, js: true do
+feature 'Resolving all open discussions in a merge request from an issue', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -9,13 +9,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
describe 'as a user with access to the project' do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in user
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a button to resolve all discussions by creating a new issue' do
within('#resolve-count-app') do
- expect(page).to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
@@ -25,13 +25,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
end
it 'hides the link for creating a new issue' do
- expect(page).not_to have_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).not_to have_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
context 'creating an issue for discussions' do
before do
- click_link "Resolve all discussions in new issue", href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ click_link "Resolve all discussions in new issue", href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it_behaves_like 'creating an issue for a discussion'
@@ -45,7 +45,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
context 'with the internal tracker disabled' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to create a new issue' do
@@ -55,7 +55,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
context 'merge request has discussions that need to be resolved' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a warning that the merge request contains unresolved discussions' do
@@ -64,13 +64,13 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
it 'has a link to resolve all discussions by creating an issue' do
page.within '.mr-widget-body' do
- expect(page).to have_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ expect(page).to have_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
end
context 'creating an issue for discussions' do
before do
- page.click_link 'Create an issue to resolve them later', href: new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ page.click_link 'Create an issue to resolve them later', href: new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it_behaves_like 'creating an issue for a discussion'
@@ -82,8 +82,8 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
describe 'as a reporter' do
before do
project.team << [user, :reporter]
- gitlab_sign_in user
- visit new_namespace_project_issue_path(project.namespace, project, merge_request_to_resolve_discussions_of: merge_request.iid)
+ sign_in user
+ visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it 'Shows a notice to ask someone else to resolve the discussions' do
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index 9e9e214060f..d468c7d9c75 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Resolve an open discussion in a merge request by creating an issue', feature: true do
+feature 'Resolve an open discussion in a merge request by creating an issue' do
let(:user) { create(:user) }
let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -9,14 +9,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
describe 'As a user with access to the project' do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in user
+ visit project_merge_request_path(project, merge_request)
end
context 'with the internal tracker disabled' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to create a new issue' do
@@ -43,14 +43,14 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
end
it 'has a link to create a new issue for a discussion' do
- new_issue_link = new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+ new_issue_link = new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
expect(page).to have_link 'Resolve this discussion in a new issue', href: new_issue_link
end
context 'creating the issue' do
before do
- click_link 'Resolve this discussion in a new issue', href: new_namespace_project_issue_path(project.namespace, project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
+ click_link 'Resolve this discussion in a new issue', href: new_project_issue_path(project, discussion_to_resolve: discussion.id, merge_request_to_resolve_discussions_of: merge_request.iid)
end
it 'has a hidden field for the discussion' do
@@ -66,10 +66,9 @@ feature 'Resolve an open discussion in a merge request by creating an issue', fe
describe 'as a reporter' do
before do
project.team << [user, :reporter]
- gitlab_sign_in user
- visit new_namespace_project_issue_path(project.namespace, project,
- merge_request_to_resolve_discussions_of: merge_request.iid,
- discussion_to_resolve: discussion.id)
+ sign_in user
+ visit new_project_issue_path(project, merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id)
end
it 'Shows a notice to ask someone else to resolve the discussions' do
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 96f6739af2d..a403d885de0 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Dropdown assignee', :feature, :js do
+describe 'Dropdown assignee', :js do
include FilteredSearchHelpers
let!(:project) { create(:empty_project) }
@@ -23,10 +23,10 @@ describe 'Dropdown assignee', :feature, :js do
project.team << [user, :master]
project.team << [user_john, :master]
project.team << [user_jacob, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 5ee824c662a..b7d9bbd7e1d 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Dropdown author', js: true, feature: true do
+describe 'Dropdown author', js: true do
include FilteredSearchHelpers
let!(:project) { create(:empty_project) }
@@ -31,10 +31,10 @@ describe 'Dropdown author', js: true, feature: true do
project.team << [user, :master]
project.team << [user_john, :master]
project.team << [user_jacob, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index a05e4394ffd..292fd683271 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Dropdown hint', :js, :feature do
+describe 'Dropdown hint', :js do
include FilteredSearchHelpers
let!(:project) { create(:empty_project) }
@@ -14,10 +14,10 @@ describe 'Dropdown hint', :js, :feature do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index aec9d7ceb5d..e8f005d7752 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Dropdown label', js: true, feature: true do
+describe 'Dropdown label', js: true do
include FilteredSearchHelpers
let(:project) { create(:empty_project) }
@@ -34,10 +34,10 @@ describe 'Dropdown label', js: true, feature: true do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'keyboard navigation' do
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index b21f41946b7..ace73f4b1a6 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Dropdown milestone', :feature, :js do
+describe 'Dropdown milestone', :js do
include FilteredSearchHelpers
let!(:project) { create(:empty_project) }
@@ -30,10 +30,10 @@ describe 'Dropdown milestone', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'behavior' do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 4cb728cc82b..cd2cbf4bfe7 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Filter issues', js: true, feature: true do
+describe 'Filter issues', js: true do
include Devise::Test::IntegrationHelpers
include FilteredSearchHelpers
@@ -89,7 +89,7 @@ describe 'Filter issues', js: true, feature: true do
milestone: future_milestone,
project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'filter issues by author' do
@@ -804,7 +804,7 @@ describe 'Filter issues', js: true, feature: true do
describe 'RSS feeds' do
it 'updates atom feed link for project issues' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id)
+ visit project_issues_path(project, milestone_title: milestone.title, assignee_id: user.id)
link = find_link('Subscribe')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
@@ -836,7 +836,7 @@ describe 'Filter issues', js: true, feature: true do
context 'URL has a trailing slash' do
before do
- visit "#{namespace_project_issues_path(project.namespace, project)}/"
+ visit "#{project_issues_path(project)}/"
end
it 'milestone dropdown loads milestones' do
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 09f228bcf49..5842bb22beb 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Recent searches', js: true, feature: true do
+describe 'Recent searches', js: true do
include FilteredSearchHelpers
let(:project_1) { create(:empty_project, :public) }
@@ -22,7 +22,7 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searching adds to recent searches' do
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
@@ -35,8 +35,8 @@ describe 'Recent searches', js: true, feature: true do
end
it 'visiting URL with search params adds to recent searches' do
- visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'foo', search: 'bar')
- visit namespace_project_issues_path(project_1.namespace, project_1, label_name: 'qux', search: 'garply')
+ visit project_issues_path(project_1, label_name: 'foo', search: 'bar')
+ visit project_issues_path(project_1, label_name: 'qux', search: 'garply')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -48,7 +48,7 @@ describe 'Recent searches', js: true, feature: true do
it 'saved recent searches are restored last on the list' do
set_recent_searches(project_1_local_storage_key, '["saved1", "saved2"]')
- visit namespace_project_issues_path(project_1.namespace, project_1, search: 'foo')
+ visit project_issues_path(project_1, search: 'foo')
items = all('.filtered-search-history-dropdown-item', visible: false)
@@ -59,12 +59,12 @@ describe 'Recent searches', js: true, feature: true do
end
it 'searches are scoped to projects' do
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
- visit namespace_project_issues_path(project_2.namespace, project_2)
+ visit project_issues_path(project_2)
input_filtered_search('more', submit: true)
input_filtered_search('things', submit: true)
@@ -78,7 +78,7 @@ describe 'Recent searches', js: true, feature: true do
it 'clicking item fills search input' do
set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
wait_for_filtered_search('foo')
@@ -88,7 +88,7 @@ describe 'Recent searches', js: true, feature: true do
it 'clear recent searches button, clears recent searches' do
set_recent_searches(project_1_local_storage_key, '["foo"]')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
items_before = all('.filtered-search-history-dropdown-item', visible: false)
@@ -102,7 +102,7 @@ describe 'Recent searches', js: true, feature: true do
it 'shows flash error when failed to parse saved history' do
set_recent_searches(project_1_local_storage_key, 'fail')
- visit namespace_project_issues_path(project_1.namespace, project_1)
+ visit project_issues_path(project_1)
expect(find('.flash-alert')).to have_text('An error occured while parsing recent searches')
end
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 806c732b935..115875d72ce 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Search bar', js: true, feature: true do
+describe 'Search bar', js: true do
include FilteredSearchHelpers
let!(:project) { create(:empty_project) }
@@ -9,10 +9,10 @@ describe 'Search bar', js: true, feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
def get_left_style(style)
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 22488f34813..d00d0a9c81b 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Visual tokens', js: true, feature: true do
+describe 'Visual tokens', js: true do
include FilteredSearchHelpers
include WaitForRequests
@@ -25,10 +25,10 @@ describe 'Visual tokens', js: true, feature: true do
before do
project.add_user(user, :master)
project.add_user(user_rock, :master)
- gitlab_sign_in(user)
+ sign_in(user)
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
end
describe 'editing author token' do
@@ -133,7 +133,7 @@ describe 'Visual tokens', js: true, feature: true do
describe 'editing milestone token' do
before do
input_filtered_search('milestone:%10.0 author:none', submit: false)
- first('.tokens-container .filtered-search-token').double_click
+ first('.tokens-container .filtered-search-token').click
first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item')
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 58f6bd277e4..b56b8744d7e 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
-describe 'New/edit issue', :feature, :js do
- include GitlabRoutingHelper
+describe 'New/edit issue', :js do
include ActionView::Helpers::JavaScriptHelper
include FormHelper
@@ -16,15 +15,15 @@ describe 'New/edit issue', :feature, :js do
before do
project.team << [user, :master]
project.team << [user2, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new issue' do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
- describe 'shorten users API pagination limit (CE)' do
+ describe 'shorten users API pagination limit' do
before do
# Using `allow_any_instance_of`/`and_wrap_original`, `original` would
# somehow refer to the very block we defined to _wrap_ that method, instead of
@@ -39,7 +38,7 @@ describe 'New/edit issue', :feature, :js do
options
end
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
click_button 'Unassigned'
@@ -64,7 +63,7 @@ describe 'New/edit issue', :feature, :js do
end
end
- describe 'single assignee (CE)' do
+ describe 'single assignee' do
before do
click_button 'Unassigned'
@@ -221,7 +220,7 @@ describe 'New/edit issue', :feature, :js do
context 'edit issue' do
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
end
it 'allows user to update issue' do
@@ -282,7 +281,7 @@ describe 'New/edit issue', :feature, :js do
before do
sub_group_project.add_master(user)
- visit new_namespace_project_issue_path(sub_group_project.namespace, sub_group_project)
+ visit new_project_issue_path(sub_group_project)
end
it 'creates new label from dropdown' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index e61eb5233d0..b84635c5134 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'GFM autocomplete', feature: true, js: true do
+feature 'GFM autocomplete', js: true do
let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
@@ -8,12 +8,24 @@ feature 'GFM autocomplete', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
wait_for_requests
end
+ it 'updates issue descripton with GFM reference' do
+ find('.issuable-edit').click
+
+ find('#issue-description').native.send_keys("@#{user.name[0...3]}")
+
+ find('.atwho-view .cur').trigger('click')
+
+ click_button 'Save changes'
+
+ expect(find('.description')).to have_content(user.to_reference)
+ end
+
it 'opens autocomplete menu when field starts with text' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index fc8515cfe9b..a8ac1d605cb 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -1,18 +1,14 @@
require 'rails_helper'
-describe 'Group label on issue', :feature do
+describe 'Group label on issue' do
it 'renders link to the project issues page' do
group = create(:group)
project = create(:empty_project, :public, namespace: group)
feature = create(:group_label, group: group, title: 'feature')
issue = create(:labeled_issue, project: project, labels: [feature])
- label_link = namespace_project_issues_path(
- project.namespace,
- project,
- label_name: [feature.name]
- )
+ label_link = project_issues_path(project, label_name: [feature.name])
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
link = find('.issuable-show-labels a')
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
new file mode 100644
index 00000000000..28b636f9359
--- /dev/null
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+feature 'Issue Detail', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project, author: user) }
+
+ context 'when user displays the issue' do
+ before do
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'shows the issue' do
+ page.within('.issuable-details') do
+ expect(find('h2')).to have_content(issue.title)
+ end
+ end
+ end
+
+ context 'when edited by a user who is later deleted' do
+ before do
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+
+ click_link 'Edit'
+ fill_in 'issue-title', with: 'issue title'
+ click_button 'Save'
+
+ visit profile_account_path
+ click_link 'Delete account'
+
+ visit project_issue_path(project, issue)
+ end
+
+ it 'shows the issue' do
+ page.within('.issuable-details') do
+ expect(find('h2')).to have_content(issue.reload.title)
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 09724781a27..8e22441e0e8 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Issue Sidebar', feature: true do
+feature 'Issue Sidebar' do
include MobileHelpers
let(:group) { create(:group, :nested) }
@@ -10,7 +10,7 @@ feature 'Issue Sidebar', feature: true do
let!(:label) { create(:label, project: project, title: 'bug') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'assignee', js: true do
@@ -166,7 +166,7 @@ feature 'Issue Sidebar', feature: true do
end
def visit_issue(project, issue)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
def open_issue_sidebar
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index 66d823ec9d0..6aed27e8790 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-feature 'Issue markdown toolbar', feature: true, js: true do
+feature 'Issue markdown toolbar', js: true do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it "doesn't include first new line when adding bold" do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index d6e78157e5f..833eb47efb2 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -9,7 +9,7 @@ feature 'issue move to another project' do
create(:issue, description: text, project: old_project, author: user)
end
- background { gitlab_sign_in(user) }
+ background { sign_in(user) }
context 'user does not have permission to move issue' do
background do
@@ -95,10 +95,6 @@ feature 'issue move to another project' do
end
def issue_path(issue)
- namespace_project_issue_path(issue.project.namespace, issue.project, issue)
- end
-
- def project_path(project)
- namespace_project_path(new_project.namespace, new_project)
+ project_issue_path(issue.project, issue)
end
end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index bd31e44ef33..b524260750e 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Issue notes polling', :feature, :js do
+feature 'Issue notes polling', :js do
include NoteInteractionHelpers
let(:project) { create(:empty_project, :public) }
@@ -8,7 +8,7 @@ feature 'Issue notes polling', :feature, :js do
describe 'creates' do
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'displays the new comment' do
@@ -27,8 +27,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:existing_note) { create(:note, noteable: issue, project: project, author: user, note: note_text) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
@@ -93,8 +93,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:existing_note) { create(:note, noteable: issue, project: project, author: user1, note: note_text) }
before do
- gitlab_sign_in(user2)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user2)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
@@ -114,8 +114,8 @@ feature 'Issue notes polling', :feature, :js do
let!(:system_note) { create(:system_note, noteable: issue, project: project, author: user, note: note_text) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'has .original-note-content to compare against' do
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index f648295416f..be4c23645d9 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Create notes on issues', :js, :feature do
+describe 'Create notes on issues', :js do
let(:user) { create(:user) }
shared_examples 'notes with reference' do
@@ -9,8 +9,8 @@ describe 'Create notes on issues', :js, :feature do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
fill_in 'note[note]', with: note_text
click_button 'Comment'
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 57c783790b5..332ce78b138 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'New issue', feature: true, js: true do
+describe 'New issue', js: true do
include StubENV
let(:project) { create(:project, :public) }
@@ -18,14 +18,14 @@ describe 'New issue', feature: true, js: true do
)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when identified as a spam' do
before do
WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: "true", status: 200)
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'creates an issue after solving reCaptcha' do
@@ -50,7 +50,7 @@ describe 'New issue', feature: true, js: true do
before do
WebMock.stub_request(:any, /.*akismet.com.*/).to_return(body: 'false', status: 200)
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'creates an issue' do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index a1c00dd64f6..7ca5ef4649a 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-feature 'Manually create a todo item from issue', feature: true, js: true do
+feature 'Manually create a todo item from issue', js: true do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
it 'creates todo when clicking button' do
@@ -21,7 +21,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
expect(page).to have_content '1'
end
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within '.header-content .todos-count' do
expect(page).to have_content '1'
@@ -36,7 +36,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
expect(page).to have_selector('.todos-count', visible: false)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_selector('.todos-count', visible: false)
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index df704b55839..5a7c4f54cb6 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -12,7 +12,7 @@ feature 'Multiple issue updating from issues#index', :js do
context 'status' do
it 'sets to closed' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -25,7 +25,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'sets to open' do
create_closed
- visit namespace_project_issues_path(project.namespace, project, state: 'closed')
+ visit project_issues_path(project, state: 'closed')
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -39,7 +39,7 @@ feature 'Multiple issue updating from issues#index', :js do
context 'assignee' do
it 'updates to current user' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -55,7 +55,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'updates to unassigned' do
create_assigned
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -71,7 +71,7 @@ feature 'Multiple issue updating from issues#index', :js do
let!(:milestone) { create(:milestone, project: project) }
it 'updates milestone' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button 'Edit Issues'
find('#check-all-issues').click
@@ -85,7 +85,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'sets to no milestone' do
create_with_milestone
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(first('.issue')).to have_content milestone.title
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 168cdd08137..4b63cc844f3 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Issues > User uses quick actions', feature: true, js: true do
+feature 'Issues > User uses quick actions', js: true do
include QuickActionsHelpers
it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
@@ -13,14 +13,24 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
after do
wait_for_requests
end
+ describe 'time tracking' do
+ let(:issue) { create(:issue, project: project) }
+
+ before do
+ visit project_issue_path(project, issue)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) }
@@ -42,8 +52,8 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(guest)
+ visit project_issue_path(project, issue)
end
it 'does not create a note, and sets the due date accordingly' do
@@ -82,8 +92,8 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
before do
project.team << [guest, :guest]
gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(guest)
+ visit project_issue_path(project, issue)
end
it 'does not create a note, and sets the due date accordingly' do
@@ -99,65 +109,50 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
end
end
- describe 'Issuable time tracking' do
+ describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) }
- before do
- project.team << [user, :developer]
- end
-
- context 'Issue' do
- before do
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
- context 'Merge Request' do
- let(:merge_request) { create(:merge_request, source_project: project) }
-
- before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- end
+ it 'does not recognize the command nor create a note' do
+ write_note("/wip")
- it_behaves_like 'issuable time tracker'
+ expect(page).not_to have_content '/wip'
end
end
- describe 'Issuable time tracking' do
+ describe 'mark issue as duplicate' do
let(:issue) { create(:issue, project: project) }
+ let(:original_issue) { create(:issue, project: project) }
- before do
- project.team << [user, :developer]
- end
+ context 'when the current user can update issues' do
+ it 'does not create a note, and marks the issue as a duplicate' do
+ write_note("/duplicate ##{original_issue.to_reference}")
- context 'Issue' do
- before do
- visit namespace_project_issue_path(project.namespace, project, issue)
- end
+ expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
- it_behaves_like 'issuable time tracker'
+ expect(issue.reload).to be_closed
+ end
end
- context 'Merge Request' do
- let(:merge_request) { create(:merge_request, source_project: project) }
-
+ context 'when the current user cannot update the issue' do
+ let(:guest) { create(:user) }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ project.team << [guest, :guest]
+ gitlab_sign_out
+ sign_in(guest)
+ visit project_issue_path(project, issue)
end
- it_behaves_like 'issuable time tracker'
- end
- end
-
- describe 'toggling the WIP prefix from the title from note' do
- let(:issue) { create(:issue, project: project) }
+ it 'does not create a note, and does not mark the issue as a duplicate' do
+ write_note("/duplicate ##{original_issue.to_reference}")
- it 'does not recognize the command nor create a note' do
- write_note("/wip")
+ expect(page).to have_content "/duplicate ##{original_issue.to_reference}"
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
- expect(page).not_to have_content '/wip'
+ expect(issue.reload).to be_open
+ end
end
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index f47b89fd718..722237481ea 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Issues', feature: true do
+describe 'Issues' do
include DropzoneHelper
include IssueHelpers
include SortingHelper
@@ -24,7 +24,7 @@ describe 'Issues', feature: true do
end
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
find('.js-zen-enter').click
end
@@ -42,7 +42,7 @@ describe 'Issues', feature: true do
end
it 'allows user to select unassigned', js: true do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
expect(page).to have_content "Assignee #{user.name}"
@@ -62,7 +62,7 @@ describe 'Issues', feature: true do
describe 'due date', js: true do
context 'on new form' do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'saves with due date' do
@@ -90,7 +90,7 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, author: user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
before do
- visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ visit edit_project_issue_path(project, issue)
end
it 'saves with due date' do
@@ -135,7 +135,7 @@ describe 'Issues', feature: true do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'foobar')
create(:award_emoji, awardable: issue)
- visit namespace_project_issues_path(project.namespace, project, assignee_id: user.id)
+ visit project_issues_path(project, assignee_id: user.id)
expect(page).to have_content 'foobar'
expect(page.all('.no-comments').first.text).to eq "0"
@@ -161,7 +161,7 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
it 'allows filtering by issues with no specified assignee' do
- visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
+ visit project_issues_path(project, assignee_id: IssuableFinder::NONE)
expect(page).to have_content 'foobar'
expect(page).not_to have_content 'barbaz'
@@ -169,7 +169,7 @@ describe 'Issues', feature: true do
end
it 'allows filtering by a specified assignee' do
- visit namespace_project_issues_path(project.namespace, project, assignee_id: user.id)
+ visit project_issues_path(project, assignee_id: user.id)
expect(page).not_to have_content 'foobar'
expect(page).to have_content 'barbaz'
@@ -190,14 +190,14 @@ describe 'Issues', feature: true do
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
it 'sorts by newest' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
+ visit project_issues_path(project, sort: sort_value_recently_created)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
it 'sorts by oldest' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
+ visit project_issues_path(project, sort: sort_value_oldest_created)
expect(first_issue).to include('baz')
expect(last_issue).to include('foo')
@@ -206,7 +206,7 @@ describe 'Issues', feature: true do
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_updated)
+ visit project_issues_path(project, sort: sort_value_recently_updated)
expect(first_issue).to include('baz')
end
@@ -214,7 +214,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently updated' do
baz.updated_at = Time.now - 100
baz.save
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_updated)
+ visit project_issues_path(project, sort: sort_value_oldest_updated)
expect(first_issue).to include('baz')
end
@@ -226,13 +226,13 @@ describe 'Issues', feature: true do
end
it 'sorts by recently due date' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_soon)
+ visit project_issues_path(project, sort: sort_value_due_date_soon)
expect(first_issue).to include('foo')
end
it 'sorts by least recently due date' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+ visit project_issues_path(project, sort: sort_value_due_date_later)
expect(first_issue).to include('bar')
end
@@ -240,7 +240,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently due date by excluding nil due dates' do
bar.update(due_date: nil)
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_due_date_later)
+ visit project_issues_path(project, sort: sort_value_due_date_later)
expect(first_issue).to include('foo')
end
@@ -255,7 +255,7 @@ describe 'Issues', feature: true do
it 'sorts by least recently due date by excluding nil due dates' do
bar.update(due_date: nil)
- visit namespace_project_issues_path(project.namespace, project, label_names: [label.name], sort: sort_value_due_date_later)
+ visit project_issues_path(project, label_names: [label.name], sort: sort_value_due_date_later)
expect(first_issue).to include('foo')
end
@@ -269,7 +269,7 @@ describe 'Issues', feature: true do
end
it 'filters by none' do
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::NoDueDate.name)
+ visit project_issues_path(project, due_date: Issue::NoDueDate.name)
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
@@ -277,7 +277,7 @@ describe 'Issues', feature: true do
end
it 'filters by any' do
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::AnyDueDate.name)
+ visit project_issues_path(project, due_date: Issue::AnyDueDate.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -289,7 +289,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today.end_of_week)
baz.update(due_date: Date.today - 8.days)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisWeek.name)
+ visit project_issues_path(project, due_date: Issue::DueThisWeek.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -301,7 +301,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today.end_of_month)
baz.update(due_date: Date.today - 50.days)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::DueThisMonth.name)
+ visit project_issues_path(project, due_date: Issue::DueThisMonth.name)
expect(page).to have_content('foo')
expect(page).to have_content('bar')
@@ -313,7 +313,7 @@ describe 'Issues', feature: true do
bar.update(due_date: Date.today + 20.days)
baz.update(due_date: Date.yesterday)
- visit namespace_project_issues_path(project.namespace, project, due_date: Issue::Overdue.name)
+ visit project_issues_path(project, due_date: Issue::Overdue.name)
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
@@ -330,14 +330,14 @@ describe 'Issues', feature: true do
end
it 'sorts by recently due milestone' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon)
+ visit project_issues_path(project, sort: sort_value_milestone_soon)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
it 'sorts by least recently due milestone' do
- visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later)
+ visit project_issues_path(project, sort: sort_value_milestone_later)
expect(first_issue).to include('bar')
expect(last_issue).to include('baz')
@@ -355,7 +355,7 @@ describe 'Issues', feature: true do
end
it 'sorts with a filter applied' do
- visit namespace_project_issues_path(project.namespace, project,
+ visit project_issues_path(project,
sort: sort_value_oldest_created,
assignee_id: user2.id)
@@ -397,7 +397,7 @@ describe 'Issues', feature: true do
let!(:label) { create(:label, project: project) }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'will not send ajax request when no data is changed' do
@@ -416,7 +416,7 @@ describe 'Issues', feature: true do
context 'by authorized user' do
it 'allows user to select unassigned', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.assignee') do
expect(page).to have_content "#{user.name}"
@@ -435,7 +435,7 @@ describe 'Issues', feature: true do
it 'allows user to select an assignee', js: true do
issue2 = create(:issue, project: project, author: user)
- visit namespace_project_issue_path(project.namespace, project, issue2)
+ visit project_issue_path(project, issue2)
page.within('.assignee') do
expect(page).to have_content "No assignee"
@@ -456,7 +456,7 @@ describe 'Issues', feature: true do
it 'allows user to unselect themselves', js: true do
issue2 = create(:issue, project: project, author: user)
- visit namespace_project_issue_path(project.namespace, project, issue2)
+ visit project_issue_path(project, issue2)
page.within '.assignee' do
click_link 'Edit'
@@ -487,7 +487,7 @@ describe 'Issues', feature: true do
sign_out(:user)
sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_content issue.assignees.first.name
end
end
@@ -499,7 +499,7 @@ describe 'Issues', feature: true do
context 'by authorized user' do
it 'allows user to select unassigned', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.milestone') do
expect(page).to have_content "None"
@@ -517,7 +517,7 @@ describe 'Issues', feature: true do
end
it 'allows user to de-select milestone', js: true do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
page.within('.milestone') do
click_link 'Edit'
@@ -550,7 +550,7 @@ describe 'Issues', feature: true do
sign_out(:user)
sign_in(guest)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_content milestone.title
end
end
@@ -565,23 +565,21 @@ describe 'Issues', feature: true do
end
it 'redirects to signin then back to new issue after signin' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_link 'New issue'
expect(current_path).to eq new_user_session_path
- # NOTE: This is specifically testing the redirect after login, so we
- # need the full login flow
gitlab_sign_in(create(:user))
- expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project)
+ expect(current_path).to eq new_project_issue_path(project)
end
end
context 'dropzone upload file', js: true do
before do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
end
it 'uploads file when dragging into textarea' do
@@ -608,7 +606,7 @@ describe 'Issues', feature: true do
message: 'added issue template',
branch_name: 'master')
- visit new_namespace_project_issue_path(project.namespace, project, issuable_template: 'bug')
+ visit new_project_issue_path(project, issuable_template: 'bug')
end
it 'fills in template' do
@@ -625,7 +623,7 @@ describe 'Issues', feature: true do
project.issues << issue
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
click_button('Email a new issue')
end
@@ -654,7 +652,7 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
before do
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'adds due date to issue' do
@@ -698,7 +696,7 @@ describe 'Issues', feature: true do
it 'updates the title', js: true do
issue = create(:issue, author: user, assignees: [user], project: project, title: 'new title')
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
expect(page).to have_text("new title")
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index a8055b21cee..c9983f0941f 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Login', feature: true do
+feature 'Login' do
describe 'initial login after setup' do
it 'allows the initial admin to create a password' do
# This behavior is dependent on there only being one user
@@ -41,7 +41,7 @@ feature 'Login', feature: true do
expect(page).to have_content('Your account has been blocked.')
end
- it 'does not update Devise trackable attributes', :redis do
+ it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do
user = create(:user, :blocked)
expect { gitlab_sign_in(user) }.not_to change { user.reload.sign_in_count }
@@ -55,7 +55,7 @@ feature 'Login', feature: true do
expect(page).to have_content('Invalid Login or password.')
end
- it 'does not update Devise trackable attributes', :redis do
+ it 'does not update Devise trackable attributes', :clean_gitlab_redis_shared_state do
expect { gitlab_sign_in(User.ghost) }.not_to change { User.ghost.reload.sign_in_count }
end
end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 534be3ab5a7..b70d3060f05 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -24,7 +24,7 @@ require 'erb'
#
# See the MarkdownFeature class for setup details.
-describe 'GitLab Markdown', feature: true do
+describe 'GitLab Markdown' do
include Capybara::Node::Matchers
include MarkupHelper
include MarkdownMatchers
@@ -100,7 +100,7 @@ describe 'GitLab Markdown', feature: true do
end
it 'permits img elements' do
- expect(doc).to have_selector('img[src*="smile.png"]')
+ expect(doc).to have_selector('img[data-src*="smile.png"]')
end
it 'permits br elements' do
diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb
index cb835f533e0..04c2a694fff 100644
--- a/spec/features/merge_requests/assign_issues_spec.rb
+++ b/spec/features/merge_requests/assign_issues_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge request issue assignment', js: true, feature: true do
+feature 'Merge request issue assignment', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue1) { create(:issue, project: project) }
@@ -13,8 +13,8 @@ feature 'Merge request issue assignment', js: true, feature: true do
end
def visit_merge_request(current_user = nil)
- gitlab_sign_in(current_user || user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(current_user || user)
+ visit project_merge_request_path(project, merge_request)
end
context 'logged in as author' do
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
index e9dd755b6af..5a12d6f43f3 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_requests/award_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-feature 'Merge request awards', js: true, feature: true do
+feature 'Merge request awards', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
describe 'logged in' do
before do
- gitlab_sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
it 'adds award to merge request' do
@@ -16,7 +16,7 @@ feature 'Merge request awards', js: true, feature: true do
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(first('.js-emoji-btn')).to have_content '1'
end
@@ -25,7 +25,7 @@ feature 'Merge request awards', js: true, feature: true do
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(first('.js-emoji-btn')).to have_content '0'
end
@@ -39,7 +39,7 @@ feature 'Merge request awards', js: true, feature: true do
describe 'logged out' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not see award menu button' do
diff --git a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
index 060cfb8fdd1..c3d7d2fa23f 100644
--- a/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
+++ b/spec/features/merge_requests/check_if_mergeable_with_unresolved_discussions_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Check if mergeable with unresolved discussions', js: true, feature: true do
+feature 'Check if mergeable with unresolved discussions', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let!(:merge_request) { create(:merge_request_with_diff_notes, source_project: project, author: user) }
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
@@ -64,6 +64,6 @@ feature 'Check if mergeable with unresolved discussions', js: true, feature: tru
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb
index 6ba96570e3d..e4c33d57e8d 100644
--- a/spec/features/merge_requests/cherry_pick_spec.rb
+++ b/spec/features/merge_requests/cherry_pick_spec.rb
@@ -7,7 +7,7 @@ describe 'Cherry-pick Merge Requests', js: true do
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) }
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
@@ -28,7 +28,7 @@ describe 'Cherry-pick Merge Requests', js: true do
end
it "doesn't show a Cherry-pick button" do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link "Cherry-pick"
end
@@ -36,7 +36,7 @@ describe 'Cherry-pick Merge Requests', js: true do
context "With a merge commit" do
it "shows a Cherry-pick button" do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).to have_link "Cherry-pick"
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index 365b2555c35..24000ea75d1 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Merge Request closing issues message', feature: true, js: true do
+feature 'Merge Request closing issues message', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue_1) { create(:issue, project: project)}
@@ -20,9 +20,9 @@ feature 'Merge Request closing issues message', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
wait_for_requests
end
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 9c091befa27..3c5ddf8396d 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -1,9 +1,14 @@
require 'spec_helper'
-feature 'Merge request conflict resolution', js: true, feature: true do
+feature 'Merge request conflict resolution', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
+ before do
+ # In order to have the diffs collapsed, we need to disable the increase feature
+ stub_feature_flags(gitlab_git_diff_size_limit_increase: false)
+ end
+
def create_merge_request(source_branch)
create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr|
mr.mark_as_unmergeable
@@ -79,14 +84,14 @@ feature 'Merge request conflict resolution', js: true, feature: true do
context 'can be resolved in the UI' do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'the conflicts are resolvable' do
let(:merge_request) { create_merge_request('conflict-resolvable') }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows a link to the conflict resolution page' do
@@ -117,7 +122,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
let(:merge_request) { create_merge_request('conflict-contains-conflict-markers') }
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
click_link('conflicts', href: /\/conflicts\Z/)
end
@@ -164,9 +169,9 @@ feature 'Merge request conflict resolution', js: true, feature: true do
before do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show a link to the conflict resolution page' do
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 6a08e50bf5e..67322bdc8a8 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,17 +1,17 @@
require 'spec_helper'
-feature 'Create New Merge Request', feature: true, js: true do
+feature 'Create New Merge Request', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
it 'selects the source branch sha when a tag with the same name exists' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
click_link 'New merge request'
expect(page).to have_content('Source branch')
@@ -24,7 +24,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'selects the target branch sha when a tag with the same name exists' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
click_link 'New merge request'
@@ -38,7 +38,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'generates a diff for an orphaned branch' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
expect(page).to have_content('Source branch')
@@ -65,7 +65,7 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'does not leak the private project name & namespace' do
private_project = create(:project, :private)
- visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+ visit project_new_merge_request_path(project, merge_request: { target_project_id: private_project.id })
expect(page).not_to have_content private_project.path_with_namespace
expect(page).to have_content project.path_with_namespace
@@ -76,7 +76,7 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'does not leak the private project name & namespace' do
private_project = create(:project, :private)
- visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { source_project_id: private_project.id })
+ visit project_new_merge_request_path(project, merge_request: { source_project_id: private_project.id })
expect(page).not_to have_content private_project.path_with_namespace
expect(page).to have_content project.path_with_namespace
@@ -84,13 +84,13 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'populates source branch button' do
- visit namespace_project_new_merge_request_path(project.namespace, project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, change_branches: true, merge_request: { target_branch: 'master', source_branch: 'fix' })
expect(find('.js-source-branch')).to have_content('fix')
end
it 'allows to change the diff view' do
- visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'fix' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'fix' })
click_link 'Changes'
@@ -106,7 +106,7 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'does not allow non-existing branches' do
- visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'non-exist-target', source_branch: 'non-exist-source' })
expect(page).to have_content('The form contains the following errors')
expect(page).to have_content('Source branch "non-exist-source" does not exist')
@@ -115,7 +115,7 @@ feature 'Create New Merge Request', feature: true, js: true do
context 'when a branch contains commits that both delete and add the same image' do
it 'renders the diff successfully' do
- visit namespace_project_new_merge_request_path(project.namespace, project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
+ visit project_new_merge_request_path(project, merge_request: { target_branch: 'master', source_branch: 'deleted-image-test' })
click_link "Changes"
@@ -125,7 +125,7 @@ feature 'Create New Merge Request', feature: true, js: true do
# Isolates a regression (see #24627)
it 'does not show error messages on initial form' do
- visit namespace_project_new_merge_request_path(project.namespace, project)
+ visit project_new_merge_request_path(project)
expect(page).not_to have_selector('#error_explanation')
expect(page).not_to have_content('The form contains the following error')
end
@@ -138,8 +138,8 @@ feature 'Create New Merge Request', feature: true, js: true do
end
it 'shows pipelines for a new merge request' do
- visit namespace_project_new_merge_request_path(
- project.namespace, project,
+ visit project_new_merge_request_path(
+ project,
merge_request: { target_branch: 'master', source_branch: 'fix' })
page.within('.merge-request') do
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index 69059dfa562..9b7795ace62 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -16,7 +16,7 @@ feature 'Merge request created from fork' do
background do
fork_project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
scenario 'user can access merge request' do
@@ -64,7 +64,6 @@ feature 'Merge request created from fork' do
end
def visit_merge_request(mr)
- visit namespace_project_merge_request_path(project.namespace,
- project, mr)
+ visit project_merge_request_path(project, mr)
end
end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index f2af3198319..874c6e2ff69 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -3,19 +3,15 @@ require 'spec_helper'
# This test serves as a regression test for a bug that caused an error
# message to be shown by JavaScript when the source branch was deleted.
# Please do not remove "js: true".
-describe 'Deleted source branch', feature: true, js: true do
+describe 'Deleted source branch', js: true do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
before do
- gitlab_sign_in user
+ sign_in user
merge_request.project.team << [user, :master]
merge_request.update!(source_branch: 'this-branch-does-not-exist')
- visit namespace_project_merge_request_path(
- merge_request.project.namespace,
- merge_request.project,
- merge_request
- )
+ visit project_merge_request_path(merge_request.project, merge_request)
end
it 'shows a message about missing source branch' do
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 989dfb71d10..7f082f12d47 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Diff note avatars', feature: true, js: true do
+feature 'Diff note avatars', js: true do
include NoteInteractionHelpers
let(:user) { create(:user) }
@@ -20,12 +20,12 @@ feature 'Diff note avatars', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'discussion tab' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not show avatars on discussion tab' do
@@ -50,7 +50,7 @@ feature 'Diff note avatars', feature: true, js: true do
context 'commit view' do
before do
- visit namespace_project_commit_path(project.namespace, project, merge_request.commits.first.id)
+ visit project_commit_path(project, merge_request.commits.first.id)
end
it 'does not render avatar after commenting' do
@@ -65,7 +65,7 @@ feature 'Diff note avatars', feature: true, js: true do
wait_for_requests
end
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).to have_content('test comment')
expect(page).not_to have_selector('.js-avatar-container')
@@ -76,7 +76,7 @@ feature 'Diff note avatars', feature: true, js: true do
%w(inline parallel).each do |view|
context "#{view} view" do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+ visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
end
@@ -168,7 +168,7 @@ feature 'Diff note avatars', feature: true, js: true do
before do
create_list(:diff_note_on_merge_request, 3, project: project, noteable: merge_request, in_reply_to: note)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
+ visit diffs_project_merge_request_path(project, merge_request, view: view)
wait_for_requests
end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 0f8ca6f90d1..a2f0b24d6a8 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Diff notes resolve', feature: true, js: true do
+feature 'Diff notes resolve', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") }
@@ -19,7 +19,7 @@ feature 'Diff notes resolve', feature: true, js: true do
context 'no discussions' do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
note.destroy
visit_merge_request
end
@@ -33,7 +33,7 @@ feature 'Diff notes resolve', feature: true, js: true do
context 'as authorized user' do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
visit_merge_request
end
@@ -402,7 +402,7 @@ feature 'Diff notes resolve', feature: true, js: true do
before do
project.team << [guest, :guest]
- gitlab_sign_in guest
+ sign_in guest
end
context 'someone elses merge request' do
@@ -494,6 +494,6 @@ feature 'Diff notes resolve', feature: true, js: true do
def visit_merge_request(mr = nil)
mr = mr || merge_request
- visit namespace_project_merge_request_path(mr.project.namespace, mr.project, mr)
+ visit project_merge_request_path(mr.project, mr)
end
end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index cb6cd6571a8..499cd38e648 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Diffs URL', js: true, feature: true do
+feature 'Diffs URL', js: true do
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -12,7 +12,7 @@ feature 'Diffs URL', js: true, feature: true do
it 'renders the notes' do
create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master'
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
# Load notes and diff through AJAX
expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
@@ -26,7 +26,7 @@ feature 'Diffs URL', js: true, feature: true do
let(:fragment) { "#note_#{note.id}" }
before do
- visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
end
it 'shows expanded note' do
@@ -39,7 +39,7 @@ feature 'Diffs URL', js: true, feature: true do
let(:fragment) { "#note_#{note.id}" }
before do
- visit "#{diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment}"
+ visit "#{diffs_project_merge_request_path(project, merge_request)}#{fragment}"
end
it 'shows expanded note' do
@@ -52,7 +52,7 @@ feature 'Diffs URL', js: true, feature: true do
it 'displays warning' do
allow(Commit).to receive(:max_diff_options).and_return(max_files: 3)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
page.within('.alert') do
expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
@@ -74,8 +74,8 @@ feature 'Diffs URL', js: true, feature: true do
context 'as author' do
it 'shows direct edit link' do
- gitlab_sign_in(author_user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(author_user)
+ visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
expect(page).to have_selector("[id=\"#{changelog_id}\"] a.js-edit-blob")
@@ -84,8 +84,8 @@ feature 'Diffs URL', js: true, feature: true do
context 'as user who needs to fork' do
it 'shows fork/cancel confirmation' do
- gitlab_sign_in(user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
find("[id=\"#{changelog_id}\"] .js-edit-blob").click
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index 88ae257236c..d1cc43e0690 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-feature 'Merge Request Discussions', feature: true do
+feature 'Merge Request Discussions' do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
end
describe "Diff discussions" do
@@ -27,13 +27,13 @@ feature 'Merge Request Discussions', feature: true do
let(:outdated_diff_refs) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e").diff_refs }
before(:each) do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'active discussions' do
it 'shows a link to the diff' do
within(".discussion[data-discussion-id='#{active_discussion.id}']") do
- path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: active_discussion.line_code)
+ path = diffs_project_merge_request_path(project, merge_request, anchor: active_discussion.line_code)
expect(page).to have_link('the diff', href: path)
end
end
@@ -42,7 +42,7 @@ feature 'Merge Request Discussions', feature: true do
context 'outdated discussions' do
it 'shows a link to the outdated diff' do
within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
- path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
+ path = diffs_project_merge_request_path(project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
expect(page).to have_link('an old version of the diff', href: path)
end
end
@@ -72,7 +72,7 @@ feature 'Merge Request Discussions', feature: true do
end
before(:each) do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'a regular commit comment' do
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index 804bf6967d6..2b0ecfc8c72 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Edit Merge Request', feature: true do
+feature 'Edit Merge Request' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
@@ -8,9 +8,9 @@ feature 'Edit Merge Request', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
context 'editing a MR' do
@@ -33,7 +33,7 @@ feature 'Edit Merge Request', feature: true do
merge_request.update(merge_params: { 'force_remove_source_branch' => '1' })
expect(merge_request.merge_params['force_remove_source_branch']).to be_truthy
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
uncheck 'Remove source branch when merge request is accepted'
click_button 'Save changes'
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 9b677aeca0a..220e0c8dc68 100644
--- a/spec/features/merge_requests/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Issue filtering by Labels', feature: true, js: true do
+feature 'Issue filtering by Labels', js: true do
include FilteredSearchHelpers
include MergeRequestHelpers
@@ -26,9 +26,9 @@ feature 'Issue filtering by Labels', feature: true, js: true do
mr3.labels << feature
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
context 'filter by label bug' do
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index 79bca0c9de2..f6b9aa733a4 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge Request filtering by Milestone', feature: true do
+feature 'Merge Request filtering by Milestone' do
include FilteredSearchHelpers
include MergeRequestHelpers
@@ -15,7 +15,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'filters by no Milestone', js: true do
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index c12edf1fdf3..38eb158d06e 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Filter merge requests', feature: true do
+describe 'Filter merge requests' do
include FilteredSearchHelpers
include MergeRequestHelpers
@@ -14,10 +14,10 @@ describe 'Filter merge requests', feature: true do
before do
project.team << [user, :master]
group.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
create(:merge_request, source_project: project, target_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
describe 'for assignee from mr#index' do
@@ -132,19 +132,13 @@ describe 'Filter merge requests', feature: true do
end
end
- describe 'for assignee and label from issues#index' do
+ describe 'for assignee and label from mr#index' do
let(:search_query) { "assignee:@#{user.username} label:~#{label.title}" }
before do
- input_filtered_search("assignee:@#{user.username}")
-
- expect_mr_list_count(1)
- expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
- expect_filtered_search_input_empty
-
- input_filtered_search_keys("label:~#{label.title}")
+ input_filtered_search(search_query)
- expect_mr_list_count(1)
+ expect_mr_list_count(0)
end
context 'assignee and label', js: true do
@@ -191,7 +185,7 @@ describe 'Filter merge requests', feature: true do
assignee: user)
mr.labels << bug_label
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
context 'only text', js: true do
@@ -275,7 +269,7 @@ describe 'Filter merge requests', feature: true do
mr1.labels << bug_label
mr2.labels << bug_label
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'is able to filter and sort merge requests' do
@@ -297,7 +291,7 @@ describe 'Filter merge requests', feature: true do
describe 'filter by assignee id', js: true do
it 'filter by current user' do
- visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: user.id)
+ visit project_merge_requests_path(project, assignee_id: user.id)
expect_tokens([{ name: 'assignee', value: "@#{user.username}" }])
expect_filtered_search_input_empty
@@ -307,7 +301,7 @@ describe 'Filter merge requests', feature: true do
new_user = create(:user)
project.add_developer(new_user)
- visit namespace_project_merge_requests_path(project.namespace, project, assignee_id: new_user.id)
+ visit project_merge_requests_path(project, assignee_id: new_user.id)
expect_tokens([{ name: 'assignee', value: "@#{new_user.username}" }])
expect_filtered_search_input_empty
@@ -316,7 +310,7 @@ describe 'Filter merge requests', feature: true do
describe 'filter by author id', js: true do
it 'filter by current user' do
- visit namespace_project_merge_requests_path(project.namespace, project, author_id: user.id)
+ visit project_merge_requests_path(project, author_id: user.id)
expect_tokens([{ name: 'author', value: "@#{user.username}" }])
expect_filtered_search_input_empty
@@ -326,7 +320,7 @@ describe 'Filter merge requests', feature: true do
new_user = create(:user)
project.add_developer(new_user)
- visit namespace_project_merge_requests_path(project.namespace, project, author_id: new_user.id)
+ visit project_merge_requests_path(project, author_id: new_user.id)
expect_tokens([{ name: 'author', value: "@#{new_user.username}" }])
expect_filtered_search_input_empty
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index d03d498ce21..e06cd627b20 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
-describe 'New/edit merge request', feature: true, js: true do
- include GitlabRoutingHelper
-
+describe 'New/edit merge request', :js do
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:fork_project) { create(:project, forked_from_project: project) }
let!(:user) { create(:user)}
@@ -18,13 +16,12 @@ describe 'New/edit merge request', feature: true, js: true do
context 'owned projects' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit namespace_project_new_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
@@ -114,7 +111,7 @@ describe 'New/edit merge request', feature: true, js: true do
target_branch: 'master'
)
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
it 'updates merge request' do
@@ -177,13 +174,12 @@ describe 'New/edit merge request', feature: true, js: true do
context 'forked project' do
before do
fork_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit namespace_project_new_merge_request_path(
- fork_project.namespace,
+ visit project_new_merge_request_path(
fork_project,
merge_request: {
source_project_id: fork_project.id,
@@ -251,7 +247,7 @@ describe 'New/edit merge request', feature: true, js: true do
target_branch: 'master'
)
- visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit edit_project_merge_request_path(project, merge_request)
end
it 'should update merge request' do
diff --git a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
index 27ba380b005..784f362a28f 100644
--- a/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
+++ b/spec/features/merge_requests/merge_commit_message_toggle_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Clicking toggle commit message link', feature: true, js: true do
+feature 'Clicking toggle commit message link', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue_1) { create(:issue, project: project)}
@@ -34,9 +34,9 @@ feature 'Clicking toggle commit message link', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
expect(page).not_to have_selector('.js-commit-message')
click_button "Modify commit message"
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index 8af7d985036..13142d3b3c9 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Merge immediately', :feature, :js do
+feature 'Merge immediately', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -28,8 +28,8 @@ feature 'Merge immediately', :feature, :js do
end
before do
- gitlab_sign_in user
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ sign_in user
+ visit project_merge_request_path(merge_request.project, merge_request)
end
it 'enables merge immediately' do
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index bfadd7cb81a..9fbdc1b2c80 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Merge When Pipeline Succeeds', :feature, :js do
+feature 'Merge When Pipeline Succeeds', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -28,7 +28,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
visit_merge_request(merge_request)
end
@@ -121,7 +121,7 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
before do
- gitlab_sign_in user
+ sign_in user
visit_merge_request(merge_request)
end
@@ -155,6 +155,6 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 7664fbfbb4c..ee6e440e70d 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Mini Pipeline Graph', :js, :feature do
+feature 'Mini Pipeline Graph', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
@@ -11,12 +11,12 @@ feature 'Mini Pipeline Graph', :js, :feature do
before do
build.run
- gitlab_sign_in(user)
+ sign_in(user)
visit_merge_request
end
def visit_merge_request(format = :html)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request, format: format)
+ visit project_merge_request_path(project, merge_request, format: format)
end
it 'should display a mini pipeline graph' do
@@ -111,7 +111,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
build_item.click
find('.build-page')
- expect(current_path).to eql(namespace_project_job_path(project.namespace, project, build))
+ expect(current_path).to eql(project_job_path(project, build))
end
it 'should show tooltip when hovered' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 5cd9a7fbe26..5c6eec44ff7 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Only allow merge requests to be merged if the pipeline succeeds', feature: true, js: true do
+feature 'Only allow merge requests to be merged if the pipeline succeeds', js: true do
let(:merge_request) { create(:merge_request_with_diffs) }
let(:project) { merge_request.target_project }
before do
- gitlab_sign_in merge_request.author
+ sign_in merge_request.author
project.team << [merge_request.author, :master]
end
@@ -145,6 +145,6 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
end
def visit_merge_request(merge_request)
- visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ visit project_merge_request_path(merge_request.project, merge_request)
end
end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index c2241317e04..b3d6cf8deb4 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Pipelines for Merge Requests', feature: true, js: true do
+feature 'Pipelines for Merge Requests', js: true do
given(:user) { create(:user) }
given(:merge_request) { create(:merge_request) }
given(:project) { merge_request.target_project }
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'with pipelines' do
@@ -19,7 +19,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'user visits merge request pipelines tab' do
@@ -34,7 +34,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
context 'without pipelines' do
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'user visits merge request page' do
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index 275f81f50dc..423213709a3 100644
--- a/spec/features/merge_requests/reset_filters_spec.rb
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge requests filter clear button', feature: true, js: true do
+feature 'Merge requests filter clear button', js: true do
include FilteredSearchHelpers
include MergeRequestHelpers
include IssueHelpers
diff --git a/spec/features/merge_requests/target_branch_spec.rb b/spec/features/merge_requests/target_branch_spec.rb
index 4328d66c748..9bbf2610bcb 100644
--- a/spec/features/merge_requests/target_branch_spec.rb
+++ b/spec/features/merge_requests/target_branch_spec.rb
@@ -1,19 +1,16 @@
require 'spec_helper'
-describe 'Target branch', feature: true, js: true do
+describe 'Target branch', js: true do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
def path_to_merge_request
- namespace_project_merge_request_path(
- project.namespace,
- project, merge_request
- )
+ project_merge_request_path(project, merge_request)
end
before do
- gitlab_sign_in user
+ sign_in user
project.team << [user, :master]
end
diff --git a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
index cba9a2cda99..dd989fd49b2 100644
--- a/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
+++ b/spec/features/merge_requests/toggle_whitespace_changes_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Toggle Whitespace Changes', js: true, feature: true do
+feature 'Toggle Whitespace Changes', js: true do
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
merge_request = create(:merge_request)
project = merge_request.source_project
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
end
it 'has a button to toggle whitespace changes' do
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
index c4c06e9a7a0..2283164ca2f 100644
--- a/spec/features/merge_requests/toggler_behavior_spec.rb
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'toggler_behavior', js: true, feature: true do
+feature 'toggler_behavior', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project, author: user) }
@@ -8,10 +8,10 @@ feature 'toggler_behavior', js: true, feature: true do
let(:fragment_id) { "#note_#{note.id}" }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
project = merge_request.source_project
page.current_window.resize_to(1000, 300)
- visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+ visit "#{project_merge_request_path(project, merge_request)}#{fragment_id}"
end
describe 'scroll position' do
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index d0418c74699..db2f2b523d2 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -1,19 +1,19 @@
require 'rails_helper'
-feature 'Multiple merge requests updating from merge_requests#index', feature: true do
+feature 'Multiple merge requests updating from merge_requests#index' do
let!(:user) { create(:user)}
let!(:project) { create(:project) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'status', js: true do
describe 'close merge request' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'closes merge request' do
@@ -26,7 +26,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
describe 'reopen merge request' do
before do
merge_request.close
- visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed')
+ visit project_merge_requests_path(project, state: 'closed')
end
it 'reopens merge request' do
@@ -40,7 +40,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
context 'assignee', js: true do
describe 'set assignee' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "updates merge request with assignee" do
@@ -56,7 +56,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
before do
merge_request.assignee = user
merge_request.save
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "removes assignee from the merge request" do
@@ -72,7 +72,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
describe 'set milestone' do
before do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "updates merge request with milestone" do
@@ -86,7 +86,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
before do
merge_request.milestone = milestone
merge_request.save
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it "removes milestone from the merge request" do
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index cabb8e455f9..d38d2d57b35 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Merge requests > User lists merge requests', feature: true do
+describe 'Projects > Merge requests > User lists merge requests' do
include MergeRequestHelpers
include SortingHelper
@@ -37,7 +37,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
it 'filters on no assignee' do
visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
- expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
+ expect(current_path).to eq(project_merge_requests_path(project))
expect(page).to have_content 'merge_lfs'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
@@ -136,7 +136,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
end
it 'sorts by recently due milestone' do
- visit namespace_project_merge_requests_path(project.namespace, project,
+ visit project_merge_requests_path(project,
label_name: [label.name, label2.name],
assignee_id: user.id,
sort: sort_value_milestone_soon)
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index ac7e0eb2727..1cfd78663e5 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -7,7 +7,7 @@ feature 'Merge requests > User posts diff notes', :js do
before do
project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
let(:comment_button_class) { '.add-diff-note' }
@@ -17,7 +17,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over a parallel view diff file' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'parallel')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'parallel')
end
context 'with an old line on the left and no line on the right' do
@@ -92,7 +92,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over an inline view diff file' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
@@ -136,9 +136,9 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when hovering over a diff discussion' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not allow commenting' do
@@ -149,7 +149,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when cancelling the comment addition' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
@@ -161,7 +161,7 @@ feature 'Merge requests > User posts diff notes', :js do
describe 'with muliple note forms' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
click_diff_line(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
click_diff_line(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
end
@@ -181,7 +181,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'when the MR only supports legacy diff notes' do
before do
merge_request.merge_request_diff.update_attributes(start_commit_sha: nil)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: 'inline')
+ visit diffs_project_merge_request_path(project, merge_request, view: 'inline')
end
context 'with a new line' do
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 12f987e12ea..35ed08e0a5e 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -13,8 +13,8 @@ describe 'Merge requests > User posts notes', :js do
end
before do
- gitlab_sign_in :admin
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(create(:admin))
+ visit project_merge_request_path(project, merge_request)
end
subject { page }
diff --git a/spec/features/merge_requests/user_sees_system_notes_spec.rb b/spec/features/merge_requests/user_sees_system_notes_spec.rb
index 0d88a8172b0..624a425ae52 100644
--- a/spec/features/merge_requests/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_requests/user_sees_system_notes_spec.rb
@@ -11,11 +11,11 @@ feature 'Merge requests > User sees system notes' do
before do
user = create(:user)
private_project.add_developer(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'shows the system note' do
- visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+ visit project_merge_request_path(public_project, merge_request)
expect(page).to have_css('.system-note')
end
@@ -23,7 +23,7 @@ feature 'Merge requests > User sees system notes' do
context 'when not logged-in' do
it 'hides the system note' do
- visit namespace_project_merge_request_path(public_project.namespace, public_project, merge_request)
+ visit project_merge_request_path(public_project, merge_request)
expect(page).not_to have_css('.system-note')
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index a1f123f15ec..881527e1501 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Merge Requests > User uses quick actions', feature: true, js: true do
+feature 'Merge Requests > User uses quick actions', js: true do
include QuickActionsHelpers
let(:user) { create(:user) }
@@ -16,14 +16,22 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
describe 'merge-request-only commands' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
after do
wait_for_requests
end
+ describe 'time tracking' do
+ before do
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it_behaves_like 'issuable time tracker'
+ end
+
describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do
it 'adds the WIP: prefix to the title' do
@@ -51,9 +59,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not change the WIP prefix' do
@@ -97,9 +105,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not merge the MR' do
@@ -125,13 +133,13 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
before do
- gitlab_sign_out
+ sign_out(:user)
another_project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'changes target_branch in new merge_request' do
- visit namespace_project_new_merge_request_path(another_project.namespace, another_project, new_url_opts)
+ visit project_new_merge_request_path(another_project, new_url_opts)
fill_in "merge_request_title", with: 'My brand new feature'
fill_in "merge_request_description", with: "le feature \n/target_branch fix\nFeature description:"
@@ -145,7 +153,7 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
it 'does not change target branch when merge request is edited' do
new_merge_request = create(:merge_request, source_project: another_project)
- visit edit_namespace_project_merge_request_path(another_project.namespace, another_project, new_merge_request)
+ visit edit_project_merge_request_path(another_project, new_merge_request)
fill_in "merge_request_description", with: "Want to update target branch\n/target_branch fix\n"
click_button "Save changes"
@@ -181,9 +189,9 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
let(:guest) { create(:user) }
before do
project.team << [guest, :guest]
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_out(:user)
+ sign_in(guest)
+ visit project_merge_request_path(project, merge_request)
end
it 'does not change target branch' do
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 04a72d3be34..8e231fbc281 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Merge Request versions', js: true, feature: true do
+feature 'Merge Request versions', js: true do
let(:merge_request) { create(:merge_request, importing: true) }
let(:project) { merge_request.source_project }
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
@@ -8,8 +8,8 @@ feature 'Merge Request versions', js: true, feature: true do
let!(:merge_request_diff3) { merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
before do
- gitlab_sign_in :admin
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ sign_in(create(:admin))
+ visit diffs_project_merge_request_path(project, merge_request)
end
it 'show the latest version of the diff' do
@@ -96,8 +96,7 @@ feature 'Merge Request versions', js: true, feature: true do
end
it 'has a path with comparison context' do
- expect(page).to have_current_path diffs_namespace_project_merge_request_path(
- project.namespace,
+ expect(page).to have_current_path diffs_project_merge_request_path(
project,
merge_request.iid,
diff_id: merge_request_diff3.id,
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index e82e69c5f4a..c0221525c9f 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Widget Deployments Header', feature: true, js: true do
+feature 'Widget Deployments Header', js: true do
describe 'when deployed to an environment' do
given(:user) { create(:user) }
given(:project) { merge_request.target_project }
@@ -12,9 +12,9 @@ feature 'Widget Deployments Header', feature: true, js: true do
given!(:manual) { }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
scenario 'displays that the environment is deployed' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index d8e9b949204..8d7bbf14d86 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -1,27 +1,25 @@
require 'rails_helper'
-describe 'Merge request', :feature, :js do
+describe 'Merge request', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'new merge request' do
before do
- visit namespace_project_new_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'feature',
target_branch: 'master'
- }
- )
+ })
end
it 'shows widget status after creating new merge request' do
@@ -44,7 +42,7 @@ describe 'Merge request', :feature, :js do
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows environments link' do
@@ -71,7 +69,7 @@ describe 'Merge request', :feature, :js do
type: 'CiService',
category: 'ci')
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has danger button while waiting for external CI status' do
@@ -92,7 +90,7 @@ describe 'Merge request', :feature, :js do
head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has danger button when not succeeded' do
@@ -112,9 +110,7 @@ describe 'Merge request', :feature, :js do
status: :manual,
head_pipeline_of: merge_request)
- visit namespace_project_merge_request_path(project.namespace,
- project,
- merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about blocked pipeline' do
@@ -136,7 +132,7 @@ describe 'Merge request', :feature, :js do
head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'has info button when MWBS button' do
@@ -154,7 +150,7 @@ describe 'Merge request', :feature, :js do
merge_error: 'Something went wrong'
)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about the merge error' do
@@ -175,7 +171,7 @@ describe 'Merge request', :feature, :js do
merge_error: 'Something went wrong'
)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'shows information about the merge error' do
@@ -191,7 +187,7 @@ describe 'Merge request', :feature, :js do
context 'merge error' do
before do
allow_any_instance_of(Repository).to receive(:merge).and_return(false)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'updates the MR widget' do
@@ -209,10 +205,10 @@ describe 'Merge request', :feature, :js do
before do
project.team << [user2, :master]
- gitlab_sign_out
- gitlab_sign_in user2
+ sign_out(:user)
+ sign_in(user2)
merge_request.update(target_project: fork_project)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
it 'user can merge into the source project' do
diff --git a/spec/features/merge_requests/wip_message_spec.rb b/spec/features/merge_requests/wip_message_spec.rb
index 0e304ba50af..5a685056bf3 100644
--- a/spec/features/merge_requests/wip_message_spec.rb
+++ b/spec/features/merge_requests/wip_message_spec.rb
@@ -1,26 +1,24 @@
require 'spec_helper'
-feature 'Work In Progress help message', feature: true do
+feature 'Work In Progress help message' do
let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let!(:user) { create(:user) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'with WIP commits' do
it 'shows a specific WIP hint' do
- visit namespace_project_new_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'wip',
target_branch: 'master'
- }
- )
+ })
within_wip_explanation do
expect(page).to have_text(
@@ -32,16 +30,14 @@ feature 'Work In Progress help message', feature: true do
context 'without WIP commits' do
it 'shows the regular WIP message' do
- visit namespace_project_new_merge_request_path(
- project.namespace,
+ visit project_new_merge_request_path(
project,
merge_request: {
source_project_id: project.id,
target_project_id: project.id,
source_branch: 'fix',
target_branch: 'master'
- }
- )
+ })
within_wip_explanation do
expect(page).not_to have_text(
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index 58989581ffe..1d05184d6fc 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,17 +1,19 @@
require 'rails_helper'
-feature 'Milestone', feature: true do
- let(:project) { create(:empty_project, :public) }
+feature 'Milestone' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
let(:user) { create(:user) }
before do
+ create(:group_member, group: group, user: user)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
feature 'Create a milestone' do
scenario 'shows an informative message for a new milestone' do
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
page.within '.milestone-form' do
fill_in "milestone_title", with: '8.7'
@@ -31,23 +33,36 @@ feature 'Milestone', feature: true do
milestone = create(:milestone, project: project, title: 8.7)
create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.')
end
end
- feature 'Open a milestone with an existing title' do
- scenario 'displays validation message' do
+ feature 'Open a project milestone with an existing title' do
+ scenario 'displays validation message when there is a project milestone with same title' do
milestone = create(:milestone, project: project, title: 8.7)
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
page.within '.milestone-form' do
fill_in "milestone_title", with: milestone.title
end
find('input[name="commit"]').click
- expect(find('.alert-danger')).to have_content('Title has already been taken')
+ expect(find('.alert-danger')).to have_content('already being used for another group or project milestone.')
+ end
+
+ scenario 'displays validation message when there is a group milestone with same title' do
+ milestone = create(:milestone, project_id: nil, group: project.group, title: 8.7)
+
+ visit new_group_milestone_path(project.group)
+
+ page.within '.milestone-form' do
+ fill_in "milestone_title", with: milestone.title
+ end
+ find('input[name="commit"]').click
+
+ expect(find('.alert-danger')).to have_content('already being used for another group or project milestone.')
end
end
end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index cdf6cfba402..199a5ba83b3 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Milestone show', feature: true do
+describe 'Milestone show' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, project: project) }
@@ -9,11 +9,11 @@ describe 'Milestone show', feature: true do
before do
project.add_user(user, :developer)
- gitlab_sign_in(user)
+ sign_in(user)
end
def visit_milestone
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'avoids N+1 database queries' do
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
new file mode 100644
index 00000000000..49d8e52f861
--- /dev/null
+++ b/spec/features/oauth_login_spec.rb
@@ -0,0 +1,114 @@
+require 'spec_helper'
+
+feature 'OAuth Login', :js, :allow_forgery_protection do
+ include DeviseHelpers
+
+ def enter_code(code)
+ fill_in 'user_otp_attempt', with: code
+ click_button 'Verify code'
+ end
+
+ def stub_omniauth_config(provider)
+ OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
+ set_devise_mapping(context: Rails.application)
+ Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
+ end
+
+ providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
+ :facebook, :cas3, :auth0, :authentiq]
+
+ before(:all) do
+ # The OmniAuth `full_host` parameter doesn't get set correctly (it gets set to something like `http://localhost`
+ # here), and causes integration tests to fail with 404s. We set the `full_host` by removing the request path (and
+ # anything after it) from the request URI.
+ @omniauth_config_full_host = OmniAuth.config.full_host
+ OmniAuth.config.full_host = ->(request) { request['REQUEST_URI'].sub(/#{request['REQUEST_PATH']}.*/, '') }
+ end
+
+ after(:all) do
+ OmniAuth.config.full_host = @omniauth_config_full_host
+ end
+
+ providers.each do |provider|
+ context "when the user logs in using the #{provider} provider" do
+ context 'when two-factor authentication is disabled' do
+ it 'logs the user in' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid')
+
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'logs the user in' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid')
+
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when "remember me" is checked' do
+ context 'when two-factor authentication is disabled' do
+ it 'remembers the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: true)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'remembers the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: true)
+ enter_code(user.current_otp)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq root_path
+ end
+ end
+ end
+
+ context 'when "remember me" is not checked' do
+ context 'when two-factor authentication is disabled' do
+ it 'does not remember the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: false)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+
+ context 'when two-factor authentication is enabled' do
+ it 'does not remember the user after a browser restart' do
+ stub_omniauth_config(provider)
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider.to_s)
+ login_via(provider.to_s, user, 'my-uid', remember_me: false)
+ enter_code(user.current_otp)
+
+ clear_browser_session
+
+ visit(root_path)
+ expect(current_path).to eq new_user_session_path
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index b8966cf621c..81b0a2f541b 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -8,7 +8,7 @@ feature 'Member autocomplete', :js do
before do
note # actually create the note
- gitlab_sign_in(user)
+ sign_in(user)
end
shared_examples "open suggestions when typing @" do
@@ -29,7 +29,7 @@ feature 'Member autocomplete', :js do
context 'adding a new note on a Issue' do
let(:noteable) { create(:issue, author: author, project: project) }
before do
- visit namespace_project_issue_path(project.namespace, project, noteable)
+ visit project_issue_path(project, noteable)
end
include_examples "open suggestions when typing @"
@@ -42,7 +42,7 @@ feature 'Member autocomplete', :js do
target_project: project, author: author)
end
before do
- visit namespace_project_merge_request_path(project.namespace, project, noteable)
+ visit project_merge_request_path(project, noteable)
end
include_examples "open suggestions when typing @"
@@ -54,9 +54,10 @@ feature 'Member autocomplete', :js do
let(:note) { create(:note_on_commit, project: project, commit_id: project.commit.id) }
before do
- allow_any_instance_of(Commit).to receive(:author).and_return(author)
+ allow(User).to receive(:find_by_any_email)
+ .with(noteable.author_email.downcase).and_return(author)
- visit namespace_project_commit_path(project.namespace, project, noteable)
+ visit project_commit_path(project, noteable)
end
include_examples "open suggestions when typing @"
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index 257d363438c..5e1e7dc078f 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Password reset', feature: true do
+feature 'Password reset' do
describe 'throttling' do
it 'sends reset instructions when not previously sent' do
user = create(:user)
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index bb4263d83f3..672022304da 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe 'Profile account page', feature: true do
+describe 'Profile account page' do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'when signup is enabled' do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 33fd29b429b..7791047877d 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,10 +1,10 @@
require 'rails_helper'
-feature 'Profile > Account', feature: true do
+feature 'Profile > Account' do
given(:user) { create(:user, username: 'foo') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'Change username' do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index 1a162d6be0e..35793539e0e 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'Profile > Chat', feature: true do
+feature 'Profile > Chat' do
given(:user) { create(:user) }
given(:service) { create(:service) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'uses authorization link' do
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
new file mode 100644
index 00000000000..6edc482b47e
--- /dev/null
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -0,0 +1,58 @@
+require 'rails_helper'
+
+feature 'Profile > GPG Keys' do
+ let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
+
+ before do
+ login_as(user)
+ end
+
+ describe 'User adds a key' do
+ before do
+ visit profile_gpg_keys_path
+ end
+
+ scenario 'saves the new key' do
+ fill_in('Key', with: GpgHelpers::User2.public_key)
+ click_button('Add key')
+
+ expect(page).to have_content('bette.cartwright@example.com Verified')
+ expect(page).to have_content('bette.cartwright@example.net Unverified')
+ expect(page).to have_content(GpgHelpers::User2.fingerprint)
+ end
+ end
+
+ scenario 'User sees their key' do
+ create(:gpg_key, user: user, key: GpgHelpers::User2.public_key)
+ visit profile_gpg_keys_path
+
+ expect(page).to have_content('bette.cartwright@example.com Verified')
+ expect(page).to have_content('bette.cartwright@example.net Unverified')
+ expect(page).to have_content(GpgHelpers::User2.fingerprint)
+ end
+
+ scenario 'User removes a key via the key index' do
+ create(:gpg_key, user: user, key: GpgHelpers::User2.public_key)
+ visit profile_gpg_keys_path
+
+ click_link('Remove')
+
+ expect(page).to have_content('Your GPG keys (0)')
+ end
+
+ scenario 'User revokes a key via the key index' do
+ gpg_key = create :gpg_key, user: user, key: GpgHelpers::User2.public_key
+ gpg_signature = create :gpg_signature, gpg_key: gpg_key, valid_signature: true
+
+ visit profile_gpg_keys_path
+
+ click_link('Revoke')
+
+ expect(page).to have_content('Your GPG keys (0)')
+
+ expect(gpg_signature.reload).to have_attributes(
+ valid_signature: false,
+ gpg_key: nil
+ )
+ end
+end
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index 13f9afd4ce0..6541ea6bf57 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -1,10 +1,10 @@
require 'rails_helper'
-feature 'Profile > SSH Keys', feature: true do
+feature 'Profile > SSH Keys' do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'User adds a key' do
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index a6f9beafe17..45f78444362 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe 'Profile > Applications', feature: true do
+describe 'Profile > Applications' do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'User manages applications', js: true do
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 86c9df5ff86..2c757f99a27 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -1,44 +1,74 @@
require 'spec_helper'
-describe 'Profile > Password', feature: true do
- let(:user) { create(:user, password_automatically_set: true) }
+describe 'Profile > Password' do
+ context 'Password authentication enabled' do
+ let(:user) { create(:user, password_automatically_set: true) }
- before do
- gitlab_sign_in(user)
- visit edit_profile_password_path
- end
+ before do
+ sign_in(user)
+ visit edit_profile_password_path
+ end
- def fill_passwords(password, confirmation)
- fill_in 'New password', with: password
- fill_in 'Password confirmation', with: confirmation
+ def fill_passwords(password, confirmation)
+ fill_in 'New password', with: password
+ fill_in 'Password confirmation', with: confirmation
- click_button 'Save password'
- end
+ click_button 'Save password'
+ end
+
+ context 'User with password automatically set' do
+ describe 'User puts different passwords in the field and in the confirmation' do
+ it 'shows an error message' do
+ fill_passwords('mypassword', 'mypassword2')
- context 'User with password automatically set' do
- describe 'User puts different passwords in the field and in the confirmation' do
- it 'shows an error message' do
- fill_passwords('mypassword', 'mypassword2')
+ page.within('.alert-danger') do
+ expect(page).to have_content("Password confirmation doesn't match Password")
+ end
+ end
+
+ it 'does not contain the current password field after an error' do
+ fill_passwords('mypassword', 'mypassword2')
- page.within('.alert-danger') do
- expect(page).to have_content("Password confirmation doesn't match Password")
+ expect(page).to have_no_field('user[current_password]')
end
end
- it 'does not contain the current password field after an error' do
- fill_passwords('mypassword', 'mypassword2')
+ describe 'User puts the same passwords in the field and in the confirmation' do
+ it 'shows a success message' do
+ fill_passwords('mypassword', 'mypassword')
- expect(page).to have_no_field('user[current_password]')
+ page.within('.flash-notice') do
+ expect(page).to have_content('Password was successfully updated. Please login with it')
+ end
+ end
end
end
+ end
- describe 'User puts the same passwords in the field and in the confirmation' do
- it 'shows a success message' do
- fill_passwords('mypassword', 'mypassword')
+ context 'Password authentication unavailable' do
+ before do
+ gitlab_sign_in(user)
+ end
- page.within('.flash-notice') do
- expect(page).to have_content('Password was successfully updated. Please login with it')
- end
+ context 'Regular user' do
+ let(:user) { create(:user) }
+
+ it 'renders 404 when sign-in is disabled' do
+ stub_application_setting(password_authentication_enabled: false)
+
+ visit edit_profile_password_path
+
+ expect(page).to have_http_status(404)
+ end
+ end
+
+ context 'LDAP user' do
+ let(:user) { create(:omniauth_user, provider: 'ldapmain') }
+
+ it 'renders 404' do
+ visit edit_profile_password_path
+
+ expect(page).to have_http_status(404)
end
end
end
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index d7acaaf1eb8..f3124bbf29e 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-describe 'Profile > Personal Access Tokens', feature: true, js: true do
+describe 'Profile > Personal Access Tokens', js: true do
let(:user) { create(:user) }
def active_personal_access_tokens
find(".table.active-tokens")
end
- def inactive_personal_access_tokens
- find(".table.inactive-tokens")
+ def no_personal_access_tokens_message
+ find(".settings-message")
end
def created_personal_access_token
@@ -23,7 +23,7 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "token creation" do
@@ -80,14 +80,16 @@ describe 'Profile > Personal Access Tokens', feature: true, js: true do
visit profile_personal_access_tokens_path
click_on "Revoke"
- expect(inactive_personal_access_tokens).to have_text(personal_access_token.name)
+ expect(page).to have_selector(".settings-message")
+ expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.")
end
- it "moves expired tokens to the 'inactive' section" do
+ it "removes expired tokens from 'active' section" do
personal_access_token.update(expires_at: 5.days.ago)
visit profile_personal_access_tokens_path
- expect(inactive_personal_access_tokens).to have_text(personal_access_token.name)
+ expect(page).to have_selector(".settings-message")
+ expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.")
end
context "when revocation fails" do
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 8e7ef6bc110..9123aa9d155 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe 'Profile > Preferences', feature: true do
+describe 'Profile > Preferences' do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit profile_preferences_path
end
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index c0092836e3b..6a4173d43e1 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-feature 'Profile > Notifications > User changes notified_of_own_activity setting', feature: true, js: true do
+feature 'Profile > Notifications > User changes notified_of_own_activity setting', js: true do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'User opts into receiving notifications about their own activity' do
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 84c81d43448..b054f543dc6 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
feature 'Project Activity RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { activity_namespace_project_path(project.namespace, project) }
+ let(:path) { activity_project_path(project) }
before do
create(:issue, project: project)
@@ -10,9 +11,8 @@ feature 'Project Activity RSS' do
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/artifacts/browse_spec.rb b/spec/features/projects/artifacts/browse_spec.rb
index 68375956273..7dfb19f29bd 100644
--- a/spec/features/projects/artifacts/browse_spec.rb
+++ b/spec/features/projects/artifacts/browse_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Browse artifact', :js, feature: true do
+feature 'Browse artifact', :js do
let(:project) { create(:project, :public) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
def browse_path(path)
- browse_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ browse_project_job_artifacts_path(project, job, path)
end
context 'when visiting old URL' do
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
index dd9454840ee..f1c210281ad 100644
--- a/spec/features/projects/artifacts/download_spec.rb
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download artifact', :js, feature: true do
+feature 'Download artifact', :js do
let(:project) { create(:project, :public) }
let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project, sha: project.commit.sha, ref: 'master') }
let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
@@ -22,7 +22,7 @@ feature 'Download artifact', :js, feature: true do
context 'via job id' do
let(:download_url) do
- download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ download_project_job_artifacts_path(project, job)
end
it_behaves_like 'downloading'
@@ -30,7 +30,7 @@ feature 'Download artifact', :js, feature: true do
context 'via branch name and job name' do
let(:download_url) do
- latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
end
it_behaves_like 'downloading'
@@ -44,7 +44,7 @@ feature 'Download artifact', :js, feature: true do
context 'via job id' do
let(:download_url) do
- download_namespace_project_job_artifacts_path(project.namespace, project, job)
+ download_project_job_artifacts_path(project, job)
end
it_behaves_like 'downloading'
@@ -52,7 +52,7 @@ feature 'Download artifact', :js, feature: true do
context 'via branch name and job name' do
let(:download_url) do
- latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{pipeline.ref}/download", job: job.name)
+ latest_succeeded_project_artifacts_path(project, "#{pipeline.ref}/download", job: job.name)
end
it_behaves_like 'downloading'
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 860373e531b..aaa98ac4851 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Artifact file', :js, feature: true do
+feature 'Artifact file', :js do
let(:project) { create(:project, :public) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
@@ -10,7 +10,7 @@ feature 'Artifact file', :js, feature: true do
end
def file_path(path)
- file_namespace_project_job_artifacts_path(project.namespace, project, build, path)
+ file_project_job_artifacts_path(project, build, path)
end
context 'Text file' do
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
index b589701729d..73292cb3873 100644
--- a/spec/features/projects/artifacts/raw_spec.rb
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Raw artifact', :js, feature: true do
+feature 'Raw artifact', :js do
let(:project) { create(:project, :public) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
def raw_path(path)
- raw_namespace_project_job_artifacts_path(project.namespace, project, job, path)
+ raw_project_job_artifacts_path(project, job, path)
end
context 'when visiting old URL' do
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 9624e1a71b0..5c5a7c96763 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -7,7 +7,7 @@ feature 'test coverage badge' do
context 'when user has access to view badge' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user requests coverage badge image for pipeline' do
@@ -45,7 +45,7 @@ feature 'test coverage badge' do
end
context 'when user does not have access to view badge' do
- background { gitlab_sign_in(user) }
+ background { sign_in(user) }
scenario 'user requests test coverage badge image' do
show_test_coverage_badge
@@ -70,8 +70,7 @@ feature 'test coverage badge' do
end
def show_test_coverage_badge(job: nil)
- visit coverage_namespace_project_badges_path(
- project.namespace, project, ref: :master, job: job, format: :svg)
+ visit coverage_project_badges_path(project, ref: :master, job: job, format: :svg)
end
def expect_coverage_badge(coverage)
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 348748152bb..fd8e9232b02 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -5,21 +5,21 @@ feature 'list of badges' do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_pipelines_settings_path(project.namespace, project)
+ sign_in(user)
+ visit project_pipelines_settings_path(project)
end
scenario 'user wants to see build status badge' do
- page.within('.build-status') do
- expect(page).to have_content 'build status'
+ page.within('.pipeline-status') do
+ expect(page).to have_content 'pipeline status'
expect(page).to have_content 'Markdown'
expect(page).to have_content 'HTML'
expect(page).to have_content 'AsciiDoc'
expect(page).to have_css('.highlight', count: 3)
- expect(page).to have_xpath("//img[@alt='build status']")
+ expect(page).to have_xpath("//img[@alt='pipeline status']")
page.within('.highlight', match: :first) do
- expect(page).to have_content 'badges/master/build.svg'
+ expect(page).to have_content 'badges/master/pipeline.svg'
end
end
end
@@ -40,14 +40,14 @@ feature 'list of badges' do
end
scenario 'user changes current ref of build status badge', js: true do
- page.within('.build-status') do
+ page.within('.pipeline-status') do
first('.js-project-refs-dropdown').click
page.within '.project-refs-form' do
click_link 'improve/awesome'
end
- expect(page).to have_content 'badges/improve/awesome/build.svg'
+ expect(page).to have_content 'badges/improve/awesome/pipeline.svg'
end
end
end
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
new file mode 100644
index 00000000000..b83ea8f4eaa
--- /dev/null
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -0,0 +1,70 @@
+require 'spec_helper'
+
+feature 'Pipeline Badge' do
+ set(:project) { create(:project, :repository, :public) }
+ let(:ref) { project.default_branch }
+
+ # this can't be tested in the controller, as it bypasses the rails router
+ # and constructs a route based on the controller being tested
+ # Keep around until 10.0, see gitlab-org/gitlab-ce#35307
+ context 'when the deprecated badge is requested' do
+ it 'displays the badge' do
+ visit build_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ end
+ end
+
+ context 'when the project has a pipeline' do
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: ref, sha: project.commit(ref).sha) }
+ let!(:job) { create(:ci_build, pipeline: pipeline) }
+
+ context 'when the pipeline was successfull' do
+ it 'displays so on the badge' do
+ job.success
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('passed')
+ end
+ end
+
+ context 'when the pipeline failed' do
+ it 'shows displays so on the badge' do
+ job.drop
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('failed')
+ end
+ end
+
+ context 'when the pipeline is running' do
+ it 'shows displays so on the badge' do
+ create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run')
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('running')
+ end
+ end
+
+ context 'when a new pipeline is created' do
+ it 'shows a fresh badge' do
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect(page.response_headers['Cache-Control']).to include 'no-cache'
+ end
+ end
+
+ def expect_badge(status)
+ svg = Nokogiri::XML.parse(page.body)
+ expect(page.response_headers['Content-Type']).to include('image/svg+xml')
+ expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
+ end
+ end
+end
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index d94204230f6..1160f674974 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true, js: true do
+feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', js: true do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
@@ -13,14 +13,14 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
end
def visit_blob(fragment = nil)
- visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+ visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'Click "Permalink" button' do
it 'works with no initial line number fragment hash' do
visit_blob
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path))))
end
it 'maintains intitial fragment hash' do
@@ -28,7 +28,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
visit_blob(fragment)
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
@@ -39,7 +39,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -51,7 +51,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: ending_fragment)))
+ expect(find('.js-data-file-blob-permalink-url')['href']).to eq(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: ending_fragment)))
end
end
@@ -59,7 +59,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
it 'works with no initial line number fragment hash' do
visit_blob
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path))))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path))))
end
it 'maintains intitial fragment hash' do
@@ -67,7 +67,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
visit_blob(fragment)
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: fragment)))
end
it 'changes fragment hash if line number clicked' do
@@ -78,7 +78,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
it 'with initial fragment hash, changes fragment hash if line number clicked' do
@@ -90,7 +90,7 @@ feature 'Blob button line permalinks (BlobLinePermalinkUpdater)', feature: true,
find('#L3').click
find("##{ending_fragment}").click
- expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(namespace_project_blame_path(project.namespace, project, tree_join('master', path), anchor: ending_fragment)))
+ expect(find('.js-blob-blame-link')['href']).to eq(get_absolute_url(project_blame_path(project, tree_join('master', path), anchor: ending_fragment)))
end
end
end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 71ffa352f80..8a32488b845 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-feature 'File blob', :js, feature: true do
+feature 'File blob', :js do
let(:project) { create(:project, :public) }
def visit_blob(path, anchor: nil, ref: 'master')
- visit namespace_project_blob_path(project.namespace, project, File.join(ref, path), anchor: anchor)
+ visit project_blob_path(project, File.join(ref, path), anchor: anchor)
wait_for_requests
end
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index d0bc032ee93..9855cfd85c4 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-feature 'Editing file blob', feature: true, js: true do
+feature 'Editing file blob', js: true do
include TreeHelper
- let(:project) { create(:project, :public, :test_repo) }
+ let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') }
let(:branch) { 'master' }
let(:file_path) { project.repository.ls_files(project.repository.root_ref)[1] }
@@ -14,7 +14,7 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
def edit_and_commit
@@ -26,7 +26,7 @@ feature 'Editing file blob', feature: true, js: true do
context 'from MR diff' do
before do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
edit_and_commit
end
@@ -37,7 +37,7 @@ feature 'Editing file blob', feature: true, js: true do
context 'from blob file path' do
before do
- visit namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_blob_path(project, tree_join(branch, file_path))
edit_and_commit
end
@@ -55,7 +55,7 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, :developer]
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
@@ -63,7 +63,7 @@ feature 'Editing file blob', feature: true, js: true do
gitlab_sign_in(user)
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
end
end
@@ -71,7 +71,7 @@ feature 'Editing file blob', feature: true, js: true do
let(:user) { create(:user) }
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'redirects to sign in and returns' do
@@ -79,7 +79,7 @@ feature 'Editing file blob', feature: true, js: true do
gitlab_sign_in(user)
- expect(page).to have_current_path(namespace_project_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_blob_path(project, tree_join(branch, file_path)))
end
end
end
@@ -92,23 +92,23 @@ feature 'Editing file blob', feature: true, js: true do
project.team << [user, :developer]
project.repository.add_branch(user, protected_branch, 'master')
create(:protected_branch, project: project, name: protected_branch)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'on some branch' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
context 'with protected branch' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(protected_branch, file_path))
+ visit project_edit_blob_path(project, tree_join(protected_branch, file_path))
end
it 'shows blob editor with patch branch' do
@@ -122,12 +122,12 @@ feature 'Editing file blob', feature: true, js: true do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path))
+ sign_in(user)
+ visit project_edit_blob_path(project, tree_join(branch, file_path))
end
it 'shows blob editor with same branch' do
- expect(page).to have_current_path(namespace_project_edit_blob_path(project.namespace, project, tree_join(branch, file_path)))
+ expect(page).to have_current_path(project_edit_blob_path(project, tree_join(branch, file_path)))
expect(find('.js-branch-name').value).to eq(branch)
end
end
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index 30e2d587267..1e3080fa319 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Blob shortcuts', feature: true do
+feature 'Blob shortcuts' do
include TreeHelper
let(:project) { create(:project, :public, :repository) }
let(:path) { project.repository.ls_files(project.repository.root_ref)[0] }
@@ -12,7 +12,7 @@ feature 'Blob shortcuts', feature: true do
end
def visit_blob(fragment = nil)
- visit namespace_project_blob_path(project.namespace, project, tree_join('master', path), anchor: fragment)
+ visit project_blob_path(project, tree_join('master', path), anchor: fragment)
end
describe 'pressing "y"' do
@@ -21,7 +21,7 @@ feature 'Blob shortcuts', feature: true do
find('body').native.send_key('y')
- expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path))), url: true)
+ expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path))), url: true)
end
it 'maintains fragment hash when redirecting' do
@@ -30,7 +30,7 @@ feature 'Blob shortcuts', feature: true do
find('body').native.send_key('y')
- expect(page).to have_current_path(get_absolute_url(namespace_project_blob_path(project.namespace, project, tree_join(sha, path), anchor: fragment)), url: true)
+ expect(page).to have_current_path(get_absolute_url(project_blob_path(project, tree_join(sha, path), anchor: fragment)), url: true)
end
end
end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index d8c4d475a2c..47d7f27290b 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download buttons in branches page', feature: true do
+feature 'Download buttons in branches page' do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
@@ -22,20 +22,18 @@ feature 'Download buttons in branches page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking branches' do
context 'with artifacts' do
before do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, 'binary-encoding/download',
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
index 406fa52e723..2123e96b816 100644
--- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
+++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'New Branch Ref Dropdown', :js, :feature do
+describe 'New Branch Ref Dropdown', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:toggle) { find('.create-from .dropdown-menu-toggle') }
@@ -8,8 +8,8 @@ describe 'New Branch Ref Dropdown', :js, :feature do
before do
project.add_master(user)
- gitlab_sign_in(user)
- visit new_namespace_project_branch_path(project.namespace, project)
+ sign_in(user)
+ visit new_project_branch_path(project)
end
it 'filters a list of branches and tags' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 0050864d305..498dd8ee4bf 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Branches', feature: true do
+describe 'Branches' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:repository) { project.repository }
@@ -19,16 +19,15 @@ describe 'Branches', feature: true do
describe 'Initial branches page' do
it 'shows all the branches' do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
repository.branches_sorted_by(:name).first(20).each do |branch|
expect(page).to have_content("#{branch.name}")
end
- expect(page).to have_content("Protected branches can be managed in project settings")
end
it 'sorts the branches by name' do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
click_button "Name" # Open sorting dropdown
click_link "Name"
@@ -40,7 +39,7 @@ describe 'Branches', feature: true do
end
it 'sorts the branches by last updated' do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
click_button "Name" # Open sorting dropdown
click_link "Last updated"
@@ -52,7 +51,7 @@ describe 'Branches', feature: true do
end
it 'sorts the branches by oldest updated' do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
click_button "Name" # Open sorting dropdown
click_link "Oldest updated"
@@ -64,17 +63,17 @@ describe 'Branches', feature: true do
end
it 'avoids a N+1 query in branches index' do
- control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_branches_path(project.namespace, project) }.count
+ control_count = ActiveRecord::QueryRecorder.new { visit project_branches_path(project) }.count
%w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
- expect { visit namespace_project_branches_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+ expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count)
end
end
describe 'Find branches' do
it 'shows filtered branches', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -86,7 +85,7 @@ describe 'Branches', feature: true do
describe 'Delete unprotected branch' do
it 'removes branch after confirmation', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
@@ -104,7 +103,7 @@ describe 'Branches', feature: true do
describe 'Delete protected branch' do
before do
project.add_user(user, :master)
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('fix')
click_on "Protect"
@@ -114,7 +113,7 @@ describe 'Branches', feature: true do
end
it 'does not allow devleoper to removes protected branch', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -130,9 +129,17 @@ describe 'Branches', feature: true do
project.team << [user, :master]
end
+ describe 'Initial branches page' do
+ it 'shows description for admin' do
+ visit project_branches_path(project)
+
+ expect(page).to have_content("Protected branches can be managed in project settings")
+ end
+ end
+
describe 'Delete protected branch' do
before do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('fix')
click_on "Protect"
@@ -141,7 +148,7 @@ describe 'Branches', feature: true do
end
it 'removes branch after modal confirmation', js: true do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -164,7 +171,7 @@ describe 'Branches', feature: true do
context 'logged out' do
before do
- visit namespace_project_branches_path(project.namespace, project)
+ visit project_branches_path(project)
end
it 'does not show merge request button' do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index e5b1f95f2b9..257a7418f16 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -6,7 +6,7 @@ feature 'project commit pipelines', js: true do
background do
user = create(:user)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when no builds triggered yet' do
@@ -17,7 +17,7 @@ feature 'project commit pipelines', js: true do
end
scenario 'user views commit pipelines page' do
- visit pipelines_namespace_project_commit_path(project.namespace, project, project.commit.sha)
+ visit pipelines_project_commit_path(project, project.commit.sha)
page.within('.table-holder') do
expect(page).to have_content project.pipelines[0].status # pipeline status
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index 0d3fa72fbf5..2d18add82b5 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -10,7 +10,7 @@ describe 'Cherry-pick Commits' do
before do
sign_in(user)
project.team << [user, :master]
- visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ visit project_commit_path(project, master_pickable_commit.id)
end
context "I cherry-pick a commit" do
@@ -43,7 +43,7 @@ describe 'Cherry-pick Commits' do
uncheck 'create_merge_request'
click_button 'Cherry-pick'
end
- visit namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ visit project_commit_path(project, master_pickable_commit.id)
find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do
uncheck 'create_merge_request'
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 570a7ae7b16..81a12225779 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'Mini Pipeline Graph in Commit View', :js, :feature do
+feature 'Mini Pipeline Graph in Commit View', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when commit has pipelines' do
@@ -22,7 +22,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
before do
build.run
- visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ visit project_commit_path(project, project.commit.id)
end
it 'should display a mini pipeline graph' do
@@ -43,7 +43,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
context 'when commit does not have pipelines' do
before do
- visit namespace_project_commit_path(project.namespace, project, project.commit.id)
+ visit project_commit_path(project, project.commit.id)
end
it 'should not display a mini pipeline graph' do
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index f7548a56984..db958346f06 100644
--- a/spec/features/projects/commit/rss_spec.rb
+++ b/spec/features/projects/commit/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project Commits RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_commits_path(project.namespace, project, :master) }
+ let(:path) { project_commits_path(project, :master) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 4743d69fb75..0f48751fa10 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -6,8 +6,8 @@ describe "Compare", js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master")
+ sign_in user
+ visit project_compare_index_path(project, from: "master", to: "master")
end
describe "branches" do
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index a31960639fe..2d1a9b931b5 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe 'Project deploy keys', :js, :feature do
+describe 'Project deploy keys', :js do
let(:user) { create(:user) }
let(:project) { create(:project_empty_repo) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'removing key' do
@@ -15,7 +15,7 @@ describe 'Project deploy keys', :js, :feature do
end
it 'removes association between project and deploy key' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
page.within(find('.deploy-keys')) do
expect(page).to have_selector('.deploy-keys li', count: 1)
diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
index a943f1e6a08..7145e286229 100644
--- a/spec/features/projects/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb
@@ -1,13 +1,13 @@
require 'rails_helper'
-feature 'Developer views empty project instructions', feature: true do
+feature 'Developer views empty project instructions' do
let(:project) { create(:empty_project, :empty_repo) }
let(:developer) { create(:user) }
background do
project.team << [developer, :developer]
- gitlab_sign_in(developer)
+ sign_in(developer)
end
context 'without an SSH key' do
@@ -47,7 +47,7 @@ feature 'Developer views empty project instructions', feature: true do
end
def visit_project
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
def select_protocol(protocol)
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index 48b7f1e0f34..bc102895aaf 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-feature 'Diff file viewer', :js, feature: true do
+feature 'Diff file viewer', :js do
let(:project) { create(:project, :public, :repository) }
def visit_commit(sha, anchor: nil)
- visit namespace_project_commit_path(project.namespace, project, sha, anchor: anchor)
+ visit project_commit_path(project, sha, anchor: anchor)
wait_for_requests
end
@@ -110,6 +110,10 @@ feature 'Diff file viewer', :js, feature: true do
context 'binary file that appears to be text in the first 1024 bytes' do
before do
+ # The file we're visiting is smaller than 10 KB and we want it collapsed
+ # so we need to disable the size increase feature.
+ stub_feature_flags(gitlab_git_diff_size_limit_increase: false)
+
visit_commit('7b1cf4336b528e0f3d1d140ee50cafdbc703597c')
end
diff --git a/spec/features/projects/edit_spec.rb b/spec/features/projects/edit_spec.rb
index ca202b95a44..d3b1d1f7be3 100644
--- a/spec/features/projects/edit_spec.rb
+++ b/spec/features/projects/edit_spec.rb
@@ -1,14 +1,14 @@
require 'rails_helper'
-feature 'Project edit', feature: true, js: true do
+feature 'Project edit', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
context 'feature visibility' do
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index a98a69a0fd6..82a722c5960 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Environment > Metrics', :feature do
+feature 'Environment > Metrics' do
include PrometheusHelpers
given(:user) { create(:user) }
@@ -15,7 +15,7 @@ feature 'Environment > Metrics', :feature do
create(:deployment, environment: environment, deployable: build)
stub_all_prometheus_requests(environment.slug)
- gitlab_sign_in(user)
+ sign_in(user)
visit_environment(environment)
end
@@ -32,8 +32,6 @@ feature 'Environment > Metrics', :feature do
end
def visit_environment(environment)
- visit namespace_project_environment_path(environment.project.namespace,
- environment.project,
- environment)
+ visit project_environment_path(environment.project, environment)
end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 7d565555f1f..db29055812c 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Environment', :feature do
+feature 'Environment' do
given(:project) { create(:empty_project) }
given(:user) { create(:user) }
given(:role) { :developer }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
@@ -114,7 +114,7 @@ feature 'Environment', :feature do
before do
# Stub #terminals as it causes js-enabled feature specs to render the page incorrectly
allow_any_instance_of(Environment).to receive(:terminals) { nil }
- visit terminal_namespace_project_environment_path(project.namespace, project, environment)
+ visit terminal_project_environment_path(project, environment)
end
it 'displays a web terminal' do
@@ -194,9 +194,7 @@ feature 'Environment', :feature do
name: 'staging-1.0/review',
state: :available)
- visit folder_namespace_project_environments_path(project.namespace,
- project,
- id: 'staging-1.0')
+ visit folder_project_environments_path(project, id: 'staging-1.0')
end
it 'renders a correct environment folder' do
@@ -221,7 +219,7 @@ feature 'Environment', :feature do
end
scenario 'user deletes the branch with running environment' do
- visit namespace_project_branches_path(project.namespace, project, search: 'feature')
+ visit project_branches_path(project, search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
@@ -249,12 +247,10 @@ feature 'Environment', :feature do
end
def visit_environment(environment)
- visit namespace_project_environment_path(environment.project.namespace,
- environment.project,
- environment)
+ visit project_environment_path(environment.project, environment)
end
def have_terminal_button
- have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ have_link(nil, href: terminal_project_environment_path(project, environment))
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index cf4d996a32d..e40e0b0c871 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Environments page', :feature, :js do
+feature 'Environments page', :js do
given(:project) { create(:empty_project) }
given(:user) { create(:user) }
given(:role) { :developer }
background do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
given!(:environment) { }
@@ -29,7 +29,7 @@ feature 'Environments page', :feature, :js do
describe 'in available tab page' do
it 'should show one environment' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ visit project_environments_path(project, scope: 'available')
expect(page).to have_css('.environments-container')
expect(page.all('.environment-name').length).to eq(1)
end
@@ -37,7 +37,7 @@ feature 'Environments page', :feature, :js do
describe 'in stopped tab page' do
it 'should show no environments' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ visit project_environments_path(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
expect(page).to have_content('You don\'t have any environments right now')
end
@@ -49,7 +49,7 @@ feature 'Environments page', :feature, :js do
describe 'in available tab page' do
it 'should show no environments' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'available')
+ visit project_environments_path(project, scope: 'available')
expect(page).to have_css('.environments-container')
expect(page).to have_content('You don\'t have any environments right now')
end
@@ -57,7 +57,7 @@ feature 'Environments page', :feature, :js do
describe 'in stopped tab page' do
it 'should show one environment' do
- visit namespace_project_environments_path(project.namespace, project, scope: 'stopped')
+ visit project_environments_path(project, scope: 'stopped')
expect(page).to have_css('.environments-container')
expect(page.all('.environment-name').length).to eq(1)
end
@@ -277,10 +277,10 @@ feature 'Environments page', :feature, :js do
end
def have_terminal_button
- have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ have_link(nil, href: terminal_project_environment_path(project, environment))
end
def visit_environments(project)
- visit namespace_project_environments_path(project.namespace, project)
+ visit project_environments_path(project)
end
end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index db2790a4bce..16b0fa6e1ae 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Edit Project Settings', feature: true do
+describe 'Edit Project Settings' do
let(:member) { create(:user) }
let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') }
let!(:issue) { create(:issue, project: project) }
@@ -9,7 +9,7 @@ describe 'Edit Project Settings', feature: true do
describe 'project features visibility selectors', js: true do
before do
project.team << [member, :master]
- gitlab_sign_in(member)
+ sign_in(member)
end
tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
@@ -17,7 +17,7 @@ describe 'Edit Project Settings', feature: true do
tools.each do |tool_name, shortcut_name|
describe "feature #{tool_name}" do
it 'toggles visibility' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
@@ -39,20 +39,31 @@ describe 'Edit Project Settings', feature: true do
end
end
- context "When external issue tracker is enabled" do
- it "does not hide issues tab" do
- project.project_feature.update(issues_access_level: ProjectFeature::DISABLED)
+ context 'When external issue tracker is enabled and issues enabled on project settings' do
+ it 'does not hide issues tab' do
allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
- expect(page).to have_selector(".shortcuts-issues")
+ expect(page).to have_selector('.shortcuts-issues')
+ end
+ end
+
+ context 'When external issue tracker is enabled and issues disabled on project settings' do
+ it 'hides issues tab' do
+ project.issues_enabled = false
+ project.save!
+ allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new)
+
+ visit project_path(project)
+
+ expect(page).not_to have_selector('.shortcuts-issues')
end
end
context "pipelines subtabs" do
it "shows builds when enabled" do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
expect(page).to have_selector(".shortcuts-builds")
end
@@ -60,7 +71,7 @@ describe 'Edit Project Settings', feature: true do
it "hides builds when disabled" do
allow(Ability).to receive(:allowed?).with(member, :read_builds, project).and_return(false)
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
expect(page).not_to have_selector(".shortcuts-builds")
end
@@ -73,17 +84,17 @@ describe 'Edit Project Settings', feature: true do
let(:tools) do
{
- builds: namespace_project_job_path(project.namespace, project, job),
- issues: namespace_project_issues_path(project.namespace, project),
- wiki: namespace_project_wiki_path(project.namespace, project, :home),
- snippets: namespace_project_snippets_path(project.namespace, project),
- merge_requests: namespace_project_merge_requests_path(project.namespace, project)
+ builds: project_job_path(project, job),
+ issues: project_issues_path(project),
+ wiki: project_wiki_path(project, :home),
+ snippets: project_snippets_path(project),
+ merge_requests: project_merge_requests_path(project)
}
end
context 'normal user' do
before do
- gitlab_sign_in(member)
+ sign_in(member)
end
it 'renders 200 if tool is enabled' do
@@ -130,7 +141,7 @@ describe 'Edit Project Settings', feature: true do
context 'admin user' do
before do
non_member.update_attribute(:admin, true)
- gitlab_sign_in(non_member)
+ sign_in(non_member)
end
it 'renders 404 if feature is disabled' do
@@ -156,8 +167,8 @@ describe 'Edit Project Settings', feature: true do
describe 'repository visibility', js: true do
before do
project.team << [member, :master]
- gitlab_sign_in(member)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit edit_project_path(project)
end
it "disables repository related features" do
@@ -174,7 +185,7 @@ describe 'Edit Project Settings', feature: true do
click_button "Save changes"
wait_for_requests
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content "Customize your workflow!"
end
@@ -187,7 +198,7 @@ describe 'Edit Project Settings', feature: true do
click_button "Save changes"
wait_for_requests
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
page.within(".event-filter") do
expect(page).to have_selector("a", count: 2)
@@ -205,7 +216,7 @@ describe 'Edit Project Settings', feature: true do
expect(page).to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select "Disabled", from: "project_project_feature_attributes_merge_requests_access_level"
@@ -213,7 +224,7 @@ describe 'Edit Project Settings', feature: true do
expect(page).to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select "Disabled", from: "project_project_feature_attributes_repository_access_level"
@@ -221,14 +232,14 @@ describe 'Edit Project Settings', feature: true do
expect(page).not_to have_content("Comments")
end
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
def save_changes_and_check_activity_tab
click_button "Save changes"
wait_for_requests
- visit activity_namespace_project_path(project.namespace, project)
+ visit activity_project_path(project)
page.within(".event-filter") do
yield
@@ -242,8 +253,8 @@ describe 'Edit Project Settings', feature: true do
before do
project.team << [member, :guest]
- gitlab_sign_in(member)
- visit namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit project_path(project)
end
it "does not show project statistic for guest" do
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 34aef958ec6..77b2fd4f80f 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'user browses project', feature: true, js: true do
+feature 'user browses project', js: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tree_path(project.namespace, project, project.default_branch)
+ sign_in(user)
+ visit project_tree_path(project, project.default_branch)
end
scenario "can see blame of '.gitignore'" do
diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb
index 2a1cc01fe68..d49e1541869 100644
--- a/spec/features/projects/files/creating_a_file_spec.rb
+++ b/spec/features/projects/files/creating_a_file_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'User wants to create a file', feature: true do
+feature 'User wants to create a file' do
let(:project) { create(:project) }
let(:user) { create(:user) }
background do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+ sign_in user
+ visit project_new_blob_path(project, project.default_branch)
end
def submit_new_file(options)
@@ -30,11 +30,6 @@ feature 'User wants to create a file', feature: true do
expect(page).to have_content 'The file has been successfully created'
end
- scenario 'file name contains invalid characters' do
- submit_new_file(file_name: '\\')
- expect(page).to have_content 'Path can contain only'
- end
-
scenario 'file name contains directory traversal' do
submit_new_file(file_name: '../README.md')
expect(page).to have_content 'Path cannot include directory traversal'
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 4f1b8588462..6d44c49bb6f 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
require 'fileutils'
-feature 'User wants to add a Dockerfile file', feature: true do
+feature 'User wants to add a Dockerfile file' do
before do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'Dockerfile')
+ visit project_new_blob_path(project, 'master', file_name: 'Dockerfile')
end
scenario 'user can see Dockerfile dropdown' do
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 60182bfebe9..25168203d15 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download buttons in files tree', feature: true do
+feature 'Download buttons in files tree' do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
@@ -22,21 +22,18 @@ feature 'Download buttons in files tree', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when files tree' do
context 'with artifacts' do
before do
- visit namespace_project_tree_path(
- project.namespace, project, project.default_branch)
+ visit project_tree_path(project, project.default_branch)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{project.default_branch}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index 6e361ac4312..8a4511df842 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'User uses soft wrap whilst editing file', feature: true, js: true do
+feature 'User uses soft wrap whilst editing file', js: true do
before do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: 'test_file-name')
+ sign_in user
+ visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
editor = find('.file-editor.code')
editor.click
editor.send_keys 'Touch water with paw then recoil in horror chase dog then
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index e97ff5fded7..e084ab17441 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'User wants to edit a file', feature: true do
+feature 'User wants to edit a file' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:commit_params) do
@@ -17,8 +17,8 @@ feature 'User wants to edit a file', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_edit_blob_path(project.namespace, project,
+ sign_in user
+ visit project_edit_blob_path(project,
File.join(project.default_branch, '.gitignore'))
end
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 83a837fba44..702b99de733 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'User views files page', feature: true do
+feature 'User views files page' do
let(:user) { create(:user) }
let(:project) { create(:forked_project_with_submodules) }
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ sign_in user
+ visit project_tree_path(project, project.repository.root_ref)
end
scenario 'user sees folders and submodules sorted together, followed by files' do
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index 6a914820ac9..d7e96811e33 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-feature 'Find file keyboard shortcuts', feature: true, js: true do
+feature 'Find file keyboard shortcuts', js: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
- visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
+ visit project_find_file_path(project, project.repository.root_ref)
wait_for_requests
end
diff --git a/spec/features/projects/files/find_files_spec.rb b/spec/features/projects/files/find_files_spec.rb
index 166ec5c921b..05003f6a4f5 100644
--- a/spec/features/projects/files/find_files_spec.rb
+++ b/spec/features/projects/files/find_files_spec.rb
@@ -1,29 +1,22 @@
require 'spec_helper'
-feature 'Find files button in the tree header', feature: true do
+feature 'Find files button in the tree header' do
given(:user) { create(:user) }
given(:project) { create(:project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
scenario 'project main screen' do
- visit namespace_project_path(
- project.namespace,
- project
- )
+ visit project_path(project)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
scenario 'project tree screen' do
- visit namespace_project_tree_path(
- project.namespace,
- project,
- project.default_branch
- )
+ visit project_tree_path(project, project.default_branch)
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
end
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 7f02ec6b73d..4340c98d0d9 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'User wants to add a .gitignore file', feature: true do
+feature 'User wants to add a .gitignore file' do
before do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ sign_in user
+ visit project_new_blob_path(project, 'master', file_name: '.gitignore')
end
scenario 'user can see .gitignore dropdown' do
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index f4b17c2518c..f42a26b6954 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'User wants to add a .gitlab-ci.yml file', feature: true do
+feature 'User wants to add a .gitlab-ci.yml file' do
before do
user = create(:user)
project = create(:project)
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+ sign_in user
+ visit project_new_blob_path(project, 'master', file_name: '.gitlab-ci.yml')
end
scenario 'user can see .gitlab-ci.yml dropdown' do
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 7daf016dd22..300b631a2f4 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -1,18 +1,18 @@
require 'spec_helper'
-feature 'project owner creates a license file', feature: true, js: true do
+feature 'project owner creates a license file', js: true do
let(:project_master) { create(:user) }
let(:project) { create(:project) }
background do
project.repository.delete_file(project_master, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
project.team << [project_master, :master]
- gitlab_sign_in(project_master)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project_master)
+ visit project_path(project)
end
scenario 'project master creates a license file manually from a template' do
- visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ visit project_tree_path(project, project.repository.root_ref)
find('.add-to-tree').click
click_link 'New file'
@@ -30,7 +30,7 @@ feature 'project owner creates a license file', feature: true, js: true do
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
@@ -40,7 +40,7 @@ feature 'project owner creates a license file', feature: true, js: true do
expect(page).to have_content('New file')
expect(current_path).to eq(
- namespace_project_new_blob_path(project.namespace, project, 'master'))
+ project_new_blob_path(project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector')
@@ -54,7 +54,7 @@ feature 'project owner creates a license file', feature: true, js: true do
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index eab19d52030..1f4b3763b40 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -1,21 +1,21 @@
require 'spec_helper'
-feature 'project owner sees a link to create a license file in empty project', feature: true, js: true do
+feature 'project owner sees a link to create a license file in empty project', js: true do
let(:project_master) { create(:user) }
let(:project) { create(:empty_project) }
background do
project.team << [project_master, :master]
- gitlab_sign_in(project_master)
+ sign_in(project_master)
end
scenario 'project master creates a license file from a template' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
expect(page).to have_content('New file')
expect(current_path).to eq(
- namespace_project_new_blob_path(project.namespace, project, 'master'))
+ project_new_blob_path(project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
expect(page).to have_selector('.license-selector')
@@ -31,7 +31,7 @@ feature 'project owner sees a link to create a license file in empty project', f
click_button 'Commit changes'
expect(current_path).to eq(
- namespace_project_blob_path(project.namespace, project, 'master/LICENSE'))
+ project_blob_path(project, 'master/LICENSE'))
expect(page).to have_content('MIT License')
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 028a0919640..a0846643269 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -6,7 +6,7 @@ feature 'Template type dropdown selector', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'editing a non-matching file' do
@@ -31,7 +31,7 @@ feature 'Template type dropdown selector', js: true do
context 'editing a matching file' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, 'LICENSE'))
+ visit project_edit_blob_path(project, File.join(project.default_branch, 'LICENSE'))
end
scenario 'displayed' do
@@ -61,7 +61,7 @@ feature 'Template type dropdown selector', js: true do
context 'creating a matching file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitignore')
+ visit project_new_blob_path(project, 'master', file_name: '.gitignore')
end
scenario 'is displayed' do
@@ -79,7 +79,7 @@ feature 'Template type dropdown selector', js: true do
context 'creating a file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, project.default_branch)
+ visit project_new_blob_path(project, project.default_branch)
end
scenario 'type selector is shown' do
@@ -129,7 +129,7 @@ def check_type_selector_toggle_text(template_type)
end
def create_and_edit_file(file_name)
- visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: file_name)
+ visit project_new_blob_path(project, 'master', file_name: file_name)
click_button "Commit changes"
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, file_name))
+ visit project_edit_blob_path(project, File.join(project.default_branch, file_name))
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 4ccd123f46e..d50ddb1f1a9 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -6,12 +6,12 @@ feature 'Template Undo Button', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'editing a matching file and applying a template' do
before do
- visit namespace_project_edit_blob_path(project.namespace, project, File.join(project.default_branch, "LICENSE"))
+ visit project_edit_blob_path(project, File.join(project.default_branch, "LICENSE"))
select_file_template('.js-license-selector', 'Apache License 2.0')
end
@@ -22,7 +22,7 @@ feature 'Template Undo Button', js: true do
context 'creating a non-matching file' do
before do
- visit namespace_project_new_blob_path(project.namespace, project, 'master')
+ visit project_new_blob_path(project, 'master')
select_file_template_type('LICENSE')
select_file_template('.js-license-selector', 'Apache License 2.0')
end
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index aa4ed217a34..7bcd3f632a8 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe 'GFM autocomplete loading', feature: true, js: true do
+describe 'GFM autocomplete loading', js: true do
let(:project) { create(:project) }
before do
- gitlab_sign_in :admin
+ sign_in(create(:admin))
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'does not load on project#show' do
@@ -14,7 +14,7 @@ describe 'GFM autocomplete loading', feature: true, js: true do
end
it 'loads on new issue page' do
- visit new_namespace_project_issue_path(project.namespace, project)
+ visit new_project_issue_path(project)
expect(evaluate_script('gl.GfmAutoComplete.dataSources')).not_to eq({})
end
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index 778f5d61ae3..5195d027a9f 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Project group links', :feature, :js do
+feature 'Project group links', :js do
include Select2Helper
let(:master) { create(:user) }
@@ -9,12 +9,12 @@ feature 'Project group links', :feature, :js do
background do
project.add_master(master)
- gitlab_sign_in(master)
+ sign_in(master)
end
context 'setting an expiration date for a group link' do
before do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
click_on 'share-with-group-tab'
@@ -43,7 +43,7 @@ feature 'Project group links', :feature, :js do
end
it 'does not show ancestors', :nested_groups do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
click_on 'share-with-group-tab'
click_link 'Search for a group'
@@ -61,7 +61,7 @@ feature 'Project group links', :feature, :js do
group.add_owner(master)
group_two.add_owner(master)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
execute_script 'GroupsSelect.PER_PAGE = 1;'
open_select2 '#link_group_id'
end
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index e1f7f06c113..1c5f89fa898 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -7,11 +7,11 @@ describe 'Guest navigation menu' do
before do
project.team << [guest, :guest]
- gitlab_sign_in(guest)
+ sign_in(guest)
end
it 'shows allowed tabs only' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.layout-nav') do
expect(page).to have_content 'Project'
@@ -25,7 +25,7 @@ describe 'Guest navigation menu' do
end
it 'does not show fork button' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.count-buttons') do
expect(page).not_to have_link 'Fork'
@@ -33,7 +33,7 @@ describe 'Guest navigation menu' do
end
it 'does not show clone path' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
within('.project-repo-buttons') do
expect(page).not_to have_selector '.project-clone-holder'
@@ -49,7 +49,7 @@ describe 'Guest navigation menu' do
end
it 'does not show the project file list landing page' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_selector '.project-stats'
expect(page).not_to have_selector '.project-last-commit'
@@ -58,7 +58,7 @@ describe 'Guest navigation menu' do
end
it 'shows the customize workflow when issues and wiki are disabled' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.project-show-customize_workflow'
end
@@ -66,7 +66,7 @@ describe 'Guest navigation menu' do
it 'shows the wiki when enabled' do
project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.project-show-wiki'
end
@@ -74,7 +74,7 @@ describe 'Guest navigation menu' do
it 'shows the issues when enabled' do
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_selector '.issues-list'
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index b5c64777934..62d244ff259 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
# It looks up for any sensitive word inside the JSON, so if a sensitive word is found
# we''l have to either include it adding the model that includes it to the +safe_list+
# or make sure the attribute is blacklisted in the +import_export.yml+ configuration
-feature 'Import/Export - project export integration test', feature: true, js: true do
+feature 'Import/Export - project export integration test', js: true do
include Select2Helper
include ExportFileHelper
@@ -33,17 +33,17 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
context 'admin user' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'exports a project successfully' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Export project')
click_link 'Export project'
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Download export')
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 3f8d2255298..c0cfb9eafe2 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Import/Export - project import integration test', feature: true, js: true do
+feature 'Import/Export - project import integration test', js: true do
include Select2Helper
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
@@ -53,7 +53,6 @@ feature 'Import/Export - project import integration test', feature: true, js: tr
select2(namespace.id, from: '#project_namespace_id')
fill_in :project_path, with: project.name, visible: true
click_link 'GitLab export'
-
attach_file('file', file)
click_on 'Import project'
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index b0a68f0d61f..3917e72c39e 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Import/Export - Namespace export file cleanup', feature: true, js: true do
+feature 'Import/Export - Namespace export file cleanup', js: true do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
@@ -16,7 +16,7 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
context 'admin user' do
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
context 'moving the namespace' do
@@ -48,13 +48,13 @@ feature 'Import/Export - Namespace export file cleanup', feature: true, js: true
end
def setup_export_project
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Export project')
click_link 'Export project'
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
expect(page).to have_content('Download export')
end
diff --git a/spec/features/projects/issuable_counts_caching_spec.rb b/spec/features/projects/issuable_counts_caching_spec.rb
new file mode 100644
index 00000000000..703d1cbd327
--- /dev/null
+++ b/spec/features/projects/issuable_counts_caching_spec.rb
@@ -0,0 +1,132 @@
+require 'spec_helper'
+
+describe 'Issuable counts caching', :use_clean_rails_memory_store_caching do
+ let!(:member) { create(:user) }
+ let!(:member_2) { create(:user) }
+ let!(:non_member) { create(:user) }
+ let!(:project) { create(:empty_project, :public) }
+ let!(:open_issue) { create(:issue, project: project) }
+ let!(:confidential_issue) { create(:issue, :confidential, project: project, author: non_member) }
+ let!(:closed_issue) { create(:issue, :closed, project: project) }
+
+ before do
+ project.add_developer(member)
+ project.add_developer(member_2)
+ end
+
+ it 'caches issuable counts correctly for non-members' do
+ # We can't use expect_any_instance_of because that uses a single instance.
+ counts = 0
+
+ allow_any_instance_of(IssuesFinder).to receive(:count_by_state).and_wrap_original do |m, *args|
+ counts += 1
+
+ m.call(*args)
+ end
+
+ aggregate_failures 'only counts once on first load with no params, and caches for later loads' do
+ expect { visit project_issues_path(project) }
+ .to change { counts }.by(1)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+ end
+
+ aggregate_failures 'uses counts from cache on load from non-member' do
+ sign_in(non_member)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_out(non_member)
+ end
+
+ aggregate_failures 'does not use the same cache for a member' do
+ sign_in(member)
+
+ expect { visit project_issues_path(project) }
+ .to change { counts }.by(1)
+
+ sign_out(member)
+ end
+
+ aggregate_failures 'uses the same cache for all members' do
+ sign_in(member_2)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_out(member_2)
+ end
+
+ aggregate_failures 'shares caches when params are passed' do
+ expect { visit project_issues_path(project, author_username: non_member.username) }
+ .to change { counts }.by(1)
+
+ sign_in(member)
+
+ expect { visit project_issues_path(project, author_username: non_member.username) }
+ .to change { counts }.by(1)
+
+ sign_in(non_member)
+
+ expect { visit project_issues_path(project, author_username: non_member.username) }
+ .not_to change { counts }
+
+ sign_in(member_2)
+
+ expect { visit project_issues_path(project, author_username: non_member.username) }
+ .not_to change { counts }
+
+ sign_out(member_2)
+ end
+
+ aggregate_failures 'resets caches on issue close' do
+ Issues::CloseService.new(project, member).execute(open_issue)
+
+ expect { visit project_issues_path(project) }
+ .to change { counts }.by(1)
+
+ sign_in(member)
+
+ expect { visit project_issues_path(project) }
+ .to change { counts }.by(1)
+
+ sign_in(non_member)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_in(member_2)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_out(member_2)
+ end
+
+ aggregate_failures 'does not reset caches on issue update' do
+ Issues::UpdateService.new(project, member, title: 'new title').execute(open_issue)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_in(member)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_in(non_member)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_in(member_2)
+
+ expect { visit project_issues_path(project) }
+ .not_to change { counts }
+
+ sign_out(member_2)
+ end
+ end
+end
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 26a09985312..8c2e49377e7 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'issuable templates', feature: true, js: true do
+feature 'issuable templates', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
project.team << [user, :master]
- gitlab_sign_in user
+ sign_in user
end
context 'user creates an issue using templates' do
@@ -28,7 +28,7 @@ feature 'issuable templates', feature: true, js: true do
longtemplate_content,
message: 'added issue template',
branch_name: 'master')
- visit edit_namespace_project_issue_path project.namespace, project, issue
+ visit edit_project_issue_path project, issue
fill_in :'issue[title]', with: 'test issue title'
end
@@ -81,7 +81,7 @@ feature 'issuable templates', feature: true, js: true do
template_content,
message: 'added issue template',
branch_name: 'master')
- visit edit_namespace_project_issue_path project.namespace, project, issue
+ visit edit_project_issue_path project, issue
fill_in :'issue[title]', with: 'test issue title'
fill_in :'issue[description]', with: prior_description
end
@@ -105,7 +105,7 @@ feature 'issuable templates', feature: true, js: true do
template_content,
message: 'added merge request template',
branch_name: 'master')
- visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+ visit edit_project_merge_request_path project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
@@ -124,18 +124,21 @@ feature 'issuable templates', feature: true, js: true do
let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
background do
- gitlab_sign_out
+ sign_out(:user)
+
project.team << [fork_user, :developer]
fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
- gitlab_sign_in fork_user
+
+ sign_in(fork_user)
+
project.repository.create_file(
fork_user,
'.gitlab/merge_request_templates/feature-proposal.md',
template_content,
message: 'added merge request template',
branch_name: 'master')
- visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
+ visit edit_project_merge_request_path project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
diff --git a/spec/features/projects/issues/list_spec.rb b/spec/features/projects/issues/list_spec.rb
index b2db07a75ef..c2ca62508a4 100644
--- a/spec/features/projects/issues/list_spec.rb
+++ b/spec/features/projects/issues/list_spec.rb
@@ -7,13 +7,13 @@ feature 'Issues List' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user does not see create new list button' do
create(:issue, project: project)
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).not_to have_selector('.js-new-board-list')
end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index 38733d39932..d274a1760a4 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -2,17 +2,18 @@ require 'spec_helper'
feature 'Project Issues RSS' do
let(:project) { create(:empty_project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_issues_path(project.namespace, project) }
+ let(:path) { project_issues_path(project) }
before do
create(:issue, project: project)
end
context 'when signed in' do
+ let(:user) { create(:user) }
+
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 070cdbf1cef..395d68d3fb4 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
require 'tempfile'
-feature 'Jobs', :feature do
+feature 'Jobs' do
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project) }
- let(:namespace) { project.namespace }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:job) { create(:ci_build, :trace, pipeline: pipeline) }
@@ -17,7 +16,7 @@ feature 'Jobs', :feature do
before do
project.team << [user, user_access_level]
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "GET /:project/jobs" do
@@ -25,7 +24,7 @@ feature 'Jobs', :feature do
context "Pending scope" do
before do
- visit namespace_project_jobs_path(project.namespace, project, scope: :pending)
+ visit project_jobs_path(project, scope: :pending)
end
it "shows Pending tab jobs" do
@@ -40,7 +39,7 @@ feature 'Jobs', :feature do
context "Running scope" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project, scope: :running)
+ visit project_jobs_path(project, scope: :running)
end
it "shows Running tab jobs" do
@@ -55,7 +54,7 @@ feature 'Jobs', :feature do
context "Finished scope" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project, scope: :finished)
+ visit project_jobs_path(project, scope: :finished)
end
it "shows Finished tab jobs" do
@@ -68,7 +67,7 @@ feature 'Jobs', :feature do
context "All jobs" do
before do
project.builds.running_or_pending.each(&:success)
- visit namespace_project_jobs_path(project.namespace, project)
+ visit project_jobs_path(project)
end
it "shows All tab jobs" do
@@ -82,7 +81,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:jobs_url) do
- namespace_project_jobs_path(project.namespace, project)
+ project_jobs_path(project)
end
before do
@@ -98,7 +97,7 @@ feature 'Jobs', :feature do
describe "POST /:project/jobs/:id/cancel_all" do
before do
job.run!
- visit namespace_project_jobs_path(project.namespace, project)
+ visit project_jobs_path(project)
click_link "Cancel running"
end
@@ -117,7 +116,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows status name', :js do
@@ -140,7 +139,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, pipeline: pipeline) }
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows retry button' do
@@ -157,7 +156,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :failed, pipeline: pipeline) }
before do
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows New issue button' do
@@ -166,10 +165,10 @@ feature 'Jobs', :feature do
it 'links to issues/new with the title and description filled in' do
button_title = "Build Failed ##{job.id}"
- job_path = namespace_project_job_path(namespace, project, job)
+ job_path = project_job_path(project, job)
options = { issue: { title: button_title, description: job_path } }
- href = new_namespace_project_issue_path(namespace, project, options)
+ href = new_project_issue_path(project, options)
page.within('.header-action-buttons') do
expect(find('.js-new-issue')['href']).to include(href)
@@ -180,7 +179,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit namespace_project_job_path(project.namespace, project, job2)
+ visit project_job_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
@@ -189,7 +188,7 @@ feature 'Jobs', :feature do
context "Download artifacts" do
before do
job.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'has button to download artifacts' do
@@ -202,7 +201,7 @@ feature 'Jobs', :feature do
job.update_attributes(artifacts_file: artifacts_file,
artifacts_expire_at: expire_at)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
context 'no expire date defined' do
@@ -248,7 +247,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:job_url) do
- namespace_project_job_path(project.namespace, project, job)
+ project_job_path(project, job)
end
before do
@@ -264,7 +263,7 @@ feature 'Jobs', :feature do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it do
@@ -276,7 +275,7 @@ feature 'Jobs', :feature do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
context 'when job has an initial trace' do
@@ -300,7 +299,7 @@ feature 'Jobs', :feature do
end
before do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
end
it 'shows variable key and value after click', js: true do
@@ -325,7 +324,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link environment.name
end
@@ -335,7 +334,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link environment.name
end
@@ -346,7 +345,7 @@ feature 'Jobs', :feature do
let(:job) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
expect(page).to have_link('latest deployment')
end
@@ -358,7 +357,7 @@ feature 'Jobs', :feature do
context "Job from project" do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-cancel-job').click()
end
@@ -373,7 +372,7 @@ feature 'Jobs', :feature do
context "Job from project", :js do
before do
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-cancel-job').click()
find('.js-retry-button').trigger('click')
end
@@ -392,9 +391,9 @@ feature 'Jobs', :feature do
job.cancel!
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- gitlab_sign_out_direct
- gitlab_sign_in(create(:user))
- visit namespace_project_job_path(project.namespace, project, job)
+ sign_out(:user)
+ sign_in(create(:user))
+ visit project_job_path(project, job)
end
it 'does not show the Retry button' do
@@ -408,14 +407,14 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/download" do
before do
job.update_attributes(artifacts_file: artifacts_file)
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
click_link 'Download'
end
context "Build from other project" do
before do
job2.update_attributes(artifacts_file: artifacts_file)
- visit download_namespace_project_job_artifacts_path(project.namespace, project, job2)
+ visit download_project_job_artifacts_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
@@ -428,7 +427,7 @@ feature 'Jobs', :feature do
before do
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job.run!
- visit namespace_project_job_path(project.namespace, project, job)
+ visit project_job_path(project, job)
find('.js-raw-link-controller').click()
end
@@ -443,7 +442,7 @@ feature 'Jobs', :feature do
before do
Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job2.run!
- visit raw_namespace_project_job_path(project.namespace, project, job2)
+ visit raw_project_job_path(project, job2)
end
it 'sends the right headers' do
@@ -467,7 +466,7 @@ feature 'Jobs', :feature do
.to receive(:paths)
.and_return([existing_file])
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
find('.js-raw-link-controller').click
end
@@ -485,7 +484,7 @@ feature 'Jobs', :feature do
.to receive(:paths)
.and_return([])
- visit namespace_project_job_path(namespace, project, job)
+ visit project_job_path(project, job)
end
it 'sends the right headers' do
@@ -496,7 +495,7 @@ feature 'Jobs', :feature do
context "when visiting old URL" do
let(:raw_job_url) do
- raw_namespace_project_job_path(project.namespace, project, job)
+ raw_project_job_path(project, job)
end
before do
@@ -512,7 +511,7 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/trace.json" do
context "Job from project" do
before do
- visit trace_namespace_project_job_path(project.namespace, project, job, format: :json)
+ visit trace_project_job_path(project, job, format: :json)
end
it { expect(page.status_code).to eq(200) }
@@ -520,7 +519,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit trace_namespace_project_job_path(project.namespace, project, job2, format: :json)
+ visit trace_project_job_path(project, job2, format: :json)
end
it { expect(page.status_code).to eq(404) }
@@ -530,7 +529,7 @@ feature 'Jobs', :feature do
describe "GET /:project/jobs/:id/status" do
context "Job from project" do
before do
- visit status_namespace_project_job_path(project.namespace, project, job)
+ visit status_project_job_path(project, job)
end
it { expect(page.status_code).to eq(200) }
@@ -538,7 +537,7 @@ feature 'Jobs', :feature do
context "Job from other project" do
before do
- visit status_namespace_project_job_path(project.namespace, project, job2)
+ visit status_project_job_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 2c47758f30e..0292a3192d8 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Issue prioritization', feature: true do
+feature 'Issue prioritization' do
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
@@ -28,8 +28,8 @@ feature 'Issue prioritization', feature: true do
issue_2.labels << label_4
issue_1.labels << label_5
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+ sign_in user
+ visit project_issues_path(project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
@@ -67,8 +67,8 @@ feature 'Issue prioritization', feature: true do
issue_4.labels << label_4 # 7
issue_6.labels << label_5 # 8 - No priority
- gitlab_sign_in user
- visit namespace_project_issues_path(project.namespace, project, sort: 'label_priority')
+ sign_in user
+ visit project_issues_path(project, sort: 'label_priority')
expect(page).to have_selector('.dropdown-toggle', text: 'Label priority')
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 584dc294f05..3115a643d5d 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Labels subscription', feature: true do
+feature 'Labels subscription' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:empty_project, :public, namespace: group) }
@@ -10,11 +10,11 @@ feature 'Labels subscription', feature: true do
context 'when signed in' do
before do
project.team << [user, :developer]
- gitlab_sign_in user
+ sign_in user
end
scenario 'users can subscribe/unsubscribe to labels', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('bug')
expect(page).to have_content('feature')
@@ -55,7 +55,7 @@ feature 'Labels subscription', feature: true do
context 'when not signed in' do
it 'users can not subscribe/unsubscribe to labels' do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 589bfb9fbc9..223f94ff9f9 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Prioritize labels', feature: true do
+feature 'Prioritize labels' do
include DragTo
let(:user) { create(:user) }
@@ -14,11 +14,11 @@ feature 'Prioritize labels', feature: true do
before do
project.team << [user, :developer]
- gitlab_sign_in user
+ sign_in user
end
scenario 'user can prioritize a group label', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('Star labels to start sorting by priority')
@@ -37,7 +37,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can unprioritize a group label', js: true do
create(:label_priority, project: project, label: feature, priority: 1)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
page.within('.prioritized-labels') do
expect(page).to have_content('feature')
@@ -53,7 +53,7 @@ feature 'Prioritize labels', feature: true do
end
scenario 'user can prioritize a project label', js: true do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content('Star labels to start sorting by priority')
@@ -72,7 +72,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can unprioritize a project label', js: true do
create(:label_priority, project: project, label: bug, priority: 1)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
page.within('.prioritized-labels') do
expect(page).to have_content('bug')
@@ -92,7 +92,7 @@ feature 'Prioritize labels', feature: true do
create(:label_priority, project: project, label: bug, priority: 1)
create(:label_priority, project: project, label: feature, priority: 2)
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
@@ -114,31 +114,39 @@ feature 'Prioritize labels', feature: true do
expect(page.all('li').last).to have_content('bug')
end
end
+
+ it 'shows a help message about prioritized labels' do
+ visit project_labels_path(project)
+
+ expect(page).to have_content 'Star a label'
+ end
end
context 'as a guest' do
it 'does not prioritize labels' do
guest = create(:user)
- gitlab_sign_in guest
+ sign_in guest
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
expect(page).to have_content 'feature'
expect(page).not_to have_css('.prioritized-labels')
+ expect(page).not_to have_content 'Star a label'
end
end
context 'as a non signed in user' do
it 'does not prioritize labels' do
- visit namespace_project_labels_path(project.namespace, project)
+ visit project_labels_path(project)
expect(page).to have_content 'bug'
expect(page).to have_content 'wontfix'
expect(page).to have_content 'feature'
expect(page).not_to have_css('.prioritized-labels')
+ expect(page).not_to have_content 'Star a label'
end
end
end
diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb
index 514453db472..a8ae0ecbae0 100644
--- a/spec/features/projects/main/download_buttons_spec.rb
+++ b/spec/features/projects/main/download_buttons_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download buttons in project main page', feature: true do
+feature 'Download buttons in project main page' do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
@@ -22,20 +22,18 @@ feature 'Download buttons in project main page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking project main page' do
context 'with artifacts' do
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{project.default_branch}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index fee8cfe2c33..7914180b951 100644
--- a/spec/features/projects/main/rss_spec.rb
+++ b/spec/features/projects/main/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_path(project.namespace, project) }
+ let(:path) { project_path(project) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index d82cf53c690..a26e7becdb9 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Anonymous user sees members', feature: true do
+feature 'Projects > Members > Anonymous user sees members' do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:empty_project, :public) }
@@ -11,10 +11,10 @@ feature 'Projects > Members > Anonymous user sees members', feature: true do
end
scenario "anonymous user visits the project's members page and sees the list of members" do
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect(current_path).to eq(
- namespace_project_settings_members_path(project.namespace, project))
+ project_project_members_path(project))
expect(page).to have_content(user.name)
end
end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index 00d2a27597b..acda5808313 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Anonymous user sees members', feature: true, js: true do
+feature 'Projects > Members > Anonymous user sees members', js: true do
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:empty_project, :public) }
@@ -9,8 +9,8 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
project.team << [user, :master]
@group_link = create(:project_group_link, project: project, group: group)
- gitlab_sign_in(user)
- visit namespace_project_settings_members_path(project.namespace, project)
+ sign_in(user)
+ visit project_settings_members_path(project)
end
it 'updates group access level' do
@@ -22,7 +22,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
wait_for_requests
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
expect(first('.group_member')).to have_content('Guest')
end
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index 7e71dbc24c0..6b450fa4e45 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-feature 'Projects > Members > Group member cannot leave group project', feature: true do
+feature 'Projects > Members > Group member cannot leave group project' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
background do
group.add_developer(user)
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index 60a5cd9ec63..296a80a3c60 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Group member cannot request access to his group project', feature: true do
+feature 'Projects > Members > Group member cannot request access to his group project' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
@@ -41,7 +41,7 @@ feature 'Projects > Members > Group member cannot request access to his group pr
end
def login_and_visit_project_page(user)
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
end
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 76fe6a00dab..154f9f4a26c 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects members', feature: true do
+feature 'Projects members' do
let(:user) { create(:user) }
let(:developer) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
@@ -13,13 +13,13 @@ feature 'Projects members', feature: true do
background do
project.team << [developer, :developer]
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'with a group invitee' do
before do
group_invitee
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'does not appear in the project members page' do
@@ -33,7 +33,7 @@ feature 'Projects members', feature: true do
before do
group_invitee
project_invitee
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'shows the project invitee, the project developer, and the group owner' do
@@ -54,7 +54,7 @@ feature 'Projects members', feature: true do
context 'with a group requester' do
before do
group.request_access(group_requester)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'does not appear in the project members page' do
@@ -68,7 +68,7 @@ feature 'Projects members', feature: true do
before do
group.request_access(group_requester)
project.request_access(project_requester)
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
scenario 'shows the project requester, the project developer, and the group owner' do
diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
index 66da28b07fe..c8988aa63a7 100644
--- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
+++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Group requester cannot request access to project', feature: true, js: true do
+feature 'Projects > Members > Group requester cannot request access to project', js: true do
let(:user) { create(:user) }
let(:owner) { create(:user) }
let(:group) { create(:group, :public, :access_requestable) }
@@ -8,10 +8,10 @@ feature 'Projects > Members > Group requester cannot request access to project',
background do
group.add_owner(owner)
- gitlab_sign_in(user)
+ sign_in(user)
visit group_path(group)
perform_enqueued_jobs { click_link 'Request Access' }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
scenario 'group requester does not see the request access / withdraw access request button' do
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index 9fdd7df0ee5..237c059e595 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Project members list', feature: true do
+feature 'Project members list' do
include Select2Helper
let(:user1) { create(:user, name: 'John Doe') }
@@ -9,7 +9,7 @@ feature 'Project members list', feature: true do
let(:project) { create(:project, namespace: group) }
background do
- gitlab_sign_in(user1)
+ sign_in(user1)
group.add_owner(user1)
end
@@ -85,6 +85,6 @@ feature 'Project members list', feature: true do
end
def visit_members_page
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_settings_members_path(project)
end
end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 21b48b7fdd1..cd621b6b3ce 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Master adds member with expiration date', feature: true, js: true do
+feature 'Projects > Members > Master adds member with expiration date', js: true do
include Select2Helper
include ActiveSupport::Testing::TimeHelpers
@@ -10,13 +10,13 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
background do
project.team << [master, :master]
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'expiration date is displayed in the members list' do
travel_to Time.zone.parse('2016-08-06 08:00') do
date = 4.days.from_now
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
@@ -34,7 +34,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
travel_to Time.zone.parse('2016-08-06 08:00') do
date = 3.days.from_now
project.team.add_users([new_member.id], :developer, expires_at: Date.today.to_s(:medium))
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within "#project_member_#{new_member.project_members.first.id}" do
find('.js-access-expiration-date').set date.to_s(:medium)
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index bd445e27243..0f96a7cc70d 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Master manages access requests', feature: true do
+feature 'Projects > Members > Master manages access requests' do
let(:user) { create(:user) }
let(:master) { create(:user) }
let(:project) { create(:empty_project, :public, :access_requestable) }
@@ -8,17 +8,17 @@ feature 'Projects > Members > Master manages access requests', feature: true do
background do
project.request_access(user)
project.team << [master, :master]
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'master can see access requests' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
end
scenario 'master can grant access' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
@@ -29,7 +29,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
end
scenario 'master can deny access' do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit project_project_members_path(project)
expect_visible_access_request(project, user)
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
index 703f5dff6b5..04806f8fd9e 100644
--- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Projects > Members > Member cannot request access to his project', feature: true do
+feature 'Projects > Members > Member cannot request access to his project' do
let(:member) { create(:user) }
let(:project) { create(:project) }
background do
project.team << [member, :developer]
- gitlab_sign_in(member)
- visit namespace_project_path(project.namespace, project)
+ sign_in(member)
+ visit project_path(project)
end
scenario 'member does not see the request access button' do
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index 8e1788f7f2a..e72283d3628 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Projects > Members > Member leaves project', feature: true do
+feature 'Projects > Members > Member leaves project' do
let(:user) { create(:user) }
let(:project) { create(:project) }
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'user leaves project' do
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
index 70e4bb19c0f..15162d01c44 100644
--- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Projects > Members > Owner cannot leave project', feature: true do
+feature 'Projects > Members > Owner cannot leave project' do
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project.owner)
+ visit project_path(project)
end
scenario 'user does not see a "Leave project" link' do
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
index 0cd7e3afeda..c27925c8dc4 100644
--- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Projects > Members > Owner cannot request access to his project', feature: true do
+feature 'Projects > Members > Owner cannot request access to his project' do
let(:project) { create(:project) }
background do
- gitlab_sign_in(project.owner)
- visit namespace_project_path(project.namespace, project)
+ sign_in(project.owner)
+ visit project_path(project)
end
scenario 'owner does not see the request access button' do
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index 66d98ef8b90..45c2647e6e2 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Members > Sorting', feature: true do
+feature 'Projects > Members > Sorting' do
let(:master) { create(:user, name: 'John Doe') }
let(:developer) { create(:user, name: 'Mary Jane', last_sign_in_at: 5.days.ago) }
let(:project) { create(:empty_project, namespace: master.namespace, creator: master) }
@@ -8,7 +8,7 @@ feature 'Projects > Members > Sorting', feature: true do
background do
create(:project_member, :developer, user: developer, project: project, created_at: 3.days.ago)
- gitlab_sign_in(master)
+ sign_in(master)
end
scenario 'sorts alphabetically by default' do
@@ -67,7 +67,7 @@ feature 'Projects > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
- scenario 'sorts by recent sign in', :redis do
+ scenario 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(master.name)
@@ -75,7 +75,7 @@ feature 'Projects > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
- scenario 'sorts by oldest sign in', :redis do
+ scenario 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
@@ -84,7 +84,7 @@ feature 'Projects > Members > Sorting', feature: true do
end
def visit_members_list(sort:)
- visit namespace_project_project_members_path(project.namespace.to_param, project, sort: sort)
+ visit project_project_members_path(project, sort: sort)
end
def first_member
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 081009f2325..2979563f33a 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -1,18 +1,18 @@
require 'spec_helper'
-feature 'Projects > Members > User requests access', feature: true do
+feature 'Projects > Members > User requests access' do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :access_requestable) }
let(:master) { project.owner }
background do
- gitlab_sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit project_path(project)
end
scenario 'request access feature is disabled' do
project.update_attributes(request_access_enabled: false)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_content 'Request Access'
end
@@ -35,7 +35,7 @@ feature 'Projects > Members > User requests access', feature: true do
project.project_feature.update!(repository_access_level: ProjectFeature::PRIVATE,
builds_access_level: ProjectFeature::PRIVATE,
merge_requests_access_level: ProjectFeature::PRIVATE)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content 'Request Access'
end
@@ -46,10 +46,11 @@ feature 'Projects > Members > User requests access', feature: true do
expect(project.requesters.exists?(user_id: user)).to be_truthy
- open_project_settings_menu
- click_link 'Members'
+ page.within('.layout-nav .nav-links') do
+ click_link('Members')
+ end
- visit namespace_project_settings_members_path(project.namespace, project)
+ visit project_project_members_path(project)
page.within('.content') do
expect(page).not_to have_content(user.name)
end
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 58054bbbbed..8cbd26551bc 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Merge Request button', feature: true do
+feature 'Merge Request button' do
shared_examples 'Merge request button only shown when allowed' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -10,27 +10,24 @@ feature 'Merge Request button', feature: true do
it 'does not show Create merge request button' do
visit url
- within("#content-body") do
- expect(page).not_to have_link(label)
- end
+ expect(page).not_to have_link(label)
end
end
context 'logged in as developer' do
before do
- gitlab_sign_in(user)
- project.team << [user, :developer]
+ sign_in(user)
+ project.add_developer(user)
end
it 'shows Create merge request button' do
- href = namespace_project_new_merge_request_path(project.namespace,
- project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ href = project_new_merge_request_path(project,
+ merge_request: { source_branch: 'feature',
+ target_branch: 'master' })
visit url
- within("#content-body") do
+ within('#content-body') do
expect(page).to have_link(label, href: href)
end
end
@@ -43,7 +40,7 @@ feature 'Merge Request button', feature: true do
it 'does not show Create merge request button' do
visit url
- within("#content-body") do
+ within('#content-body') do
expect(page).not_to have_link(label)
end
end
@@ -52,13 +49,13 @@ feature 'Merge Request button', feature: true do
context 'logged in as non-member' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
it 'does not show Create merge request button' do
visit url
- within("#content-body") do
+ within('#content-body') do
expect(page).not_to have_link(label)
end
end
@@ -67,10 +64,9 @@ feature 'Merge Request button', feature: true do
let(:user) { forked_project.owner }
it 'shows Create merge request button' do
- href = namespace_project_new_merge_request_path(forked_project.namespace,
- forked_project,
- merge_request: { source_branch: 'feature',
- target_branch: 'master' })
+ href = project_new_merge_request_path(forked_project,
+ merge_request: { source_branch: 'feature',
+ target_branch: 'master' })
visit fork_url
@@ -85,24 +81,24 @@ feature 'Merge Request button', feature: true do
context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' }
- let(:url) { namespace_project_branches_path(project.namespace, project, search: 'feature') }
- let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project, search: 'feature') }
+ let(:url) { project_branches_path(project, search: 'feature') }
+ let(:fork_url) { project_branches_path(forked_project, search: 'feature') }
end
end
context 'on compare page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Create merge request' }
- let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') }
- let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') }
+ let(:url) { project_compare_path(project, from: 'master', to: 'feature') }
+ let(:fork_url) { project_compare_path(forked_project, from: 'master', to: 'feature') }
end
end
context 'on commits page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Create merge request' }
- let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') }
- let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') }
+ let(:url) { project_commits_path(project, 'feature') }
+ let(:fork_url) { project_commits_path(forked_project, 'feature') }
end
end
end
diff --git a/spec/features/projects/merge_requests/list_spec.rb b/spec/features/projects/merge_requests/list_spec.rb
index 7ce3156215a..6548b4b83e6 100644
--- a/spec/features/projects/merge_requests/list_spec.rb
+++ b/spec/features/projects/merge_requests/list_spec.rb
@@ -7,34 +7,34 @@ feature 'Merge Requests List' do
background do
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user does not see create new list button' do
create(:merge_request, source_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).not_to have_selector('.js-new-board-list')
end
it 'should show an empty state' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_selector('.empty-state')
end
it 'empty state should have a create merge request button' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
- expect(page).to have_link 'New merge request', href: namespace_project_new_merge_request_path(project.namespace, project)
+ expect(page).to have_link 'New merge request', href: project_new_merge_request_path(project)
end
context 'if there are merge requests' do
before do
create(:merge_request, assignee: user, source_project: project)
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
end
it 'should not show an empty state' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index a02e4118784..b1c38ecc9ab 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -1,17 +1,17 @@
require 'spec_helper'
-feature 'Project milestone', :feature do
+feature 'Project milestone' do
let(:user) { create(:user) }
let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) }
let(:milestone) { create(:milestone, project: project) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when project has enabled issues' do
before do
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'shows issues tab' do
@@ -38,7 +38,7 @@ feature 'Project milestone', :feature do
context 'when project has disabled issues' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
it 'hides issues tab' do
@@ -68,7 +68,7 @@ feature 'Project milestone', :feature do
before do
create(:issue, project: project, milestone: milestone)
- visit namespace_project_milestone_path(project.namespace, project, milestone)
+ visit project_milestone_path(project, milestone)
end
describe 'the collapsed sidebar' do
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index 2350089255d..4bd1929ac1e 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Milestones sorting', :feature, :js do
+feature 'Milestones sorting', :js do
include SortingHelper
let(:user) { create(:user) }
let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) }
@@ -15,11 +15,11 @@ feature 'Milestones sorting', :feature, :js do
due_date: 11.days.from_now,
created_at: 1.hour.ago,
title: "bbb", project: project)
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'visit project milestones and sort by due_date_asc' do
- visit namespace_project_milestones_path(project.namespace, project)
+ visit project_milestones_path(project)
expect(page).to have_button('Due soon')
diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb
index 7403822c7fb..7cfcccda439 100644
--- a/spec/features/projects/milestones/new_spec.rb
+++ b/spec/features/projects/milestones/new_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Creating a new project milestone', :feature, :js do
+feature 'Creating a new project milestone', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project, name: 'test', namespace: user.namespace) }
before do
login_as(user)
- visit new_namespace_project_milestone_path(project.namespace, project)
+ visit new_project_milestone_path(project)
end
it 'description has autocomplete' do
diff --git a/spec/features/projects/no_password_spec.rb b/spec/features/projects/no_password_spec.rb
index 30a16e38e3c..d22a6daac08 100644
--- a/spec/features/projects/no_password_spec.rb
+++ b/spec/features/projects/no_password_spec.rb
@@ -6,7 +6,7 @@ feature 'No Password Alert' do
context 'with internal auth enabled' do
before do
sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
context 'when user has a password' do
@@ -30,14 +30,14 @@ feature 'No Password Alert' do
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'saml') }
before do
- stub_application_setting(signin_enabled?: false)
+ stub_application_setting(password_authentication_enabled?: false)
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
end
context 'when user has no personal access tokens' do
it 'has a personal access token alert' do
gitlab_sign_in_via('saml', user, 'my-uid')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
@@ -47,7 +47,7 @@ feature 'No Password Alert' do
it 'shows no alert' do
create(:personal_access_token, user: user)
gitlab_sign_in_via('saml', user, 'my-uid')
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).not_to have_content "You won't be able to pull or push project code via HTTP until you create a personal access token on your account"
end
@@ -59,7 +59,7 @@ feature 'No Password Alert' do
before do
sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows no alert' do
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index e9a3cfb7f60..42f23ee5dec 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Pages', feature: true do
+feature 'Pages' do
given(:project) { create(:empty_project) }
given(:user) { create(:user) }
given(:role) { :master }
@@ -10,12 +10,12 @@ feature 'Pages', feature: true do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
shared_examples 'no pages deployed' do
scenario 'does not see anything to destroy' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).not_to have_link('Remove pages')
expect(page).not_to have_text('Only the project owner can remove pages')
@@ -33,7 +33,7 @@ feature 'Pages', feature: true do
end
scenario 'sees "Remove pages" link' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).to have_link('Remove pages')
end
@@ -49,7 +49,7 @@ feature 'Pages', feature: true do
end
scenario 'sees "Only the project owner can remove pages" text' do
- visit namespace_project_pages_path(project.namespace, project)
+ visit project_pages_path(project)
expect(page).to have_text('Only the project owner can remove pages')
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index dfb973c37e5..47ed4c7e7c1 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Pipeline Schedules', :feature do
+feature 'Pipeline Schedules', :js do
include PipelineSchedulesHelper
let!(:project) { create(:project) }
@@ -9,141 +9,260 @@ feature 'Pipeline Schedules', :feature do
let(:scope) { nil }
let!(:user) { create(:user) }
- before do
- project.add_master(user)
+ context 'logged in as master' do
+ before do
+ project.add_master(user)
+ gitlab_sign_in(user)
+ end
- gitlab_sign_in(user)
- visit_page
- end
+ describe 'GET /projects/pipeline_schedules' do
+ before do
+ visit_pipelines_schedules
+ end
- describe 'GET /projects/pipeline_schedules' do
- let(:visit_page) { visit_pipelines_schedules }
+ describe 'The view' do
+ it 'displays the required information description' do
+ page.within('.pipeline-schedule-table-row') do
+ expect(page).to have_content('pipeline schedule')
+ expect(find(".next-run-cell time")['data-original-title'])
+ .to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y'))
+ expect(page).to have_link('master')
+ expect(page).to have_link("##{pipeline.id}")
+ end
+ end
- it 'avoids N + 1 queries' do
- control_count = ActiveRecord::QueryRecorder.new { visit_pipelines_schedules }.count
+ it 'creates a new scheduled pipeline' do
+ click_link 'New schedule'
- create_list(:ci_pipeline_schedule, 2, project: project)
+ expect(page).to have_content('Schedule a new pipeline')
+ end
- expect { visit_pipelines_schedules }.not_to exceed_query_limit(control_count)
- end
+ it 'changes ownership of the pipeline' do
+ click_link 'Take ownership'
+ page.within('.pipeline-schedule-table-row') do
+ expect(page).not_to have_content('No owner')
+ expect(page).to have_link('John Doe')
+ end
+ end
- describe 'The view' do
- it 'displays the required information description' do
- page.within('.pipeline-schedule-table-row') do
- expect(page).to have_content('pipeline schedule')
- expect(page).to have_content(pipeline_schedule.real_next_run.strftime('%b %d, %Y'))
- expect(page).to have_link('master')
- expect(page).to have_link("##{pipeline.id}")
+ it 'edits the pipeline' do
+ page.within('.pipeline-schedule-table-row') do
+ click_link 'Edit'
+ end
+
+ expect(page).to have_content('Edit Pipeline Schedule')
end
- end
- it 'creates a new scheduled pipeline' do
- click_link 'New schedule'
+ it 'deletes the pipeline' do
+ click_link 'Delete'
- expect(page).to have_content('Schedule a new pipeline')
+ expect(page).not_to have_css(".pipeline-schedule-table-row")
+ end
end
- it 'changes ownership of the pipeline' do
- click_link 'Take ownership'
- page.within('.pipeline-schedule-table-row') do
- expect(page).not_to have_content('No owner')
- expect(page).to have_link('John Doe')
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ visit_pipelines_schedules
+ end
+
+ it 'shows a list of the pipeline schedules with empty ref column' do
+ expect(first('.branch-name-cell').text).to eq('')
end
end
- it 'edits the pipeline' do
- page.within('.pipeline-schedule-table-row') do
- click_link 'Edit'
+ context 'when ref is empty' do
+ before do
+ pipeline_schedule.update_attribute(:ref, '')
+ visit_pipelines_schedules
end
- expect(page).to have_content('Edit Pipeline Schedule')
+ it 'shows a list of the pipeline schedules with empty ref column' do
+ expect(first('.branch-name-cell').text).to eq('')
+ end
end
+ end
- it 'deletes the pipeline' do
- click_link 'Delete'
+ describe 'POST /projects/pipeline_schedules/new' do
+ before do
+ visit_new_pipeline_schedule
+ end
- expect(page).not_to have_content('pipeline schedule')
+ it 'sets defaults for timezone and target branch' do
+ expect(page).to have_button('master')
+ expect(page).to have_button('UTC')
+ end
+
+ it 'it creates a new scheduled pipeline' do
+ fill_in_schedule_form
+ save_pipeline_schedule
+
+ expect(page).to have_content('my fancy description')
+ end
+
+ it 'it prevents an invalid form from being submitted' do
+ save_pipeline_schedule
+
+ expect(page).to have_content('This field is required')
end
end
- context 'when ref is nil' do
+ describe 'PATCH /projects/pipelines_schedules/:id/edit' do
before do
- pipeline_schedule.update_attribute(:ref, nil)
- visit_pipelines_schedules
+ edit_pipeline_schedule
end
- it 'shows a list of the pipeline schedules with empty ref column' do
- expect(first('.branch-name-cell').text).to eq('')
+ it 'it displays existing properties' do
+ description = find_field('schedule_description').value
+ expect(description).to eq('pipeline schedule')
+ expect(page).to have_button('master')
+ expect(page).to have_button('UTC')
end
- end
- end
- describe 'POST /projects/pipeline_schedules/new', js: true do
- let(:visit_page) { visit_new_pipeline_schedule }
+ it 'edits the scheduled pipeline' do
+ fill_in 'schedule_description', with: 'my brand new description'
- it 'sets defaults for timezone and target branch' do
- expect(page).to have_button('master')
- expect(page).to have_button('UTC')
- end
+ save_pipeline_schedule
+
+ expect(page).to have_content('my brand new description')
+ end
+
+ context 'when ref is nil' do
+ before do
+ pipeline_schedule.update_attribute(:ref, nil)
+ edit_pipeline_schedule
+ end
+
+ it 'shows the pipeline schedule with default ref' do
+ page.within('.js-target-branch-dropdown') do
+ expect(first('.dropdown-toggle-text').text).to eq('master')
+ end
+ end
+ end
- it 'it creates a new scheduled pipeline' do
- fill_in_schedule_form
- save_pipeline_schedule
+ context 'when ref is empty' do
+ before do
+ pipeline_schedule.update_attribute(:ref, '')
+ edit_pipeline_schedule
+ end
- expect(page).to have_content('my fancy description')
+ it 'shows the pipeline schedule with default ref' do
+ page.within('.js-target-branch-dropdown') do
+ expect(first('.dropdown-toggle-text').text).to eq('master')
+ end
+ end
+ end
end
- it 'it prevents an invalid form from being submitted' do
- save_pipeline_schedule
+ context 'when user creates a new pipeline schedule with variables' do
+ background do
+ visit_pipelines_schedules
+ click_link 'New schedule'
+ fill_in_schedule_form
+ all('[name="schedule[variables_attributes][][key]"]')[0].set('AAA')
+ all('[name="schedule[variables_attributes][][value]"]')[0].set('AAA123')
+ all('[name="schedule[variables_attributes][][key]"]')[1].set('BBB')
+ all('[name="schedule[variables_attributes][][value]"]')[1].set('BBB123')
+ save_pipeline_schedule
+ end
- expect(page).to have_content('This field is required')
+ scenario 'user sees the new variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('AAA')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('AAA123')
+ expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-key-input").value).to eq('BBB')
+ expect(find(".pipeline-variable-row:nth-child(2) .pipeline-variable-value-input").value).to eq('BBB123')
+ end
+ end
end
- end
- describe 'PATCH /projects/pipelines_schedules/:id/edit', js: true do
- let(:visit_page) do
- edit_pipeline_schedule
+ context 'when user edits a variable of a pipeline schedule' do
+ background do
+ create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
+ create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule)
+ end
+
+ visit_pipelines_schedules
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ all('[name="schedule[variables_attributes][][key]"]')[0].set('foo')
+ all('[name="schedule[variables_attributes][][value]"]')[0].set('bar')
+ click_button 'Save pipeline schedule'
+ end
+
+ scenario 'user sees the updated variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('foo')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('bar')
+ end
+ end
end
- it 'it displays existing properties' do
- description = find_field('schedule_description').value
- expect(description).to eq('pipeline schedule')
- expect(page).to have_button('master')
- expect(page).to have_button('UTC')
+ context 'when user removes a variable of a pipeline schedule' do
+ background do
+ create(:ci_pipeline_schedule, project: project, owner: user).tap do |pipeline_schedule|
+ create(:ci_pipeline_schedule_variable, key: 'AAA', value: 'AAA123', pipeline_schedule: pipeline_schedule)
+ end
+
+ visit_pipelines_schedules
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ find('.pipeline-variable-list .pipeline-variable-row-remove-button').click
+ click_button 'Save pipeline schedule'
+ end
+
+ scenario 'user does not see the removed variable in edit window' do
+ find(".content-list .pipeline-schedule-table-row:nth-child(1) .btn-group a[title='Edit']").click
+ page.within('.pipeline-variable-list') do
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-key-input").value).to eq('')
+ expect(find(".pipeline-variable-row:nth-child(1) .pipeline-variable-value-input").value).to eq('')
+ end
+ end
end
+ end
- it 'edits the scheduled pipeline' do
- fill_in 'schedule_description', with: 'my brand new description'
+ context 'logged in as non-member' do
+ before do
+ gitlab_sign_in(user)
+ end
- save_pipeline_schedule
+ describe 'GET /projects/pipeline_schedules' do
+ before do
+ visit_pipelines_schedules
+ end
- expect(page).to have_content('my brand new description')
+ describe 'The view' do
+ it 'does not show create schedule button' do
+ expect(page).not_to have_link('New schedule')
+ end
+ end
end
+ end
- context 'when ref is nil' do
+ context 'not logged in' do
+ describe 'GET /projects/pipeline_schedules' do
before do
- pipeline_schedule.update_attribute(:ref, nil)
- edit_pipeline_schedule
+ visit_pipelines_schedules
end
- it 'shows the pipeline schedule with default ref' do
- page.within('.js-target-branch-dropdown') do
- expect(first('.dropdown-toggle-text').text).to eq('master')
+ describe 'The view' do
+ it 'does not show create schedule button' do
+ expect(page).not_to have_link('New schedule')
end
end
end
end
def visit_new_pipeline_schedule
- visit new_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+ visit new_project_pipeline_schedule_path(project, pipeline_schedule)
end
def edit_pipeline_schedule
- visit edit_namespace_project_pipeline_schedule_path(project.namespace, project, pipeline_schedule)
+ visit edit_project_pipeline_schedule_path(project, pipeline_schedule)
end
def visit_pipelines_schedules
- visit namespace_project_pipeline_schedules_path(project.namespace, project, scope: scope)
+ visit project_pipeline_schedules_path(project, scope: scope)
end
def select_timezone
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index e182995922d..d4319bca331 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,13 +1,11 @@
require 'spec_helper'
-describe 'Pipeline', :feature, :js do
- include GitlabRoutingHelper
-
+describe 'Pipeline', :js do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
@@ -48,7 +46,7 @@ describe 'Pipeline', :feature, :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
before do
- visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit project_pipeline_path(project, pipeline)
end
it 'shows the pipeline graph' do
@@ -194,7 +192,7 @@ describe 'Pipeline', :feature, :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
- visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit builds_project_pipeline_path(project, pipeline)
end
it 'shows a list of jobs' do
@@ -266,7 +264,7 @@ describe 'Pipeline', :feature, :js do
describe 'GET /:project/pipelines/:id/failures' do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
- let(:pipeline_failures_page) { failures_namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
context 'with failed build' do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index d36d073e022..6bef7317d30 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe 'Pipelines', :feature, :js do
+describe 'Pipelines', :js do
let(:project) { create(:empty_project) }
context 'when user is logged in' do
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :developer]
end
@@ -51,7 +51,7 @@ describe 'Pipelines', :feature, :js do
context 'header tabs' do
before do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
wait_for_requests
end
@@ -369,14 +369,14 @@ describe 'Pipelines', :feature, :js do
end
it 'should render pagination' do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
wait_for_requests
expect(page).to have_selector('.gl-pagination')
end
it 'should render second page of pipelines' do
- visit namespace_project_pipelines_path(project.namespace, project, page: '2')
+ visit project_pipelines_path(project, page: '2')
wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2)
@@ -405,7 +405,7 @@ describe 'Pipelines', :feature, :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
- visit namespace_project_pipeline_path(project.namespace, project, pipeline)
+ visit project_pipeline_path(project, pipeline)
wait_for_requests
end
@@ -440,7 +440,7 @@ describe 'Pipelines', :feature, :js do
let(:project) { create(:project) }
before do
- visit new_namespace_project_pipeline_path(project.namespace, project)
+ visit new_project_pipeline_path(project)
end
context 'for valid commit', js: true do
@@ -479,7 +479,7 @@ describe 'Pipelines', :feature, :js do
let(:project) { create(:project) }
before do
- visit new_namespace_project_pipeline_path(project.namespace, project)
+ visit new_project_pipeline_path(project)
end
describe 'new pipeline page' do
@@ -508,7 +508,7 @@ describe 'Pipelines', :feature, :js do
context 'when user is not logged in' do
before do
- visit namespace_project_pipelines_path(project.namespace, project)
+ visit project_pipelines_path(project)
end
context 'when project is public' do
@@ -526,7 +526,7 @@ describe 'Pipelines', :feature, :js do
end
def visit_project_pipelines(**query)
- visit namespace_project_pipelines_path(project.namespace, project, query)
+ visit project_pipelines_path(project, query)
wait_for_requests
end
end
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
index baa38ff8cca..7e43ef92a72 100644
--- a/spec/features/projects/project_settings_spec.rb
+++ b/spec/features/projects/project_settings_spec.rb
@@ -1,18 +1,18 @@
require 'spec_helper'
-describe 'Edit Project Settings', feature: true do
+describe 'Edit Project Settings' do
include Select2Helper
let(:user) { create(:user) }
let(:project) { create(:empty_project, namespace: user.namespace, path: 'gitlab', name: 'sample') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'Project settings section', js: true do
it 'shows errors for invalid project name' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in 'project_name_edit', with: 'foo&bar'
click_button 'Save changes'
expect(page).to have_field 'project_name_edit', with: 'foo&bar'
@@ -21,7 +21,7 @@ describe 'Edit Project Settings', feature: true do
end
it 'shows a successful notice when the project is updated' do
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in 'project_name_edit', with: 'hello world'
click_button 'Save changes'
expect(page).to have_content "Project 'hello world' was successfully updated."
@@ -75,7 +75,7 @@ describe 'Edit Project Settings', feature: true do
end
specify 'the project is accessible via a redirect from the old path' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
rename_project(project, path: 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
@@ -85,7 +85,7 @@ describe 'Edit Project Settings', feature: true do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
rename_project(project, path: 'bar')
new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
@@ -122,7 +122,7 @@ describe 'Edit Project Settings', feature: true do
end
specify 'the project is accessible via a redirect from the old path' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
transfer_project(project, group)
new_path = namespace_project_path(group, project)
visit old_path
@@ -132,7 +132,7 @@ describe 'Edit Project Settings', feature: true do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
- old_path = namespace_project_path(project.namespace, project)
+ old_path = project_path(project)
transfer_project(project, group)
new_project = create(:empty_project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
@@ -144,7 +144,7 @@ describe 'Edit Project Settings', feature: true do
end
def rename_project(project, name: nil, path: nil)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
fill_in('project_name', with: name) if name
fill_in('Path', with: path) if path
click_button('Rename project')
@@ -153,7 +153,7 @@ def rename_project(project, name: nil, path: nil)
end
def transfer_project(project, namespace)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
select2(namespace.id, from: '#new_namespace_id')
click_button('Transfer project')
confirm_transfer_modal
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 016a992bdcf..2512818b297 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -1,13 +1,13 @@
require 'rails_helper'
-feature 'Ref switcher', feature: true, js: true do
+feature 'Ref switcher', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tree_path(project.namespace, project, 'master')
+ sign_in(user)
+ visit project_tree_path(project, 'master')
end
it 'allow user to change ref by enter key' do
@@ -19,14 +19,14 @@ feature 'Ref switcher', feature: true, js: true do
input.set 'binary'
wait_for_requests
- expect(find('.dropdown-content ul')).to have_selector('li', count: 6)
+ expect(find('.dropdown-content ul')).to have_selector('li', count: 7)
page.within '.dropdown-content ul' do
input.native.send_keys :enter
end
end
- expect(page).to have_title 'binary-encoding'
+ expect(page).to have_title 'add-pdf-text-binary'
end
it "user selects ref with special characters" do
diff --git a/spec/features/projects/services/jira_service_spec.rb b/spec/features/projects/services/jira_service_spec.rb
index 8cd216c8fdb..d0e26cc7dac 100644
--- a/spec/features/projects/services/jira_service_spec.rb
+++ b/spec/features/projects/services/jira_service_spec.rb
@@ -1,22 +1,17 @@
require 'spec_helper'
-feature 'Setup Jira service', :feature, :js do
+feature 'Setup Jira service', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:service) { project.create_jira_service }
let(:url) { 'http://jira.example.com' }
-
- def stub_project_url
- WebMock.stub_request(:get, 'http://jira.example.com/rest/api/2/project/GitLabProject')
- .with(basic_auth: %w(username password))
- end
+ let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
def fill_form(active = true)
check 'Active' if active
fill_in 'service_url', with: url
- fill_in 'service_project_key', with: 'GitLabProject'
fill_in 'service_username', with: 'username'
fill_in 'service_password', with: 'password'
fill_in 'service_jira_issue_transition_id', with: '25'
@@ -24,33 +19,28 @@ feature 'Setup Jira service', :feature, :js do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_settings_integrations_path(project.namespace, project)
+ visit project_settings_integrations_path(project)
end
describe 'user sets and activates Jira Service' do
context 'when Jira connection test succeeds' do
- before do
- stub_project_url
- end
-
it 'activates the JIRA service' do
+ server_info = { key: 'value' }.to_json
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info)
+
click_link('JIRA')
fill_form
click_button('Test settings and save changes')
wait_for_requests
expect(page).to have_content('JIRA activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
context 'when Jira connection test fails' do
- before do
- stub_project_url.to_return(status: 401)
- end
-
it 'shows errors when some required fields are not filled in' do
click_link('JIRA')
@@ -64,6 +54,9 @@ feature 'Setup Jira service', :feature, :js do
end
it 'activates the JIRA service' do
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password))
+ .to_raise(JIRA::HTTPError.new(double(message: 'message')))
+
click_link('JIRA')
fill_form
click_button('Test settings and save changes')
@@ -76,7 +69,7 @@ feature 'Setup Jira service', :feature, :js do
wait_for_requests
expect(page).to have_content('JIRA activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
end
@@ -89,7 +82,7 @@ feature 'Setup Jira service', :feature, :js do
click_button('Save changes')
expect(page).to have_content('JIRA settings saved, but not activated.')
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
end
end
end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index d87985f1c92..134d7e5e8b7 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Setup Mattermost slash commands', :feature, :js do
+feature 'Setup Mattermost slash commands', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:service) { project.create_mattermost_slash_commands_service }
@@ -9,8 +9,8 @@ feature 'Setup Mattermost slash commands', :feature, :js do
before do
stub_mattermost_setting(enabled: mattermost_enabled)
project.team << [user, :master]
- gitlab_sign_in(user)
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ sign_in(user)
+ visit edit_project_service_path(project, service)
end
describe 'user visits the mattermost slash command config page' do
@@ -30,7 +30,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
fill_in 'service_token', with: token
click_on 'Save changes'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Mattermost slash commands settings saved, but not activated.')
end
@@ -41,7 +41,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
check 'service_active'
click_on 'Save changes'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Mattermost slash commands activated.')
end
@@ -159,7 +159,7 @@ feature 'Setup Mattermost slash commands', :feature, :js do
it 'shows the correct trigger url' do
value = find_field('request_url').value
- expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+ expect(value).to match("api/v4/projects/#{project.id}/services/mattermost_slash_commands/trigger")
end
it 'shows a token placeholder' do
diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb
index 50707e6a49f..c10ec5e2987 100644
--- a/spec/features/projects/services/slack_service_spec.rb
+++ b/spec/features/projects/services/slack_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Slack service > Setup events', feature: true do
+feature 'Projects > Slack service > Setup events' do
let(:user) { create(:user) }
let(:service) { SlackService.new }
let(:project) { create(:project, slack_service: service) }
@@ -9,11 +9,11 @@ feature 'Projects > Slack service > Setup events', feature: true do
service.fields
service.update_attributes(push_channel: 1, issue_channel: 2, merge_request_channel: 3, note_channel: 4, tag_push_channel: 5, pipeline_channel: 6, wiki_page_channel: 7)
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'user can filter events by channel' do
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ visit edit_project_service_path(project, service)
expect(page.find_field("service_push_channel").value).to have_content '1'
expect(page.find_field("service_issue_channel").value).to have_content '2'
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
index 3fae38c1799..a8baf126269 100644
--- a/spec/features/projects/services/slack_slash_command_spec.rb
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-feature 'Slack slash commands', feature: true do
+feature 'Slack slash commands' do
given(:user) { create(:user) }
given(:project) { create(:project) }
given(:service) { project.create_slack_slash_commands_service }
background do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ sign_in(user)
+ visit edit_project_service_path(project, service)
end
it 'shows a token placeholder' do
@@ -25,7 +25,7 @@ feature 'Slack slash commands', feature: true do
fill_in 'service_token', with: 'token'
click_on 'Save'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
@@ -34,12 +34,12 @@ feature 'Slack slash commands', feature: true do
check 'service_active'
click_on 'Save'
- expect(current_path).to eq(namespace_project_settings_integrations_path(project.namespace, project))
+ expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands activated.')
end
it 'shows the correct trigger url' do
value = find_field('url').value
- expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger")
+ expect(value).to match("api/v4/projects/#{project.id}/services/slack_slash_commands/trigger")
end
end
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index a59374b37ea..1de4918e142 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Integration settings', feature: true do
+feature 'Integration settings' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:role) { :developer }
- let(:integrations_path) { namespace_project_settings_integrations_path(project.namespace, project) }
+ let(:integrations_path) { project_settings_integrations_path(project) }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
@@ -36,14 +36,14 @@ feature 'Integration settings', feature: true do
expect(page.status_code).to eq(200)
expect(page).to have_content(hook.url)
expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Push Events')
- expect(page).to have_content('Tag Push Events')
- expect(page).to have_content('Issues Events')
- expect(page).to have_content('Confidential Issues Events')
- expect(page).to have_content('Note Events')
- expect(page).to have_content('Merge Requests Events')
- expect(page).to have_content('Pipeline Events')
- expect(page).to have_content('Wiki Page Events')
+ expect(page).to have_content('Push events')
+ expect(page).to have_content('Tag push events')
+ expect(page).to have_content('Issues events')
+ expect(page).to have_content('Confidential issues events')
+ expect(page).to have_content('Note events')
+ expect(page).to have_content('Merge requests events')
+ expect(page).to have_content('Pipeline events')
+ expect(page).to have_content('Wiki page events')
end
scenario 'create webhook' do
@@ -58,8 +58,8 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Push Events')
- expect(page).to have_content('Tag Push Events')
+ expect(page).to have_content('Push events')
+ expect(page).to have_content('Tag push events')
expect(page).to have_content('Job events')
end
@@ -76,11 +76,12 @@ feature 'Integration settings', feature: true do
expect(page).to have_content(url)
end
- scenario 'test existing webhook' do
+ scenario 'test existing webhook', js: true do
WebMock.stub_request(:post, hook.url)
visit integrations_path
- click_link 'Test'
+ find('.hook-test-button.dropdown').click
+ click_link 'Push events'
expect(current_path).to eq(integrations_path)
end
@@ -109,7 +110,7 @@ feature 'Integration settings', feature: true do
scenario 'show list of hook logs' do
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
expect(page).to have_content('Recent Deliveries')
expect(page).to have_content(hook_log.url)
@@ -117,7 +118,7 @@ feature 'Integration settings', feature: true do
scenario 'show hook log details' do
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
click_link 'View details'
expect(page).to have_content("POST #{hook_log.url}")
@@ -129,11 +130,11 @@ feature 'Integration settings', feature: true do
WebMock.stub_request(:post, hook.url)
hook_log
- visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ visit edit_project_hook_path(project, hook)
click_link 'View details'
click_link 'Resend Request'
- expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook))
+ expect(current_path).to eq(edit_project_hook_path(project, hook))
end
end
end
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index f2af14ceab2..796e2026905 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -1,14 +1,12 @@
require 'spec_helper'
-feature 'Project settings > Merge Requests', feature: true, js: true do
- include GitlabRoutingHelper
-
+feature 'Project settings > Merge Requests', :js do
let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) }
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when Merge Request and Pipelines are initially enabled' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index c33fbd49d21..f24d7ff64d0 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,16 +1,14 @@
require 'spec_helper'
-feature "Pipelines settings", feature: true do
- include GitlabRoutingHelper
-
+feature "Pipelines settings" do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:role) { :developer }
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
- visit namespace_project_pipelines_settings_path(project.namespace, project)
+ visit project_pipelines_settings_path(project)
end
context 'for developer' do
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 35cd0d6e832..15180d4b498 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
-feature 'Repository settings', feature: true do
+feature 'Repository settings' do
let(:project) { create(:project_empty_repo) }
let(:user) { create(:user) }
let(:role) { :developer }
background do
project.team << [user, role]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'for developer' do
given(:role) { :developer }
scenario 'is not allowed to view' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
expect(page.status_code).to eq(404)
end
@@ -32,7 +32,7 @@ feature 'Repository settings', feature: true do
project.deploy_keys << private_deploy_key
project.deploy_keys << public_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
expect(page.status_code).to eq(200)
expect(page).to have_content('private_deploy_key')
@@ -40,7 +40,7 @@ feature 'Repository settings', feature: true do
end
scenario 'add a new deploy key' do
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
fill_in 'deploy_key_title', with: 'new_deploy_key'
fill_in 'deploy_key_key', with: new_ssh_key
@@ -53,7 +53,7 @@ feature 'Repository settings', feature: true do
scenario 'edit an existing deploy key' do
project.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_link('Edit')
@@ -70,7 +70,7 @@ feature 'Repository settings', feature: true do
project2.team << [user, role]
project2.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_link('Edit')
@@ -84,7 +84,7 @@ feature 'Repository settings', feature: true do
scenario 'remove an existing deploy key' do
project.deploy_keys << private_deploy_key
- visit namespace_project_settings_repository_path(project.namespace, project)
+ visit project_settings_repository_path(project)
find('li', text: private_deploy_key.title).click_button('Remove')
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 18c71dee41b..1756c7d00fe 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Visibility settings', feature: true, js: true do
+feature 'Visibility settings', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace, visibility_level: 20) }
context 'as owner' do
before do
- gitlab_sign_in(user)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(user)
+ visit edit_project_path(project)
end
scenario 'project visibility select is available' do
@@ -32,8 +32,8 @@ feature 'Visibility settings', feature: true, js: true do
before do
project.team << [master_user, :master]
- gitlab_sign_in(master_user)
- visit edit_namespace_project_path(project.namespace, project)
+ sign_in(master_user)
+ visit edit_project_path(project)
end
scenario 'project visibility is locked' do
diff --git a/spec/features/projects/shortcuts_spec.rb b/spec/features/projects/shortcuts_spec.rb
index cec79277c33..bf18c444c3d 100644
--- a/spec/features/projects/shortcuts_spec.rb
+++ b/spec/features/projects/shortcuts_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-feature 'Project shortcuts', feature: true do
+feature 'Project shortcuts' do
let(:project) { create(:project, name: 'Victorialand') }
let(:user) { create(:user) }
describe 'On a project', js: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_path(project.namespace, project)
+ sign_in user
+ visit project_path(project)
end
describe 'pressing "i"' do
diff --git a/spec/features/projects/show_project_spec.rb b/spec/features/projects/show_project_spec.rb
new file mode 100644
index 00000000000..1bc6fae9e7f
--- /dev/null
+++ b/spec/features/projects/show_project_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'Project show page', feature: true do
+ context 'when project pending delete' do
+ let(:project) { create(:project, :empty_repo, pending_delete: true) }
+
+ before do
+ sign_in(project.owner)
+ end
+
+ it 'shows error message if deletion for project fails' do
+ project.update_attributes(delete_error: "Something went wrong", pending_delete: false)
+
+ visit project_path(project)
+
+ expect(page).to have_selector('.project-deletion-failed-message')
+ expect(page).to have_content("This project was scheduled for deletion, but failed with the following message: #{project.delete_error}")
+ end
+ end
+end
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index c75d6dbc307..23d4b9233b3 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Create Snippet', :js, feature: true do
+feature 'Create Snippet', :js do
include DropzoneHelper
let(:user) { create(:user) }
@@ -17,9 +17,9 @@ feature 'Create Snippet', :js, feature: true do
context 'when a user is authenticated' do
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
click_on('New snippet')
end
@@ -77,7 +77,7 @@ feature 'Create Snippet', :js, feature: true do
it 'shows a public snippet on the index page but not the New snippet button' do
snippet = create(:project_snippet, :public, project: project)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
expect(page).to have_content(snippet.title)
expect(page).not_to have_content('New snippet')
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index 9e73ba4123b..08dc7cf6c5b 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Project snippet', :js, feature: true do
+feature 'Project snippet', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'Ruby file' do
@@ -15,7 +15,7 @@ feature 'Project snippet', :js, feature: true do
let(:content) { project.repository.blob_at('master', 'files/ruby/popen.rb').data }
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
wait_for_requests
end
@@ -46,7 +46,7 @@ feature 'Project snippet', :js, feature: true do
context 'visiting directly' do
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
wait_for_requests
end
@@ -118,7 +118,7 @@ feature 'Project snippet', :js, feature: true do
context 'visiting with a line number anchor' do
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
+ visit project_snippet_path(project, snippet, anchor: 'L1')
wait_for_requests
end
diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb
index 80dbffaffc7..0822684a42c 100644
--- a/spec/features/projects/snippets_spec.rb
+++ b/spec/features/projects/snippets_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Project snippets', :js, feature: true do
+describe 'Project snippets', :js do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
@@ -10,7 +10,7 @@ describe 'Project snippets', :js, feature: true do
before do
allow(Snippet).to receive(:default_per_page).and_return(1)
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
end
it_behaves_like 'paginated snippets'
@@ -18,7 +18,7 @@ describe 'Project snippets', :js, feature: true do
context 'list content' do
it 'contains all project snippets' do
- visit namespace_project_snippets_path(project.namespace, project)
+ visit project_snippets_path(project)
expect(page).to have_selector('.snippet-row', count: 2)
@@ -29,8 +29,8 @@ describe 'Project snippets', :js, feature: true do
context 'when submitting a note' do
before do
- gitlab_sign_in :admin
- visit namespace_project_snippet_path(project.namespace, project, snippets[0])
+ sign_in(create(:admin))
+ visit project_snippet_path(project, snippets[0])
end
it 'should have autocomplete' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 63eb97d5a92..262dcc0abff 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Subgroup Issuables', :feature, :js, :nested_groups do
+describe 'Subgroup Issuables', :js, :nested_groups do
let!(:group) { create(:group, name: 'group') }
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') }
@@ -8,17 +8,17 @@ describe 'Subgroup Issuables', :feature, :js, :nested_groups do
before do
project.add_master(user)
- gitlab_sign_in user
+ sign_in user
end
it 'shows the full subgroup title when issues index page is empty' do
- visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+ visit project_issues_path(project)
expect_to_have_full_subgroup_title
end
it 'shows the full subgroup title when merge requests index page is empty' do
- visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+ visit project_merge_requests_path(project)
expect_to_have_full_subgroup_title
end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index ca00a51aa3c..a6c5a486bcc 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download buttons in tags page', feature: true do
+feature 'Download buttons in tags page' do
given(:user) { create(:user) }
given(:role) { :developer }
given(:status) { 'success' }
@@ -23,20 +23,18 @@ feature 'Download buttons in tags page', feature: true do
end
background do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, role]
end
describe 'when checking tags' do
context 'with artifacts' do
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'shows download artifacts button' do
- href = latest_succeeded_namespace_project_artifacts_path(
- project.namespace, project, "#{tag}/download",
- job: 'build')
+ href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
expect(page).to have_link "Download '#{build.name}'", href: href
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 135584e5bf8..4f2e0a76a65 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
feature 'Project Tree RSS' do
+ let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
- let(:path) { namespace_project_tree_path(project.namespace, project, :master) }
+ let(:path) { project_tree_path(project, :master) }
context 'when signed in' do
before do
- user = create(:user)
project.team << [user, :developer]
- gitlab_sign_in(user)
+ sign_in(user)
visit path
end
diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb
new file mode 100644
index 00000000000..263a3a29a66
--- /dev/null
+++ b/spec/features/projects/user_browses_files_spec.rb
@@ -0,0 +1,188 @@
+require 'spec_helper'
+
+describe 'User browses files' do
+ include DropzoneHelper
+
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:tree_path_ref_6d39438) { project_tree_path(project, '6d39438') }
+ let(:tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ context 'when browsing the master branch' do
+ before do
+ visit(tree_path_root_ref)
+ end
+
+ it 'shows files from a repository' do
+ expect(page).to have_content('VERSION')
+ expect(page).to have_content('.gitignore')
+ expect(page).to have_content('LICENSE')
+ end
+
+ it 'shows the "Browse Directory" link' do
+ click_link('files')
+ click_link('History')
+
+ expect(page).to have_link('Browse Directory')
+ expect(page).not_to have_link('Browse Code')
+ end
+
+ it 'shows the "Browse File" link' do
+ page.within('.tree-table') do
+ click_link('README.md')
+ end
+ click_link('History')
+
+ expect(page).to have_link('Browse File')
+ expect(page).not_to have_link('Browse Files')
+ end
+
+ it 'shows the "Browse Code" link' do
+ click_link('History')
+
+ expect(page).to have_link('Browse Files')
+ expect(page).not_to have_link('Browse Directory')
+ end
+
+ it 'redirects to the permalink URL' do
+ click_link('.gitignore')
+ click_link('Permalink')
+
+ permalink_path = project_blob_path(project, "#{project.repository.commit.sha}/.gitignore")
+
+ expect(current_path).to eq(permalink_path)
+ end
+ end
+
+ context 'when browsing a specific ref' do
+ before do
+ visit(tree_path_ref_6d39438)
+ end
+
+ it 'shows files from a repository for "6d39438"' do
+ expect(current_path).to eq(tree_path_ref_6d39438)
+ expect(page).to have_content('.gitignore')
+ expect(page).to have_content('LICENSE')
+ end
+
+ it 'shows files from a repository with apostroph in its name', js: true do
+ first('.js-project-refs-dropdown').click
+
+ page.within('.project-refs-form') do
+ click_link("'test'")
+ end
+
+ expect(page).to have_selector('.dropdown-toggle-text', text: "'test'")
+
+ visit(project_tree_path(project, "'test'"))
+
+ expect(page).to have_css('.tree-commit-link', visible: true)
+ expect(page).not_to have_content('Loading commit data...')
+ end
+
+ it 'shows the code with a leading dot in the directory', js: true do
+ first('.js-project-refs-dropdown').click
+
+ page.within('.project-refs-form') do
+ click_link('fix')
+ end
+
+ visit(project_tree_path(project, 'fix/.testdir'))
+
+ expect(page).to have_css('.tree-commit-link', visible: true)
+ expect(page).not_to have_content('Loading commit data...')
+ end
+
+ it 'does not show the permalink link' do
+ click_link('.gitignore')
+
+ expect(page).not_to have_link('permalink')
+ end
+ end
+
+ context 'when browsing a file content' do
+ before do
+ visit(tree_path_root_ref)
+ click_link('.gitignore')
+ end
+
+ it 'shows a file content', js: true do
+ wait_for_requests
+ expect(page).to have_content('*.rbc')
+ end
+ end
+
+ context 'when browsing a raw file' do
+ before do
+ visit(project_blob_path(project, File.join(RepoHelpers.sample_commit.id, RepoHelpers.sample_blob.path)))
+ end
+
+ it 'shows a raw file content' do
+ click_link('Open raw')
+ expect(source).to eq('') # Body is filled in by gitlab-workhorse
+ end
+ end
+
+ context 'when browsing an LFS object' do
+ before do
+ allow_any_instance_of(Project).to receive(:lfs_enabled?).and_return(true)
+ visit(project_tree_path(project, 'lfs'))
+ end
+
+ it 'shows an LFS object' do
+ click_link('files')
+ click_link('lfs')
+ click_link('lfs_object.iso')
+
+ expect(page).to have_content('Download (1.5 MB)')
+ expect(page).not_to have_content('version https://git-lfs.github.com/spec/v1')
+ expect(page).not_to have_content('oid sha256:91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897')
+ expect(page).not_to have_content('size 1575078')
+
+ page.within('.content') do
+ expect(page).to have_content('Delete')
+ expect(page).to have_content('History')
+ expect(page).to have_content('Permalink')
+ expect(page).to have_content('Replace')
+ expect(page).not_to have_content('Annotate')
+ expect(page).not_to have_content('Blame')
+ expect(page).not_to have_content('Edit')
+ expect(page).to have_link('Download')
+ end
+ end
+ end
+
+ context 'when previewing a file content' do
+ before do
+ visit(tree_path_root_ref)
+ end
+
+ it 'shows a preview of a file content', js: true do
+ find('.add-to-tree').click
+ click_link('Upload file')
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ fill_in(:branch_name, with: 'new_branch_name', visible: true)
+ click_button('Upload file')
+
+ visit(project_blob_path(project, 'new_branch_name/logo_sample.svg'))
+
+ expect(page).to have_css('.file-content img')
+ end
+ end
+end
diff --git a/spec/features/projects/user_create_dir_spec.rb b/spec/features/projects/user_create_dir_spec.rb
deleted file mode 100644
index 5d0acad3832..00000000000
--- a/spec/features/projects/user_create_dir_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'spec_helper'
-
-feature 'New directory creation', feature: true, js: true do
- given(:user) { create(:user) }
- given(:role) { :developer }
- given(:project) { create(:project) }
-
- background do
- gitlab_sign_in(user)
- project.team << [user, role]
- visit namespace_project_tree_path(project.namespace, project, 'master')
- open_new_directory_modal
- fill_in 'dir_name', with: 'new_directory'
- end
-
- def open_new_directory_modal
- first('.add-to-tree').click
- click_link 'New directory'
- end
-
- def create_directory
- click_button 'Create directory'
- end
-
- context 'with default target branch' do
- background do
- create_directory
- end
-
- scenario 'creates the directory in the default branch' do
- expect(page).to have_content 'master'
- expect(page).to have_content 'The directory has been successfully created'
- expect(page).to have_content 'new_directory'
- end
- end
-
- context 'with a new target branch' do
- given(:new_branch_name) { 'new-feature' }
-
- background do
- fill_in :branch_name, with: new_branch_name
- create_directory
- end
-
- scenario 'creates the directory in the new branch' do
- expect(page).to have_content new_branch_name
- expect(page).to have_content 'The directory has been successfully created'
- end
-
- scenario 'redirects to the merge request' do
- expect(page).to have_content 'New Merge Request'
- expect(page).to have_content "From #{new_branch_name} into master"
- expect(page).to have_content 'Add new directory'
- expect(current_path).to eq(namespace_project_new_merge_request_path(project.namespace, project))
- end
- end
-end
diff --git a/spec/features/projects/user_creates_directory_spec.rb b/spec/features/projects/user_creates_directory_spec.rb
new file mode 100644
index 00000000000..635bd4493dd
--- /dev/null
+++ b/spec/features/projects/user_creates_directory_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+feature 'User creates a directory', js: true do
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project) }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :developer]
+ sign_in(user)
+ visit project_tree_path(project, 'master')
+ end
+
+ context 'with default target branch' do
+ before do
+ first('.add-to-tree').click
+ click_link('New directory')
+ end
+
+ it 'creates the directory in the default branch' do
+ fill_in(:dir_name, with: 'new_directory')
+ click_button('Create directory')
+
+ expect(page).to have_content('master')
+ expect(page).to have_content('The directory has been successfully created')
+ expect(page).to have_content('new_directory')
+ end
+
+ it 'does not create a directory with a name of already existed directory' do
+ fill_in(:dir_name, with: 'files')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Create directory')
+
+ expect(page).to have_content('A directory with this name already exists')
+ expect(current_path).to eq(project_tree_path(project, 'master'))
+ end
+ end
+
+ context 'with a new target branch' do
+ before do
+ first('.add-to-tree').click
+ click_link('New directory')
+ fill_in(:dir_name, with: 'new_directory')
+ fill_in(:branch_name, with: 'new-feature')
+ click_button('Create directory')
+ end
+
+ it 'creates the directory in the new branch and redirect to the merge request' do
+ expect(page).to have_content('new-feature')
+ expect(page).to have_content('The directory has been successfully created')
+ expect(page).to have_content('New Merge Request')
+ expect(page).to have_content('From new-feature into master')
+ expect(page).to have_content('Add new directory')
+
+ expect(current_path).to eq(project_new_merge_request_path(project))
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'creates a directory in a forked project' do
+ find('.add-to-tree').click
+ click_link('New directory')
+
+ expect(page).to have_content(fork_message)
+
+ find('.add-to-tree').click
+ click_link('New directory')
+ fill_in(:dir_name, with: 'new_directory')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Create directory')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+ end
+ end
+end
diff --git a/spec/features/projects/user_creates_files_spec.rb b/spec/features/projects/user_creates_files_spec.rb
new file mode 100644
index 00000000000..0c7f1a775c1
--- /dev/null
+++ b/spec/features/projects/user_creates_files_spec.rb
@@ -0,0 +1,153 @@
+require 'spec_helper'
+
+describe 'User creates files' do
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ context 'without commiting a new file' do
+ context 'when an user has write access' do
+ before do
+ visit(project_tree_path_root_ref)
+ end
+
+ it 'opens new file page' do
+ find('.add-to-tree').click
+ click_link('New file')
+
+ expect(page).to have_content('New file')
+ expect(page).to have_content('Commit message')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'opens new file page on a forked project' do
+ find('.add-to-tree').click
+ click_link('New file')
+
+ expect(page).to have_selector('.file-editor')
+ expect(page).to have_content(fork_message)
+ expect(page).to have_content('New file')
+ expect(page).to have_content('Commit message')
+ end
+ end
+ end
+
+ context 'with commiting a new file' do
+ context 'when an user has write access' do
+ before do
+ visit(project_tree_path_root_ref)
+
+ find('.add-to-tree').click
+ click_link('New file')
+ end
+
+ it 'creates and commit a new file', js: true do
+ expect(page).to have_selector('.file-editor')
+
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:file_name, with: 'not_a_file.md')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ new_file_path = project_blob_path(project, 'master/not_a_file.md')
+
+ expect(current_path).to eq(new_file_path)
+
+ wait_for_requests
+
+ expect(page).to have_content('*.rbca')
+ end
+
+ it 'creates and commit a new file with new lines at the end of file', js: true do
+ execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
+ fill_in(:file_name, with: 'not_a_file.md')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ new_file_path = project_blob_path(project, 'master/not_a_file.md')
+
+ expect(current_path).to eq(new_file_path)
+
+ find('.js-edit-blob').click
+
+ expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
+ end
+
+ it 'creates and commit a new file with a directory name', js: true do
+ fill_in(:file_name, with: 'foo/bar/baz.txt')
+
+ expect(page).to have_selector('.file-editor')
+
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ expect(current_path).to eq(project_blob_path(project, 'master/foo/bar/baz.txt'))
+
+ wait_for_requests
+
+ expect(page).to have_content('*.rbca')
+ end
+
+ it 'creates and commit a new file specifying a new branch', js: true do
+ expect(page).to have_selector('.file-editor')
+
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:file_name, with: 'not_a_file.md')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ fill_in(:branch_name, with: 'new_branch_name', visible: true)
+ click_button('Commit changes')
+
+ expect(current_path).to eq(project_new_merge_request_path(project))
+
+ click_link('Changes')
+
+ wait_for_requests
+
+ expect(page).to have_content('*.rbca')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'creates and commit new file in forked project', js: true do
+ find('.add-to-tree').click
+ click_link('New file')
+
+ expect(page).to have_selector('.file-editor')
+
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+
+ fill_in(:file_name, with: 'not_a_file.md')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(page).to have_content('New commit message')
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index 29f1eb8d73e..1c3791f63ac 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -18,7 +18,7 @@ feature 'User creates a project', js: true do
project = Project.last
- expect(current_path).to eq(namespace_project_path(project.namespace, project))
+ expect(current_path).to eq(project_path(project))
expect(page).to have_content('Empty')
expect(page).to have_content('git init')
expect(page).to have_content('git remote')
diff --git a/spec/features/projects/user_deletes_files_spec.rb b/spec/features/projects/user_deletes_files_spec.rb
new file mode 100644
index 00000000000..97e60862b4f
--- /dev/null
+++ b/spec/features/projects/user_deletes_files_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe 'User deletes files' do
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when an user has write access' do
+ before do
+ project.team << [user, :master]
+ visit(project_tree_path_root_ref)
+ end
+
+ it 'deletes the file', js: true do
+ click_link('.gitignore')
+
+ expect(page).to have_content('.gitignore')
+
+ click_on('Delete')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Delete file')
+
+ expect(current_path).to eq(project_tree_path(project, 'master'))
+ expect(page).not_to have_content('.gitignore')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'deletes the file in a forked project', js: true do
+ click_link('.gitignore')
+
+ expect(page).to have_content('.gitignore')
+
+ click_on('Delete')
+
+ expect(page).to have_link('Fork')
+ expect(page).to have_button('Cancel')
+
+ click_link('Fork')
+
+ expect(page).to have_content(fork_message)
+
+ click_on('Delete')
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Delete file')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+ expect(page).to have_content('New commit message')
+ end
+ end
+end
diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb
new file mode 100644
index 00000000000..eb26f1bc123
--- /dev/null
+++ b/spec/features/projects/user_edits_files_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe 'User edits files' do
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when an user has write access' do
+ before do
+ project.team << [user, :master]
+ visit(project_tree_path_root_ref)
+ end
+
+ it 'inserts a content of a file', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+
+ expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ end
+
+ it 'does not show the edit link if a file is binary' do
+ binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png')
+ visit(project_blob_path(project, binary_file))
+
+ expect(page).not_to have_link('edit')
+ end
+
+ it 'commits an edited file', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ expect(current_path).to eq(project_blob_path(project, 'master/.gitignore'))
+
+ wait_for_requests
+
+ expect(page).to have_content('*.rbca')
+ end
+
+ it 'commits an edited file to a new branch', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ fill_in(:branch_name, with: 'new_branch_name', visible: true)
+ click_button('Commit changes')
+
+ expect(current_path).to eq(project_new_merge_request_path(project))
+
+ click_link('Changes')
+
+ wait_for_requests
+ expect(page).to have_content('*.rbca')
+ end
+
+ it 'shows the diff of an edited file', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ click_link('Preview changes')
+
+ expect(page).to have_css('.line_holder.new')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'inserts a content of a file in a forked project', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+
+ expect(page).to have_link('Fork')
+ expect(page).to have_button('Cancel')
+
+ click_link('Fork')
+
+ expect(page).to have_content(fork_message)
+
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+
+ expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca')
+ end
+
+ it 'commits an edited file in a forked project', js: true do
+ click_link('.gitignore')
+ find('.js-edit-blob').click
+
+ expect(page).to have_link('Fork')
+ expect(page).to have_button('Cancel')
+
+ click_link('Fork')
+ execute_script("ace.edit('editor').setValue('*.rbca')")
+ fill_in(:commit_message, with: 'New commit message', visible: true)
+ click_button('Commit changes')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+
+ wait_for_requests
+
+ expect(page).to have_content('New commit message')
+ end
+ end
+end
diff --git a/spec/features/projects/user_replaces_files_spec.rb b/spec/features/projects/user_replaces_files_spec.rb
new file mode 100644
index 00000000000..50f2ffc4bbf
--- /dev/null
+++ b/spec/features/projects/user_replaces_files_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe 'User replaces files' do
+ include DropzoneHelper
+
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when an user has write access' do
+ before do
+ project.team << [user, :master]
+ visit(project_tree_path_root_ref)
+ end
+
+ it 'replaces an existed file with a new one', js: true do
+ click_link('.gitignore')
+
+ expect(page).to have_content('.gitignore')
+
+ click_on('Replace')
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'Replacement file commit message')
+ end
+
+ click_button('Replace file')
+
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ expect(page).to have_content('Replacement file commit message')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'replaces an existed file with a new one in a forked project', js: true do
+ click_link('.gitignore')
+
+ expect(page).to have_content('.gitignore')
+
+ click_on('Replace')
+
+ expect(page).to have_link('Fork')
+ expect(page).to have_button('Cancel')
+
+ click_link('Fork')
+
+ expect(page).to have_content(fork_message)
+
+ click_on('Replace')
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'Replacement file commit message')
+ end
+
+ click_button('Replace file')
+
+ expect(page).to have_content('Replacement file commit message')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+
+ click_link('Changes')
+
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ end
+ end
+end
diff --git a/spec/features/projects/user_uploads_files_spec.rb b/spec/features/projects/user_uploads_files_spec.rb
new file mode 100644
index 00000000000..64a1439badd
--- /dev/null
+++ b/spec/features/projects/user_uploads_files_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe 'User uploads files' do
+ include DropzoneHelper
+
+ let(:fork_message) do
+ "You're not allowed to make changes to this project directly. "\
+ "A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+ let(:project) { create(:project, name: 'Shop') }
+ let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
+ let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
+ let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ context 'when an user has write access' do
+ before do
+ visit(project_tree_path_root_ref)
+ end
+
+ it 'uploads and commit a new file', js: true do
+ find('.add-to-tree').click
+ click_link('Upload file')
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ fill_in(:branch_name, with: 'new_branch_name', visible: true)
+ click_button('Upload file')
+
+ expect(page).to have_content('New commit message')
+ expect(current_path).to eq(project_new_merge_request_path(project))
+
+ click_link('Changes')
+
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ end
+ end
+
+ context 'when an user does not have write access' do
+ before do
+ project2.team << [user, :reporter]
+ visit(project2_tree_path_root_ref)
+ end
+
+ it 'uploads and commit a new fileto a forked project', js: true do
+ find('.add-to-tree').click
+ click_link('Upload file')
+
+ expect(page).to have_content(fork_message)
+
+ find('.add-to-tree').click
+ click_link('Upload file')
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ page.within('#modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ click_button('Upload file')
+
+ expect(page).to have_content('New commit message')
+
+ fork = user.fork_of(project2)
+
+ expect(current_path).to eq(project_new_merge_request_path(fork))
+
+ click_link('Changes')
+
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ end
+ end
+end
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index f6a640b90b4..2a316a0d0db 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -50,9 +50,9 @@ describe 'View on environment', js: true do
let(:merge_request) { create(:merge_request, :simple, source_project: project, source_branch: branch_name) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit diffs_project_merge_request_path(project, merge_request)
wait_for_requests
end
@@ -66,9 +66,9 @@ describe 'View on environment', js: true do
context 'when visiting a comparison for the branch' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name)
+ visit project_compare_path(project, from: 'master', to: branch_name)
wait_for_requests
end
@@ -80,9 +80,9 @@ describe 'View on environment', js: true do
context 'when visiting a comparison for the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha)
+ visit project_compare_path(project, from: 'master', to: sha)
wait_for_requests
end
@@ -94,9 +94,9 @@ describe 'View on environment', js: true do
context 'when visiting a blob on the branch' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path))
+ visit project_blob_path(project, File.join(branch_name, file_path))
wait_for_requests
end
@@ -108,9 +108,9 @@ describe 'View on environment', js: true do
context 'when visiting a blob on the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path))
+ visit project_blob_path(project, File.join(sha, file_path))
wait_for_requests
end
@@ -122,9 +122,9 @@ describe 'View on environment', js: true do
context 'when visiting the commit' do
before do
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_commit_path(project.namespace, project, sha)
+ visit project_commit_path(project, sha)
wait_for_requests
end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index fd6c09943e3..45f799ebb48 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Wiki > User previews markdown changes', feature: true, js: true do
+feature 'Projects > Wiki > User previews markdown changes', js: true do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_content) do
@@ -16,9 +16,9 @@ feature 'Projects > Wiki > User previews markdown changes', feature: true, js: t
project.team << [user, :master]
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
find('.shortcuts-wiki').trigger('click')
end
diff --git a/spec/features/projects/wiki/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index ab0ed9b8204..2c668185666 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Wiki shortcuts', :feature, :js do
+feature 'Wiki shortcuts', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project, namespace: user.namespace) }
let(:wiki_page) do
@@ -8,8 +8,8 @@ feature 'Wiki shortcuts', :feature, :js do
end
before do
- gitlab_sign_in(user)
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ sign_in(user)
+ visit project_wiki_path(project, wiki_page)
end
scenario 'Visit edit wiki page using "e" keyboard shortcut' do
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 a477dcf7ee9..9d66f482c8d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -1,20 +1,23 @@
require 'spec_helper'
-feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
+feature 'Projects > Wiki > User creates wiki page', :js do
let(:user) { create(:user) }
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_path(project.namespace, project)
- find('.shortcuts-wiki').trigger('click')
+ visit project_path(project)
end
context 'in the user namespace' do
let(:project) { create(:project, namespace: user.namespace) }
context 'when wiki is empty' do
+ before do
+ find('.shortcuts-wiki').trigger('click')
+ end
+
scenario 'commit message field has value "Create home"' do
expect(page).to have_field('wiki[message]', with: 'Create home')
end
@@ -67,10 +70,11 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ find('.shortcuts-wiki').trigger('click')
end
context 'via the "new wiki page" page' do
- scenario 'when the wiki page has a single word name', js: true do
+ scenario 'when the wiki page has a single word name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -91,7 +95,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
expect(page).to have_content('My awesome wiki!')
end
- scenario 'when the wiki page has spaces in the name', js: true do
+ scenario 'when the wiki page has spaces in the name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -112,7 +116,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
expect(page).to have_content('My awesome wiki!')
end
- scenario 'when the wiki page has hyphens in the name', js: true do
+ scenario 'when the wiki page has hyphens in the name' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -134,7 +138,7 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
end
end
- scenario 'content has autocomplete', :js do
+ scenario 'content has autocomplete' do
click_link 'New page'
page.within '#modal-new-wiki' do
@@ -156,6 +160,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
let(:project) { create(:project, namespace: create(:group, :public)) }
context 'when wiki is empty' do
+ before do
+ find('.shortcuts-wiki').trigger('click')
+ end
+
scenario 'commit message field has value "Create home"' do
expect(page).to have_field('wiki[message]', with: 'Create home')
end
@@ -175,9 +183,10 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
context 'when wiki is not empty' do
before do
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
+ find('.shortcuts-wiki').trigger('click')
end
- scenario 'via the "new wiki page" page', js: true do
+ scenario 'via the "new wiki page" page' do
click_link 'New page'
page.within '#modal-new-wiki' do
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index 7d31122af35..3450c91260b 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects > Wiki > User views Git access wiki page', :feature do
+describe 'Projects > Wiki > User views Git access wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:wiki_page) do
@@ -13,11 +13,11 @@ describe 'Projects > Wiki > User views Git access wiki page', :feature do
end
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
scenario 'Visit Wiki Page Current Commit' do
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ visit project_wiki_path(project, wiki_page)
click_link 'Clone repository'
expect(page).to have_text("Clone repository #{project.wiki.path_with_namespace}")
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 64a30438681..6ebf59cba98 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
-feature 'Projects > Wiki > User updates wiki page', feature: true do
+feature 'Projects > Wiki > User updates wiki page' do
let(:user) { create(:user) }
background do
project.team << [user, :master]
WikiPages::CreateService.new(project, user, title: 'home', content: 'Home page').execute
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_wikis_path(project.namespace, project)
+ visit project_wikis_path(project)
end
context 'in the user namespace' do
diff --git a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
index 8a88ab247f3..92e96f11219 100644
--- a/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_project_wiki_page_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projects > Wiki > User views the wiki page', feature: true do
+feature 'Projects > Wiki > User views the wiki page' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:old_page_version_id) { wiki_page.versions.last.id }
@@ -15,7 +15,7 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
background do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
WikiPages::UpdateService.new(
project,
user,
@@ -26,18 +26,13 @@ feature 'Projects > Wiki > User views the wiki page', feature: true do
end
scenario 'Visit Wiki Page Current Commit' do
- visit namespace_project_wiki_path(project.namespace, project, wiki_page)
+ visit project_wiki_path(project, wiki_page)
expect(page).to have_selector('a.btn', text: 'Edit')
end
scenario 'Visit Wiki Page Historical Commit' do
- visit namespace_project_wiki_path(
- project.namespace,
- project,
- wiki_page,
- version_id: old_page_version_id
- )
+ visit project_wiki_path(project, wiki_page, version_id: old_page_version_id)
expect(page).not_to have_selector('a.btn', text: 'Edit')
end
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index 36799925167..8d1e6f66039 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe 'Projects > Wiki > User views wiki in project page', feature: true do
+describe 'Projects > Wiki > User views wiki in project page' do
let(:user) { create(:user) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when repository is disabled for project' do
@@ -27,14 +27,10 @@ describe 'Projects > Wiki > User views wiki in project page', feature: true do
end
it 'displays the correct URL for the link' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
expect(page).to have_link(
'some link',
- href: namespace_project_wiki_path(
- project.namespace,
- project,
- 'other-page'
- )
+ href: project_wiki_path(project, 'other-page')
)
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 7e8a703db93..3295f7f9174 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-feature 'Project', feature: true do
+feature 'Project' do
describe 'description' do
let(:project) { create(:project, :repository) }
- let(:path) { namespace_project_path(project.namespace, project) }
+ let(:path) { project_path(project) }
before do
- gitlab_sign_in(:admin)
+ sign_in(create(:admin))
end
it 'parses Markdown' do
@@ -39,9 +39,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace) }
before do
- gitlab_sign_in user
+ sign_in user
create(:forked_project_link, forked_to_project: project)
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
it 'removes fork' do
@@ -60,9 +60,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace, name: 'project1') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
- visit edit_namespace_project_path(project.namespace, project)
+ visit edit_project_path(project)
end
it 'removes a project' do
@@ -79,9 +79,9 @@ feature 'Project', feature: true do
let(:project) { create(:empty_project, namespace: user.namespace) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'clicks toggle and shows dropdown', js: true do
@@ -98,10 +98,10 @@ feature 'Project', feature: true do
context 'on issues page', js: true do
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.add_user(user, Gitlab::Access::MASTER)
project2.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it 'clicks toggle and shows dropdown' do
@@ -123,8 +123,8 @@ feature 'Project', feature: true do
before do
project.team << [user, :master]
- gitlab_sign_in user
- visit namespace_project_path(project.namespace, project)
+ sign_in user
+ visit project_path(project)
end
it 'has working links to files' do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 20b8e10f0f7..3677bf38724 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Protected Branches', feature: true, js: true do
+feature 'Protected Branches', js: true do
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
def set_protected_branch_name(branch_name)
@@ -16,7 +16,7 @@ feature 'Protected Branches', feature: true, js: true do
describe "explicit protected branches" do
it "allows creating explicit protected branches" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -29,7 +29,7 @@ feature 'Protected Branches', feature: true, js: true do
commit = create(:commit, project: project)
project.repository.add_branch(user, 'some-branch', commit.id)
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -37,7 +37,7 @@ feature 'Protected Branches', feature: true, js: true do
end
it "displays an error message if the named branch does not exist" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('some-branch')
click_on "Protect"
@@ -47,7 +47,7 @@ feature 'Protected Branches', feature: true, js: true do
describe "wildcard protected branches" do
it "allows creating protected branches with a wildcard" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -60,7 +60,7 @@ feature 'Protected Branches', feature: true, js: true do
project.repository.add_branch(user, 'production-stable', 'master')
project.repository.add_branch(user, 'staging-stable', 'master')
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
@@ -72,11 +72,11 @@ feature 'Protected Branches', feature: true, js: true do
project.repository.add_branch(user, 'staging-stable', 'master')
project.repository.add_branch(user, 'development', 'master')
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('*-stable')
click_on "Protect"
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
click_on "2 matching branches"
within(".protected-branches-list") do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 73a80692154..c9ba1a8c088 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-feature 'Projected Tags', feature: true, js: true do
+feature 'Projected Tags', js: true do
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
def set_protected_tag_name(tag_name)
@@ -17,7 +17,7 @@ feature 'Projected Tags', feature: true, js: true do
describe "explicit protected tags" do
it "allows creating explicit protected tags" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -30,7 +30,7 @@ feature 'Projected Tags', feature: true, js: true do
commit = create(:commit, project: project)
project.repository.add_tag(user, 'some-tag', commit.id)
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -38,7 +38,7 @@ feature 'Projected Tags', feature: true, js: true do
end
it "displays an error message if the named tag does not exist" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
click_on "Protect"
@@ -48,7 +48,7 @@ feature 'Projected Tags', feature: true, js: true do
describe "wildcard protected tags" do
it "allows creating protected tags with a wildcard" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
@@ -61,7 +61,7 @@ feature 'Projected Tags', feature: true, js: true do
project.repository.add_tag(user, 'production-stable', 'master')
project.repository.add_tag(user, 'staging-stable', 'master')
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
@@ -73,11 +73,11 @@ feature 'Projected Tags', feature: true, js: true do
project.repository.add_tag(user, 'staging-stable', 'master')
project.repository.add_tag(user, 'development', 'master')
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
click_on "Protect"
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
click_on "2 matching tags"
within(".protected-tags-list") do
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
index e8fa49c18cb..b1f51959d54 100644
--- a/spec/features/raven_js_spec.rb
+++ b/spec/features/raven_js_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'RavenJS', :feature, :js do
+feature 'RavenJS', :js do
let(:raven_path) { '/raven.bundle.js' }
it 'should not load raven if sentry is disabled' do
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 12049822753..1074eb62b33 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Reportable note on commit', :feature, :js do
+describe 'Reportable note on commit', :js do
include RepoHelpers
let(:user) { create(:user) }
@@ -8,14 +8,14 @@ describe 'Reportable note on commit', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'a normal note' do
let!(:note) { create(:note_on_commit, commit_id: sample_commit.id, project: project) }
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'reportable note'
@@ -25,7 +25,7 @@ describe 'Reportable note on commit', :feature, :js do
let!(:note) { create(:diff_note_on_commit, commit_id: sample_commit.id, project: project) }
before do
- visit namespace_project_commit_path(project.namespace, project, sample_commit.id)
+ visit project_commit_path(project, sample_commit.id)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index ca2a7f41496..9964a32db2e 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Reportable note on issue', :feature, :js do
+describe 'Reportable note on issue', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
@@ -8,9 +8,9 @@ describe 'Reportable note on issue', :feature, :js do
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index 8e75b4af3eb..a491abdb6cb 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
-describe 'Reportable note on merge request', :feature, :js do
+describe 'Reportable note on merge request', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
- visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ visit project_merge_request_path(project, merge_request)
end
context 'a normal note' do
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index 5bee4a31379..9a14024684c 100644
--- a/spec/features/reportable_note/snippets_spec.rb
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe 'Reportable note on snippets', :feature, :js do
+describe 'Reportable note on snippets', :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
before do
project.add_master(user)
- gitlab_sign_in(user)
+ sign_in(user)
end
describe 'on project snippet' do
@@ -14,7 +14,7 @@ describe 'Reportable note on snippets', :feature, :js do
let!(:note) { create(:note_on_project_snippet, noteable: snippet, project: project) }
before do
- visit namespace_project_snippet_path(project.namespace, project, snippet)
+ visit project_snippet_path(project, snippet)
end
it_behaves_like 'reportable note'
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index ea18879b4bf..1725b70acf3 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,12 +1,10 @@
require 'spec_helper'
describe "Runners" do
- include GitlabRoutingHelper
-
let(:user) { create(:user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
end
describe "specific runners" do
@@ -124,7 +122,7 @@ describe "Runners" do
end
scenario 'user checks default configuration' do
- visit namespace_project_runner_path(project.namespace, project, runner)
+ visit project_runner_path(project, runner)
expect(page).to have_content 'Can run untagged jobs Yes'
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 64469f999af..b29f19f1562 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Search", feature: true do
+describe "Search" do
include FilteredSearchHelpers
let(:user) { create(:user) }
@@ -9,7 +9,7 @@ describe "Search", feature: true do
let!(:issue2) { create(:issue, project: project, author: user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :reporter]
visit search_path
end
@@ -88,7 +88,7 @@ describe "Search", feature: true do
end
it 'finds comment' do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: note.note
@@ -111,7 +111,7 @@ describe "Search", feature: true do
project: project)
# Must visit project dashboard since global search won't search
# everything (e.g. comments, snippets, etc.)
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: note.note
@@ -125,7 +125,7 @@ describe "Search", feature: true do
it 'finds a commit' do
project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: 'add'
@@ -139,7 +139,7 @@ describe "Search", feature: true do
it 'finds a code' do
project = create(:project, :repository) { |p| p.add_reporter(user) }
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
page.within '.search' do
fill_in 'search', with: 'application.js'
@@ -154,9 +154,9 @@ describe "Search", feature: true do
end
end
- describe 'Right header search field', feature: true do
+ describe 'Right header search field' do
it 'allows enter key to search', js: true do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
fill_in 'search', with: 'gitlab'
find('#search').native.send_keys(:enter)
@@ -167,7 +167,7 @@ describe "Search", feature: true do
describe 'Search in project page' do
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
end
it 'shows top right search form' do
@@ -256,7 +256,7 @@ describe "Search", feature: true do
click_button 'Search'
- expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+ expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
end
it 'redirects to single commit regardless of query case' do
@@ -264,7 +264,7 @@ describe "Search", feature: true do
click_button 'Search'
- expect(page).to have_current_path(namespace_project_commit_path(project.namespace, project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
+ expect(page).to have_current_path(project_commit_path(project, '6d394385cf567f80a8fd85055db1ab4c5295806f'))
end
it 'holds on /search page when the only commit is found by message' do
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index e180ca53eb5..3ca1303bda6 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Admin::Projects", feature: true do
+describe "Admin::Projects" do
include AccessMatchers
describe "GET /admin/projects" do
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 40f773956d1..149bd32e736 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Dashboard access", feature: true do
+describe "Dashboard access" do
include AccessMatchers
describe "GET /dashboard" do
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index 87cce32d6c6..53573043ac7 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Internal Group access', feature: true do
+describe 'Internal Group access' do
include AccessMatchers
let(:group) { create(:group, :internal) }
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 1d6b3e77c22..76c4e2c7197 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Private Group access', feature: true do
+describe 'Private Group access' do
include AccessMatchers
let(:group) { create(:group, :private) }
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index d7d76177269..52e87d8d055 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe 'Public Group access', feature: true do
+describe 'Public Group access' do
include AccessMatchers
let(:group) { create(:group, :public) }
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index c19678ab381..41eb7b26578 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Profile access", feature: true do
+describe "Profile access" do
include AccessMatchers
describe "GET /profile/keys" do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 5e26b8bbed6..290cebf511a 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Internal Project Access", feature: true do
+describe "Internal Project Access" do
include AccessMatchers
set(:project) { create(:project, :internal) }
@@ -13,7 +13,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -239,7 +239,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/merge_requests/new" do
- subject { namespace_project_new_merge_request_path(project.namespace, project) }
+ subject { project_new_merge_request_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -253,7 +253,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -272,7 +272,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -291,7 +291,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/settings/integrations" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -305,7 +305,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -320,7 +320,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -334,7 +334,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
context "when allowed for public and internal" do
before do
@@ -372,7 +372,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
context "when allowed for public and internal" do
before do
@@ -410,7 +410,7 @@ describe "Internal Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
context 'when allowed for public and internal' do
before do
@@ -446,7 +446,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -460,7 +460,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -475,7 +475,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -490,7 +490,7 @@ describe "Internal Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -504,7 +504,7 @@ describe "Internal Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Internal Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index b676c236758..276d817b59c 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Private Project Access", feature: true do
+describe "Private Project Access" do
include AccessMatchers
set(:project) { create(:project, :private, public_builds: false) }
@@ -13,7 +13,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -126,7 +126,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore'))}
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -168,7 +168,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -183,7 +183,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -197,7 +197,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -211,7 +211,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -225,7 +225,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -244,7 +244,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -263,7 +263,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/namespace/hooks" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -277,7 +277,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -304,7 +304,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -330,7 +330,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -358,7 +358,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -391,7 +391,7 @@ describe "Private Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -421,7 +421,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -436,7 +436,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -451,7 +451,7 @@ describe "Private Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -465,7 +465,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -479,7 +479,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -493,7 +493,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules/new" do
- subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+ subject { new_project_pipeline_schedule_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -507,7 +507,7 @@ describe "Private Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_pipeline_schedule_path(project.namespace, project) }
+ subject { new_project_pipeline_schedule_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -529,7 +529,7 @@ describe "Private Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 59655b0c31a..417b0894a5c 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Public Project Access", feature: true do
+describe "Public Project Access" do
include AccessMatchers
set(:project) { create(:project, :public) }
@@ -13,7 +13,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path" do
- subject { namespace_project_path(project.namespace, project) }
+ subject { project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -27,7 +27,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/tree/master" do
- subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) }
+ subject { project_tree_path(project, project.repository.root_ref) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -41,7 +41,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/commits/master" do
- subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) }
+ subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -55,7 +55,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/commit/:sha" do
- subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) }
+ subject { project_commit_path(project, project.repository.commit) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -69,7 +69,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/compare" do
- subject { namespace_project_compare_index_path(project.namespace, project) }
+ subject { project_compare_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/members" do
- subject { namespace_project_settings_members_path(project.namespace, project) }
+ subject { project_settings_members_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/ci_cd" do
- subject { namespace_project_settings_ci_cd_path(project.namespace, project) }
+ subject { project_settings_ci_cd_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/repository" do
- subject { namespace_project_settings_repository_path(project.namespace, project) }
+ subject { project_settings_repository_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -125,7 +125,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/pipelines" do
- subject { namespace_project_pipelines_path(project.namespace, project) }
+ subject { project_pipelines_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -140,7 +140,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/pipelines/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
- subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+ subject { project_pipeline_path(project, pipeline) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -154,7 +154,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/builds" do
- subject { namespace_project_jobs_path(project.namespace, project) }
+ subject { project_jobs_path(project) }
context "when allowed for public" do
before do
@@ -192,7 +192,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/builds/:id" do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { namespace_project_job_path(project.namespace, project, build.id) }
+ subject { project_job_path(project, build.id) }
context "when allowed for public" do
before do
@@ -230,7 +230,7 @@ describe "Public Project Access", feature: true do
describe 'GET /:project_path/builds/:id/trace' do
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, pipeline: pipeline) }
- subject { trace_namespace_project_job_path(project.namespace, project, build.id) }
+ subject { trace_project_job_path(project, build.id) }
context 'when allowed for public' do
before do
@@ -266,7 +266,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/pipeline_schedules" do
- subject { namespace_project_pipeline_schedules_path(project.namespace, project) }
+ subject { project_pipeline_schedules_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -280,7 +280,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/environments" do
- subject { namespace_project_environments_path(project.namespace, project) }
+ subject { project_environments_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -295,7 +295,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_path(project.namespace, project, environment) }
+ subject { project_environment_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -310,7 +310,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id/deployments" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environment_deployments_path(project.namespace, project, environment) }
+ subject { project_environment_deployments_path(project, environment) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -324,7 +324,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/environments/new" do
- subject { new_namespace_project_environment_path(project.namespace, project) }
+ subject { new_project_environment_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -340,7 +340,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { namespace_project_blob_path(project.namespace, project, File.join(commit.id, '.gitignore')) }
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -353,7 +353,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/edit" do
- subject { edit_namespace_project_path(project.namespace, project) }
+ subject { edit_project_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -367,7 +367,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/deploy_keys" do
- subject { namespace_project_deploy_keys_path(project.namespace, project) }
+ subject { project_deploy_keys_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -381,7 +381,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/issues" do
- subject { namespace_project_issues_path(project.namespace, project) }
+ subject { project_issues_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -396,7 +396,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/issues/:id/edit" do
let(:issue) { create(:issue, project: project) }
- subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+ subject { edit_project_issue_path(project, issue) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -410,7 +410,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -424,7 +424,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -438,7 +438,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/merge_requests" do
- subject { namespace_project_merge_requests_path(project.namespace, project) }
+ subject { project_merge_requests_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -452,7 +452,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/merge_requests/new" do
- subject { namespace_project_new_merge_request_path(project.namespace, project) }
+ subject { project_new_merge_request_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -466,7 +466,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/branches" do
- subject { namespace_project_branches_path(project.namespace, project) }
+ subject { project_branches_path(project) }
before do
# Speed increase
@@ -485,7 +485,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/tags" do
- subject { namespace_project_tags_path(project.namespace, project) }
+ subject { project_tags_path(project) }
before do
# Speed increase
@@ -504,7 +504,7 @@ describe "Public Project Access", feature: true do
end
describe "GET /:project_path/settings/integrations" do
- subject { namespace_project_settings_integrations_path(project.namespace, project) }
+ subject { project_settings_integrations_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -526,7 +526,7 @@ describe "Public Project Access", feature: true do
project.container_repositories << container_repository
end
- subject { namespace_project_container_registry_index_path(project.namespace, project) }
+ subject { project_container_registry_index_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 2659b3ee3ec..178782af56c 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Internal Project Snippets Access", feature: true do
+describe "Internal Project Snippets Access" do
include AccessMatchers
let(:project) { create(:empty_project, :internal) }
@@ -9,7 +9,7 @@ describe "Internal Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -23,7 +23,7 @@ describe "Internal Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -38,7 +38,7 @@ describe "Internal Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id" do
context "for an internal snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -52,7 +52,7 @@ describe "Internal Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -68,7 +68,7 @@ describe "Internal Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id/raw" do
context "for an internal snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { raw_project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -82,7 +82,7 @@ describe "Internal Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 6eb9f163bd5..7725c25ca1f 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Private Project Snippets Access", feature: true do
+describe "Private Project Snippets Access" do
include AccessMatchers
let(:project) { create(:empty_project, :private) }
@@ -8,7 +8,7 @@ describe "Private Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -22,7 +22,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -36,7 +36,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/:id for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -50,7 +50,7 @@ describe "Private Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/:id/raw for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index f3329d0bc96..52aec75dcd0 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Public Project Snippets Access", feature: true do
+describe "Public Project Snippets Access" do
include AccessMatchers
let(:project) { create(:empty_project, :public) }
@@ -10,7 +10,7 @@ describe "Public Project Snippets Access", feature: true do
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
- subject { namespace_project_snippets_path(project.namespace, project) }
+ subject { project_snippets_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -24,7 +24,7 @@ describe "Public Project Snippets Access", feature: true do
end
describe "GET /:project_path/snippets/new" do
- subject { new_namespace_project_snippet_path(project.namespace, project) }
+ subject { new_project_snippet_path(project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -39,7 +39,7 @@ describe "Public Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id" do
context "for a public snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, public_snippet) }
+ subject { project_snippet_path(project, public_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -53,7 +53,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for an internal snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -67,7 +67,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -83,7 +83,7 @@ describe "Public Project Snippets Access", feature: true do
describe "GET /:project_path/snippets/:id/raw" do
context "for a public snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) }
+ subject { raw_project_snippet_path(project, public_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -97,7 +97,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for an internal snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) }
+ subject { raw_project_snippet_path(project, internal_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
@@ -111,7 +111,7 @@ describe "Public Project Snippets Access", feature: true do
end
context "for a private snippet" do
- subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) }
+ subject { raw_project_snippet_path(project, private_snippet) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 5d6d1e79af2..b6367b88e17 100644
--- a/spec/features/signup_spec.rb
+++ b/spec/features/signup_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Signup', feature: true do
+feature 'Signup' do
describe 'signup with no errors' do
context "when sending confirmation email" do
before do
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index ec75817b942..835fd90adc8 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -1,12 +1,12 @@
require 'rails_helper'
-feature 'Explore Snippets', feature: true do
+feature 'Explore Snippets' do
let!(:public_snippet) { create(:personal_snippet, :public) }
let!(:internal_snippet) { create(:personal_snippet, :internal) }
let!(:private_snippet) { create(:personal_snippet, :private) }
scenario 'User should see snippets that are not private' do
- gitlab_sign_in create(:user)
+ sign_in create(:user)
visit explore_snippets_path
expect(page).to have_content(public_snippet.title)
@@ -15,7 +15,7 @@ feature 'Explore Snippets', feature: true do
end
scenario 'External user should see only public snippets' do
- gitlab_sign_in create(:user, :external)
+ sign_in create(:user, :external)
visit explore_snippets_path
expect(page).to have_content(public_snippet.title)
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index 3babb1c02cc..3a229612235 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'Internal Snippets', feature: true, js: true do
+feature 'Internal Snippets', js: true do
let(:internal_snippet) { create(:personal_snippet, :internal) }
describe 'normal user' do
before do
- gitlab_sign_in :user
+ sign_in(create(:user))
end
scenario 'sees internal snippets' do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index c7e2e3d8a34..f1d0905738b 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Comments on personal snippets', :js, feature: true do
+describe 'Comments on personal snippets', :js do
include NoteInteractionHelpers
let!(:user) { create(:user) }
@@ -14,7 +14,7 @@ describe 'Comments on personal snippets', :js, feature: true do
let!(:other_note) { create(:note_on_personal_snippet) }
before do
- gitlab_sign_in user
+ sign_in user
visit snippet_path(snippet)
end
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index afd945a8555..bdeeca7187e 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Public Snippets', :js, feature: true do
+feature 'Public Snippets', :js do
scenario 'Unauthenticated user should see public snippets' do
public_snippet = create(:personal_snippet, :public)
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index 4c21e7321f4..cd66a2cb20c 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'Search Snippets', feature: true do
+feature 'Search Snippets' do
scenario 'User searches for snippets by title' do
public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
private_snippet = create(:personal_snippet, :private, title: 'Middle and End')
- gitlab_sign_in private_snippet.author
+ sign_in private_snippet.author
visit dashboard_snippets_path
page.within '.search' do
@@ -41,7 +41,7 @@ feature 'Search Snippets', feature: true do
CONTENT
)
- gitlab_sign_in create(:user)
+ sign_in create(:user)
visit dashboard_snippets_path
page.within '.search' do
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index 95fc1d2bb62..5a48f5774ca 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Snippet', :js, feature: true do
+feature 'Snippet', :js do
let(:project) { create(:project, :repository) }
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content) }
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index ac5c14ed427..a919f5fa20b 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,10 +1,12 @@
require 'rails_helper'
-feature 'Create Snippet', :js, feature: true do
+feature 'User creates snippet', :js do
include DropzoneHelper
+ let(:user) { create(:user) }
+
before do
- gitlab_sign_in :user
+ sign_in(user)
visit new_snippet_path
end
@@ -39,7 +41,7 @@ feature 'Create Snippet', :js, feature: true do
expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/temp/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/system/temp/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
@@ -57,7 +59,7 @@ feature 'Create Snippet', :js, feature: true do
wait_for_requests
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
@@ -82,7 +84,7 @@ feature 'Create Snippet', :js, feature: true do
end
expect(page).to have_content('Hello World!')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
visit(link)
expect(page.status_code).to eq(200)
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
new file mode 100644
index 00000000000..ae5b883c477
--- /dev/null
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -0,0 +1,19 @@
+require 'rails_helper'
+
+feature 'User deletes snippet' do
+ let(:user) { create(:user) }
+ let(:content) { 'puts "test"' }
+ let(:snippet) { create(:personal_snippet, :public, content: content, author: user) }
+
+ before do
+ sign_in(user)
+
+ visit snippet_path(snippet)
+ end
+
+ it 'deletes the snippet' do
+ first(:link, 'Delete').click
+
+ expect(page).not_to have_content(snippet.title)
+ end
+end
diff --git a/spec/features/snippets/edit_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 860e1b156d6..26070e508e2 100644
--- a/spec/features/snippets/edit_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Edit Snippet', :js, feature: true do
+feature 'User edits snippet', :js do
include DropzoneHelper
let(:file_name) { 'test.rb' }
@@ -10,7 +10,7 @@ feature 'Edit Snippet', :js, feature: true do
let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, author: user) }
before do
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_snippet_path(snippet)
wait_for_requests
@@ -27,12 +27,32 @@ feature 'Edit Snippet', :js, feature: true do
it 'updates the snippet with files attached' do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
- expect(page.find_field("personal_snippet_description").value).to have_content('banana_sample')
+ expect(page.find_field('personal_snippet_description').value).to have_content('banana_sample')
click_button('Save changes')
wait_for_requests
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
- expect(link).to match(%r{/uploads/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
+ expect(link).to match(%r{/uploads/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
+ end
+
+ it 'updates the snippet to make it internal' do
+ choose 'Internal'
+
+ click_button 'Save changes'
+ wait_for_requests
+
+ expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+ expect(page).to have_xpath("//i[@class='fa fa-shield']")
+ end
+
+ it 'updates the snippet to make it public' do
+ choose 'Public'
+
+ click_button 'Save changes'
+ wait_for_requests
+
+ expect(page).to have_no_xpath("//i[@class='fa fa-lock']")
+ expect(page).to have_xpath("//i[@class='fa fa-globe']")
end
end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index b971c6aab53..7bc27486787 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -1,13 +1,13 @@
require 'rails_helper'
-feature 'User Snippets', feature: true do
+feature 'User Snippets' do
let(:author) { create(:user) }
let!(:public_snippet) { create(:personal_snippet, :public, author: author, title: "This is a public snippet") }
let!(:internal_snippet) { create(:personal_snippet, :internal, author: author, title: "This is an internal snippet") }
let!(:private_snippet) { create(:personal_snippet, :private, author: author, title: "This is a private snippet") }
background do
- gitlab_sign_in author
+ sign_in author
visit dashboard_snippets_path
end
diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb
index 70b16bfc810..ae3b876f87c 100644
--- a/spec/features/snippets_spec.rb
+++ b/spec/features/snippets_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Snippets', feature: true do
+describe 'Snippets' do
context 'when the project has snippets' do
let(:project) { create(:empty_project, :public) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
index 52db3583dac..35e1ca32f67 100644
--- a/spec/features/tags/master_creates_tag_spec.rb
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -1,17 +1,17 @@
require 'spec_helper'
-feature 'Master creates tag', feature: true do
+feature 'Master creates tag' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'from tag list' do
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'with an invalid name displays an error' do
@@ -36,7 +36,7 @@ feature 'Master creates tag', feature: true do
create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v3.0'))
+ project_tag_path(project, 'v3.0'))
expect(page).to have_content 'v3.0'
page.within 'pre.wrap' do
expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
@@ -47,7 +47,7 @@ feature 'Master creates tag', feature: true do
create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v4.0'))
+ project_tag_path(project, 'v4.0'))
expect(page).to have_content 'v4.0'
page.within '.description' do
expect(page).to have_content 'Awesome release notes'
@@ -72,7 +72,7 @@ feature 'Master creates tag', feature: true do
context 'from new tag page' do
before do
- visit new_namespace_project_tag_path(project.namespace, project)
+ visit new_project_tag_path(project)
end
it 'description has autocomplete', :js do
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 58f33e954f9..e3c904ef3aa 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Master deletes tag', feature: true do
+feature 'Master deletes tag' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tags_path(project.namespace, project)
+ sign_in(user)
+ visit project_tags_path(project)
end
context 'from the tags list page', js: true do
@@ -24,12 +24,12 @@ feature 'Master deletes tag', feature: true do
scenario 'deletes the tag' do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Delete tag'
expect(current_path).to eq(
- namespace_project_tags_path(project.namespace, project))
+ project_tags_path(project))
expect(page).not_to have_content 'v1.0.0'
end
end
diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
index 18c8c4c511c..d6e84a1c685 100644
--- a/spec/features/tags/master_updates_tag_spec.rb
+++ b/spec/features/tags/master_updates_tag_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-feature 'Master updates tag', feature: true do
+feature 'Master updates tag' do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
- visit namespace_project_tags_path(project.namespace, project)
+ sign_in(user)
+ visit project_tags_path(project)
end
context 'from the tags list page' do
@@ -20,7 +20,7 @@ feature 'Master updates tag', feature: true do
click_button 'Save changes'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ project_tag_path(project, 'v1.1.0'))
expect(page).to have_content 'v1.1.0'
expect(page).to have_content 'Awesome release notes'
end
@@ -45,7 +45,7 @@ feature 'Master updates tag', feature: true do
click_button 'Save changes'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
+ project_tag_path(project, 'v1.1.0'))
expect(page).to have_content 'v1.1.0'
expect(page).to have_content 'Awesome release notes'
end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
index 3c21fa06694..27936bc7f52 100644
--- a/spec/features/tags/master_views_tags_spec.rb
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -1,23 +1,23 @@
require 'spec_helper'
-feature 'Master views tags', feature: true do
+feature 'Master views tags' do
let(:user) { create(:user) }
before do
project.team << [user, :master]
- gitlab_sign_in(user)
+ sign_in(user)
end
context 'when project has no tags' do
let(:project) { create(:project_empty_repo) }
before do
- visit namespace_project_path(project.namespace, project)
+ visit project_path(project)
click_on 'README'
fill_in :commit_message, with: 'Add a README file', visible: true
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
click_button 'Commit changes'
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'displays a specific message' do
@@ -30,15 +30,15 @@ feature 'Master views tags', feature: true do
let(:repository) { project.repository }
before do
- visit namespace_project_tags_path(project.namespace, project)
+ visit project_tags_path(project)
end
scenario 'avoids a N+1 query in branches index' do
- control_count = ActiveRecord::QueryRecorder.new { visit namespace_project_tags_path(project.namespace, project) }.count
+ control_count = ActiveRecord::QueryRecorder.new { visit project_tags_path(project) }.count
%w(one two three four five).each { |tag| repository.add_tag(user, tag, 'master', 'foo') }
- expect { visit namespace_project_tags_path(project.namespace, project) }.not_to exceed_query_limit(control_count)
+ expect { visit project_tags_path(project) }.not_to exceed_query_limit(control_count)
end
scenario 'views the tags list page' do
@@ -49,7 +49,7 @@ feature 'Master views tags', feature: true do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
expect(page).to have_content 'v1.0.0'
expect(page).to have_content 'This tag has no release notes.'
end
@@ -59,24 +59,24 @@ feature 'Master views tags', feature: true do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Browse files'
expect(current_path).to eq(
- namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
+ project_tree_path(project, 'v1.0.0'))
end
scenario 'has a button to browse commits' do
click_on 'v1.0.0'
expect(current_path).to eq(
- namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
+ project_tag_path(project, 'v1.0.0'))
click_on 'Browse commits'
expect(current_path).to eq(
- namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
+ project_commits_path(project, 'v1.0.0'))
end
end
end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 51b1b8e2328..7e198ce0677 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Task Lists', feature: true do
+feature 'Task Lists' do
include Warden::Test::Helpers
let(:project) { create(:empty_project) }
@@ -59,10 +59,10 @@ feature 'Task Lists', feature: true do
end
def visit_issue(project, issue)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ visit project_issue_path(project, issue)
end
- describe 'for Issues', feature: true do
+ describe 'for Issues' do
describe 'multiple tasks', js: true do
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
@@ -98,7 +98,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
@@ -116,7 +116,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("0 of 1 task completed")
end
@@ -135,7 +135,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on Issues#index' do
- visit namespace_project_issues_path(project.namespace, project)
+ visit project_issues_path(project)
expect(page).to have_content("1 of 1 task completed")
end
@@ -242,7 +242,7 @@ feature 'Task Lists', feature: true do
describe 'for Merge Requests' do
def visit_merge_request(project, merge)
- visit namespace_project_merge_request_path(project.namespace, project, merge)
+ visit project_merge_request_path(project, merge)
end
describe 'multiple tasks' do
@@ -281,7 +281,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("2 of 6 tasks completed")
end
end
@@ -298,7 +298,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("0 of 1 task completed")
end
end
@@ -315,7 +315,7 @@ feature 'Task Lists', feature: true do
end
it 'provides a summary on MergeRequests#index' do
- visit namespace_project_merge_requests_path(project.namespace, project)
+ visit project_merge_requests_path(project)
expect(page).to have_content("1 of 1 task completed")
end
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 5af2c0e9035..604fe326e96 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Triggers', feature: true, js: true do
+feature 'Triggers', js: true do
let(:trigger_title) { 'trigger desc' }
let(:user) { create(:user) }
let(:user2) { create(:user) }
@@ -14,7 +14,7 @@ feature 'Triggers', feature: true, js: true do
@project.team << [user2, :master]
@project.team << [guest_user, :guest]
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
describe 'create trigger workflow' do
@@ -42,7 +42,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'click on edit trigger opens edit trigger page' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if edit page has correct descrption
find('a[title="Edit"]').click
@@ -51,7 +51,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'edit trigger and save' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if edit page opens, then fill in new description and save
find('a[title="Edit"]').click
@@ -67,7 +67,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
create(:ci_trigger, owner: nil, project: @project)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
find('a[title="Edit"]').click
@@ -84,7 +84,7 @@ feature 'Triggers', feature: true, js: true do
describe 'trigger "Take ownership" workflow' do
before(:each) do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
scenario 'button "Take ownership" has correct alert' do
@@ -106,7 +106,7 @@ feature 'Triggers', feature: true, js: true do
describe 'trigger "Revoke" workflow' do
before(:each) do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
end
scenario 'button "Revoke" has correct alert' do
@@ -131,7 +131,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "legacy" badge for legacy trigger' do
create(:ci_trigger, owner: nil, project: @project)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
expect(page.find('.triggers-list')).to have_content 'legacy'
@@ -140,7 +140,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
expect(page.find('.triggers-list')).to have_content 'invalid'
@@ -150,7 +150,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
@@ -163,7 +163,7 @@ feature 'Triggers', feature: true, js: true do
scenario 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
- visit namespace_project_settings_ci_cd_path(@project.namespace, @project)
+ visit project_settings_ci_cd_path(@project)
# See if trigger shows full token and has copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index 352f8ba70ac..d728dc59b3f 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Unsubscribe links', feature: true do
+describe 'Unsubscribe links' do
include Warden::Test::Helpers
let(:recipient) { create(:user) }
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 797b7b3d50d..e8884bc1a00 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -1,11 +1,11 @@
require 'rails_helper'
-feature 'User uploads avatar to group', feature: true do
+feature 'User uploads avatar to group' do
scenario 'they see the new avatar' do
user = create(:user)
group = create(:group)
group.add_owner(user)
- gitlab_sign_in(user)
+ sign_in(user)
visit edit_group_path(group)
attach_file(
@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
visit group_path(group)
- expect(page).to have_selector(%Q(img[src$="/uploads/system/group/avatar/#{group.id}/dk.png"]))
+ expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/group/avatar/#{group.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(group.reload.avatar.file).to exist
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index a3f8027f4da..52003bb0859 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
-feature 'User uploads avatar to profile', feature: true do
+feature 'User uploads avatar to profile' do
scenario 'they see their new avatar' do
user = create(:user)
- gitlab_sign_in(user)
+ sign_in(user)
visit profile_path
attach_file(
@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
visit user_path(user)
- expect(page).to have_selector(%Q(img[src$="/uploads/system/user/avatar/#{user.id}/dk.png"]))
+ expect(page).to have_selector(%Q(img[data-src$="/uploads/-/system/user/avatar/#{user.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important
expect(user.reload.avatar.file).to exist
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 77a1012762d..0654923d9a6 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'User uploads file to note', feature: true do
+feature 'User uploads file to note' do
include DropzoneHelper
let(:user) { create(:user) }
@@ -8,8 +8,8 @@ feature 'User uploads file to note', feature: true do
let(:issue) { create(:issue, project: project, author: user) }
before do
- gitlab_sign_in(user)
- visit namespace_project_issue_path(project.namespace, project, issue)
+ sign_in(user)
+ visit project_issue_path(project, issue)
end
context 'before uploading' do
diff --git a/spec/features/user_callout_spec.rb b/spec/features/user_callout_spec.rb
index 7538a6e4a04..93768aa46df 100644
--- a/spec/features/user_callout_spec.rb
+++ b/spec/features/user_callout_spec.rb
@@ -6,7 +6,7 @@ describe 'User Callouts', js: true do
let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index 24fff1a3052..670e8dda916 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -33,22 +33,24 @@ describe 'User can display performance bar', :js do
end
end
+ let(:group) { create(:group) }
+
context 'when user is logged-out' do
before do
visit root_path
end
- context 'when the gitlab_performance_bar feature is disabled' do
+ context 'when the performance_bar feature is disabled' do
before do
- Feature.disable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: nil)
end
it_behaves_like 'performance bar is disabled'
end
- context 'when the gitlab_performance_bar feature is enabled' do
+ context 'when the performance_bar feature is enabled' do
before do
- Feature.enable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: group.id)
end
it_behaves_like 'performance bar is disabled'
@@ -57,22 +59,25 @@ describe 'User can display performance bar', :js do
context 'when user is logged-in' do
before do
- gitlab_sign_in(create(:user))
+ user = create(:user)
+
+ sign_in(user)
+ group.add_guest(user)
visit root_path
end
- context 'when the gitlab_performance_bar feature is disabled' do
+ context 'when the performance_bar feature is disabled' do
before do
- Feature.disable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: nil)
end
it_behaves_like 'performance bar is disabled'
end
- context 'when the gitlab_performance_bar feature is enabled' do
+ context 'when the performance_bar feature is enabled' do
before do
- Feature.enable('gitlab_performance_bar')
+ stub_application_setting(performance_bar_allowed_group_id: group.id)
end
it_behaves_like 'performance bar is enabled'
diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb
index 377b1a0148f..b961d2337ed 100644
--- a/spec/features/users/projects_spec.rb
+++ b/spec/features/users/projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Projects tab on a user profile', :feature, :js do
+describe 'Projects tab on a user profile', :js do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, namespace: user.namespace) }
let!(:project2) { create(:empty_project, namespace: user.namespace) }
@@ -8,7 +8,7 @@ describe 'Projects tab on a user profile', :feature, :js do
before do
allow(Project).to receive(:default_per_page).and_return(1)
- gitlab_sign_in(user)
+ sign_in(user)
visit user_path(user)
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 797b317a9bb..7c5abe54d56 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -1,11 +1,12 @@
require 'spec_helper'
feature 'User RSS' do
+ let(:user) { create(:user) }
let(:path) { user_path(create(:user)) }
context 'when signed in' do
before do
- gitlab_sign_in(create(:user))
+ sign_in(user)
visit path
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 74c5cbd7887..13760b4c2fc 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Snippets tab on a user profile', feature: true, js: true do
+describe 'Snippets tab on a user profile', js: true do
context 'when the user has snippets' do
let(:user) { create(:user) }
@@ -24,7 +24,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
let!(:other_snippet) { create(:snippet, :public) }
it 'contains only internal and public snippets of a user when a user is logged in' do
- gitlab_sign_in(:user)
+ sign_in(create(:user))
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
wait_for_requests
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 84af13d3e49..ff004d85272 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Users', feature: true, js: true do
+feature 'Users', js: true do
let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') }
scenario 'GET /users/sign_in creates a new user account' do
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index 85085bf305a..dd770fe5043 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -6,11 +6,11 @@ describe 'Project variables', js: true do
let(:variable) { create(:ci_variable, key: 'test_key', value: 'test value') }
before do
- gitlab_sign_in(user)
+ sign_in(user)
project.team << [user, :master]
project.variables << variable
- visit namespace_project_settings_ci_cd_path(project.namespace, project)
+ visit project_settings_ci_cd_path(project)
end
it 'shows list of variables' do
@@ -24,7 +24,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('key')
expect(page).to have_content('No')
@@ -36,7 +36,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: '')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('new_key')
end
@@ -48,7 +48,7 @@ describe 'Project variables', js: true do
check('Protected')
click_button('Add new variable')
- expect(page).to have_content('Variables were successfully updated.')
+ expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do
expect(page).to have_content('key')
expect(page).to have_content('Yes')
@@ -82,7 +82,7 @@ describe 'Project variables', js: true do
it 'deletes variable' do
page.within('.variables-table') do
- find('.btn-variable-delete').click
+ click_on 'Remove'
end
expect(page).not_to have_selector('variables-table')
@@ -90,7 +90,7 @@ describe 'Project variables', js: true do
it 'edits variable' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -104,7 +104,7 @@ describe 'Project variables', js: true do
it 'edits variable with empty value' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -117,7 +117,7 @@ describe 'Project variables', js: true do
it 'edits variable to be protected' do
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
@@ -132,7 +132,7 @@ describe 'Project variables', js: true do
project.variables.first.update(protected: true)
page.within('.variables-table') do
- find('.btn-variable-edit').click
+ click_on 'Update'
end
expect(page).to have_content('Update variable')
diff --git a/spec/finders/access_requests_finder_spec.rb b/spec/finders/access_requests_finder_spec.rb
index c7278e971ae..1d0c15392b2 100644
--- a/spec/finders/access_requests_finder_spec.rb
+++ b/spec/finders/access_requests_finder_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe AccessRequestsFinder, services: true do
+describe AccessRequestsFinder do
let(:user) { create(:user) }
let(:access_requester) { create(:user) }
diff --git a/spec/finders/admin/projects_finder_spec.rb b/spec/finders/admin/projects_finder_spec.rb
new file mode 100644
index 00000000000..296d6c51d04
--- /dev/null
+++ b/spec/finders/admin/projects_finder_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe Admin::ProjectsFinder do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :public) }
+
+ let!(:private_project) do
+ create(:empty_project, :private, name: 'A', path: 'A')
+ end
+
+ let!(:internal_project) do
+ create(:empty_project, :internal, group: group, name: 'B', path: 'B')
+ end
+
+ let!(:public_project) do
+ create(:empty_project, :public, group: group, name: 'C', path: 'C')
+ end
+
+ let!(:shared_project) do
+ create(:empty_project, :private, name: 'D', path: 'D')
+ end
+
+ let(:params) { {} }
+ let(:current_user) { user }
+ let(:project_ids_relation) { nil }
+ let(:finder) { described_class.new(params: params, current_user: current_user) }
+
+ subject { finder.execute.to_a }
+
+ context 'without a user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'with a user' do
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'filter by namespace_id' do
+ let(:namespace) { create(:namespace) }
+ let!(:project_in_namespace) { create(:empty_project, namespace: namespace) }
+ let(:params) { { namespace_id: namespace.id } }
+
+ it { is_expected.to eq([project_in_namespace]) }
+ end
+
+ context 'filter by visibility_level' do
+ before do
+ private_project.add_master(user)
+ end
+
+ context 'private' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
+
+ it { is_expected.to match_array([shared_project, private_project]) }
+ end
+
+ context 'internal' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::INTERNAL } }
+
+ it { is_expected.to eq([internal_project]) }
+ end
+
+ context 'public' do
+ let(:params) { { visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
+
+ it { is_expected.to eq([public_project]) }
+ end
+ end
+
+ context 'filter by push' do
+ let(:pushed_event) { create(:event, :pushed) }
+ let!(:project_with_push) { pushed_event.project }
+ let(:params) { { with_push: true } }
+
+ it { is_expected.to eq([project_with_push]) }
+ end
+
+ context 'filter by abandoned' do
+ before do
+ private_project.update(last_activity_at: Time.zone.now - 6.months - 1.minute)
+ end
+
+ let(:params) { { abandoned: true } }
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ context 'filter by last_repository_check_failed' do
+ before do
+ private_project.update(last_repository_check_failed: true)
+ end
+
+ let(:params) { { last_repository_check_failed: true } }
+
+ it { is_expected.to eq([private_project]) }
+ end
+
+ context 'filter by archived' do
+ let!(:archived_project) { create(:empty_project, :public, :archived, name: 'E', path: 'E') }
+
+ context 'archived=false' do
+ let(:params) { { archived: false } }
+
+ it { is_expected.to match_array([shared_project, public_project, internal_project, private_project]) }
+ end
+
+ context 'archived=true' do
+ let(:params) { { archived: true } }
+
+ it { is_expected.to match_array([archived_project, shared_project, public_project, internal_project, private_project]) }
+ end
+ end
+
+ context 'filter by personal' do
+ let!(:personal_project) { create(:empty_project, namespace: user.namespace) }
+ let(:params) { { personal: true } }
+
+ it { is_expected.to eq([personal_project]) }
+ end
+
+ context 'filter by name' do
+ let(:params) { { name: 'C' } }
+
+ it { is_expected.to match_array([shared_project, public_project, private_project]) }
+ end
+
+ context 'sorting' do
+ let(:params) { { sort: 'name_asc' } }
+
+ it { is_expected.to eq([private_project, internal_project, public_project, shared_project]) }
+ end
+ end
+end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 4a52f0d5c58..bef4fd44331 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -59,6 +59,23 @@ describe IssuesFinder do
end
end
+ context 'filtering by group milestone' do
+ let!(:group) { create(:group, :public) }
+ let(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let(:params) { { milestone_title: group_milestone.title } }
+
+ before do
+ project2.update(namespace: group)
+ issue2.update(milestone: group_milestone)
+ issue3.update(milestone: group_milestone)
+ end
+
+ it 'returns issues assigned to that group milestone' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+
context 'filtering by no milestone' do
let(:params) { { milestone_title: Milestone::None.title } }
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 5eb26de6c92..b46218bf72e 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -47,6 +47,25 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1)
end
+ context 'filtering by group milestone' do
+ let!(:group) { create(:group, :public) }
+ let(:group_milestone) { create(:milestone, group: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let(:params) { { milestone_title: group_milestone.title } }
+
+ before do
+ project2.update(namespace: group)
+ merge_request2.update(milestone: group_milestone)
+ merge_request3.update(milestone: group_milestone)
+ end
+
+ it 'returns issues assigned to that group milestone' do
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
+ end
+ end
+
context 'with created_after and created_before params' do
let(:project4) { create(:empty_project, forked_from_project: project1) }
diff --git a/spec/finders/milestones_finder_spec.rb b/spec/finders/milestones_finder_spec.rb
new file mode 100644
index 00000000000..32ec983c5b8
--- /dev/null
+++ b/spec/finders/milestones_finder_spec.rb
@@ -0,0 +1,90 @@
+require 'spec_helper'
+
+describe MilestonesFinder do
+ let(:group) { create(:group) }
+ let(:project_1) { create(:empty_project, namespace: group) }
+ let(:project_2) { create(:empty_project, namespace: group) }
+ let!(:milestone_1) { create(:milestone, group: group, title: 'one test', due_date: Date.today) }
+ let!(:milestone_2) { create(:milestone, group: group) }
+ let!(:milestone_3) { create(:milestone, project: project_1, state: 'active', due_date: Date.tomorrow) }
+ let!(:milestone_4) { create(:milestone, project: project_2, state: 'active') }
+
+ it 'it returns milestones for projects' do
+ result = described_class.new(project_ids: [project_1.id, project_2.id], state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_3, milestone_4)
+ end
+
+ it 'returns milestones for groups' do
+ result = described_class.new(group_ids: group.id, state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_2)
+ end
+
+ it 'returns milestones for groups and projects' do
+ result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
+ end
+
+ context 'with filters' do
+ let(:params) do
+ {
+ project_ids: [project_1.id, project_2.id],
+ group_ids: group.id,
+ state: 'all'
+ }
+ end
+
+ before do
+ milestone_1.close
+ milestone_3.close
+ end
+
+ it 'filters by active state' do
+ params[:state] = 'active'
+ result = described_class.new(params).execute
+
+ expect(result).to contain_exactly(milestone_2, milestone_4)
+ end
+
+ it 'filters by closed state' do
+ params[:state] = 'closed'
+ result = described_class.new(params).execute
+
+ expect(result).to contain_exactly(milestone_1, milestone_3)
+ end
+
+ it 'filters by title' do
+ result = described_class.new(params.merge(title: 'one test')).execute
+
+ expect(result.to_a).to contain_exactly(milestone_1)
+ end
+ end
+
+ context 'with order' do
+ let(:params) do
+ {
+ project_ids: [project_1.id, project_2.id],
+ group_ids: group.id,
+ state: 'all'
+ }
+ end
+
+ it "default orders by due date" do
+ result = described_class.new(params).execute
+
+ expect(result.first).to eq(milestone_1)
+ expect(result.second).to eq(milestone_3)
+ end
+
+ it "orders by parameter" do
+ result = described_class.new(params.merge(order: 'id DESC')).execute
+
+ expect(result.first).to eq(milestone_4)
+ expect(result.second).to eq(milestone_3)
+ expect(result.third).to eq(milestone_2)
+ expect(result.fourth).to eq(milestone_1)
+ end
+ end
+end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index 780b309b45e..1bab6d64388 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -45,6 +45,17 @@ describe UsersFinder do
expect(users).to contain_exactly(user, user1, user2, omniauth_user)
end
+
+ it 'filters by created_at' do
+ filtered_user_before = create(:user, created_at: 3.days.ago)
+ filtered_user_after = create(:user, created_at: Time.now + 3.days)
+
+ users = described_class.new(user,
+ created_after: 2.days.ago,
+ created_before: Time.now + 2.days).execute
+
+ expect(users.map(&:username)).not_to include([filtered_user_before.username, filtered_user_after.username])
+ end
end
context 'with an admin user' do
diff --git a/spec/fixtures/api/schemas/entities/merge_request.json b/spec/fixtures/api/schemas/entities/merge_request.json
index b6a59a6cc47..7ffa82fc4bd 100644
--- a/spec/fixtures/api/schemas/entities/merge_request.json
+++ b/spec/fixtures/api/schemas/entities/merge_request.json
@@ -75,6 +75,7 @@
"additionalProperties": false
},
"target_branch_commits_path": { "type": "string" },
+ "target_branch_tree_path": { "type": "string" },
"source_branch_path": { "type": "string" },
"conflict_resolution_path": { "type": ["string", "null"] },
"cancel_merge_when_pipeline_succeeds_path": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v3/issues.json b/spec/fixtures/api/schemas/public_api/v3/issues.json
index f2ee9c925ae..51b0822bc66 100644
--- a/spec/fixtures/api/schemas/public_api/v3/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v3/issues.json
@@ -22,7 +22,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
index 01f9fbb2c89..b5c74bcc26e 100644
--- a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
@@ -53,7 +53,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/branch.json b/spec/fixtures/api/schemas/public_api/v4/branch.json
new file mode 100644
index 00000000000..a3581178974
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/branch.json
@@ -0,0 +1,20 @@
+{
+ "type": "object",
+ "required" : [
+ "name",
+ "commit",
+ "merged",
+ "protected",
+ "developers_can_push",
+ "developers_can_merge"
+ ],
+ "properties" : {
+ "name": { "type": "string" },
+ "commit": { "$ref": "commit/basic.json" },
+ "merged": { "type": "boolean" },
+ "protected": { "type": "boolean" },
+ "developers_can_push": { "type": "boolean" },
+ "developers_can_merge": { "type": "boolean" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/branches.json b/spec/fixtures/api/schemas/public_api/v4/branches.json
new file mode 100644
index 00000000000..854c902b485
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/branches.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "branch.json" }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/basic.json b/spec/fixtures/api/schemas/public_api/v4/commit/basic.json
new file mode 100644
index 00000000000..9d99628a286
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/commit/basic.json
@@ -0,0 +1,37 @@
+{
+ "type": "object",
+ "required" : [
+ "id",
+ "short_id",
+ "title",
+ "created_at",
+ "parent_ids",
+ "message",
+ "author_name",
+ "author_email",
+ "authored_date",
+ "committer_name",
+ "committer_email",
+ "committed_date"
+ ],
+ "properties" : {
+ "id": { "type": ["string", "null"] },
+ "short_id": { "type": ["string", "null"] },
+ "title": { "type": "string" },
+ "created_at": { "type": "date" },
+ "parent_ids": {
+ "type": ["array", "null"],
+ "items": {
+ "type": "string",
+ "additionalProperties": false
+ }
+ },
+ "message": { "type": "string" },
+ "author_name": { "type": "string" },
+ "author_email": { "type": "string" },
+ "authored_date": { "type": "date" },
+ "committer_name": { "type": "string" },
+ "committer_email": { "type": "string" },
+ "committed_date": { "type": "date" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json
index 2d1c84ee93d..bd6bfc03199 100644
--- a/spec/fixtures/api/schemas/public_api/v4/issues.json
+++ b/spec/fixtures/api/schemas/public_api/v4/issues.json
@@ -22,7 +22,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index 51642e8cbb8..60aa47c1259 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -53,7 +53,8 @@
"properties": {
"id": { "type": "integer" },
"iid": { "type": "integer" },
- "project_id": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/admin.json b/spec/fixtures/api/schemas/public_api/v4/user/admin.json
new file mode 100644
index 00000000000..f733914fbf8
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/user/admin.json
@@ -0,0 +1,34 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "username",
+ "email",
+ "name",
+ "state",
+ "avatar_url",
+ "web_url",
+ "created_at",
+ "is_admin",
+ "bio",
+ "location",
+ "skype",
+ "linkedin",
+ "twitter",
+ "website_url",
+ "organization",
+ "last_sign_in_at",
+ "confirmed_at",
+ "color_scheme_id",
+ "projects_limit",
+ "current_sign_in_at",
+ "identities",
+ "can_create_group",
+ "can_create_project",
+ "two_factor_enabled",
+ "external"
+ ],
+ "properties": {
+ "$ref": "full.json"
+ }
+}
diff --git a/spec/fixtures/config/kubeconfig-without-ca.yml b/spec/fixtures/config/kubeconfig-without-ca.yml
new file mode 100644
index 00000000000..b2cb989d548
--- /dev/null
+++ b/spec/fixtures/config/kubeconfig-without-ca.yml
@@ -0,0 +1,18 @@
+---
+apiVersion: v1
+clusters:
+- name: gitlab-deploy
+ cluster:
+ server: https://kube.domain.com
+contexts:
+- name: gitlab-deploy
+ context:
+ cluster: gitlab-deploy
+ namespace: NAMESPACE
+ user: gitlab-deploy
+current-context: gitlab-deploy
+kind: Config
+users:
+- name: gitlab-deploy
+ user:
+ token: TOKEN
diff --git a/spec/fixtures/config/kubeconfig.yml b/spec/fixtures/config/kubeconfig.yml
new file mode 100644
index 00000000000..c4e8e573c32
--- /dev/null
+++ b/spec/fixtures/config/kubeconfig.yml
@@ -0,0 +1,19 @@
+---
+apiVersion: v1
+clusters:
+- name: gitlab-deploy
+ cluster:
+ server: https://kube.domain.com
+ certificate-authority-data: "UEVN\n"
+contexts:
+- name: gitlab-deploy
+ context:
+ cluster: gitlab-deploy
+ namespace: NAMESPACE
+ user: gitlab-deploy
+current-context: gitlab-deploy
+kind: Config
+users:
+- name: gitlab-deploy
+ user:
+ token: TOKEN
diff --git a/spec/fixtures/config/redis_cache_config_with_env.yml b/spec/fixtures/config/redis_cache_config_with_env.yml
new file mode 100644
index 00000000000..52fd5a06460
--- /dev/null
+++ b/spec/fixtures/config/redis_cache_config_with_env.yml
@@ -0,0 +1,2 @@
+test:
+ url: <%= ENV['TEST_GITLAB_REDIS_CACHE_URL'] %>
diff --git a/spec/fixtures/config/redis_cache_new_format_host.yml b/spec/fixtures/config/redis_cache_new_format_host.yml
new file mode 100644
index 00000000000..a24f3716391
--- /dev/null
+++ b/spec/fixtures/config/redis_cache_new_format_host.yml
@@ -0,0 +1,29 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development:
+ url: redis://:mynewpassword@localhost:6380/10
+ sentinels:
+ -
+ host: localhost
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26380 # point to sentinel, not to redis port
+test:
+ url: redis://:mynewpassword@localhost:6380/10
+ sentinels:
+ -
+ host: localhost
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26380 # point to sentinel, not to redis port
+production:
+ url: redis://:mynewpassword@localhost:6380/10
+ sentinels:
+ -
+ host: slave1
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26380 # point to sentinel, not to redis port
diff --git a/spec/fixtures/config/redis_cache_new_format_socket.yml b/spec/fixtures/config/redis_cache_new_format_socket.yml
new file mode 100644
index 00000000000..3634c550163
--- /dev/null
+++ b/spec/fixtures/config/redis_cache_new_format_socket.yml
@@ -0,0 +1,6 @@
+development:
+ url: unix:/path/to/redis.cache.sock
+test:
+ url: unix:/path/to/redis.cache.sock
+production:
+ url: unix:/path/to/redis.cache.sock
diff --git a/spec/fixtures/config/redis_cache_old_format_host.yml b/spec/fixtures/config/redis_cache_old_format_host.yml
new file mode 100644
index 00000000000..3609dcd022e
--- /dev/null
+++ b/spec/fixtures/config/redis_cache_old_format_host.yml
@@ -0,0 +1,5 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development: redis://:mypassword@localhost:6380/10
+test: redis://:mypassword@localhost:6380/10
+production: redis://:mypassword@localhost:6380/10
diff --git a/spec/fixtures/config/redis_cache_old_format_socket.yml b/spec/fixtures/config/redis_cache_old_format_socket.yml
new file mode 100644
index 00000000000..26fa0eda245
--- /dev/null
+++ b/spec/fixtures/config/redis_cache_old_format_socket.yml
@@ -0,0 +1,3 @@
+development: unix:/path/to/old/redis.cache.sock
+test: unix:/path/to/old/redis.cache.sock
+production: unix:/path/to/old/redis.cache.sock
diff --git a/spec/fixtures/config/redis_new_format_host.yml b/spec/fixtures/config/redis_new_format_host.yml
index 13772677a45..8d134d467e9 100644
--- a/spec/fixtures/config/redis_new_format_host.yml
+++ b/spec/fixtures/config/redis_new_format_host.yml
@@ -5,25 +5,25 @@ development:
sentinels:
-
host: localhost
- port: 26380 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
-
host: slave2
- port: 26381 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
test:
url: redis://:mynewpassword@localhost:6379/99
sentinels:
-
host: localhost
- port: 26380 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
-
host: slave2
- port: 26381 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
production:
url: redis://:mynewpassword@localhost:6379/99
sentinels:
-
host: slave1
- port: 26380 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
-
host: slave2
- port: 26381 # point to sentinel, not to redis port
+ port: 26379 # point to sentinel, not to redis port
diff --git a/spec/fixtures/config/redis_queues_config_with_env.yml b/spec/fixtures/config/redis_queues_config_with_env.yml
new file mode 100644
index 00000000000..d16a9d8a7f8
--- /dev/null
+++ b/spec/fixtures/config/redis_queues_config_with_env.yml
@@ -0,0 +1,2 @@
+test:
+ url: <%= ENV['TEST_GITLAB_REDIS_QUEUES_URL'] %>
diff --git a/spec/fixtures/config/redis_queues_new_format_host.yml b/spec/fixtures/config/redis_queues_new_format_host.yml
new file mode 100644
index 00000000000..1535584d779
--- /dev/null
+++ b/spec/fixtures/config/redis_queues_new_format_host.yml
@@ -0,0 +1,29 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development:
+ url: redis://:mynewpassword@localhost:6381/11
+ sentinels:
+ -
+ host: localhost
+ port: 26381 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
+test:
+ url: redis://:mynewpassword@localhost:6381/11
+ sentinels:
+ -
+ host: localhost
+ port: 26381 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
+production:
+ url: redis://:mynewpassword@localhost:6381/11
+ sentinels:
+ -
+ host: slave1
+ port: 26381 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
diff --git a/spec/fixtures/config/redis_queues_new_format_socket.yml b/spec/fixtures/config/redis_queues_new_format_socket.yml
new file mode 100644
index 00000000000..b488d84d022
--- /dev/null
+++ b/spec/fixtures/config/redis_queues_new_format_socket.yml
@@ -0,0 +1,6 @@
+development:
+ url: unix:/path/to/redis.queues.sock
+test:
+ url: unix:/path/to/redis.queues.sock
+production:
+ url: unix:/path/to/redis.queues.sock
diff --git a/spec/fixtures/config/redis_queues_old_format_host.yml b/spec/fixtures/config/redis_queues_old_format_host.yml
new file mode 100644
index 00000000000..6531748a8d7
--- /dev/null
+++ b/spec/fixtures/config/redis_queues_old_format_host.yml
@@ -0,0 +1,5 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development: redis://:mypassword@localhost:6381/11
+test: redis://:mypassword@localhost:6381/11
+production: redis://:mypassword@localhost:6381/11
diff --git a/spec/fixtures/config/redis_queues_old_format_socket.yml b/spec/fixtures/config/redis_queues_old_format_socket.yml
new file mode 100644
index 00000000000..53f5db72758
--- /dev/null
+++ b/spec/fixtures/config/redis_queues_old_format_socket.yml
@@ -0,0 +1,3 @@
+development: unix:/path/to/old/redis.queues.sock
+test: unix:/path/to/old/redis.queues.sock
+production: unix:/path/to/old/redis.queues.sock
diff --git a/spec/fixtures/config/redis_shared_state_config_with_env.yml b/spec/fixtures/config/redis_shared_state_config_with_env.yml
new file mode 100644
index 00000000000..eab7203d0de
--- /dev/null
+++ b/spec/fixtures/config/redis_shared_state_config_with_env.yml
@@ -0,0 +1,2 @@
+test:
+ url: <%= ENV['TEST_GITLAB_REDIS_SHARED_STATE_URL'] %>
diff --git a/spec/fixtures/config/redis_shared_state_new_format_host.yml b/spec/fixtures/config/redis_shared_state_new_format_host.yml
new file mode 100644
index 00000000000..1180b2b4a82
--- /dev/null
+++ b/spec/fixtures/config/redis_shared_state_new_format_host.yml
@@ -0,0 +1,29 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development:
+ url: redis://:mynewpassword@localhost:6382/12
+ sentinels:
+ -
+ host: localhost
+ port: 26382 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26382 # point to sentinel, not to redis port
+test:
+ url: redis://:mynewpassword@localhost:6382/12
+ sentinels:
+ -
+ host: localhost
+ port: 26382 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26382 # point to sentinel, not to redis port
+production:
+ url: redis://:mynewpassword@localhost:6382/12
+ sentinels:
+ -
+ host: slave1
+ port: 26382 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26382 # point to sentinel, not to redis port
diff --git a/spec/fixtures/config/redis_shared_state_new_format_socket.yml b/spec/fixtures/config/redis_shared_state_new_format_socket.yml
new file mode 100644
index 00000000000..1b0e699729e
--- /dev/null
+++ b/spec/fixtures/config/redis_shared_state_new_format_socket.yml
@@ -0,0 +1,6 @@
+development:
+ url: unix:/path/to/redis.shared_state.sock
+test:
+ url: unix:/path/to/redis.shared_state.sock
+production:
+ url: unix:/path/to/redis.shared_state.sock
diff --git a/spec/fixtures/config/redis_shared_state_old_format_host.yml b/spec/fixtures/config/redis_shared_state_old_format_host.yml
new file mode 100644
index 00000000000..fef5e768c5d
--- /dev/null
+++ b/spec/fixtures/config/redis_shared_state_old_format_host.yml
@@ -0,0 +1,5 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development: redis://:mypassword@localhost:6382/12
+test: redis://:mypassword@localhost:6382/12
+production: redis://:mypassword@localhost:6382/12
diff --git a/spec/fixtures/config/redis_shared_state_old_format_socket.yml b/spec/fixtures/config/redis_shared_state_old_format_socket.yml
new file mode 100644
index 00000000000..4746afbb5ef
--- /dev/null
+++ b/spec/fixtures/config/redis_shared_state_old_format_socket.yml
@@ -0,0 +1,3 @@
+development: unix:/path/to/old/redis.shared_state.sock
+test: unix:/path/to/old/redis.shared_state.sock
+production: unix:/path/to/old/redis.shared_state.sock
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 51a3e91d201..58b43805705 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -166,9 +166,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
-- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>
+- Issue by URL: <%= urls.project_issue_url(issue.project, issue) %>
- Link to issue by reference: [Issue](<%= issue.to_reference %>)
-- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
+- Link to issue by URL: [Issue](<%= urls.project_issue_url(issue.project, issue) %>)
#### MergeRequestReferenceFilter
@@ -176,9 +176,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
-- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>
+- Merge request by URL: <%= urls.project_merge_request_url(merge_request.project, merge_request) %>
- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>)
-- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
+- Link to merge request by URL: [Merge request](<%= urls.project_merge_request_url(merge_request.project, merge_request) %>)
#### SnippetReferenceFilter
@@ -186,9 +186,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
-- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>
+- Snippet by URL: <%= urls.project_snippet_url(snippet.project, snippet) %>
- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>)
-- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
+- Link to snippet by URL: [Snippet](<%= urls.project_snippet_url(snippet.project, snippet) %>)
#### CommitRangeReferenceFilter
@@ -196,9 +196,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
-- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>
+- Range by URL: <%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>
- Link to range by reference: [Range](<%= commit_range.to_reference %>)
-- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
+- Link to range by URL: [Range](<%= urls.project_compare_url(commit_range.project, commit_range.to_param) %>)
#### CommitReferenceFilter
@@ -206,9 +206,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
-- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>
+- Commit by URL: <%= urls.project_commit_url(commit.project, commit) %>
- Link to commit by reference: [Commit](<%= commit.to_reference %>)
-- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
+- Link to commit by URL: [Commit](<%= urls.project_commit_url(commit.project, commit) %>)
#### LabelReferenceFilter
@@ -227,7 +227,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Milestone in another project: <%= xmilestone.to_reference(project) %>
- Ignored in code: `<%= simple_milestone.to_reference %>`
- Ignored in links: [Link to <%= simple_milestone.to_reference %>](#milestone-link)
-- Milestone by URL: <%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>
+- Milestone by URL: <%= urls.project_milestone_url(milestone.project, milestone) %>
- Link to milestone by URL: [Milestone](<%= milestone.to_reference %>)
### Task Lists
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 56daeffde27..ac5a58ac189 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -59,16 +59,16 @@ describe ApplicationHelper do
describe 'project_icon' do
it 'returns an url for the avatar' do
project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
- avatar_url = "/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
+ avatar_url = "/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
- avatar_url = "#{gitlab_host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
+ avatar_url = "#{gitlab_host}/uploads/-/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s)
- .to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
end
it 'gives uploaded icon when present' do
@@ -76,8 +76,9 @@ describe ApplicationHelper do
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
- avatar_url = "#{gitlab_host}#{namespace_project_avatar_path(project.namespace, project)}"
- expect(helper.project_icon(project.full_path).to_s).to match(image_tag(avatar_url))
+ avatar_url = "#{gitlab_host}#{project_avatar_path(project)}"
+ expect(helper.project_icon(project.full_path).to_s)
+ .to eq "<img data-src=\"#{avatar_url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />"
end
end
@@ -88,7 +89,7 @@ describe ApplicationHelper do
context 'when there is a matching user' do
it 'returns a relative URL for the avatar' do
expect(helper.avatar_icon(user.email).to_s)
- .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
context 'when an asset_host is set in the config' do
@@ -100,14 +101,14 @@ describe ApplicationHelper do
it 'returns an absolute URL on that asset host' do
expect(helper.avatar_icon(user.email, only_path: false).to_s)
- .to eq("#{asset_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("#{asset_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
end
context 'when only_path is set to false' do
it 'returns an absolute URL for the avatar' do
expect(helper.avatar_icon(user.email, only_path: false).to_s)
- .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
end
@@ -120,7 +121,7 @@ describe ApplicationHelper do
it 'returns a relative URL with the correct prefix' do
expect(helper.avatar_icon(user.email).to_s)
- .to eq("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("/gitlab/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
end
end
@@ -138,14 +139,14 @@ describe ApplicationHelper do
context 'when only_path is true' do
it 'returns a relative URL for the avatar' do
expect(helper.avatar_icon(user, only_path: true).to_s)
- .to eq("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
end
context 'when only_path is false' do
it 'returns an absolute URL for the avatar' do
expect(helper.avatar_icon(user, only_path: false).to_s)
- .to eq("#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
+ .to eq("#{gitlab_host}/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif")
end
end
end
@@ -292,7 +293,7 @@ describe ApplicationHelper do
let(:alternate_url) { 'http://company.example.com/getting-help' }
before do
- allow(current_application_settings).to receive(:help_page_support_url) { alternate_url }
+ stub_application_setting(help_page_support_url: alternate_url)
end
it 'returns the alternate support url' do
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index a0e1265efff..c94fedd615b 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -70,7 +70,7 @@ describe AuthHelper do
end
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0].each do |provider|
+ [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
it "returns false if the provider is #{provider}" do
expect(helper.unlink_allowed?(provider)).to be true
end
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 049475a5408..d16fcf21e45 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -27,11 +27,11 @@ describe AvatarsHelper do
it 'displays user avatar' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, 16) }
)
end
@@ -40,22 +40,8 @@ describe AvatarsHelper do
it 'uses provided css_class' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: "avatar has-tooltip s16 #{options[:css_class]}",
- alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body' }
- )
- end
- end
-
- context 'with lazy parameter' do
- let(:options) { { user: user, lazy: true } }
-
- it 'uses data-src instead of src' do
- is_expected.to eq image_tag(
- '',
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: "avatar has-tooltip s16 #{options[:css_class]} lazy",
alt: "#{user.name}'s avatar",
title: user.name,
data: { container: 'body', src: avatar_icon(user, 16) }
@@ -68,11 +54,11 @@ describe AvatarsHelper do
it 'uses provided size' do
is_expected.to eq image_tag(
- avatar_icon(user, options[:size]),
- class: "avatar has-tooltip s#{options[:size]} ",
+ LazyImageTagHelper.placeholder_image,
+ class: "avatar has-tooltip s#{options[:size]} lazy",
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, options[:size]) }
)
end
end
@@ -82,11 +68,11 @@ describe AvatarsHelper do
it 'uses provided url' do
is_expected.to eq image_tag(
- options[:url],
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: options[:url] }
)
end
end
@@ -99,22 +85,22 @@ describe AvatarsHelper do
it 'prefers user parameter' do
is_expected.to eq image_tag(
- avatar_icon(user, 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{user.name}'s avatar",
title: user.name,
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(user, 16) }
)
end
end
it 'uses user_name and user_email parameter if user is not present' do
is_expected.to eq image_tag(
- avatar_icon(options[:user_email], 16),
- class: 'avatar has-tooltip s16 ',
+ LazyImageTagHelper.placeholder_image,
+ class: 'avatar has-tooltip s16 lazy',
alt: "#{options[:user_name]}'s avatar",
title: options[:user_name],
- data: { container: 'body' }
+ data: { container: 'body', src: avatar_icon(options[:user_email], 16) }
)
end
end
diff --git a/spec/helpers/award_emoji_helper_spec.rb b/spec/helpers/award_emoji_helper_spec.rb
index 7dfd6a3f6b4..035960ed96e 100644
--- a/spec/helpers/award_emoji_helper_spec.rb
+++ b/spec/helpers/award_emoji_helper_spec.rb
@@ -40,7 +40,7 @@ describe AwardEmojiHelper do
it 'returns correct url' do
@project = merge_request.project
- expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.id}/toggle_award_emoji"
+ expected_url = "/#{@project.namespace.path}/#{@project.path}/merge_requests/#{merge_request.iid}/toggle_award_emoji"
expect(helper.toggle_award_url(merge_request)).to eq(expected_url)
end
@@ -52,7 +52,7 @@ describe AwardEmojiHelper do
it 'returns correct url' do
@project = issue.project
- expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.id}/toggle_award_emoji"
+ expected_url = "/#{@project.namespace.path}/#{@project.path}/issues/#{issue.iid}/toggle_award_emoji"
expect(helper.toggle_award_url(issue)).to eq(expected_url)
end
diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb
index 661327d4432..7ecb75da8ce 100644
--- a/spec/helpers/button_helper_spec.rb
+++ b/spec/helpers/button_helper_spec.rb
@@ -35,7 +35,7 @@ describe ButtonHelper do
context 'with internal auth disabled' do
before do
- stub_application_setting(signin_enabled?: false)
+ stub_application_setting(password_authentication_enabled?: false)
end
context 'when user has no personal access tokens' do
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index c68e4f56b05..2390c1f3e5d 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -52,7 +52,7 @@ describe EmailsHelper do
)
expect(header_logo).to eq(
- %{<img style="height: 50px" src="/uploads/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
+ %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
)
end
end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index 14847d0a49e..717ac1962d1 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -2,40 +2,34 @@ require 'spec_helper'
describe GitlabRoutingHelper do
describe 'Project URL helpers' do
- describe '#project_members_url' do
- let(:project) { build_stubbed(:empty_project) }
-
- it { expect(project_members_url(project)).to eq namespace_project_project_members_url(project.namespace, project) }
- end
-
describe '#project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(project_member_path(project_member)).to eq namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(project_member_path(project_member)).to eq project_project_member_path(project_member.source, project_member) }
end
describe '#request_access_project_members_path' do
let(:project) { build_stubbed(:empty_project) }
- it { expect(request_access_project_members_path(project)).to eq request_access_namespace_project_project_members_path(project.namespace, project) }
+ it { expect(request_access_project_members_path(project)).to eq request_access_project_project_members_path(project) }
end
describe '#leave_project_members_path' do
let(:project) { build_stubbed(:empty_project) }
- it { expect(leave_project_members_path(project)).to eq leave_namespace_project_project_members_path(project.namespace, project) }
+ it { expect(leave_project_members_path(project)).to eq leave_project_project_members_path(project) }
end
describe '#approve_access_request_project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(approve_access_request_project_member_path(project_member)).to eq approve_access_request_project_project_member_path(project_member.source, project_member) }
end
describe '#resend_invite_project_member_path' do
let(:project_member) { create(:project_member) }
- it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) }
+ it { expect(resend_invite_project_member_path(project_member)).to eq resend_invite_project_project_member_path(project_member.source, project_member) }
end
end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index e3f9d9db9eb..3a246f10283 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -11,7 +11,7 @@ describe GroupsHelper do
group.avatar = fixture_file_upload(avatar_file_path)
group.save!
expect(group_icon(group.path).to_s)
- .to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif")
+ .to match("/uploads/-/system/group/avatar/#{group.id}/banana_sample.gif")
end
it 'gives default avatar_icon when no avatar is present' do
diff --git a/spec/helpers/hooks_helper_spec.rb b/spec/helpers/hooks_helper_spec.rb
new file mode 100644
index 00000000000..9f0004bf8cf
--- /dev/null
+++ b/spec/helpers/hooks_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe HooksHelper do
+ let(:project) { create(:empty_project) }
+ let(:project_hook) { create(:project_hook, project: project) }
+ let(:system_hook) { create(:system_hook) }
+ let(:trigger) { 'push_events' }
+
+ describe '#link_to_test_hook' do
+ it 'returns project namespaced link' do
+ expect(helper.link_to_test_hook(project_hook, trigger))
+ .to include("href=\"#{test_project_hook_path(project, project_hook, trigger: trigger)}\"")
+ end
+
+ it 'returns admin namespaced link' do
+ expect(helper.link_to_test_hook(system_hook, trigger))
+ .to include("href=\"#{test_admin_hook_path(system_hook, trigger: trigger)}\"")
+ end
+ end
+end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index d2e918ef014..7789cfa3554 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -60,7 +60,7 @@ describe IssuablesHelper do
end
end
- describe 'counter caching based on issuable type and params', :caching do
+ describe 'counter caching based on issuable type and params', :use_clean_rails_memory_store_caching do
let(:params) do
{
scope: 'created-by-me',
@@ -244,5 +244,25 @@ describe IssuablesHelper do
it { expect(helper.updated_at_by(unedited_issuable)).to eq({}) }
it { expect(helper.updated_at_by(edited_issuable)).to eq(edited_updated_at_by) }
+
+ context 'when updated by a deleted user' do
+ let(:edited_updated_at_by) do
+ {
+ updatedAt: edited_issuable.updated_at.to_time.iso8601,
+ updatedBy: {
+ name: User.ghost.name,
+ path: user_path(User.ghost)
+ }
+ }
+ end
+
+ before do
+ user.destroy
+ end
+
+ it 'returns "Ghost user" as edited_by' do
+ expect(helper.updated_at_by(edited_issuable.reload)).to eq(edited_updated_at_by)
+ end
+ end
end
end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 00db98fd9d2..9524a101e74 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -8,7 +8,7 @@ describe IssuesHelper do
describe "url_for_issue" do
let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
- let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
+ let(:int_expected) { polymorphic_path([@project.namespace, @project, issue]) }
it "returns internal path if used internal tracker" do
@project = project
@@ -22,6 +22,12 @@ describe IssuesHelper do
expect(url_for_issue(issue.iid)).to match(ext_expected)
end
+ it "returns path to internal issue when internal option passed" do
+ @project = ext_project
+
+ expect(url_for_issue(issue.iid, ext_project, internal: true)).to match(int_expected)
+ end
+
it "returns empty string if project nil" do
@project = nil
@@ -137,7 +143,7 @@ describe IssuesHelper do
let(:merge_request) { create(:merge_request) }
it "links just the merge request" do
- expected_path = namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
+ expected_path = project_merge_request_path(merge_request.project, merge_request)
expect(link_to_discussions_to_resolve(merge_request, nil)).to include(expected_path)
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index b4226f96a04..4b6a351cf70 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -25,17 +25,17 @@ describe MarkupHelper do
let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
it "links to the merge request" do
- expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
+ expected = project_merge_request_path(project, merge_request)
expect(helper.markdown(actual)).to match(expected)
end
it "links to the commit" do
- expected = namespace_project_commit_path(project.namespace, project, commit)
+ expected = project_commit_path(project, commit)
expect(helper.markdown(actual)).to match(expected)
end
it "links to the issue" do
- expected = namespace_project_issue_path(project.namespace, project, issue)
+ expected = project_issue_path(project, issue)
expect(helper.markdown(actual)).to match(expected)
end
end
@@ -46,7 +46,7 @@ describe MarkupHelper do
let(:second_issue) { create(:issue, project: second_project) }
it 'links to the issue' do
- expected = namespace_project_issue_path(second_project.namespace, second_project, second_issue)
+ expected = project_issue_path(second_project, second_issue)
expect(markdown(actual, project: second_project)).to match(expected)
end
end
@@ -69,7 +69,7 @@ describe MarkupHelper do
# First issue link
expect(doc.css('a')[1].attr('href'))
- .to eq namespace_project_issue_path(project.namespace, project, issues[0])
+ .to eq project_issue_path(project, issues[0])
expect(doc.css('a')[1].text).to eq issues[0].to_reference
# Internal commit link
@@ -78,7 +78,7 @@ describe MarkupHelper do
# Second issue link
expect(doc.css('a')[3].attr('href'))
- .to eq namespace_project_issue_path(project.namespace, project, issues[1])
+ .to eq project_issue_path(project, issues[1])
expect(doc.css('a')[3].text).to eq issues[1].to_reference
# Trailing commit link
diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb
index 24d4f1b4938..b8f9c02a486 100644
--- a/spec/helpers/milestones_helper_spec.rb
+++ b/spec/helpers/milestones_helper_spec.rb
@@ -10,7 +10,7 @@ describe MilestonesHelper do
it 'returns project milestones JSON URL' do
assign(:project, project)
- expect(helper.milestones_filter_dropdown_path).to eq(namespace_project_milestones_path(project.namespace, project, :json))
+ expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project, :json))
end
end
@@ -18,7 +18,7 @@ describe MilestonesHelper do
it 'returns targeted project milestones JSON URL' do
assign(:target_project, project2)
- expect(helper.milestones_filter_dropdown_path).to eq(namespace_project_milestones_path(project2.namespace, project2, :json))
+ expect(helper.milestones_filter_dropdown_path).to eq(project_milestones_path(project2, :json))
end
end
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index e5143a0263d..8365b3f5538 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NamespacesHelper, type: :helper do
+describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_group) { create(:group, :private) }
let!(:user) { create(:user) }
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index cc861af8533..56f252ba273 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -53,7 +53,7 @@ describe NotesHelper do
let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project).to_discussion }
it 'returns the diff path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
end
end
@@ -77,7 +77,7 @@ describe NotesHelper do
end
it 'returns the diff version path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff1, anchor: discussion.line_code))
end
end
@@ -101,7 +101,7 @@ describe NotesHelper do
end
it 'returns the diff version comparison path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
end
end
@@ -129,7 +129,7 @@ describe NotesHelper do
end
it 'returns the diff path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(diffs_project_merge_request_path(project, merge_request, anchor: discussion.line_code))
end
end
@@ -160,7 +160,7 @@ describe NotesHelper do
let(:discussion) { create(:diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
end
end
@@ -168,7 +168,7 @@ describe NotesHelper do
let(:discussion) { create(:legacy_diff_note_on_commit, project: project).to_discussion }
it 'returns the commit path with the line code' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit, anchor: discussion.line_code))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: discussion.line_code))
end
end
@@ -176,7 +176,7 @@ describe NotesHelper do
let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion }
it 'returns the commit path' do
- expect(helper.discussion_path(discussion)).to eq(namespace_project_commit_path(project.namespace, project, commit))
+ expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit))
end
end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index 95b4032616e..9aca3987657 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -60,7 +60,7 @@ describe PageLayoutHelper do
%w(project user group).each do |type|
context "with @#{type} assigned" do
it "uses #{type.titlecase} avatar if available" do
- object = double(avatar_url: 'http://example.com/uploads/system/avatar.png')
+ object = double(avatar_url: 'http://example.com/uploads/-/system/avatar.png')
assign(type, object)
expect(helper.page_image).to eq object.avatar_url
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 487d9800707..45066a60f50 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -63,7 +63,7 @@ describe ProjectsHelper do
end
end
- describe "#project_list_cache_key", redis: true do
+ describe "#project_list_cache_key", clean_gitlab_redis_shared_state: true do
let(:project) { create(:project) }
it "includes the route" do
@@ -160,7 +160,7 @@ describe ProjectsHelper do
context 'user requires a personal access token' do
it 'returns true' do
- stub_application_setting(signin_enabled?: false)
+ stub_application_setting(password_authentication_enabled?: false)
expect(helper.show_no_password_message?).to be_truthy
end
@@ -184,7 +184,7 @@ describe ProjectsHelper do
let(:user) { create(:user) }
it 'returns link to create a personal access token' do
- stub_application_setting(signin_enabled?: false)
+ stub_application_setting(password_authentication_enabled?: false)
expect(helper.link_to_set_password).to match %r{<a href="#{profile_personal_access_tokens_path}">create a personal access token</a>}
end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index 374517fec37..0877770c167 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative '../../config/initializers/6_validations.rb'
-describe '6_validations', lib: true do
+describe '6_validations' do
before :all do
FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/8_metrics_spec.rb
index a507d7f7f2b..4e6052a9f80 100644
--- a/spec/initializers/8_metrics_spec.rb
+++ b/spec/initializers/8_metrics_spec.rb
@@ -1,17 +1,25 @@
require 'spec_helper'
-require_relative '../../config/initializers/8_metrics'
-describe 'instrument_classes', lib: true do
+describe 'instrument_classes' do
let(:config) { double(:config) }
+ let(:unicorn_sampler) { double(:unicorn_sampler) }
+ let(:influx_sampler) { double(:influx_sampler) }
+
before do
allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_method)
allow(config).to receive(:instrument_instance_methods)
+ allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
+ allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
+ allow(unicorn_sampler).to receive(:start)
+ allow(influx_sampler).to receive(:start)
+ allow(Gitlab::Application).to receive(:configure)
end
it 'can autoload and instrument all files' do
+ require_relative '../../config/initializers/8_metrics'
expect { instrument_classes(config) }.not_to raise_error
end
end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 65c97da2efd..84ad55e9f98 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative '../../config/initializers/secret_token'
-describe 'create_tokens', lib: true do
+describe 'create_tokens' do
include StubENV
let(:secrets) { ActiveSupport::OrderedOptions.new }
diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb
index 47b4e431823..ebdabcf93f1 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -1,41 +1,41 @@
require 'spec_helper'
require_relative '../../config/initializers/1_settings'
-describe Settings, lib: true do
+describe Settings do
describe '#host_without_www' do
context 'URL with protocol' do
it 'returns the host' do
- expect(Settings.host_without_www('http://foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('http://www.foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('http://secure.foo.com')).to eq 'secure.foo.com'
- expect(Settings.host_without_www('http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ expect(described_class.host_without_www('http://foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('http://www.foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('http://secure.foo.com')).to eq 'secure.foo.com'
+ expect(described_class.host_without_www('http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
- expect(Settings.host_without_www('https://foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('https://www.foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('https://secure.foo.com')).to eq 'secure.foo.com'
- expect(Settings.host_without_www('https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'secure.gravatar.com'
+ expect(described_class.host_without_www('https://foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('https://www.foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('https://secure.foo.com')).to eq 'secure.foo.com'
+ expect(described_class.host_without_www('https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'secure.gravatar.com'
end
end
context 'URL without protocol' do
it 'returns the host' do
- expect(Settings.host_without_www('foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('www.foo.com')).to eq 'foo.com'
- expect(Settings.host_without_www('secure.foo.com')).to eq 'secure.foo.com'
- expect(Settings.host_without_www('www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ expect(described_class.host_without_www('foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('www.foo.com')).to eq 'foo.com'
+ expect(described_class.host_without_www('secure.foo.com')).to eq 'secure.foo.com'
+ expect(described_class.host_without_www('www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
end
context 'URL with user/port' do
it 'returns the host' do
- expect(Settings.host_without_www('bob:pass@foo.com:8080')).to eq 'foo.com'
- expect(Settings.host_without_www('bob:pass@www.foo.com:8080')).to eq 'foo.com'
- expect(Settings.host_without_www('bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
- expect(Settings.host_without_www('bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ expect(described_class.host_without_www('bob:pass@foo.com:8080')).to eq 'foo.com'
+ expect(described_class.host_without_www('bob:pass@www.foo.com:8080')).to eq 'foo.com'
+ expect(described_class.host_without_www('bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
+ expect(described_class.host_without_www('bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
- expect(Settings.host_without_www('http://bob:pass@foo.com:8080')).to eq 'foo.com'
- expect(Settings.host_without_www('http://bob:pass@www.foo.com:8080')).to eq 'foo.com'
- expect(Settings.host_without_www('http://bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
- expect(Settings.host_without_www('http://bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
+ expect(described_class.host_without_www('http://bob:pass@foo.com:8080')).to eq 'foo.com'
+ expect(described_class.host_without_www('http://bob:pass@www.foo.com:8080')).to eq 'foo.com'
+ expect(described_class.host_without_www('http://bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
+ expect(described_class.host_without_www('http://bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
end
end
end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 70a18f31744..02a9446ad7b 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'trusted_proxies', lib: true do
+describe 'trusted_proxies' do
context 'with default config' do
before do
set_trusted_proxies([])
diff --git a/spec/javascripts/close_reopen_report_toggle_spec.js b/spec/javascripts/close_reopen_report_toggle_spec.js
new file mode 100644
index 00000000000..925e959c85a
--- /dev/null
+++ b/spec/javascripts/close_reopen_report_toggle_spec.js
@@ -0,0 +1,270 @@
+import CloseReopenReportToggle from '~/close_reopen_report_toggle';
+import DropLab from '~/droplab/drop_lab';
+
+describe('CloseReopenReportToggle', () => {
+ describe('class constructor', () => {
+ const dropdownTrigger = {};
+ const dropdownList = {};
+ const button = {};
+ let commentTypeToggle;
+
+ beforeEach(function () {
+ commentTypeToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+ });
+
+ it('sets .dropdownTrigger', function () {
+ expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger);
+ });
+
+ it('sets .dropdownList', function () {
+ expect(commentTypeToggle.dropdownList).toBe(dropdownList);
+ });
+
+ it('sets .button', function () {
+ expect(commentTypeToggle.button).toBe(button);
+ });
+ });
+
+ describe('initDroplab', () => {
+ let closeReopenReportToggle;
+ const dropdownList = jasmine.createSpyObj('dropdownList', ['querySelector']);
+ const dropdownTrigger = {};
+ const button = {};
+ const reopenItem = {};
+ const closeItem = {};
+ const config = {};
+
+ beforeEach(() => {
+ spyOn(DropLab.prototype, 'init');
+ dropdownList.querySelector.and.returnValues(reopenItem, closeItem);
+
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'setConfig').and.returnValue(config);
+
+ closeReopenReportToggle.initDroplab();
+ });
+
+ it('sets .reopenItem and .closeItem', () => {
+ expect(dropdownList.querySelector).toHaveBeenCalledWith('.reopen-item');
+ expect(dropdownList.querySelector).toHaveBeenCalledWith('.close-item');
+ expect(closeReopenReportToggle.reopenItem).toBe(reopenItem);
+ expect(closeReopenReportToggle.closeItem).toBe(closeItem);
+ });
+
+ it('sets .droplab', () => {
+ expect(closeReopenReportToggle.droplab).toEqual(jasmine.any(Object));
+ });
+
+ it('calls .setConfig', () => {
+ expect(closeReopenReportToggle.setConfig).toHaveBeenCalled();
+ });
+
+ it('calls droplab.init', () => {
+ expect(DropLab.prototype.init).toHaveBeenCalledWith(
+ dropdownTrigger,
+ dropdownList,
+ jasmine.any(Array),
+ config,
+ );
+ });
+ });
+
+ describe('updateButton', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = jasmine.createSpyObj('button', ['blur']);
+ const isClosed = true;
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'toggleButtonType');
+
+ closeReopenReportToggle.updateButton(isClosed);
+ });
+
+ it('calls .toggleButtonType', () => {
+ expect(closeReopenReportToggle.toggleButtonType).toHaveBeenCalledWith(isClosed);
+ });
+
+ it('calls .button.blur', () => {
+ expect(closeReopenReportToggle.button.blur).toHaveBeenCalled();
+ });
+ });
+
+ describe('toggleButtonType', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ const isClosed = true;
+ const showItem = jasmine.createSpyObj('showItem', ['click']);
+ const hideItem = {};
+ showItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
+ hideItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ spyOn(closeReopenReportToggle, 'getButtonTypes').and.returnValue([showItem, hideItem]);
+
+ closeReopenReportToggle.toggleButtonType(isClosed);
+ });
+
+ it('calls .getButtonTypes', () => {
+ expect(closeReopenReportToggle.getButtonTypes).toHaveBeenCalledWith(isClosed);
+ });
+
+ it('removes hide class and add selected class to showItem, opposite for hideItem', () => {
+ expect(showItem.classList.remove).toHaveBeenCalledWith('hidden');
+ expect(showItem.classList.add).toHaveBeenCalledWith('droplab-item-selected');
+ expect(hideItem.classList.add).toHaveBeenCalledWith('hidden');
+ expect(hideItem.classList.remove).toHaveBeenCalledWith('droplab-item-selected');
+ });
+
+ it('clicks the showItem', () => {
+ expect(showItem.click).toHaveBeenCalled();
+ });
+ });
+
+ describe('getButtonTypes', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ const reopenItem = {};
+ const closeItem = {};
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ closeReopenReportToggle.reopenItem = reopenItem;
+ closeReopenReportToggle.closeItem = closeItem;
+ });
+
+ it('returns reopenItem, closeItem if isClosed is true', () => {
+ const buttonTypes = closeReopenReportToggle.getButtonTypes(true);
+
+ expect(buttonTypes).toEqual([reopenItem, closeItem]);
+ });
+
+ it('returns closeItem, reopenItem if isClosed is false', () => {
+ const buttonTypes = closeReopenReportToggle.getButtonTypes(false);
+
+ expect(buttonTypes).toEqual([closeItem, reopenItem]);
+ });
+ });
+
+ describe('setDisable', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
+ const button = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+ });
+
+ it('disable .button and .dropdownTrigger if shouldDisable is true', () => {
+ closeReopenReportToggle.setDisable(true);
+
+ expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ });
+
+ it('disable .button and .dropdownTrigger if shouldDisable is undefined', () => {
+ closeReopenReportToggle.setDisable();
+
+ expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
+ });
+
+ it('enable .button and .dropdownTrigger if shouldDisable is false', () => {
+ closeReopenReportToggle.setDisable(false);
+
+ expect(button.removeAttribute).toHaveBeenCalledWith('disabled');
+ expect(dropdownTrigger.removeAttribute).toHaveBeenCalledWith('disabled');
+ });
+ });
+
+ describe('setConfig', () => {
+ let closeReopenReportToggle;
+ const dropdownList = {};
+ const dropdownTrigger = {};
+ const button = {};
+ let config;
+
+ beforeEach(() => {
+ closeReopenReportToggle = new CloseReopenReportToggle({
+ dropdownTrigger,
+ dropdownList,
+ button,
+ });
+
+ config = closeReopenReportToggle.setConfig();
+ });
+
+ it('returns a config object', () => {
+ expect(config).toEqual({
+ InputSetter: [
+ {
+ input: button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'data-value',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-text',
+ inputAttribute: 'title',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-button-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: dropdownTrigger,
+ valueAttribute: 'data-toggle-class',
+ inputAttribute: 'class',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-url',
+ inputAttribute: 'href',
+ },
+ {
+ input: button,
+ valueAttribute: 'data-method',
+ inputAttribute: 'data-method',
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index 694f94efcff..a34cadec0ab 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -85,6 +85,41 @@ describe('Pipelines table in Commits and Merge requests', () => {
}, 0);
});
});
+
+ describe('pipeline badge counts', () => {
+ const pipelinesResponse = (request, next) => {
+ next(request.respondWith(JSON.stringify([pipeline]), {
+ status: 200,
+ }));
+ };
+
+ beforeEach(() => {
+ Vue.http.interceptors.push(pipelinesResponse);
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, pipelinesResponse);
+ this.component.$destroy();
+ });
+
+ it('should receive update-pipelines-count event', (done) => {
+ const element = document.createElement('div');
+ document.body.appendChild(element);
+
+ element.addEventListener('update-pipelines-count', (event) => {
+ expect(event.detail.pipelines).toEqual([pipeline]);
+ done();
+ });
+
+ this.component = new PipelinesTable({
+ propsData: {
+ endpoint: 'endpoint',
+ helpPagePath: 'foo',
+ },
+ }).$mount();
+ element.appendChild(this.component.$el);
+ });
+ });
});
describe('unsuccessfull request', () => {
diff --git a/spec/javascripts/environments/environment_spec.js b/spec/javascripts/environments/environment_spec.js
index 6639a6b5e7b..0c8817a8148 100644
--- a/spec/javascripts/environments/environment_spec.js
+++ b/spec/javascripts/environments/environment_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import '~/flash';
import environmentsComponent from '~/environments/components/environment.vue';
import { environment, folder } from './mock_data';
+import { headersInterceptor } from '../helpers/vue_resource_helper';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
@@ -25,12 +26,14 @@ describe('Environment', () => {
beforeEach(() => {
Vue.http.interceptors.push(environmentsEmptyResponseInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsEmptyResponseInterceptor,
);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
});
it('should render the empty state', (done) => {
@@ -54,6 +57,10 @@ describe('Environment', () => {
describe('with paginated environments', () => {
const environmentsResponseInterceptor = (request, next) => {
+ next((response) => {
+ response.headers.set('X-nExt-pAge', '2');
+ });
+
next(request.respondWith(JSON.stringify({
environments: [environment],
stopped_count: 1,
@@ -73,6 +80,7 @@ describe('Environment', () => {
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
component = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
});
@@ -82,6 +90,7 @@ describe('Environment', () => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsResponseInterceptor,
);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
});
it('should render a table with environments', (done) => {
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index 6e855530b21..f2c6ec24dd7 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -86,6 +86,16 @@ describe('Store', () => {
store.toggleFolder(store.state.environments[1]);
expect(store.state.environments[1].isOpen).toEqual(false);
});
+
+ it('should keep folder open when environments are updated', () => {
+ store.storeEnvironments(serverData);
+
+ store.toggleFolder(store.state.environments[1]);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].isOpen).toEqual(true);
+ });
});
describe('setfolderContent', () => {
@@ -97,6 +107,17 @@ describe('Store', () => {
expect(store.state.environments[1].children.length).toEqual(serverData.length);
expect(store.state.environments[1].children[0].isChildren).toEqual(true);
});
+
+ it('should keep folder content when environments are updated', () => {
+ store.storeEnvironments(serverData);
+
+ store.setfolderContent(store.state.environments[1], serverData);
+
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ // poll
+ store.storeEnvironments(serverData);
+ expect(store.state.environments[1].children.length).toEqual(serverData.length);
+ });
});
describe('store pagination', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 350078ad5f5..fdaea5c0b0c 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import '~/flash';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import { environmentsList } from '../mock_data';
+import { headersInterceptor } from '../../helpers/vue_resource_helper';
describe('Environments Folder View', () => {
preloadFixtures('static/environments/environments_folder_view.html.raw');
@@ -36,6 +37,8 @@ describe('Environments Folder View', () => {
beforeEach(() => {
Vue.http.interceptors.push(environmentsResponseInterceptor);
+ Vue.http.interceptors.push(headersInterceptor);
+
component = new EnvironmentsFolderViewComponent({
el: document.querySelector('#environments-folder-list-view'),
});
@@ -45,6 +48,7 @@ describe('Environments Folder View', () => {
Vue.http.interceptors = _.without(
Vue.http.interceptors, environmentsResponseInterceptor,
);
+ Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
});
it('should render a table with environments', (done) => {
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index 0132f4b7c93..b3c9bca64cc 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -12,7 +12,9 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
- dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys);
+ dropdownUser = new gl.DropdownUser({
+ tokenKeys: gl.FilteredSearchTokenKeys,
+ });
});
it('should not return the double quote found in value', () => {
@@ -78,7 +80,10 @@ describe('Dropdown User', () => {
loadFixtures(fixtureTemplate);
authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
const dummyInput = document.createElement('div');
- dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput);
+ dropdown = new gl.DropdownUser({
+ dropdown: authorFilterDropdownElement,
+ input: dummyInput,
+ });
});
const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user');
diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml
new file mode 100644
index 00000000000..7886e995e57
--- /dev/null
+++ b/spec/javascripts/fixtures/oauth_remember_me.html.haml
@@ -0,0 +1,5 @@
+#oauth-container
+ %input#remember_me{ type: "checkbox" }
+
+ %a.oauth-login.twitter{ href: "http://example.com/" }
+ %a.oauth-login.github{ href: "http://example.com/" }
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index c9c0b891237..e3d7986f2cf 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -10,10 +10,12 @@ context 'U2F' do
end
describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ include DeviseHelpers
+
render_views
before do
- @request.env['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: @request)
end
it 'u2f/authenticate.html.raw' do |example|
diff --git a/spec/javascripts/helpers/vue_resource_helper.js b/spec/javascripts/helpers/vue_resource_helper.js
new file mode 100644
index 00000000000..0d1bf5e2e80
--- /dev/null
+++ b/spec/javascripts/helpers/vue_resource_helper.js
@@ -0,0 +1,11 @@
+// eslint-disable-next-line import/prefer-default-export
+export const headersInterceptor = (request, next) => {
+ next((response) => {
+ const headers = {};
+ response.headers.forEach((value, key) => {
+ headers[key] = value;
+ });
+ // eslint-disable-next-line no-param-reassign
+ response.headers = headers;
+ });
+};
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index bc13373a27e..81ce18bf2fb 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -3,7 +3,6 @@ import '~/render_math';
import '~/render_gfm';
import issuableApp from '~/issue_show/components/app.vue';
import eventHub from '~/issue_show/event_hub';
-import Poll from '~/lib/utils/poll';
import issueShowData from '../mock_data';
function formatText(text) {
@@ -11,16 +10,26 @@ function formatText(text) {
}
describe('Issuable output', () => {
+ let requestData = issueShowData.initialRequest;
+
document.body.innerHTML = '<span id="task_status"></span>';
+ const interceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(requestData), {
+ status: 200,
+ }));
+ };
+
let vm;
- beforeEach(() => {
+ beforeEach((done) => {
spyOn(eventHub, '$emit');
- spyOn(Poll.prototype, 'makeRequest');
const IssuableDescriptionComponent = Vue.extend(issuableApp);
+ requestData = issueShowData.initialRequest;
+ Vue.http.interceptors.push(interceptor);
+
vm = new IssuableDescriptionComponent({
propsData: {
canUpdate: true,
@@ -40,15 +49,17 @@ describe('Issuable output', () => {
projectPath: '/',
},
}).$mount();
+
+ setTimeout(done);
});
- it('should render a title/description/edited and update title/description/edited on update', (done) => {
- vm.poll.options.successCallback({
- json() {
- return issueShowData.initialRequest;
- },
- });
+ afterEach(() => {
+ Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ vm.poll.stop();
+ });
+
+ it('should render a title/description/edited and update title/description/edited on update', (done) => {
let editedText;
Vue.nextTick()
.then(() => {
@@ -64,13 +75,10 @@ describe('Issuable output', () => {
expect(editedText.querySelector('time')).toBeTruthy();
})
.then(() => {
- vm.poll.options.successCallback({
- json() {
- return issueShowData.secondRequest;
- },
- });
+ requestData = issueShowData.secondRequest;
+ vm.poll.makeRequest();
})
- .then(Vue.nextTick)
+ .then(() => new Promise(resolve => setTimeout(resolve)))
.then(() => {
expect(document.querySelector('title').innerText).toContain('2 (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
@@ -304,7 +312,7 @@ describe('Issuable output', () => {
it('stops polling when deleting', (done) => {
spyOn(gl.utils, 'visitUrl');
- spyOn(vm.poll, 'stop');
+ spyOn(vm.poll, 'stop').and.callThrough();
spyOn(vm.service, 'deleteIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
json() {
@@ -347,23 +355,14 @@ describe('Issuable output', () => {
describe('open form', () => {
it('shows locked warning if form is open & data is different', (done) => {
- vm.poll.options.successCallback({
- json() {
- return issueShowData.initialRequest;
- },
- });
-
Vue.nextTick()
.then(() => {
vm.openForm();
- vm.poll.options.successCallback({
- json() {
- return issueShowData.secondRequest;
- },
- });
+ requestData = issueShowData.secondRequest;
+ vm.poll.makeRequest();
})
- .then(Vue.nextTick)
+ .then(() => new Promise(resolve => setTimeout(resolve)))
.then(() => {
expect(
vm.formState.lockedWarningVisible,
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index f3fdbff01a6..360691a3546 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -44,32 +44,34 @@ describe('Description component', () => {
});
});
- it('re-inits the TaskList when description changed', (done) => {
- spyOn(gl, 'TaskList');
- vm.descriptionHtml = 'changed';
-
- setTimeout(() => {
- expect(
- gl.TaskList,
- ).toHaveBeenCalled();
-
- done();
- });
- });
-
- it('does not re-init the TaskList when canUpdate is false', (done) => {
- spyOn(gl, 'TaskList');
- vm.canUpdate = false;
- vm.descriptionHtml = 'changed';
-
- setTimeout(() => {
- expect(
- gl.TaskList,
- ).not.toHaveBeenCalled();
-
- done();
- });
- });
+ // TODO: gl.TaskList no longer exists. rewrite these tests once we have a way to rewire ES modules
+
+ // it('re-inits the TaskList when description changed', (done) => {
+ // spyOn(gl, 'TaskList');
+ // vm.descriptionHtml = 'changed';
+ //
+ // setTimeout(() => {
+ // expect(
+ // gl.TaskList,
+ // ).toHaveBeenCalled();
+ //
+ // done();
+ // });
+ // });
+
+ // it('does not re-init the TaskList when canUpdate is false', (done) => {
+ // spyOn(gl, 'TaskList');
+ // vm.canUpdate = false;
+ // vm.descriptionHtml = 'changed';
+ //
+ // setTimeout(() => {
+ // expect(
+ // gl.TaskList,
+ // ).not.toHaveBeenCalled();
+ //
+ // done();
+ // });
+ // });
describe('taskStatus', () => {
it('adds full taskStatus', (done) => {
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index df97a100b0d..0c8c4d2cea6 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,10 +1,10 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue';
-
+import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import '~/lib/utils/text_utility';
describe('Issue', function() {
- let $boxClosed, $boxOpen, $btnClose, $btnReopen;
+ let $boxClosed, $boxOpen, $btn;
preloadFixtures('issues/closed-issue.html.raw');
preloadFixtures('issues/issue-with-task-list.html.raw');
@@ -20,9 +20,7 @@ describe('Issue', function() {
function expectIssueState(isIssueOpen) {
expectVisibility($boxClosed, !isIssueOpen);
expectVisibility($boxOpen, isIssueOpen);
-
- expectVisibility($btnClose, isIssueOpen);
- expectVisibility($btnReopen, !isIssueOpen);
+ expect($btn).toHaveText(isIssueOpen ? 'Close issue' : 'Reopen issue');
}
function expectNewBranchButtonState(isPending, canCreate) {
@@ -57,7 +55,7 @@ describe('Issue', function() {
}
}
- function findElements() {
+ function findElements(isIssueInitiallyOpen) {
$boxClosed = $('div.status-box-closed');
expect($boxClosed).toExist();
expect($boxClosed).toHaveText('Closed');
@@ -66,13 +64,9 @@ describe('Issue', function() {
expect($boxOpen).toExist();
expect($boxOpen).toHaveText('Open');
- $btnClose = $('.btn-close.btn-grouped');
- expect($btnClose).toExist();
- expect($btnClose).toHaveText('Close issue');
-
- $btnReopen = $('.btn-reopen.btn-grouped');
- expect($btnReopen).toExist();
- expect($btnReopen).toHaveText('Reopen issue');
+ $btn = $('.js-issuable-close-button');
+ expect($btn).toExist();
+ expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue');
}
describe('task lists', function() {
@@ -99,7 +93,6 @@ describe('Issue', function() {
function ajaxSpy(req) {
if (req.url === this.$triggeredButton.attr('href')) {
expect(req.type).toBe('PUT');
- expect(this.$triggeredButton).toHaveProp('disabled', true);
expectNewBranchButtonState(true, false);
return this.issueStateDeferred;
} else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) {
@@ -119,10 +112,11 @@ describe('Issue', function() {
loadFixtures('issues/closed-issue.html.raw');
}
- findElements();
+ findElements(isIssueInitiallyOpen);
this.issue = new Issue();
expectIssueState(isIssueInitiallyOpen);
- this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
+
+ this.$triggeredButton = $btn;
this.$projectIssuesCounter = $('.issue_counter');
this.$projectIssuesCounter.text('1,001');
@@ -143,7 +137,7 @@ describe('Issue', function() {
});
expectIssueState(!isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
expectNewBranchButtonState(false, !isIssueInitiallyOpen);
});
@@ -158,7 +152,7 @@ describe('Issue', function() {
});
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', false);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
@@ -172,7 +166,7 @@ describe('Issue', function() {
});
expectIssueState(isIssueInitiallyOpen);
- expect(this.$triggeredButton).toHaveProp('disabled', true);
+ expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen);
@@ -195,4 +189,37 @@ describe('Issue', function() {
});
});
});
+
+ describe('units', () => {
+ describe('class constructor', () => {
+ it('calls .initCloseReopenReport', () => {
+ spyOn(Issue.prototype, 'initCloseReopenReport');
+
+ new Issue(); // eslint-disable-line no-new
+
+ expect(Issue.prototype.initCloseReopenReport).toHaveBeenCalled();
+ });
+ });
+
+ describe('initCloseReopenReport', () => {
+ it('calls .initDroplab', () => {
+ const container = jasmine.createSpyObj('container', ['querySelector']);
+ const dropdownTrigger = {};
+ const dropdownList = {};
+ const button = {};
+
+ spyOn(document, 'querySelector').and.returnValue(container);
+ spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
+ container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
+
+ Issue.prototype.initCloseReopenReport();
+
+ expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
+ expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
new file mode 100644
index 00000000000..1d81e4e2d1a
--- /dev/null
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -0,0 +1,57 @@
+import LazyLoader from '~/lazy_loader';
+
+let lazyLoader = null;
+
+describe('LazyLoader', function () {
+ preloadFixtures('issues/issue_with_comment.html.raw');
+
+ beforeEach(function () {
+ loadFixtures('issues/issue_with_comment.html.raw');
+ lazyLoader = new LazyLoader({
+ observerNode: 'body',
+ });
+ // Doing everything that happens normally in onload
+ lazyLoader.loadCheck();
+ });
+ describe('behavior', function () {
+ it('should copy value from data-src to src for img 1', function (done) {
+ const img = document.querySelectorAll('img[data-src]')[0];
+ const originalDataSrc = img.getAttribute('data-src');
+ img.scrollIntoView();
+
+ setTimeout(() => {
+ expect(img.getAttribute('src')).toBe(originalDataSrc);
+ expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0);
+ done();
+ }, 100);
+ });
+
+ it('should lazy load dynamically added data-src images', function (done) {
+ const newImg = document.createElement('img');
+ const testPath = '/img/testimg.png';
+ newImg.className = 'lazy';
+ newImg.setAttribute('data-src', testPath);
+ document.body.appendChild(newImg);
+ newImg.scrollIntoView();
+
+ setTimeout(() => {
+ expect(newImg.getAttribute('src')).toBe(testPath);
+ expect(document.getElementsByClassName('js-lazy-loaded').length).toBeGreaterThan(0);
+ done();
+ }, 100);
+ });
+
+ it('should not alter normal images', function (done) {
+ const newImg = document.createElement('img');
+ const testPath = '/img/testimg.png';
+ newImg.setAttribute('src', testPath);
+ document.body.appendChild(newImg);
+ newImg.scrollIntoView();
+
+ setTimeout(() => {
+ expect(newImg).not.toHaveClass('js-lazy-loaded');
+ done();
+ }, 100);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 52cf217c25f..55037bbbf73 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -143,6 +143,7 @@ import '~/lib/utils/common_utils';
it('should return valid parameter', () => {
const value = gl.utils.getParameterByName('scope');
+ expect(gl.utils.getParameterByName('p')).toEqual('2');
expect(value).toBe('all');
});
diff --git a/spec/javascripts/lib/utils/poll_spec.js b/spec/javascripts/lib/utils/poll_spec.js
index 22f30191ab9..2aa7011ca51 100644
--- a/spec/javascripts/lib/utils/poll_spec.js
+++ b/spec/javascripts/lib/utils/poll_spec.js
@@ -25,23 +25,28 @@ function mockServiceCall(service, response, shouldFail = false) {
describe('Poll', () => {
const service = jasmine.createSpyObj('service', ['fetch']);
- const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error']);
+ const callbacks = jasmine.createSpyObj('callbacks', ['success', 'error', 'notification']);
+
+ function setup() {
+ return new Poll({
+ resource: service,
+ method: 'fetch',
+ successCallback: callbacks.success,
+ errorCallback: callbacks.error,
+ notificationCallback: callbacks.notification,
+ }).makeRequest();
+ }
afterEach(() => {
callbacks.success.calls.reset();
callbacks.error.calls.reset();
+ callbacks.notification.calls.reset();
service.fetch.calls.reset();
});
it('calls the success callback when no header for interval is provided', (done) => {
mockServiceCall(service, { status: 200 });
-
- new Poll({
- resource: service,
- method: 'fetch',
- successCallback: callbacks.success,
- errorCallback: callbacks.error,
- }).makeRequest();
+ setup();
waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).toHaveBeenCalled();
@@ -51,15 +56,9 @@ describe('Poll', () => {
});
});
- it('calls the error callback whe the http request returns an error', (done) => {
+ it('calls the error callback when the http request returns an error', (done) => {
mockServiceCall(service, { status: 500 }, true);
-
- new Poll({
- resource: service,
- method: 'fetch',
- successCallback: callbacks.success,
- errorCallback: callbacks.error,
- }).makeRequest();
+ setup();
waitForAllCallsToFinish(service, 1, () => {
expect(callbacks.success).not.toHaveBeenCalled();
@@ -69,15 +68,22 @@ describe('Poll', () => {
});
});
+ it('skips the error callback when request is aborted', (done) => {
+ mockServiceCall(service, { status: 0 }, true);
+ setup();
+
+ waitForAllCallsToFinish(service, 1, () => {
+ expect(callbacks.success).not.toHaveBeenCalled();
+ expect(callbacks.error).not.toHaveBeenCalled();
+ expect(callbacks.notification).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
it('should call the success callback when the interval header is -1', (done) => {
mockServiceCall(service, { status: 200, headers: { 'poll-interval': -1 } });
-
- new Poll({
- resource: service,
- method: 'fetch',
- successCallback: callbacks.success,
- errorCallback: callbacks.error,
- }).makeRequest().then(() => {
+ setup().then(() => {
expect(callbacks.success).toHaveBeenCalled();
expect(callbacks.error).not.toHaveBeenCalled();
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index f444bcaf847..6ff42e2378d 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -2,10 +2,12 @@
/* global MergeRequest */
import '~/merge_request';
+import CloseReopenReportToggle from '~/close_reopen_report_toggle';
+import IssuablesHelper from '~/helpers/issuables_helper';
(function() {
describe('MergeRequest', function() {
- return describe('task lists', function() {
+ describe('task lists', function() {
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
@@ -27,5 +29,34 @@ import '~/merge_request';
return $('.js-task-list-field').trigger('tasklist:changed');
});
});
+
+ describe('class constructor', () => {
+ it('calls .initCloseReopenReport', () => {
+ spyOn(IssuablesHelper, 'initCloseReopenReport');
+
+ new MergeRequest(); // eslint-disable-line no-new
+
+ expect(IssuablesHelper.initCloseReopenReport).toHaveBeenCalled();
+ });
+
+ it('calls .initDroplab', () => {
+ const container = jasmine.createSpyObj('container', ['querySelector']);
+ const dropdownTrigger = {};
+ const dropdownList = {};
+ const button = {};
+
+ spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
+ spyOn(document, 'querySelector').and.returnValue(container);
+ container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
+
+ new MergeRequest(); // eslint-disable-line no-new
+
+ expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
+ expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
+ expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
+ });
+ });
});
}).call(window);
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 49ef21f75de..dc40244c20e 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -6,7 +6,6 @@ import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
import '~/diff';
-import '~/single_file_diff';
import '~/files_comment_button';
import '~/notes';
import 'vendor/jquery.scrollTo';
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 56d938e1fbe..b69f4eddffc 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -2481,6 +2481,7 @@ export const singleRowMetrics = [
'queries': [
{
'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
+ 'label': 'Container CPU',
'result': [
{
'metric': {
diff --git a/spec/javascripts/monitoring/monitoring_column_spec.js b/spec/javascripts/monitoring/monitoring_column_spec.js
index b3bc97adc9f..c423024dce0 100644
--- a/spec/javascripts/monitoring/monitoring_column_spec.js
+++ b/spec/javascripts/monitoring/monitoring_column_spec.js
@@ -95,7 +95,7 @@ describe('MonitoringColumn', () => {
});
});
- it('has a title for the y-axis that comes from the backend', () => {
+ it('has a title for the y-axis and the chart legend that comes from the backend', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
@@ -104,5 +104,6 @@ describe('MonitoringColumn', () => {
});
expect(component.yAxisLabel).toEqual(component.columnData.y_label);
+ expect(component.legendTitle).toEqual(component.columnData.queries[0].label);
});
});
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
new file mode 100644
index 00000000000..f90e0093d25
--- /dev/null
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -0,0 +1,26 @@
+import OAuthRememberMe from '~/oauth_remember_me';
+
+describe('OAuthRememberMe', () => {
+ preloadFixtures('static/oauth_remember_me.html.raw');
+
+ beforeEach(() => {
+ loadFixtures('static/oauth_remember_me.html.raw');
+
+ new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
+ });
+
+ it('adds the "remember_me" query parameter to all OAuth login buttons', () => {
+ $('#oauth-container #remember_me').click();
+
+ expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/?remember_me=1');
+ expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/?remember_me=1');
+ });
+
+ it('removes the "remember_me" query parameter from all OAuth login buttons', () => {
+ $('#oauth-container #remember_me').click();
+ $('#oauth-container #remember_me').click();
+
+ expect($('#oauth-container .oauth-login.twitter').attr('href')).toBe('http://example.com/');
+ expect($('#oauth-container .oauth-login.github').attr('href')).toBe('http://example.com/');
+ });
+});
diff --git a/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
new file mode 100644
index 00000000000..5b316b319a5
--- /dev/null
+++ b/spec/javascripts/pipeline_schedules/setup_pipeline_variable_list_spec.js
@@ -0,0 +1,145 @@
+import {
+ setupPipelineVariableList,
+ insertRow,
+ removeRow,
+} from '~/pipeline_schedules/setup_pipeline_variable_list';
+
+describe('Pipeline Variable List', () => {
+ let $markup;
+
+ describe('insertRow', () => {
+ it('should insert another row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input>
+ <textarea></textarea>
+ </li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should clear `data-is-persisted` on cloned row', () => {
+ $markup = $(`<div>
+ <li class="js-row" data-is-persisted="true"></li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ const $lastRow = $markup.find('.js-row').last();
+ expect($lastRow.attr('data-is-persisted')).toBe(undefined);
+ });
+
+ it('should clear inputs on cloned row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input value="foo">
+ <textarea>bar</textarea>
+ </li>
+ </div>`);
+
+ insertRow($markup.find('.js-row'));
+
+ const $lastRow = $markup.find('.js-row').last();
+ expect($lastRow.find('input').val()).toBe('');
+ expect($lastRow.find('textarea').val()).toBe('');
+ });
+ });
+
+ describe('removeRow', () => {
+ it('should remove dynamic row', () => {
+ $markup = $(`<div>
+ <li class="js-row">
+ <input>
+ <textarea></textarea>
+ </li>
+ </div>`);
+
+ removeRow($markup.find('.js-row'));
+
+ expect($markup.find('.js-row').length).toBe(0);
+ });
+
+ it('should hide and mark to destroy with already persisted rows', () => {
+ $markup = $(`<div>
+ <li class="js-row" data-is-persisted="true">
+ <input class="js-destroy-input">
+ </li>
+ </div>`);
+
+ const $row = $markup.find('.js-row');
+ removeRow($row);
+
+ expect($row.find('.js-destroy-input').val()).toBe('1');
+ expect($markup.find('.js-row').length).toBe(1);
+ });
+ });
+
+ describe('setupPipelineVariableList', () => {
+ beforeEach(() => {
+ $markup = $(`<form>
+ <li class="js-row">
+ <input class="js-user-input" name="schedule[variables_attributes][][key]">
+ <textarea class="js-user-input" name="schedule[variables_attributes][][value]"></textarea>
+ <button class="js-row-remove-button"></button>
+ <button class="js-row-add-button"></button>
+ </li>
+ </form>`);
+
+ setupPipelineVariableList($markup);
+ });
+
+ it('should remove the row when clicking the remove button', () => {
+ $markup.find('.js-row-remove-button').trigger('click');
+
+ expect($markup.find('.js-row').length).toBe(0);
+ });
+
+ it('should add another row when editing the last rows key input', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('input.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should add another row when editing the last rows value textarea', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('textarea.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+ });
+
+ it('should remove empty row after blurring', () => {
+ const $row = $markup.find('.js-row');
+ $row.find('input.js-user-input')
+ .val('foo')
+ .trigger('input');
+
+ expect($markup.find('.js-row').length).toBe(2);
+
+ $row.find('input.js-user-input')
+ .val('')
+ .trigger('input')
+ .trigger('blur');
+
+ expect($markup.find('.js-row').length).toBe(1);
+ });
+
+ it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
+ const $row = $markup.find('.js-row');
+ expect($row.find('input').attr('name')).toBe('schedule[variables_attributes][][key]');
+ expect($row.find('textarea').attr('name')).toBe('schedule[variables_attributes][][value]');
+
+ $markup.filter('form').submit();
+
+ expect($row.find('input').attr('name')).toBe('');
+ expect($row.find('textarea').attr('name')).toBe('');
+ });
+ });
+});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index 0a32797c3e2..a53e8a94d89 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -1,8 +1,7 @@
import AccessorUtilities from '~/lib/utils/accessor';
+import SigninTabsMemoizer from '~/signin_tabs_memoizer';
-import '~/signin_tabs_memoizer';
-
-((global) => {
+(() => {
describe('SigninTabsMemoizer', () => {
const fixtureTemplate = 'static/signin_tabs.html.raw';
const tabSelector = 'ul.nav-tabs';
@@ -10,7 +9,7 @@ import '~/signin_tabs_memoizer';
let memo;
function createMemoizer() {
- memo = new global.ActiveTabMemoizer({
+ memo = new SigninTabsMemoizer({
currentTabKey,
tabSelector,
});
@@ -78,7 +77,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = false;
- global.ActiveTabMemoizer.prototype.saveData.call(memo);
+ SigninTabsMemoizer.prototype.saveData.call(memo);
});
it('should not call .setItem', () => {
@@ -92,7 +91,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = true;
- global.ActiveTabMemoizer.prototype.saveData.call(memo, value);
+ SigninTabsMemoizer.prototype.saveData.call(memo, value);
});
it('should call .setItem', () => {
@@ -117,7 +116,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = false;
- readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
+ readData = SigninTabsMemoizer.prototype.readData.call(memo);
});
it('should not call .getItem and should return `null`', () => {
@@ -130,7 +129,7 @@ import '~/signin_tabs_memoizer';
beforeEach(function () {
memo.isLocalStorageAvailable = true;
- readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
+ readData = SigninTabsMemoizer.prototype.readData.call(memo);
});
it('should call .getItem and return the localStorage value', () => {
@@ -140,4 +139,4 @@ import '~/signin_tabs_memoizer';
});
});
});
-})(window);
+})();
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index cd74aba4a4e..fd492159081 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -1,4 +1,4 @@
-import '~/todos';
+import Todos from '~/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
@@ -9,7 +9,7 @@ describe('Todos', () => {
loadFixtures('todos/todos.html.raw');
todoItem = document.querySelector('.todos-list .todo');
- return new gl.Todos();
+ return new Todos();
});
describe('goToTodoUrl', () => {
diff --git a/spec/javascripts/visibility_select_spec.js b/spec/javascripts/visibility_select_spec.js
index c2eaea7c2ed..82714cb69bd 100644
--- a/spec/javascripts/visibility_select_spec.js
+++ b/spec/javascripts/visibility_select_spec.js
@@ -1,8 +1,6 @@
-import '~/visibility_select';
+import VisibilitySelect from '~/visibility_select';
(() => {
- const VisibilitySelect = gl.VisibilitySelect;
-
describe('VisibilitySelect', function () {
const lockedElement = document.createElement('div');
lockedElement.dataset.helpBlock = 'lockedHelpBlock';
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
index d4b200875df..ab8a3f6c64c 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
@@ -10,6 +10,7 @@ const deploymentMockData = [
url: '/root/acets-review-apps/environments/15',
stop_url: '/root/acets-review-apps/environments/15/stop',
metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
external_url: 'http://diplo.',
external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z',
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_header_spec.js
index 7f3eea7d2e5..06f89fabf42 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
@@ -54,6 +54,7 @@ describe('MRWidgetHeader', () => {
sourceBranch: 'mr-widget-refactor',
sourceBranchLink: `<a href="${sourceBranchPath}">mr-widget-refactor</a>`,
targetBranchPath: 'foo/bar/commits-path',
+ targetBranchTreePath: 'foo/bar/tree/path',
targetBranch: 'master',
isOpen: true,
emailPatchesPath: '/mr/email-patches',
@@ -69,12 +70,14 @@ describe('MRWidgetHeader', () => {
expect(el.classList.contains('mr-source-target')).toBeTruthy();
const sourceBranchLink = el.querySelectorAll('.label-branch')[0];
const targetBranchLink = el.querySelectorAll('.label-branch')[1];
+ const commitsCount = el.querySelector('.diverged-commits-count');
expect(sourceBranchLink.textContent).toContain(mr.sourceBranch);
expect(targetBranchLink.textContent).toContain(mr.targetBranch);
expect(sourceBranchLink.querySelector('a').getAttribute('href')).toEqual(sourceBranchPath);
- expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath);
- expect(el.querySelector('.diverged-commits-count').textContent).toContain('12 commits behind');
+ expect(targetBranchLink.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchTreePath);
+ expect(commitsCount.textContent).toContain('12 commits behind');
+ expect(commitsCount.querySelector('a').getAttribute('href')).toEqual(mr.targetBranchPath);
expect(el.textContent).toContain('Check out branch');
expect(el.querySelectorAll('.dropdown li a')[0].getAttribute('href')).toEqual(mr.emailPatchesPath);
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 2c3d0ddff28..6adcbc73ed7 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -3,6 +3,7 @@ import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widge
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
+const monitoringUrl = '/root/acets-review-apps/environments/15/metrics';
const metricsMockData = {
success: true,
@@ -39,6 +40,7 @@ const createComponent = () => {
el: document.createElement('div'),
propsData: {
metricsUrl: url,
+ metricsMonitoringUrl: monitoringUrl,
memoryMetrics: [],
deploymentTime: 0,
hasMetrics: false,
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 4b6f171c8d6..647b59520f8 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -76,28 +76,6 @@ describe('MRWidgetPipeline', () => {
el = vm.$el;
});
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('without a pipeline', () => {
- beforeEach(() => {
- vm.mr = { pipeline: null };
- });
-
- it('should render message with spinner', (done) => {
- Vue.nextTick()
- .then(() => {
- expect(el.querySelector('.pipeline-id')).toBe(null);
- expect(el.innerText.trim()).toBe('Waiting for pipeline...');
- expect(el.querySelectorAll('i.fa.fa-spinner.fa-spin').length).toBe(1);
- done();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-
it('should render template elements correctly', () => {
expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
expect(el.querySelectorAll('.ci-status-icon.ci-status-icon-success').length).toEqual(1);
@@ -115,47 +93,39 @@ describe('MRWidgetPipeline', () => {
it('should list single stage', (done) => {
pipeline.details.stages.splice(0, 1);
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
- expect(el.innerText).toContain('with stage');
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(1);
+ expect(el.innerText).toContain('with stage');
+ done();
+ });
});
it('should not have stages when there is no stage', (done) => {
vm.mr.pipeline.details.stages = [];
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.stage-container button').length).toEqual(0);
+ done();
+ });
});
it('should not have coverage text when pipeline has no coverage info', (done) => {
vm.mr.pipeline.coverage = null;
- Vue.nextTick()
- .then(() => {
- expect(el.querySelector('.js-mr-coverage')).toEqual(null);
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelector('.js-mr-coverage')).toEqual(null);
+ done();
+ });
});
it('should show CI error when there is a CI error', (done) => {
vm.mr.ciStatus = null;
- Vue.nextTick()
- .then(() => {
- expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
- expect(el.innerText).toContain('Could not connect to the CI server');
- })
- .then(done)
- .catch(done.fail);
+ Vue.nextTick(() => {
+ expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
+ expect(el.innerText).toContain('Could not connect to the CI server');
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 1c3188cdda2..d5754aaa9e7 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -22,7 +22,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd',
title: 'Commit message',
author: {
- avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
+ avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1',
username: 'jschatz1',
@@ -45,7 +45,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd',
title: 'Commit message',
author: {
- avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
+ avatar_url: 'https://gitlab.com/uploads/-/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1',
username: 'jschatz1',
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 895e1c585b4..b0b78e34e0f 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -1,150 +1,188 @@
import Vue from 'vue';
import paginationComp from '~/vue_shared/components/table_pagination.vue';
-import '~/lib/utils/common_utils';
describe('Pagination component', () => {
let component;
let PaginationComponent;
-
- const changeChanges = {
- one: '',
- };
-
- const change = (one) => {
- changeChanges.one = one;
- };
+ let spy;
+ let mountComponet;
beforeEach(() => {
+ spy = jasmine.createSpy('spy');
PaginationComponent = Vue.extend(paginationComp);
- });
-
- it('should render and start at page 1', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 2,
- previousPage: '',
- },
- change,
- },
- }).$mount();
- expect(component.$el.classList).toContain('gl-pagination');
-
- component.changePage({ target: { innerText: '1' } });
-
- expect(changeChanges.one).toEqual(1);
+ mountComponet = function (props) {
+ return new PaginationComponent({
+ propsData: props,
+ }).$mount();
+ };
});
- it('should go to the previous page', () => {
- component = new PaginationComponent({
- propsData: {
+ describe('render', () => {
+ describe('prev button', () => {
+ it('should be disabled and non clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 2,
+ page: 1,
+ perPage: 20,
+ previousPage: NaN,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
+ ).toEqual(true);
+
+ component.$el.querySelector('.js-previous-button a').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be enabled and clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-previous-button a').click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('first button', () => {
+ it('should call the change callback with the first page', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-first-button a');
+
+ expect(button.textContent.trim()).toEqual('« First');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('last button', () => {
+ it('should call the change callback with the last page', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 3,
+ page: 2,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ const button = component.$el.querySelector('.js-last-button a');
+
+ expect(button.textContent.trim()).toEqual('Last »');
+
+ button.click();
+
+ expect(spy).toHaveBeenCalledWith(5);
+ });
+ });
+
+ describe('next button', () => {
+ it('should be disabled and non clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 5,
+ page: 5,
+ perPage: 20,
+ previousPage: 1,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(
+ component.$el.querySelector('.js-next-button').textContent.trim(),
+ ).toEqual('Next');
+
+ component.$el.querySelector('.js-next-button a').click();
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('should be enabled and clickable', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ component.$el.querySelector('.js-next-button a').click();
+
+ expect(spy).toHaveBeenCalledWith(4);
+ });
+ });
+
+ describe('numbered buttons', () => {
+ it('should render 5 pages', () => {
+ component = mountComponet({
+ pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
+ totalPages: 5,
+ },
+ change: spy,
+ });
+
+ expect(component.$el.querySelectorAll('.page').length).toEqual(5);
+ });
+ });
+
+ it('should render the spread operator', () => {
+ component = mountComponet({
pageInfo: {
+ nextPage: 4,
+ page: 3,
+ perPage: 20,
+ previousPage: 2,
+ total: 84,
totalPages: 10,
- nextPage: 3,
- previousPage: 1,
},
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Prev' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-
- it('should go to the next page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Next' } });
-
- expect(changeChanges.one).toEqual(5);
- });
-
- it('should go to the last page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: 'Last »' } });
-
- expect(changeChanges.one).toEqual(10);
- });
-
- it('should go to the first page', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 5,
- previousPage: 3,
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: '« First' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-
- it('should do nothing', () => {
- component = new PaginationComponent({
- propsData: {
- pageInfo: {
- totalPages: 10,
- nextPage: 2,
- previousPage: '',
- },
- change,
- },
- }).$mount();
-
- component.changePage({ target: { innerText: '...' } });
-
- expect(changeChanges.one).toEqual(1);
- });
-});
-
-describe('paramHelper', () => {
- afterEach(() => {
- window.history.pushState({}, null, '');
- });
-
- it('can parse url parameters correctly', () => {
- window.history.pushState({}, null, '?scope=all&p=2');
-
- const scope = gl.utils.getParameterByName('scope');
- const p = gl.utils.getParameterByName('p');
-
- expect(scope).toEqual('all');
- expect(p).toEqual('2');
- });
-
- it('returns null if param not in url', () => {
- window.history.pushState({}, null, '?p=2');
-
- const scope = gl.utils.getParameterByName('scope');
- const p = gl.utils.getParameterByName('p');
+ change: spy,
+ });
- expect(scope).toEqual(null);
- expect(p).toEqual('2');
+ expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
+ });
});
});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 4399c8b2025..a225b04c47e 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -1,9 +1,8 @@
/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, max-len */
/* global Dropzone */
/* global Mousetrap */
-/* global ZenMode */
-import '~/zen_mode';
+import ZenMode from '~/zen_mode';
(function() {
var enterZen, escapeKeydown, exitZen;
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index 787212581e2..d70749536b8 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::CrossProjectReference, lib: true do
+describe Banzai::CrossProjectReference do
include described_class
describe '#project_from_ref' do
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
index 33b812ef425..34f1657b6d3 100644
--- a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::AsciiDocPostProcessingFilter, lib: true do
+describe Banzai::Filter::AsciiDocPostProcessingFilter do
include FilterSpecHelper
it "adds class for elements with data-math-style" do
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index a6d2ea11fcc..b7c2ff03125 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::AutolinkFilter, lib: true do
+describe Banzai::Filter::AutolinkFilter do
include FilterSpecHelper
let(:link) { 'http://about.gitlab.com/' }
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 2799249ae3e..8224dc5a6b9 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Banzai::Filter::BlockquoteFenceFilter, lib: true do
+describe Banzai::Filter::BlockquoteFenceFilter do
include FilterSpecHelper
it 'converts blockquote fences to blockquote lines' do
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index fc67c7ec3c4..11d48387544 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
+describe Banzai::Filter::CommitRangeReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
@@ -29,14 +29,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference2}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
+ .to eq urls.project_compare_url(project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
+ .to eq urls.project_compare_url(project, range.to_param)
end
it 'links to a valid short ID' do
@@ -94,7 +94,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id, only_path: true)
+ expect(link).to eq urls.project_compare_url(project, from: commit1.id, to: commit2.id, only_path: true)
end
end
@@ -106,7 +106,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -141,7 +141,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -176,7 +176,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
+ .to eq urls.project_compare_url(project2, range.to_param)
end
it 'link has valid text' do
@@ -205,7 +205,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, :repository, namespace: namespace) }
let(:range) { CommitRange.new("#{commit1.id}...master", project) }
- let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
+ let(:reference) { urls.project_compare_url(project2, from: commit1.id, to: 'master') }
before do
range.project = project2
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index c4d8d3b6682..fb2a36d1ba1 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::CommitReferenceFilter, lib: true do
+describe Banzai::Filter::CommitReferenceFilter do
include FilterSpecHelper
let(:project) { create(:project, :public, :repository) }
@@ -27,7 +27,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
expect(doc.css('a').first.text).to eq commit.short_id
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_commit_url(project.namespace, project, reference)
+ .to eq urls.project_commit_url(project, reference)
end
end
@@ -90,7 +90,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_commit_url(project.namespace, project, reference, only_path: true)
+ expect(link).to eq urls.project_commit_url(project, reference, only_path: true)
end
end
@@ -175,13 +175,13 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, :repository, namespace: namespace) }
let(:commit) { project2.commit }
- let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
+ let(:reference) { urls.project_commit_url(project2, commit.id) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
+ .to eq urls.project_commit_url(project2, commit.id)
end
it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 086a006c45f..10910f22d4a 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::EmojiFilter, lib: true do
+describe Banzai::Filter::EmojiFilter do
include FilterSpecHelper
before do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index b7d82c36ddd..a0d391d981c 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
+describe Banzai::Filter::ExternalIssueReferenceFilter do
include FilterSpecHelper
def helper
@@ -108,6 +108,11 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
let(:issue) { ExternalIssue.new("#123", project) }
let(:reference) { issue.to_reference }
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
it_behaves_like "external issue tracker"
end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 0f8ec8de7a0..2a3c0cd78b8 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -17,7 +17,7 @@ shared_examples 'an external link with rel attribute' do
end
end
-describe Banzai::Filter::ExternalLinkFilter, lib: true do
+describe Banzai::Filter::ExternalLinkFilter do
include FilterSpecHelper
it 'ignores elements without an href attribute' do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 082c0d4dd0d..663e3514436 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::GollumTagsFilter, lib: true do
+describe Banzai::Filter::GollumTagsFilter do
include FilterSpecHelper
let(:project) { create(:empty_project) }
@@ -22,7 +22,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag = '[[images/image.jpg]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
- expect(doc.at_css('img')['src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg"
+ expect(doc.at_css('img')['data-src']).to eq "#{project_wiki.wiki_base_path}/images/image.jpg"
end
it 'does not creates img tag if image does not exist' do
@@ -40,7 +40,7 @@ describe Banzai::Filter::GollumTagsFilter, lib: true do
tag = '[[http://example.com/image.jpg]]'
doc = filter("See #{tag}", project_wiki: project_wiki)
- expect(doc.at_css('img')['src']).to eq "http://example.com/image.jpg"
+ expect(doc.at_css('img')['data-src']).to eq "http://example.com/image.jpg"
end
it 'does not creates img tag for invalid URL' do
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index f9e6bd609f0..91e18d876d5 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::HtmlEntityFilter, lib: true do
+describe Banzai::Filter::HtmlEntityFilter do
include FilterSpecHelper
let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' }
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
new file mode 100644
index 00000000000..c19de7b784a
--- /dev/null
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Banzai::Filter::ImageLazyLoadFilter, lib: true do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'transforms the image src to a data-src' do
+ doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+ expect(doc.at_css('img')['data-src']).to eq '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'
+ end
+
+ it 'works with external images' do
+ doc = filter(image('https://i.imgur.com/DfssX9C.jpg'))
+ expect(doc.at_css('img')['data-src']).to eq 'https://i.imgur.com/DfssX9C.jpg'
+ end
+end
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 294558b3db2..51920869545 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::ImageLinkFilter, lib: true do
+describe Banzai::Filter::ImageLinkFilter do
include FilterSpecHelper
def image(path)
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
index 9e526371294..63c4ab61b86 100644
--- a/spec/lib/banzai/filter/inline_diff_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::InlineDiffFilter, lib: true do
+describe Banzai::Filter::InlineDiffFilter do
include FilterSpecHelper
it 'adds inline diff span tags for deletions when using square brackets' do
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 9c2399815b9..7cf2f4282f8 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::IssuableStateFilter, lib: true do
+describe Banzai::Filter::IssuableStateFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index a79d365d6c5..045bf3e0cc9 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::IssueReferenceFilter, lib: true do
+describe Banzai::Filter::IssueReferenceFilter do
include FilterSpecHelper
def helper
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index cb3cf982351..1daa6ac7f9e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'html/pipeline'
-describe Banzai::Filter::LabelReferenceFilter, lib: true do
+describe Banzai::Filter::LabelReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project, :public, name: 'sample-project') }
@@ -45,7 +45,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
+ expect(link).to eq urls.project_issues_path(project, label_name: label.name)
end
context 'project that does not exist referenced' do
@@ -73,7 +73,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
end
it 'links with adjacent text' do
@@ -96,7 +96,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See gfm'
end
@@ -120,7 +120,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See 2fa'
end
@@ -144,7 +144,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See ?g.fm&'
end
@@ -169,7 +169,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
@@ -193,7 +193,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See 2 factor authentication'
end
@@ -217,7 +217,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
expect(doc.text).to eq 'See g.fm & references?'
end
@@ -250,9 +250,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{references}")
expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
- urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ urls.project_issues_url(project, label_name: bug.name),
+ urls.project_issues_url(project, label_name: feature_proposal.name),
+ urls.project_issues_url(project, label_name: technical_debt.name)
])
expect(doc.text).to eq 'See bug, feature proposal, technical debt'
end
@@ -265,9 +265,9 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{references}")
expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
- urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
- urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ urls.project_issues_url(project, label_name: bug.name),
+ urls.project_issues_url(project, label_name: feature_proposal.name),
+ urls.project_issues_url(project, label_name: technical_debt.name)
])
expect(doc.text).to eq 'See bug feature proposal technical debt'
end
@@ -288,7 +288,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ .project_issues_url(project, label_name: label.name)
end
it 'links with adjacent text' do
@@ -325,7 +325,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}", project: project)
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ .project_issues_url(project, label_name: group_label.name)
expect(doc.text).to eq 'See gfm references'
end
@@ -348,7 +348,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
doc = reference_filter("See #{reference}", project: project)
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_issues_url(project.namespace, project, label_name: group_label.name)
+ .project_issues_url(project, label_name: group_label.name)
expect(doc.text).to eq "See gfm references"
end
@@ -373,9 +373,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -407,9 +405,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -441,9 +437,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'links to a valid reference' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project2.namespace,
- project2,
- label_name: label.name)
+ .to eq urls.project_issues_url(project2, label_name: label.name)
end
it 'has valid color' do
@@ -477,9 +471,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(another_project, label_name: group_label.name)
end
it 'has valid color' do
@@ -514,9 +506,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(another_project.namespace,
- another_project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(another_project, label_name: group_label.name)
end
it 'has valid color' do
@@ -550,9 +540,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project.namespace,
- project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(project, label_name: group_label.name)
end
it 'has valid color' do
@@ -584,9 +572,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'points to referenced project issues page' do
expect(result.css('a').first.attr('href'))
- .to eq urls.namespace_project_issues_url(project.namespace,
- project,
- label_name: group_label.name)
+ .to eq urls.project_issues_url(project, label_name: group_label.name)
end
it 'has valid color' do
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index 897288b8ad5..00c407d1b69 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::MarkdownFilter, lib: true do
+describe Banzai::Filter::MarkdownFilter do
include FilterSpecHelper
context 'code block' do
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index 51883782e19..cade8cb409e 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::MathFilter, lib: true do
+describe Banzai::Filter::MathFilter do
include FilterSpecHelper
it 'leaves regular inline code unchanged' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index cd91681551e..683972a3112 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
+describe Banzai::Filter::MergeRequestReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project, :public) }
@@ -37,7 +37,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_merge_request_url(project.namespace, project, merge)
+ .project_merge_request_url(project, merge)
end
it 'links with adjacent text' do
@@ -95,7 +95,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_merge_request_url(project.namespace, project, merge, only_path: true)
+ expect(link).to eq urls.project_merge_request_url(project, merge, only_path: true)
end
end
@@ -108,8 +108,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -142,8 +141,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -176,8 +174,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_merge_request_url(project2.namespace,
- project2, merge)
+ .to eq urls.project_merge_request_url(project2, merge)
end
it 'link has valid text' do
@@ -203,7 +200,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
- let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
+ let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index fe88b9cb73e..8fe05dc2e53 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
+describe Banzai::Filter::MilestoneReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project, :public) }
@@ -45,7 +45,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
expect(link).not_to match %r(https?://)
expect(link).to eq urls
- .namespace_project_milestone_path(project.namespace, project, milestone)
+ .project_milestone_path(project, milestone)
end
context 'Integer-based references' do
@@ -53,7 +53,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
end
it 'links with adjacent text' do
@@ -76,7 +76,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
expect(doc.text).to eq 'See gfm'
end
@@ -100,7 +100,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
expect(doc.text).to eq 'See gfm references'
end
@@ -123,7 +123,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(project.namespace, project, milestone)
+ .project_milestone_url(project, milestone)
end
it 'links with adjacent text' do
@@ -157,9 +157,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
@@ -196,9 +194,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
@@ -235,9 +231,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls
- .namespace_project_milestone_url(another_project.namespace,
- another_project,
- milestone)
+ .project_milestone_url(another_project, milestone)
end
it 'link has valid text' do
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index 9b8ecb201f3..8235c411eb7 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::PlantumlFilter, lib: true do
+describe Banzai::Filter::PlantumlFilter do
include FilterSpecHelper
it 'should replace plantuml pre tag with img tag' do
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/redactor_filter_spec.rb
index b81cdbb8957..fb6b81d4f10 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/redactor_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::RedactorFilter, lib: true do
+describe Banzai::Filter::RedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index ba0fa4a609a..b9ca68e8935 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::ReferenceFilter, lib: true do
+describe Banzai::Filter::ReferenceFilter do
let(:project) { build(:project) }
describe '#each_node' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 1ce7bd7706e..ddebf2264d9 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::RelativeLinkFilter, lib: true do
+describe Banzai::Filter::RelativeLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
commit: commit,
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index a8a0aa6d395..35a32a46eff 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::SanitizationFilter, lib: true do
+describe Banzai::Filter::SanitizationFilter do
include FilterSpecHelper
describe 'default whitelist' do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index e851120bc3a..5f548888223 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::SnippetReferenceFilter, lib: true do
+describe Banzai::Filter::SnippetReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project, :public) }
@@ -23,7 +23,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls
- .namespace_project_snippet_url(project.namespace, project, snippet)
+ .project_snippet_url(project, snippet)
end
it 'links with adjacent text' do
@@ -75,7 +75,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
- expect(link).to eq urls.namespace_project_snippet_url(project.namespace, project, snippet, only_path: true)
+ expect(link).to eq urls.project_snippet_url(project, snippet, only_path: true)
end
end
@@ -89,7 +89,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -122,7 +122,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -155,7 +155,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'link has valid text' do
@@ -181,13 +181,13 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
- let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
+ let(:reference) { urls.project_snippet_url(project2, snippet) }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
+ .to eq urls.project_snippet_url(project2, snippet)
end
it 'links with adjacent text' do
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index f61fc8ceb9e..5a23e0e70cc 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
+describe Banzai::Filter::SyntaxHighlightFilter do
include FilterSpecHelper
context "when no language is specified" do
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 70b31f3a880..ff6b19459bb 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::TableOfContentsFilter, lib: true do
+describe Banzai::Filter::TableOfContentsFilter do
include FilterSpecHelper
def header(level, text)
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index 6327ca8bbfd..3bc9635b50e 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::UploadLinkFilter, lib: true do
+describe Banzai::Filter::UploadLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index edf3846b742..7ea9df5eda5 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::UserReferenceFilter, lib: true do
+describe Banzai::Filter::UserReferenceFilter do
include FilterSpecHelper
let(:project) { create(:empty_project, :public) }
@@ -43,7 +43,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
- .to eq urls.namespace_project_url(project.namespace, project)
+ .to eq urls.project_url(project)
end
it 'includes a data-author attribute when there is an author' do
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 00494f545a3..81dda0687f3 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::VideoLinkFilter, lib: true do
+describe Banzai::Filter::VideoLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index 92d88c4172c..ceafd12a68e 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::WikiLinkFilter, lib: true do
+describe Banzai::Filter::WikiLinkFilter do
include FilterSpecHelper
let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
diff --git a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
index fe70eada7eb..9f1b862ef19 100644
--- a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Banzai::Filter::YamlFrontMatterFilter, lib: true do
+describe Banzai::Filter::YamlFrontMatterFilter do
include FilterSpecHelper
it 'allows for `encoding:` before the frontmatter' do
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index 866297f94a9..728271e757b 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::IssuableExtractor, lib: true do
+describe Banzai::IssuableExtractor do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:extractor) { described_class.new(project, user) }
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 2b8c76f2bb8..601ffbb5456 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -4,30 +4,87 @@ describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
let(:project) { create(:redmine_project, :public) }
- it 'allows to use shorthand external reference syntax for Redmine' do
- markdown = '#12'
+ context 'when internal issue tracker is enabled' do
+ context 'when shorthand pattern #ISSUE_ID is used' do
+ it 'links an internal issue if it exists' do
+ issue = create(:issue, project: project)
+ markdown = issue.to_reference(project, full: true)
- result = described_class.call(markdown, project: project)[:output]
- link = result.css('a').first
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(project, issue)
+ )
+ end
+
+ it 'does not link any issue if it does not exist on GitLab' do
+ markdown = '#12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ expect(result.css('a')).to be_empty
+ end
+ end
+
+ it 'allows to use long external reference syntax for Redmine' do
+ markdown = 'API_32-12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'parses cross-project references to regular issues' do
+ other_project = create(:empty_project, :public)
+ issue = create(:issue, project: other_project)
+ markdown = issue.to_reference(project, full: true)
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
+ )
+ end
end
- it 'parses cross-project references to regular issues' do
- other_project = create(:empty_project, :public)
- issue = create(:issue, project: other_project)
- markdown = issue.to_reference(project, full: true)
+ context 'when internal issue tracker is disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'allows to use shorthand external reference syntax for Redmine' do
+ markdown = '#12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'allows to use long external reference syntax for Redmine' do
+ markdown = 'API_32-12'
+
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
+
+ expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ end
+
+ it 'parses cross-project references to regular issues' do
+ other_project = create(:empty_project, :public)
+ issue = create(:issue, project: other_project)
+ markdown = issue.to_reference(project, full: true)
- result = described_class.call(markdown, project: project)[:output]
- link = result.css('a').first
+ result = described_class.call(markdown, project: project)[:output]
+ link = result.css('a').first
- expect(link['href']).to eq(
- Gitlab::Routing.url_helpers.namespace_project_issue_path(
- other_project.namespace,
- other_project,
- issue
+ expect(link['href']).to eq(
+ Gitlab::Routing.url_helpers.project_issue_path(other_project, issue)
)
- )
+ end
end
end
end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index b444ca05b8e..0bf45329657 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::BaseParser, lib: true do
+describe Banzai::ReferenceParser::BaseParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index a314a6119cb..69bf28cdf85 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitParser, lib: true do
+describe Banzai::ReferenceParser::CommitParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index 5dca5e784da..b384a59bfb4 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::CommitRangeParser, lib: true do
+describe Banzai::ReferenceParser::CommitRangeParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index d212bbac619..a3256afdbb1 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::ExternalIssueParser, lib: true do
+describe Banzai::ReferenceParser::ExternalIssueParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index acdd23f81f3..94b989fe91d 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::IssueParser, lib: true do
+describe Banzai::ReferenceParser::IssueParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index ddd699f3c25..cf1b2a92195 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::LabelParser, lib: true do
+describe Banzai::ReferenceParser::LabelParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index cb69ca16800..775749ae3a7 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MergeRequestParser, lib: true do
+describe Banzai::ReferenceParser::MergeRequestParser do
include ReferenceParserHelpers
let(:user) { create(:user) }
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 72d4f3bc18e..2cfcafa8798 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::MilestoneParser, lib: true do
+describe Banzai::ReferenceParser::MilestoneParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 620875ece20..c6d0b7be254 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::SnippetParser, lib: true do
+describe Banzai::ReferenceParser::SnippetParser do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index dfebb971f3a..64f2b607d7c 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::ReferenceParser::UserParser, lib: true do
+describe Banzai::ReferenceParser::UserParser do
include ReferenceParserHelpers
let(:group) { create(:group) }
diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb
index a5dfb49478a..e49ecadde20 100644
--- a/spec/lib/ci/ansi2html_spec.rb
+++ b/spec/lib/ci/ansi2html_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Ansi2html, lib: true do
+describe Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 51cbfd2a848..8e2d2724426 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Charts, lib: true do
+describe Ci::Charts do
context "pipeline_times" do
let(:project) { create(:empty_project) }
let(:chart) { Ci::Charts::PipelineTime.new(project) }
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 482f03aa0cc..ed571a2ba05 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -32,6 +32,28 @@ module Ci
end
end
+ describe 'retry entry' do
+ context 'when retry count is specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec', retry: 1 })
+ end
+
+ it 'includes retry count in build options attribute' do
+ expect(subject[:options]).to include(retry: 1)
+ end
+ end
+
+ context 'when retry count is not specified' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec' })
+ end
+
+ it 'does not persist retry count in the database' do
+ expect(subject[:options]).not_to have_key(:retry)
+ end
+ end
+ end
+
describe 'allow failure entry' do
context 'when job is a manual action' do
context 'when allow_failure is defined' do
@@ -163,7 +185,10 @@ module Ci
commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [],
- options: {},
+ options: {
+ before_script: ["pwd"],
+ script: ["rspec"]
+ },
allow_failure: false,
when: "on_success",
environment: nil,
@@ -616,10 +641,12 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
- image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
- services: [{ name: "mysql" },
- { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] }]
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "mysql" },
+ { name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] }]
},
allow_failure: false,
when: "on_success",
@@ -649,10 +676,12 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
- image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
- services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
- command: ["/usr/local/bin/init", "run"] },
- { name: "docker:dind" }]
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
+ services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
+ command: ["/usr/local/bin/init", "run"] },
+ { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
@@ -680,6 +709,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }, { name: "docker:dind" }]
},
@@ -707,8 +738,10 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
- image: { name: "ruby:2.5" },
- services: [{ name: "postgresql" }, { name: "docker:dind" }]
+ before_script: ["pwd"],
+ script: ["rspec"],
+ image: { name: "ruby:2.5" },
+ services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
@@ -878,7 +911,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key'
+ key: 'key',
+ policy: 'pull-push'
)
end
@@ -896,7 +930,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["logs/", "binaries/"],
untracked: true,
- key: 'key'
+ key: 'key',
+ policy: 'pull-push'
)
end
@@ -915,7 +950,8 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq(
paths: ["test/"],
untracked: false,
- key: 'local'
+ key: 'local',
+ policy: 'pull-push'
)
end
end
@@ -948,6 +984,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
+ before_script: ["pwd"],
+ script: ["rspec"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }],
artifacts: {
@@ -1159,7 +1197,9 @@ module Ci
commands: "test",
coverage_regex: nil,
tag_list: [],
- options: {},
+ options: {
+ script: ["test"]
+ },
when: "on_success",
allow_failure: false,
environment: nil,
@@ -1205,7 +1245,9 @@ module Ci
commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [],
- options: {},
+ options: {
+ script: ["execute-script-for-job"]
+ },
when: "on_success",
allow_failure: false,
environment: nil,
@@ -1218,7 +1260,9 @@ module Ci
commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [],
- options: {},
+ options: {
+ script: ["execute-script-for-job"]
+ },
when: "on_success",
allow_failure: false,
environment: nil,
diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/ci/mask_secret_spec.rb
index 3101bed20fb..f7b753b022b 100644
--- a/spec/lib/ci/mask_secret_spec.rb
+++ b/spec/lib/ci/mask_secret_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::MaskSecret, lib: true do
+describe Ci::MaskSecret do
subject { described_class }
describe '#mask' do
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index db680489a8d..4dab58b26a0 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupUrlConstrainer, lib: true do
+describe GroupUrlConstrainer do
let!(:group) { create(:group, path: 'gitlab') }
describe '#matches?' do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index b6884e37aa3..e4b5dfc574a 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectUrlConstrainer, lib: true do
+describe ProjectUrlConstrainer do
let!(:project) { create(:empty_project) }
let!(:namespace) { project.namespace }
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index ed69b830979..cb3b4ff1391 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe UserUrlConstrainer, lib: true do
+describe UserUrlConstrainer do
let!(:user) { create(:user, username: 'dz') }
describe '#matches?' do
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
index 8f51474476d..3652d928c43 100644
--- a/spec/lib/disable_email_interceptor_spec.rb
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe DisableEmailInterceptor, lib: true do
+describe DisableEmailInterceptor do
before do
- Mail.register_interceptor(DisableEmailInterceptor)
+ Mail.register_interceptor(described_class)
end
it 'does not send emails' do
@@ -14,7 +14,7 @@ describe DisableEmailInterceptor, lib: true do
# Removing interceptor from the list because unregister_interceptor is
# implemented in later version of mail gem
# See: https://github.com/mikel/mail/pull/705
- Mail.unregister_interceptor(DisableEmailInterceptor)
+ Mail.unregister_interceptor(described_class)
end
def deliver_mail
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index d70690f589d..b1366e74802 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe EventFilter, lib: true do
+describe EventFilter do
describe '#apply_filter' do
let(:source_user) { create(:user) }
let!(:public_project) { create(:empty_project, :public) }
@@ -16,42 +16,42 @@ describe EventFilter, lib: true do
let!(:left_event) { create(:event, :left, project: public_project, target: public_project, author: source_user) }
it 'applies push filter' do
- events = EventFilter.new(EventFilter.push).apply_filter(Event.all)
+ events = described_class.new(described_class.push).apply_filter(Event.all)
expect(events).to contain_exactly(push_event)
end
it 'applies merged filter' do
- events = EventFilter.new(EventFilter.merged).apply_filter(Event.all)
+ events = described_class.new(described_class.merged).apply_filter(Event.all)
expect(events).to contain_exactly(merged_event)
end
it 'applies issue filter' do
- events = EventFilter.new(EventFilter.issue).apply_filter(Event.all)
+ events = described_class.new(described_class.issue).apply_filter(Event.all)
expect(events).to contain_exactly(created_event, updated_event, closed_event, reopened_event)
end
it 'applies comments filter' do
- events = EventFilter.new(EventFilter.comments).apply_filter(Event.all)
+ events = described_class.new(described_class.comments).apply_filter(Event.all)
expect(events).to contain_exactly(comments_event)
end
it 'applies team filter' do
- events = EventFilter.new(EventFilter.team).apply_filter(Event.all)
+ events = described_class.new(described_class.team).apply_filter(Event.all)
expect(events).to contain_exactly(joined_event, left_event)
end
it 'applies all filter' do
- events = EventFilter.new(EventFilter.all).apply_filter(Event.all)
+ events = described_class.new(described_class.all).apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end
it 'applies no filter' do
- events = EventFilter.new(nil).apply_filter(Event.all)
+ events = described_class.new(nil).apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end
it 'applies unknown filter' do
- events = EventFilter.new('').apply_filter(Event.all)
+ events = described_class.new('').apply_filter(Event.all)
expect(events).to contain_exactly(push_event, merged_event, created_event, updated_event, closed_event, reopened_event, comments_event, joined_event, left_event)
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index f2132d485ab..9b89f47ae7e 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe ExtractsPath, lib: true do
- include ExtractsPath
+describe ExtractsPath do
+ include described_class
include RepoHelpers
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
let(:project) { double('project') }
let(:request) { double('request') }
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 5cc3a3745e4..1076c63b5f2 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Feature, lib: true do
+describe Feature do
describe '.get' do
let(:feature) { double(:feature) }
let(:key) { 'my_feature' }
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index fda6f9a6c88..49501931dd2 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe FileSizeValidator, lib: true do
- let(:validator) { FileSizeValidator.new(options) }
+describe FileSizeValidator do
+ let(:validator) { described_class.new(options) }
let(:attachment) { AttachmentUploader.new }
let(:note) { create(:note) }
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 43d52b941ab..f668f78c2b8 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
require 'nokogiri'
module Gitlab
- describe Asciidoc, lib: true do
+ describe Asciidoc do
let(:input) { '<b>ascii</b>' }
let(:context) { {} }
let(:html) { 'H<sub>2</sub>O' }
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
index fc72df575be..f29431b937c 100644
--- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Auth::UniqueIpsLimiter, :redis, lib: true do
+describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
include_context 'unique ips sign in limit'
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index d09da951869..a9db0d5164d 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Auth, lib: true do
+describe Gitlab::Auth do
let(:gl_auth) { described_class }
describe 'constants' do
@@ -206,7 +206,7 @@ describe Gitlab::Auth, lib: true do
end
it 'throws an error suggesting user create a PAT when internal auth is disabled' do
- allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false }
+ allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false }
expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError)
end
@@ -279,6 +279,16 @@ describe Gitlab::Auth, lib: true do
gl_auth.find_with_user_password('ldap_user', 'password')
end
end
+
+ context "with sign-in disabled" do
+ before do
+ stub_application_setting(password_authentication_enabled: false)
+ end
+
+ it "does not find user by valid login/password" do
+ expect(gl_auth.find_with_user_password(username, password)).to be_nil
+ end
+ end
end
private
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
new file mode 100644
index 00000000000..a910fb105a5
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do
+ let(:migration) { described_class.new }
+
+ before do
+ allow(migration).to receive(:logger).and_return(Logger.new(nil))
+ end
+
+ describe '#perform' do
+ it 'renames the path of system-uploads', truncate: true do
+ upload = create(:upload, model: create(:empty_project), path: 'uploads/system/project/avatar.jpg')
+
+ migration.perform('uploads/system/', 'uploads/-/system/')
+
+ expect(upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 64f82fe27b2..4ad69aeba43 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -1,46 +1,120 @@
require 'spec_helper'
describe Gitlab::BackgroundMigration do
+ describe '.queue' do
+ it 'returns background migration worker queue' do
+ expect(described_class.queue)
+ .to eq BackgroundMigrationWorker.sidekiq_options['queue']
+ end
+ end
+
describe '.steal' do
- it 'steals jobs from a queue' do
- queue = [double(:job, args: ['Foo', [10, 20]])]
+ context 'when there are enqueued jobs present' do
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue)]
+ end
+
+ before do
+ allow(Sidekiq::Queue).to receive(:new)
+ .with(described_class.queue)
+ .and_return(queue)
+ end
+
+ context 'when queue contains unprocessed jobs' do
+ it 'steals jobs from a queue' do
+ expect(queue[0]).to receive(:delete).and_return(true)
+
+ expect(described_class).to receive(:perform)
+ .with('Foo', [10, 20])
+
+ described_class.steal('Foo')
+ end
+
+ it 'does not steal job that has already been taken' do
+ expect(queue[0]).to receive(:delete).and_return(false)
+
+ expect(described_class).not_to receive(:perform)
+
+ described_class.steal('Foo')
+ end
+
+ it 'does not steal jobs for a different migration' do
+ expect(described_class).not_to receive(:perform)
- allow(Sidekiq::Queue).to receive(:new)
- .with(BackgroundMigrationWorker.sidekiq_options['queue'])
- .and_return(queue)
+ expect(queue[0]).not_to receive(:delete)
- expect(queue[0]).to receive(:delete)
+ described_class.steal('Bar')
+ end
+ end
- expect(described_class).to receive(:perform).with('Foo', [10, 20])
+ context 'when one of the jobs raises an error' do
+ let(:migration) { spy(:migration) }
- described_class.steal('Foo')
+ let(:queue) do
+ [double(args: ['Foo', [10, 20]], queue: described_class.queue),
+ double(args: ['Foo', [20, 30]], queue: described_class.queue)]
+ end
+
+ before do
+ stub_const("#{described_class}::Foo", migration)
+
+ allow(queue[0]).to receive(:delete).and_return(true)
+ allow(queue[1]).to receive(:delete).and_return(true)
+ end
+
+ it 'enqueues the migration again and re-raises the error' do
+ allow(migration).to receive(:perform).with(10, 20)
+ .and_raise(Exception, 'Migration error').once
+
+ expect(BackgroundMigrationWorker).to receive(:perform_async)
+ .with('Foo', [10, 20]).once
+
+ expect { described_class.steal('Foo') }.to raise_error(Exception)
+ end
+ end
end
- it 'does not steal jobs for a different migration' do
- queue = [double(:job, args: ['Foo', [10, 20]])]
+ context 'when there are scheduled jobs present', :sidekiq, :redis do
+ it 'steals all jobs from the scheduled sets' do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(10.minutes, 'Object')
- allow(Sidekiq::Queue).to receive(:new)
- .with(BackgroundMigrationWorker.sidekiq_options['queue'])
- .and_return(queue)
+ expect(Sidekiq::ScheduledSet.new).to be_one
+ expect(described_class).to receive(:perform).with('Object', any_args)
- expect(described_class).not_to receive(:perform)
+ described_class.steal('Object')
- expect(queue[0]).not_to receive(:delete)
+ expect(Sidekiq::ScheduledSet.new).to be_none
+ end
+ end
+ end
- described_class.steal('Bar')
+ context 'when there are enqueued and scheduled jobs present', :sidekiq, :redis do
+ it 'steals from the scheduled sets queue first' do
+ Sidekiq::Testing.disable! do
+ expect(described_class).to receive(:perform)
+ .with('Object', [1]).ordered
+ expect(described_class).to receive(:perform)
+ .with('Object', [2]).ordered
+
+ BackgroundMigrationWorker.perform_async('Object', [2])
+ BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1])
+
+ described_class.steal('Object')
+ end
+ end
end
end
describe '.perform' do
- it 'performs a background migration' do
- instance = double(:instance)
- klass = double(:klass, new: instance)
+ let(:migration) { spy(:migration) }
- expect(described_class).to receive(:const_get)
- .with('Foo')
- .and_return(klass)
+ before do
+ stub_const("#{described_class.name}::Foo", migration)
+ end
- expect(instance).to receive(:perform).with(10, 20)
+ it 'performs a background migration' do
+ expect(migration).to receive(:perform).with(10, 20).once
described_class.perform('Foo', [10, 20])
end
diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb
index 1c3d2547fec..8772d3d5ada 100644
--- a/spec/lib/gitlab/backup/manager_spec.rb
+++ b/spec/lib/gitlab/backup/manager_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Backup::Manager, lib: true do
+describe Backup::Manager do
include StubENV
let(:progress) { StringIO.new }
@@ -214,4 +214,56 @@ describe Backup::Manager, lib: true do
end
end
end
+
+ describe '#upload' do
+ let(:backup_file) { Tempfile.new('backup', Gitlab.config.backup.path) }
+ let(:backup_filename) { File.basename(backup_file.path) }
+
+ before do
+ allow(subject).to receive(:tar_file).and_return(backup_filename)
+
+ stub_backup_setting(
+ upload: {
+ connection: {
+ provider: 'AWS',
+ aws_access_key_id: 'id',
+ aws_secret_access_key: 'secret'
+ },
+ remote_directory: 'directory',
+ multipart_chunk_size: 104857600,
+ encryption: nil,
+ storage_class: nil
+ }
+ )
+
+ # the Fog mock only knows about directories we create explicitly
+ Fog.mock!
+ connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys)
+ connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
+ end
+
+ context 'target path' do
+ it 'uses the tar filename by default' do
+ expect_any_instance_of(Fog::Collection).to receive(:create)
+ .with(hash_including(key: backup_filename))
+ .and_return(true)
+
+ Dir.chdir(Gitlab.config.backup.path) do
+ subject.upload
+ end
+ end
+
+ it 'adds the DIRECTORY environment variable if present' do
+ stub_env('DIRECTORY', 'daily')
+
+ expect_any_instance_of(Fog::Collection).to receive(:create)
+ .with(hash_including(key: "daily/#{backup_filename}"))
+ .and_return(true)
+
+ Dir.chdir(Gitlab.config.backup.path) do
+ subject.upload
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/backup/repository_spec.rb b/spec/lib/gitlab/backup/repository_spec.rb
index 51c1e9d657b..db860b01ba4 100644
--- a/spec/lib/gitlab/backup/repository_spec.rb
+++ b/spec/lib/gitlab/backup/repository_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Backup::Repository, lib: true do
+describe Backup::Repository do
let(:progress) { StringIO.new }
let!(:project) { create(:empty_project) }
diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index 9df96ea04eb..d537ce8803c 100644
--- a/spec/lib/gitlab/badge/build/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
-describe Gitlab::Badge::Build::Metadata do
+describe Gitlab::Badge::Pipeline::Metadata do
let(:badge) { double(project: create(:empty_project), ref: 'feature') }
let(:metadata) { described_class.new(badge) }
@@ -9,13 +9,13 @@ describe Gitlab::Badge::Build::Metadata do
describe '#title' do
it 'returns build status title' do
- expect(metadata.title).to eq 'build status'
+ expect(metadata.title).to eq 'pipeline status'
end
end
describe '#image_url' do
it 'returns valid url' do
- expect(metadata.image_url).to include 'badges/feature/build.svg'
+ expect(metadata.image_url).to include 'badges/feature/pipeline.svg'
end
end
diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index 6abf4ca46a9..dc835375c66 100644
--- a/spec/lib/gitlab/badge/build/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -1,36 +1,35 @@
require 'spec_helper'
-describe Gitlab::Badge::Build::Status do
+describe Gitlab::Badge::Pipeline::Status do
let(:project) { create(:project, :repository) }
let(:sha) { project.commit.sha }
let(:branch) { 'master' }
let(:badge) { described_class.new(project, branch) }
describe '#entity' do
- it 'always says build' do
- expect(badge.entity).to eq 'build'
+ it 'always says pipeline' do
+ expect(badge.entity).to eq 'pipeline'
end
end
describe '#template' do
it 'returns badge template' do
- expect(badge.template.key_text).to eq 'build'
+ expect(badge.template.key_text).to eq 'pipeline'
end
end
describe '#metadata' do
it 'returns badge metadata' do
- expect(badge.metadata.image_url)
- .to include 'badges/master/build.svg'
+ expect(badge.metadata.image_url).to include 'badges/master/pipeline.svg'
end
end
- context 'build exists' do
- let!(:build) { create_build(project, sha, branch) }
+ context 'pipeline exists' do
+ let!(:pipeline) { create_pipeline(project, sha, branch) }
- context 'build success' do
+ context 'pipeline success' do
before do
- build.success!
+ pipeline.success!
end
describe '#status' do
@@ -40,9 +39,9 @@ describe Gitlab::Badge::Build::Status do
end
end
- context 'build failed' do
+ context 'pipeline failed' do
before do
- build.drop!
+ pipeline.drop!
end
describe '#status' do
@@ -54,10 +53,10 @@ describe Gitlab::Badge::Build::Status do
context 'when outdated pipeline for given ref exists' do
before do
- build.success!
+ pipeline.success!
- old_build = create_build(project, '11eeffdd', branch)
- old_build.drop!
+ old_pipeline = create_pipeline(project, '11eeffdd', branch)
+ old_pipeline.drop!
end
it 'does not take outdated pipeline into account' do
@@ -67,10 +66,10 @@ describe Gitlab::Badge::Build::Status do
context 'when multiple pipelines exist for given sha' do
before do
- build.drop!
+ pipeline.drop!
- new_build = create_build(project, sha, branch)
- new_build.success!
+ new_pipeline = create_pipeline(project, sha, branch)
+ new_pipeline.success!
end
it 'does not take outdated pipeline into account' do
@@ -87,7 +86,7 @@ describe Gitlab::Badge::Build::Status do
end
end
- def create_build(project, sha, branch)
+ def create_pipeline(project, sha, branch)
pipeline = create(:ci_empty_pipeline,
project: project,
sha: sha,
diff --git a/spec/lib/gitlab/badge/build/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index a7e21fb8bb1..20fa4f879c3 100644
--- a/spec/lib/gitlab/badge/build/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -1,28 +1,28 @@
require 'spec_helper'
-describe Gitlab::Badge::Build::Template do
- let(:badge) { double(entity: 'build', status: 'success') }
+describe Gitlab::Badge::Pipeline::Template do
+ let(:badge) { double(entity: 'pipeline', status: 'success') }
let(:template) { described_class.new(badge) }
describe '#key_text' do
- it 'is always says build' do
- expect(template.key_text).to eq 'build'
+ it 'is always says pipeline' do
+ expect(template.key_text).to eq 'pipeline'
end
end
describe '#value_text' do
it 'is status value' do
- expect(template.value_text).to eq 'success'
+ expect(template.value_text).to eq 'passed'
end
end
describe 'widths and text anchors' do
it 'has fixed width and text anchors' do
- expect(template.width).to eq 92
- expect(template.key_width).to eq 38
+ expect(template.width).to eq 116
+ expect(template.key_width).to eq 62
expect(template.value_width).to eq 54
- expect(template.key_text_anchor).to eq 19
- expect(template.value_text_anchor).to eq 65
+ expect(template.key_text_anchor).to eq 31
+ expect(template.value_text_anchor).to eq 89
end
end
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index d8beb05601c..df66a031fec 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::Importer, lib: true do
+describe Gitlab::BitbucketImport::Importer do
include ImportSpecHelper
before do
@@ -58,7 +58,7 @@ describe Gitlab::BitbucketImport::Importer, lib: true do
)
end
- let(:importer) { Gitlab::BitbucketImport::Importer.new(project) }
+ let(:importer) { described_class.new(project) }
let(:issues_statuses_sample_data) do
{
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 773d0d4d288..d7f7b26740d 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BitbucketImport::ProjectCreator, lib: true do
+describe Gitlab::BitbucketImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
@@ -27,7 +27,7 @@ describe Gitlab::BitbucketImport::ProjectCreator, lib: true do
it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job)
- project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, 'vim', namespace, user, access_params)
+ project_creator = described_class.new(repo, 'vim', namespace, user, access_params)
project = project_creator.execute
expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git")
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index 26b1baf75be..7cab04e9fc9 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Blame, lib: true do
+describe Gitlab::Blame do
let(:project) { create(:project, :repository) }
let(:path) { 'files/ruby/popen.rb' }
let(:commit) { project.commit('master') }
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 07db6c3a640..0daf41a7c86 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
+describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
let!(:project) { create(:project) }
let(:pipeline_status) { described_class.new(project) }
let(:cache_key) { "projects/#{project.id}/pipeline_status" }
@@ -28,8 +28,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
expect(project.instance_variable_get('@pipeline_status')).to be_a(described_class)
end
- describe 'without a status in redis' do
- it 'loads the status from a commit when it was not in redis' do
+ describe 'without a status in redis_cache' do
+ it 'loads the status from a commit when it was not in redis_cache' do
empty_status = { sha: nil, status: nil, ref: nil }
fake_pipeline = described_class.new(
project_without_status,
@@ -48,9 +48,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
described_class.load_in_batch_for_projects([project_without_status])
end
- it 'only connects to redis twice' do
+ it 'only connects to redis_cache twice' do
# Once to load, once to store in the cache
- expect(Gitlab::Redis).to receive(:with).exactly(2).and_call_original
+ expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original
described_class.load_in_batch_for_projects([project_without_status])
@@ -58,9 +58,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
end
- describe 'when a status was cached in redis' do
+ describe 'when a status was cached in redis_cache' do
before do
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.mapped_hmset(cache_key,
{ sha: sha, status: status, ref: ref })
end
@@ -76,8 +76,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
expect(pipeline_status.ref).to eq(ref)
end
- it 'only connects to redis once' do
- expect(Gitlab::Redis).to receive(:with).exactly(1).and_call_original
+ it 'only connects to redis_cache once' do
+ expect(Gitlab::Redis::Cache).to receive(:with).exactly(1).and_call_original
described_class.load_in_batch_for_projects([project])
@@ -94,8 +94,8 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
describe '.cached_results_for_projects' do
- it 'loads a status from redis for all projects' do
- Gitlab::Redis.with do |redis|
+ it 'loads a status from caching for all projects' do
+ Gitlab::Redis::Cache.with do |redis|
redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
end
@@ -183,7 +183,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
end
- describe "#load_from_project" do
+ describe "#load_from_project", :clean_gitlab_redis_cache do
let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.sha) }
it 'reads the status from the pipeline for the commit' do
@@ -203,40 +203,40 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
end
- describe "#store_in_cache", :redis do
- it "sets the object in redis" do
+ describe "#store_in_cache", :clean_gitlab_redis_cache do
+ it "sets the object in caching" do
pipeline_status.sha = '123456'
pipeline_status.status = 'failed'
pipeline_status.store_in_cache
- read_sha, read_status = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status) }
+ read_sha, read_status = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status) }
expect(read_sha).to eq('123456')
expect(read_status).to eq('failed')
end
end
- describe '#store_in_cache_if_needed', :redis do
+ describe '#store_in_cache_if_needed', :clean_gitlab_redis_cache do
it 'stores the state in the cache when the sha is the HEAD of the project' do
create(:ci_pipeline, :success, project: project, sha: project.commit.sha)
pipeline_status = described_class.load_for_project(project)
pipeline_status.store_in_cache_if_needed
- sha, status, ref = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status, :ref) }
+ sha, status, ref = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status, :ref) }
expect(sha).not_to be_nil
expect(status).not_to be_nil
expect(ref).not_to be_nil
end
- it "doesn't store the status in redis when the sha is not the head of the project" do
+ it "doesn't store the status in redis_cache when the sha is not the head of the project" do
other_status = described_class.new(
project,
pipeline_info: { sha: "123456", status: "failed" }
)
other_status.store_in_cache_if_needed
- sha, status = Gitlab::Redis.with { |redis| redis.hmget(cache_key, :sha, :status) }
+ sha, status = Gitlab::Redis::Cache.with { |redis| redis.hmget(cache_key, :sha, :status) }
expect(sha).to be_nil
expect(status).to be_nil
@@ -244,7 +244,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
it "deletes the cache if the repository doesn't have a head commit" do
empty_project = create(:empty_project)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.mapped_hmset(cache_key,
{ sha: 'sha', status: 'pending', ref: 'master' })
end
@@ -255,7 +255,7 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
})
other_status.store_in_cache_if_needed
- sha, status, ref = Gitlab::Redis.with { |redis| redis.hmget("projects/#{empty_project.id}/pipeline_status", :sha, :status, :ref) }
+ sha, status, ref = Gitlab::Redis::Cache.with { |redis| redis.hmget("projects/#{empty_project.id}/pipeline_status", :sha, :status, :ref) }
expect(sha).to be_nil
expect(status).to be_nil
@@ -263,20 +263,20 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
end
- describe "with a status in redis", :redis do
+ describe "with a status in caching", :clean_gitlab_redis_cache do
let(:status) { 'success' }
let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' }
let(:ref) { 'master' }
before do
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Cache.with do |redis|
redis.mapped_hmset(cache_key,
{ sha: sha, status: status, ref: ref })
end
end
describe '#load_from_cache' do
- it 'reads the status from redis' do
+ it 'reads the status from redis_cache' do
pipeline_status.load_from_cache
expect(pipeline_status.sha).to eq(sha)
@@ -292,10 +292,10 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :redis do
end
describe '#delete_from_cache' do
- it 'deletes values from redis' do
+ it 'deletes values from redis_cache' do
pipeline_status.delete_from_cache
- key_exists = Gitlab::Redis.with { |redis| redis.exists(cache_key) }
+ key_exists = Gitlab::Redis::Cache.with { |redis| redis.exists(cache_key) }
expect(key_exists).to be_falsy
end
diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb
new file mode 100644
index 00000000000..5b82c216a13
--- /dev/null
+++ b/spec/lib/gitlab/cache/request_cache_spec.rb
@@ -0,0 +1,133 @@
+require 'spec_helper'
+
+describe Gitlab::Cache::RequestCache do
+ let(:klass) do
+ Class.new do
+ extend Gitlab::Cache::RequestCache
+
+ attr_accessor :id, :name, :result, :extra
+
+ def self.name
+ 'ExpensiveAlgorithm'
+ end
+
+ def initialize(id, name, result, extra = nil)
+ self.id = id
+ self.name = name
+ self.result = result
+ self.extra = nil
+ end
+
+ request_cache def compute(arg)
+ result << arg
+ end
+
+ request_cache def repute(arg)
+ result << arg
+ end
+
+ def dispute(arg)
+ result << arg
+ end
+ request_cache(:dispute) { extra }
+ end
+ end
+
+ let(:algorithm) { klass.new('id', 'name', []) }
+
+ shared_examples 'cache for the same instance' do
+ it 'does not compute twice for the same argument' do
+ algorithm.compute(true)
+ result = algorithm.compute(true)
+
+ expect(result).to eq([true])
+ end
+
+ it 'computes twice for the different argument' do
+ algorithm.compute(true)
+ result = algorithm.compute(false)
+
+ expect(result).to eq([true, false])
+ end
+
+ it 'computes twice for the different class name' do
+ algorithm.compute(true)
+ allow(klass).to receive(:name).and_return('CheapAlgo')
+ result = algorithm.compute(true)
+
+ expect(result).to eq([true, true])
+ end
+
+ it 'computes twice for the different method' do
+ algorithm.compute(true)
+ result = algorithm.repute(true)
+
+ expect(result).to eq([true, true])
+ end
+
+ context 'when request_cache_key is provided' do
+ before do
+ klass.request_cache_key do
+ [id, name]
+ end
+ end
+
+ it 'computes twice for the different keys, id' do
+ algorithm.compute(true)
+ algorithm.id = 'ad'
+ result = algorithm.compute(true)
+
+ expect(result).to eq([true, true])
+ end
+
+ it 'computes twice for the different keys, name' do
+ algorithm.compute(true)
+ algorithm.name = 'same'
+ result = algorithm.compute(true)
+
+ expect(result).to eq([true, true])
+ end
+
+ it 'uses extra method cache key if provided' do
+ algorithm.dispute(true) # miss
+ algorithm.extra = true
+ algorithm.dispute(true) # miss
+ result = algorithm.dispute(true) # hit
+
+ expect(result).to eq([true, true])
+ end
+ end
+ end
+
+ context 'when RequestStore is active', :request_store do
+ it_behaves_like 'cache for the same instance'
+
+ it 'computes once for different instances when keys are the same' do
+ algorithm.compute(true)
+ result = klass.new('id', 'name', algorithm.result).compute(true)
+
+ expect(result).to eq([true])
+ end
+
+ it 'computes twice if RequestStore starts over' do
+ algorithm.compute(true)
+ RequestStore.end!
+ RequestStore.clear!
+ RequestStore.begin!
+ result = algorithm.compute(true)
+
+ expect(result).to eq([true, true])
+ end
+ end
+
+ context 'when RequestStore is inactive' do
+ it_behaves_like 'cache for the same instance'
+
+ it 'computes twice for different instances even if keys are the same' do
+ algorithm.compute(true)
+ result = klass.new('id', 'name', algorithm.result).compute(true)
+
+ expect(result).to eq([true, true])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index 8c1e6efa9db..1e9fb9077fc 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ChatNameToken, lib: true do
+describe Gitlab::ChatNameToken do
context 'when using unknown token' do
let(:token) { }
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 643e590438a..6c25b7349e1 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Checks::ChangeAccess, lib: true do
+describe Gitlab::Checks::ChangeAccess do
describe '#exec' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index bc66ce83d4a..6c4cfa1203e 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Checks::ForcePush, lib: true do
+describe Gitlab::Checks::ForcePush do
let(:project) { create(:project, :repository) }
context "exit code checking" do
diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index 49457b129e3..5a21282712a 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -1,21 +1,50 @@
require 'spec_helper'
describe Gitlab::Ci::Build::Step do
- let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
-
describe '#from_commands' do
- subject { described_class.from_commands(job) }
-
- it 'fabricates an object' do
- expect(subject.name).to eq(:script)
- expect(subject.script).to eq(['ls -la', 'date'])
- expect(subject.timeout).to eq(job.timeout)
- expect(subject.when).to eq('on_success')
- expect(subject.allow_failure).to be_falsey
+ shared_examples 'has correct script' do
+ subject { described_class.from_commands(job) }
+
+ it 'fabricates an object' do
+ expect(subject.name).to eq(:script)
+ expect(subject.script).to eq(script)
+ expect(subject.timeout).to eq(job.timeout)
+ expect(subject.when).to eq('on_success')
+ expect(subject.allow_failure).to be_falsey
+ end
+ end
+
+ context 'when commands are specified' do
+ it_behaves_like 'has correct script' do
+ let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
+ let(:script) { ['ls -la', 'date'] }
+ end
+ end
+
+ context 'when script option is specified' do
+ it_behaves_like 'has correct script' do
+ let(:job) { create(:ci_build, :no_options, options: { script: ["ls -la\necho aaa", "date"] }) }
+ let(:script) { ["ls -la\necho aaa", 'date'] }
+ end
+ end
+
+ context 'when before and script option is specified' do
+ it_behaves_like 'has correct script' do
+ let(:job) do
+ create(:ci_build, options: {
+ before_script: ["ls -la\necho aaa"],
+ script: ["date"]
+ })
+ end
+
+ let(:script) { ["ls -la\necho aaa", 'date'] }
+ end
end
end
describe '#from_after_script' do
+ let(:job) { create(:ci_build) }
+
subject { described_class.from_after_script(job) }
context 'when after_script is empty' do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 878b1d6b862..8f711e02f9b 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Cache do
- let(:entry) { described_class.new(config) }
+ subject(:entry) { described_class.new(config) }
describe 'validations' do
before do
@@ -9,22 +9,44 @@ describe Gitlab::Ci::Config::Entry::Cache do
end
context 'when entry config value is correct' do
+ let(:policy) { nil }
+
let(:config) do
{ key: 'some key',
untracked: true,
- paths: ['some/path/'] }
+ paths: ['some/path/'],
+ policy: policy }
end
describe '#value' do
it 'returns hash value' do
- expect(entry.value).to eq config
+ expect(entry.value).to eq(key: 'some key', untracked: true, paths: ['some/path/'], policy: 'pull-push')
end
end
describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
+ it { is_expected.to be_valid }
+ end
+
+ context 'policy is pull-push' do
+ let(:policy) { 'pull-push' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'pull-push') }
+ end
+
+ context 'policy is push' do
+ let(:policy) { 'push' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'push') }
+ end
+
+ context 'policy is pull' do
+ let(:policy) { 'pull' }
+
+ it { is_expected.to be_valid }
+ it { expect(entry.value).to include(policy: 'pull') }
end
context 'when key is missing' do
@@ -44,12 +66,20 @@ describe Gitlab::Ci::Config::Entry::Cache do
context 'when entry value is not correct' do
describe '#errors' do
+ subject { entry.errors }
context 'when is not a hash' do
let(:config) { 'ls' }
it 'reports errors with config value' do
- expect(entry.errors)
- .to include 'cache config should be a hash'
+ is_expected.to include 'cache config should be a hash'
+ end
+ end
+
+ context 'when policy is unknown' do
+ let(:config) { { policy: "unknown" } }
+
+ it 'reports error' do
+ is_expected.to include('cache policy should be pull-push, push, or pull')
end
end
@@ -57,8 +87,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
let(:config) { { key: 1 } }
it 'reports error with descendants' do
- expect(entry.errors)
- .to include 'key config should be a string or symbol'
+ is_expected.to include 'key config should be a string or symbol'
end
end
@@ -66,8 +95,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
let(:config) { { invalid: true } }
it 'reports error with descendants' do
- expect(entry.errors)
- .to include 'cache config contains unknown keys: invalid'
+ is_expected.to include 'cache config contains unknown keys: invalid'
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 293f112b2b0..1860ed79bfd 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -143,7 +143,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#cache_value' do
it 'returns cache configuration' do
expect(global.cache_value)
- .to eq(key: 'k', untracked: true, paths: ['public/'])
+ .to eq(key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push')
end
end
@@ -157,7 +157,7 @@ describe Gitlab::Ci::Config::Entry::Global do
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'] },
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'] },
@@ -168,7 +168,7 @@ describe Gitlab::Ci::Config::Entry::Global do
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
- cache: { key: 'k', untracked: true, paths: ['public/'] },
+ cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' },
variables: {},
ignore: false,
after_script: ['make clean'] }
@@ -212,7 +212,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#cache_value' do
it 'returns correct cache definition' do
- expect(global.cache_value).to eq(key: 'a')
+ expect(global.cache_value).to eq(key: 'a', policy: 'pull-push')
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 92cba689f47..6769f64f950 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -80,6 +80,45 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include "job script can't be blank"
end
end
+
+ context 'when retry value is not correct' do
+ context 'when it is not a numeric value' do
+ let(:config) { { retry: true } }
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry is not a number'
+ end
+ end
+
+ context 'when it is lower than zero' do
+ let(:config) { { retry: -1 } }
+
+ it 'returns error about value too low' do
+ expect(entry).not_to be_valid
+ expect(entry.errors)
+ .to include 'job retry must be greater than or equal to 0'
+ end
+ end
+
+ context 'when it is not an integer' do
+ let(:config) { { retry: 1.5 } }
+
+ it 'returns error about wrong value' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry must be an integer'
+ end
+ end
+
+ context 'when the value is too high' do
+ let(:config) { { retry: 10 } }
+
+ it 'returns error about value too high' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job retry must be less than or equal to 2'
+ end
+ end
+ end
end
end
@@ -109,7 +148,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'overrides global config' do
expect(entry[:image].value).to eq(name: 'some_image')
- expect(entry[:cache].value).to eq(key: 'test')
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
end
end
@@ -123,7 +162,7 @@ describe Gitlab::Ci::Config::Entry::Job do
it 'uses config from global entry' do
expect(entry[:image].value).to eq 'specified'
- expect(entry[:cache].value).to eq(key: 'test')
+ expect(entry[:cache].value).to eq(key: 'test', policy: 'pull-push')
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 114d2490490..5a7a42d84c0 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -48,7 +48,9 @@ describe Gitlab::Ci::Status::Build::Cancelable do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index c8a97016f20..8768302eda1 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -7,7 +7,9 @@ describe Gitlab::Ci::Status::Build::Factory do
let(:factory) { described_class.new(build, user) }
before do
- project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ project.add_developer(user)
end
context 'when build is successful' do
@@ -225,19 +227,19 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when user has ability to play action' do
- before do
- project.add_developer(user)
-
- create(:protected_branch, :developers_can_merge,
- name: build.ref, project: project)
- end
-
it 'fabricates status that has action' do
expect(status).to have_action
end
end
context 'when user does not have ability to play action' do
+ before do
+ allow(build.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: build.project)
+ end
+
it 'fabricates status that has no action' do
expect(status).not_to have_action
end
@@ -262,6 +264,13 @@ describe Gitlab::Ci::Status::Build::Factory do
end
context 'when user is not allowed to execute manual action' do
+ before do
+ allow(build.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: build.ref, project: build.project)
+ end
+
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 099d873fc01..21026f2c968 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -48,7 +48,9 @@ describe Gitlab::Ci::Status::Build::Retryable do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 23902f26b1a..e0425103f41 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -20,7 +20,9 @@ describe Gitlab::Ci::Status::Build::Stop do
describe '#has_action?' do
context 'when user is allowed to update build' do
before do
- build.project.team << [user, :developer]
+ stub_not_protect_default_branch
+
+ build.project.add_developer(user)
end
it { is_expected.to have_action }
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index bbb3f9912a3..ebe5af56160 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -293,5 +293,55 @@ describe Gitlab::Ci::Trace::Stream do
it { is_expected.to eq("65") }
end
+
+ context 'malicious regexp' do
+ let(:data) { malicious_text }
+ let(:regex) { malicious_regexp }
+
+ include_examples 'malicious regexp'
+ end
+
+ context 'multi-line data with rooted regexp' do
+ let(:data) { "\n65%\n" }
+ let(:regex) { '^(\d+)\%$' }
+
+ it { is_expected.to eq('65') }
+ end
+
+ context 'long line' do
+ let(:data) { 'a' * 80000 + '100%' + 'a' * 80000 }
+ let(:regex) { '\d+\%' }
+
+ it { is_expected.to eq('100') }
+ end
+
+ context 'many lines' do
+ let(:data) { "foo\n" * 80000 + "100%\n" + "foo\n" * 80000 }
+ let(:regex) { '\d+\%' }
+
+ it { is_expected.to eq('100') }
+ end
+
+ context 'empty regex' do
+ let(:data) { 'foo' }
+ let(:regex) { '' }
+
+ it 'skips processing' do
+ expect(stream).not_to receive(:read)
+
+ is_expected.to be_nil
+ end
+ end
+
+ context 'nil regex' do
+ let(:data) { 'foo' }
+ let(:regex) { nil }
+
+ it 'skips processing' do
+ expect(stream).not_to receive(:read)
+
+ is_expected.to be_nil
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
index eaf8f1d0f1c..75b90e76083 100644
--- a/spec/lib/gitlab/ci_access_spec.rb
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::CiAccess, lib: true do
- let(:access) { Gitlab::CiAccess.new }
+describe Gitlab::CiAccess do
+ let(:access) { described_class.new }
describe '#can_do_action?' do
context 'when action is :build_download_code' do
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index ca68010cb89..8ff6125ada1 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ClosingIssueExtractor, lib: true do
+describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:empty_project) }
let(:project2) { create(:empty_project) }
let(:forked_project) { Projects::ForkService.new(project, project.creator).execute }
@@ -276,7 +276,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
context "with a cross-project URL" do
it do
- message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}"
+ message = "Closes #{urls.project_issue_url(issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([issue2])
end
end
@@ -292,7 +292,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
context "with an invalid URL" do
it do
- message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
+ message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([])
end
end
@@ -347,14 +347,14 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end
it "fetches cross-project URL references" do
- message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+ message = "Closes #{urls.project_issue_url(issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message))
.to match_array([issue, issue2])
end
it "ignores invalid cross-project URL references" do
- message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}"
+ message = "Closes https://google.com#{urls.project_issue_path(issue2.project, issue2)} and #{reference}"
expect(subject.closed_by_message(message))
.to match_array([issue])
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index 0a1ec66f199..c7be45dbcd3 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ColorSchemes, lib: true do
+describe Gitlab::ColorSchemes do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index 27f23ea70dc..a4d7628b03a 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Conflict::FileCollection, lib: true do
+describe Gitlab::Conflict::FileCollection do
let(:merge_request) { create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start') }
let(:file_collection) { described_class.read_only(merge_request) }
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 585eeb77bd5..5356e9742b4 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Conflict::File, lib: true do
+describe Gitlab::Conflict::File do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:rugged) { repository.rugged }
@@ -10,7 +10,7 @@ describe Gitlab::Conflict::File, lib: true do
let(:index) { rugged.merge_commits(our_commit, their_commit) }
let(:conflict) { index.conflicts.last }
let(:merge_file_result) { index.merge_file('files/ruby/regex.rb') }
- let(:conflict_file) { Gitlab::Conflict::File.new(merge_file_result, conflict, merge_request: merge_request) }
+ let(:conflict_file) { described_class.new(merge_file_result, conflict, merge_request: merge_request) }
describe '#resolve_lines' do
let(:section_keys) { conflict_file.sections.map { |section| section[:id] }.compact }
@@ -220,7 +220,7 @@ end
FILE
end
- let(:conflict_file) { Gitlab::Conflict::File.new({ data: file }, conflict, merge_request: merge_request) }
+ let(:conflict_file) { described_class.new({ data: file }, conflict, merge_request: merge_request) }
let(:sections) { conflict_file.sections }
it 'sets the correct match line headers' do
diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb
index aed57b75789..fce606a2bb5 100644
--- a/spec/lib/gitlab/conflict/parser_spec.rb
+++ b/spec/lib/gitlab/conflict/parser_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::Conflict::Parser, lib: true do
- let(:parser) { Gitlab::Conflict::Parser.new }
+describe Gitlab::Conflict::Parser do
+ let(:parser) { described_class.new }
describe '#parse' do
def parse_text(text)
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index a566f24f6a6..d57ffcae8e1 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -27,10 +27,23 @@ describe Gitlab::CurrentSettings do
end
it 'falls back to DB if Redis fails' do
+ db_settings = ApplicationSetting.create!(ApplicationSetting.defaults)
+
expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
- expect(ApplicationSetting).to receive(:last).and_call_original
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
- expect(current_application_settings).to be_a(ApplicationSetting)
+ expect(current_application_settings).to eq(db_settings)
+ end
+
+ it 'creates default ApplicationSettings if none are present' do
+ expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError)
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError)
+
+ settings = current_application_settings
+
+ expect(settings).to be_a(ApplicationSetting)
+ expect(settings).to be_persisted
+ expect(settings).to have_attributes(ApplicationSetting.defaults)
end
context 'with migrations pending' do
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index 3dd76ba5b8a..592448aef96 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::CycleAnalytics::StageSummary, models: true do
+describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) }
let(:from) { 1.day.ago }
let(:user) { create(:user, :admin) }
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 04ec34492e1..6415e4083d6 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Note, lib: true do
+describe Gitlab::DataBuilder::Note do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:data) { described_class.build(note, user) }
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 73936969832..cb430b47463 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::DataBuilder::Push, lib: true do
+describe Gitlab::DataBuilder::Push do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
new file mode 100644
index 00000000000..a776d888c47
--- /dev/null
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::DataBuilder::WikiPage do
+ let(:project) { create(:project, :repository) }
+ let(:wiki_page) { create(:wiki_page, wiki: project.wiki) }
+ let(:user) { create(:user) }
+
+ describe '.build' do
+ let(:data) { described_class.build(wiki_page, user, 'create') }
+
+ it { expect(data).to be_a(Hash) }
+ it { expect(data[:object_kind]).to eq('wiki_page') }
+ it { expect(data[:user]).to eq(user.hook_attrs) }
+ it { expect(data[:project]).to eq(project.hook_attrs) }
+ it { expect(data[:wiki]).to eq(project.wiki.hook_attrs) }
+
+ it { expect(data[:object_attributes]).to include(wiki_page.hook_attrs) }
+ it { expect(data[:object_attributes]).to include(url: Gitlab::UrlBuilder.build(wiki_page)) }
+ it { expect(data[:object_attributes]).to include(action: 'create') }
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 4259be3f522..d3dbd82e8ba 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Gitlab::Database::MigrationHelpers, lib: true do
+describe Gitlab::Database::MigrationHelpers do
let(:model) do
ActiveRecord::Migration.new.extend(
- Gitlab::Database::MigrationHelpers
+ described_class
)
end
@@ -174,13 +174,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
allow(Gitlab::Database).to receive(:mysql?).and_return(false)
end
- it 'creates a concurrent foreign key' do
+ it 'creates a concurrent foreign key and validates it' do
expect(model).to receive(:disable_statement_timeout)
expect(model).to receive(:execute).ordered.with(/NOT VALID/)
expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end
+
+ it 'appends a valid ON DELETE statement' do
+ expect(model).to receive(:disable_statement_timeout)
+ expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_delete: :nullify)
+ end
end
end
end
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 8813f129ef5..df7d1b5d27a 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -236,7 +236,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
subject.track_rename('namespace', 'path/to/namespace', 'path/to/renamed')
old_path, new_path = [nil, nil]
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
rename_info = redis.lpop(key)
old_path, new_path = JSON.parse(rename_info)
end
@@ -268,7 +268,7 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :trunca
key = 'rename:FakeRenameReservedPathMigrationV1:project'
stored_renames = nil
rename_count = 0
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
stored_renames = redis.lrange(key, 0, 1)
rename_count = redis.llen(key)
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index cbf6c35356e..c5f9aecd867 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database, lib: true do
+describe Gitlab::Database do
before do
stub_const('MigrationTest', Class.new { include Gitlab::Database })
end
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index df77f4037af..3a93d5e1e97 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::CartfileLinker, lib: true do
+describe Gitlab::DependencyLinker::CartfileLinker do
describe '.support?' do
it 'supports Cartfile' do
expect(described_class.support?('Cartfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index d7a926e800f..4d222564fd0 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::ComposerJsonLinker, lib: true do
+describe Gitlab::DependencyLinker::ComposerJsonLinker do
describe '.support?' do
it 'supports composer.json' do
expect(described_class.support?('composer.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index 3f8335f03ea..a97803b119e 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
+describe Gitlab::DependencyLinker::GemfileLinker do
describe '.support?' do
it 'supports Gemfile' do
expect(described_class.support?('Gemfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index d4a71403939..24ad7d12f4c 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::GemspecLinker, lib: true do
+describe Gitlab::DependencyLinker::GemspecLinker do
describe '.support?' do
it 'supports *.gemspec' do
expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index e279e0c9019..ae5ad39ad11 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::GodepsJsonLinker, lib: true do
+describe Gitlab::DependencyLinker::GodepsJsonLinker do
describe '.support?' do
it 'supports Godeps.json' do
expect(described_class.support?('Godeps.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 8c979ae1869..1e8b72afb7b 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::PackageJsonLinker, lib: true do
+describe Gitlab::DependencyLinker::PackageJsonLinker do
describe '.support?' do
it 'supports package.json' do
expect(described_class.support?('package.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 06007cf97f7..cdfd7ad9826 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::PodfileLinker, lib: true do
+describe Gitlab::DependencyLinker::PodfileLinker do
describe '.support?' do
it 'supports Podfile' do
expect(described_class.support?('Podfile')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index d722865264b..d4a398c5948 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::PodspecJsonLinker, lib: true do
+describe Gitlab::DependencyLinker::PodspecJsonLinker do
describe '.support?' do
it 'supports *.podspec.json' do
expect(described_class.support?('Reachability.podspec.json')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index dfc366b5817..ed60ab45955 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::PodspecLinker, lib: true do
+describe Gitlab::DependencyLinker::PodspecLinker do
describe '.support?' do
it 'supports *.podspec' do
expect(described_class.support?('Reachability.podspec')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 64b233f3e68..ef952b3abd5 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
+describe Gitlab::DependencyLinker::RequirementsTxtLinker do
describe '.support?' do
it 'supports requirements.txt' do
expect(described_class.support?('requirements.txt')).to be_truthy
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 3d1cfbcfbf7..10d2f701298 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab::DependencyLinker, lib: true do
+describe Gitlab::DependencyLinker do
describe '.link' do
it 'links using GemfileLinker' do
blob_name = 'Gemfile'
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index a8173558c00..c73708d90a8 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::DiffRefs, lib: true do
+describe Gitlab::Diff::DiffRefs do
let(:project) { create(:project, :repository) }
describe '#compare_in' do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index f289131cc3a..cd2fa98b14c 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::File, lib: true do
+describe Gitlab::Diff::File do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 7d7d4a55e63..cd602ccab8e 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::Highlight, lib: true do
+describe Gitlab::Diff::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
@@ -10,7 +10,7 @@ describe Gitlab::Diff::Highlight, lib: true do
describe '#highlight' do
context "with a diff file" do
- let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
+ let(:subject) { described_class.new(diff_file, repository: project.repository).highlight }
it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
@@ -41,7 +41,7 @@ describe Gitlab::Diff::Highlight, lib: true do
end
context "with diff lines" do
- let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
+ let(:subject) { described_class.new(diff_file.diff_lines, repository: project.repository).highlight }
it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
index d6e8b8ac4b2..046b096e366 100644
--- a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarkdownMarker, lib: true do
+describe Gitlab::Diff::InlineDiffMarkdownMarker do
describe '#mark' do
let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 95da344802d..c3bf34c24ae 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiffMarker, lib: true do
+describe Gitlab::Diff::InlineDiffMarker do
describe '#mark' do
context "when the rich text is html safe" do
let(:raw) { "abc 'def'" }
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 8ca3f73509e..15451c2cf99 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::InlineDiff, lib: true do
+describe Gitlab::Diff::InlineDiff do
describe '.for_lines' do
let(:diff) do
<<-EOF.strip_heredoc
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 2c7ecd1907e..42750bf9ea1 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::LineMapper, lib: true do
+describe Gitlab::Diff::LineMapper do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index 0f779339c54..e9fc7be366a 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::ParallelDiff, lib: true do
+describe Gitlab::Diff::ParallelDiff do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index e76128ecd87..c71568e2a65 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe Gitlab::Diff::Parser, lib: true do
+describe Gitlab::Diff::Parser do
include RepoHelpers
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.raw_diffs.first }
- let(:parser) { Gitlab::Diff::Parser.new }
+ let(:parser) { described_class.new }
describe '#parse' do
let(:diff) do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index b3d46e69ccb..d4a2a852c12 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::Position, lib: true do
+describe Gitlab::Diff::Position do
include RepoHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 93d30b90937..8beebc10040 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Diff::PositionTracer, lib: true do
+describe Gitlab::Diff::PositionTracer do
# Douwe's diary New York City, 2016-06-28
# --------------------------------------------------------------------------
#
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 08b2577ecc4..f61dbc67ad1 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe Gitlab::Email::AttachmentUploader, lib: true do
+describe Gitlab::Email::AttachmentUploader do
describe "#execute" do
let(:project) { build(:project) }
let(:message_raw) { fixture_file("emails/attachment.eml") }
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 4a9c9a7fe34..bd36d1d309d 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative '../email_shared_blocks'
-describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
+describe Gitlab::Email::Handler::CreateIssueHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
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 cd0309e248d..0127b012c91 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative '../email_shared_blocks'
-describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
+describe Gitlab::Email::Handler::CreateNoteHandler do
include_context :email_shared_context
it_behaves_like :reply_processing_shared_examples
diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
index 0939e6c4514..66c38498e4e 100644
--- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative '../email_shared_blocks'
-describe Gitlab::Email::Handler::UnsubscribeHandler, lib: true do
+describe Gitlab::Email::Handler::UnsubscribeHandler do
include_context :email_shared_context
before do
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index c6e3524f743..88565ea5311 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require_relative 'email_shared_blocks'
-describe Gitlab::Email::Receiver, lib: true do
+describe Gitlab::Email::Receiver do
include_context :email_shared_context
context "when the email contains a valid email address in a Delivered-To header" do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 2ea5e6460a3..e21a998adfe 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
-describe Gitlab::Email::ReplyParser, lib: true do
+describe Gitlab::Email::ReplyParser do
describe '#execute' do
def test_parse_body(mail_string)
described_class.new(Mail::Message.new(mail_string)).execute
diff --git a/spec/lib/gitlab/exclusive_lease_spec.rb b/spec/lib/gitlab/exclusive_lease_spec.rb
index 81bbd70ffb8..c1ed47cf64a 100644
--- a/spec/lib/gitlab/exclusive_lease_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ExclusiveLease, type: :redis do
+describe Gitlab::ExclusiveLease, :clean_gitlab_redis_shared_state do
let(:unique_key) { SecureRandom.hex(10) }
describe '#try_obtain' do
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
index b793176d84a..34322c2a693 100644
--- a/spec/lib/gitlab/fake_application_settings_spec.rb
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -1,25 +1,25 @@
require 'spec_helper'
describe Gitlab::FakeApplicationSettings do
- let(:defaults) { { signin_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } }
+ let(:defaults) { { password_authentication_enabled: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } }
subject { described_class.new(defaults) }
it 'wraps OpenStruct variables properly' do
- expect(subject.signin_enabled).to be_falsey
+ expect(subject.password_authentication_enabled).to be_falsey
expect(subject.signup_enabled).to be_truthy
expect(subject.foobar).to eq('asdf')
end
it 'defines predicate methods' do
- expect(subject.signin_enabled?).to be_falsey
+ expect(subject.password_authentication_enabled?).to be_falsey
expect(subject.signup_enabled?).to be_truthy
end
it 'predicate method changes when value is updated' do
- subject.signin_enabled = true
+ subject.password_authentication_enabled = true
- expect(subject.signin_enabled?).to be_truthy
+ expect(subject.password_authentication_enabled?).to be_truthy
end
it 'does not define a predicate method' do
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 5a32ffd462c..3fb6315a39a 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::FileFinder, lib: true do
+describe Gitlab::FileFinder do
describe '#find' do
let(:project) { create(:project, :public, :repository) }
let(:finder) { described_class.new(project, project.default_branch) }
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
index 252cd4c55c7..dcd1a2d9813 100644
--- a/spec/lib/gitlab/fogbugz_import/client_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::FogbugzImport::Client, lib: true do
+describe Gitlab::FogbugzImport::Client do
let(:client) { described_class.new(uri: '', token: '') }
let(:one_user) { { 'people' => { 'person' => { "ixPerson" => "2", "sFullName" => "James" } } } }
let(:two_users) { { 'people' => { 'person' => [one_user, { "ixPerson" => "3" }] } } }
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 58d3ee6b488..3c784eda4f8 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -111,7 +111,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
- describe '.raw' do
+ shared_examples 'finding blobs by ID' do
let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) }
it { expect(raw_blob.data[0..10]).to eq("require \'fi") }
@@ -136,6 +136,16 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
+ describe '.raw' do
+ context 'when the blob_raw Gitaly feature is enabled' do
+ it_behaves_like 'finding blobs by ID'
+ end
+
+ context 'when the blob_raw Gitaly feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding blobs by ID'
+ end
+ end
+
describe 'encoding' do
context 'file with russian text' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "encoding/russian.rb") }
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index d1d7ed1d02a..cdf1b8beee3 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -7,51 +7,6 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { is_expected.to be_kind_of Array }
- describe 'initialize' do
- let(:commit_id) { 'f00' }
- let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') }
- let(:committer) do
- Gitaly::FindLocalBranchCommitAuthor.new(
- name: generate(:name),
- email: generate(:email),
- date: Google::Protobuf::Timestamp.new(seconds: 123)
- )
- end
- let(:author) do
- Gitaly::FindLocalBranchCommitAuthor.new(
- name: generate(:name),
- email: generate(:email),
- date: Google::Protobuf::Timestamp.new(seconds: 456)
- )
- end
- let(:gitaly_branch) do
- Gitaly::FindLocalBranchResponse.new(
- name: 'foo', commit_id: commit_id, commit_subject: commit_subject,
- commit_author: author, commit_committer: committer
- )
- end
- let(:attributes) do
- {
- id: commit_id,
- message: commit_subject,
- authored_date: Time.at(author.date.seconds),
- author_name: author.name,
- author_email: author.email,
- committed_date: Time.at(committer.date.seconds),
- committer_name: committer.name,
- committer_email: committer.email
- }
- end
- let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
-
- it 'parses Gitaly::FindLocalBranchResponse correctly' do
- expect(Gitlab::Git::Commit).to receive(:decorate)
- .with(hash_including(attributes)).and_call_original
-
- expect(branch.dereferenced_target.message).to be_utf8
- end
- end
-
describe '#size' do
subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index f20a14155dc..730fdb112d9 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -64,6 +64,52 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
+ describe "Commit info from gitaly commit" do
+ let(:id) { 'f00' }
+ let(:subject) { "My commit".force_encoding('ASCII-8BIT') }
+ let(:body) { subject + "My body".force_encoding('ASCII-8BIT') }
+ let(:committer) do
+ Gitaly::CommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 123)
+ )
+ end
+ let(:author) do
+ Gitaly::CommitAuthor.new(
+ name: generate(:name),
+ email: generate(:email),
+ date: Google::Protobuf::Timestamp.new(seconds: 456)
+ )
+ end
+ let(:gitaly_commit) do
+ Gitaly::GitCommit.new(
+ id: id,
+ subject: subject,
+ body: body,
+ author: author,
+ committer: committer
+ )
+ end
+ let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) }
+
+ it { expect(commit.short_id).to eq(id[0..10]) }
+ it { expect(commit.id).to eq(id) }
+ it { expect(commit.sha).to eq(id) }
+ it { expect(commit.safe_message).to eq(body) }
+ it { expect(commit.created_at).to eq(Time.at(committer.date.seconds)) }
+ it { expect(commit.author_email).to eq(author.email) }
+ it { expect(commit.author_name).to eq(author.name) }
+ it { expect(commit.committer_name).to eq(committer.name) }
+ it { expect(commit.committer_email).to eq(committer.email) }
+
+ context 'no body' do
+ let(:body) { "".force_encoding('ASCII-8BIT') }
+
+ it { expect(commit.safe_message).to eq(subject) }
+ end
+ end
+
context 'Class methods' do
describe '.find' do
it "should return first head commit if without params" do
@@ -244,69 +290,85 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '.find_all' do
- it 'should return a return a collection of commits' do
- commits = described_class.find_all(repository)
-
- expect(commits).not_to be_empty
- expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
- end
+ shared_examples 'finding all commits' do
+ it 'should return a return a collection of commits' do
+ commits = described_class.find_all(repository)
- context 'while applying a sort order based on the `order` option' do
- it "allows ordering topologically (no parents shown before their children)" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
-
- described_class.find_all(repository, order: :topo)
+ expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
end
- it "allows ordering by date" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
-
- described_class.find_all(repository, order: :date)
+ context 'max_count' do
+ subject do
+ commits = Gitlab::Git::Commit.find_all(
+ repository,
+ max_count: 50
+ )
+
+ commits.map(&:id)
+ end
+
+ it 'has 33 elements' do
+ expect(subject.size).to eq(33)
+ end
+
+ it 'includes the expected commits' do
+ expect(subject).to include(
+ SeedRepo::Commit::ID,
+ SeedRepo::Commit::PARENT_ID,
+ SeedRepo::FirstCommit::ID
+ )
+ end
end
- it "applies no sorting by default" do
- expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
-
- described_class.find_all(repository)
+ context 'ref + max_count + skip' do
+ subject do
+ commits = Gitlab::Git::Commit.find_all(
+ repository,
+ ref: 'master',
+ max_count: 50,
+ skip: 1
+ )
+
+ commits.map(&:id)
+ end
+
+ it 'has 24 elements' do
+ expect(subject.size).to eq(24)
+ end
+
+ it 'includes the expected commits' do
+ expect(subject).to include(SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID)
+ expect(subject).not_to include(SeedRepo::LastCommit::ID)
+ end
end
end
- context 'max_count' do
- subject do
- commits = Gitlab::Git::Commit.find_all(
- repository,
- max_count: 50
- )
+ context 'when Gitaly find_all_commits feature is enabled' do
+ it_behaves_like 'finding all commits'
+ end
- commits.map { |c| c.id }
- end
+ context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'finding all commits'
- it 'has 31 elements' do
- expect(subject.size).to eq(33)
- end
- it { is_expected.to include(SeedRepo::Commit::ID) }
- it { is_expected.to include(SeedRepo::Commit::PARENT_ID) }
- it { is_expected.to include(SeedRepo::FirstCommit::ID) }
- end
+ context 'while applying a sort order based on the `order` option' do
+ it "allows ordering topologically (no parents shown before their children)" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
- context 'ref + max_count + skip' do
- subject do
- commits = Gitlab::Git::Commit.find_all(
- repository,
- ref: 'master',
- max_count: 50,
- skip: 1
- )
+ described_class.find_all(repository, order: :topo)
+ end
- commits.map { |c| c.id }
- end
+ it "allows ordering by date" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
+
+ described_class.find_all(repository, order: :date)
+ end
+
+ it "applies no sorting by default" do
+ expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
- it 'has 23 elements' do
- expect(subject.size).to eq(24)
+ described_class.find_all(repository)
+ end
end
- it { is_expected.to include(SeedRepo::Commit::ID) }
- it { is_expected.to include(SeedRepo::FirstCommit::ID) }
- it { is_expected.not_to include(SeedRepo::LastCommit::ID) }
end
end
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index d20298fa139..0cfb210e390 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -484,6 +484,8 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
end
def each
+ return enum_for(:each) unless block_given?
+
loop do
break if @count.zero?
# It is critical to decrement before yielding. We may never reach the lines after 'yield'.
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index d97e85364c2..7ea3386ac2a 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -34,7 +34,7 @@ EOT
describe 'size limit feature toggles' do
context 'when the feature gitlab_git_diff_size_limit_increase is enabled' do
before do
- Feature.enable('gitlab_git_diff_size_limit_increase')
+ stub_feature_flags(gitlab_git_diff_size_limit_increase: true)
end
it 'returns 200 KB for size_limit' do
@@ -48,7 +48,7 @@ EOT
context 'when the feature gitlab_git_diff_size_limit_increase is disabled' do
before do
- Feature.disable('gitlab_git_diff_size_limit_increase')
+ stub_feature_flags(gitlab_git_diff_size_limit_increase: false)
end
it 'returns 100 KB for size_limit' do
@@ -241,7 +241,7 @@ EOT
end
describe '.filter_diff_options' do
- let(:options) { { max_size: 100, invalid_opt: true } }
+ let(:options) { { max_files: 100, invalid_opt: true } }
context "without default options" do
let(:filtered_options) { described_class.filter_diff_options(options) }
@@ -253,7 +253,7 @@ EOT
context "with default options" do
let(:filtered_options) do
- default_options = { max_size: 5, bad_opt: 1, ignore_whitespace: true }
+ default_options = { max_files: 5, bad_opt: 1, ignore_whitespace_change: true }
described_class.filter_diff_options(options, default_options)
end
@@ -263,12 +263,12 @@ EOT
end
it "should merge with default options" do
- expect(filtered_options).to have_key(:ignore_whitespace)
+ expect(filtered_options).to have_key(:ignore_whitespace_change)
end
it "should override default options" do
- expect(filtered_options).to have_key(:max_size)
- expect(filtered_options[:max_size]).to eq(100)
+ expect(filtered_options).to have_key(:max_files)
+ expect(filtered_options[:max_files]).to eq(100)
end
end
end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index 73518656bde..19391a70cf6 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -1,7 +1,13 @@
require 'spec_helper'
require 'fileutils'
-describe Gitlab::Git::Hook, lib: true do
+describe Gitlab::Git::Hook do
+ before do
+ # We need this because in the spec/spec_helper.rb we define it like this:
+ # allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+ allow_any_instance_of(described_class).to receive(:trigger).and_call_original
+ end
+
describe "#trigger" do
let(:project) { create(:project, :repository) }
let(:repo_path) { project.repository.path }
@@ -42,7 +48,7 @@ describe Gitlab::Git::Hook, lib: true do
it "returns success with no errors" do
create_hook(hook_name)
- hook = Gitlab::Git::Hook.new(hook_name, project)
+ hook = described_class.new(hook_name, project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
@@ -60,7 +66,7 @@ describe Gitlab::Git::Hook, lib: true do
context "when the hook is unsuccessful" do
it "returns failure with errors" do
create_failing_hook(hook_name)
- hook = Gitlab::Git::Hook.new(hook_name, project)
+ hook = described_class.new(hook_name, project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
@@ -74,7 +80,7 @@ describe Gitlab::Git::Hook, lib: true do
context "when the hook doesn't exist" do
it "returns success with no errors" do
- hook = Gitlab::Git::Hook.new('unknown_hook', project)
+ hook = described_class.new('unknown_hook', project)
blank = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + 'new_branch'
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index f1cdd86edb9..83d067b2c31 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -3,6 +3,20 @@ require "spec_helper"
describe Gitlab::Git::Repository, seed_helper: true do
include Gitlab::EncodingHelper
+ shared_examples 'wrapping gRPC errors' do |gitaly_client_class, gitaly_client_method|
+ it 'wraps gRPC not found error' do
+ expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
+ .and_raise(GRPC::NotFound)
+ expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
+ end
+
+ it 'wraps gRPC unknown error' do
+ expect_any_instance_of(gitaly_client_class).to receive(gitaly_client_method)
+ .and_raise(GRPC::Unknown)
+ expect { subject }.to raise_error(Gitlab::Git::CommandError)
+ end
+ end
+
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
describe "Respond to" do
@@ -31,20 +45,12 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it 'gets the branch name from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
+ expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:default_branch_name)
repository.root_ref
end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- .and_raise(GRPC::NotFound)
- expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
- .and_raise(GRPC::Unknown)
- expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :default_branch_name do
+ subject { repository.root_ref }
end
end
@@ -126,21 +132,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("branch-from-space") }
it 'gets the branch names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
+ expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:branch_names)
subject
end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- .and_raise(GRPC::NotFound)
- expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- it 'wraps GRPC other exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
- .and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :branch_names
end
describe '#tag_names' do
@@ -164,21 +160,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.not_to include("v5.0.0") }
it 'gets the tag names from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
+ expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:tag_names)
subject
end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- .and_raise(GRPC::NotFound)
- expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
- .and_raise(GRPC::Unknown)
- expect { subject }.to raise_error(Gitlab::Git::CommandError)
- end
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names
end
shared_examples 'archive check' do |extenstion|
@@ -248,33 +234,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(repository.bare?).to be_truthy }
end
- describe '#heads' do
- let(:heads) { repository.heads }
- subject { heads }
-
- it { is_expected.to be_kind_of Array }
-
- describe '#size' do
- subject { super().size }
- it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
- end
-
- context :head do
- subject { heads.first }
-
- describe '#name' do
- subject { super().name }
- it { is_expected.to eq("feature") }
- end
-
- context :commit do
- subject { heads.first.dereferenced_target.sha }
-
- it { is_expected.to eq("0b4bc9a49b562e85de7cc9e834518ea6828729b9") }
- end
- end
- end
-
describe '#ref_names' do
let(:ref_names) { repository.ref_names }
subject { ref_names }
@@ -292,39 +251,35 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- describe '#search_files' do
- let(:results) { repository.search_files('rails', 'master') }
- subject { results }
-
- it { is_expected.to be_kind_of Array }
+ describe '#submodule_url_for' do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+ let(:ref) { 'master' }
- describe '#first' do
- subject { super().first }
- it { is_expected.to be_kind_of Gitlab::Git::BlobSnippet }
+ def submodule_url(path)
+ repository.submodule_url_for(ref, path)
end
- context 'blob result' do
- subject { results.first }
+ it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('nested/six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('deeper/nested/six')).to eq('git://github.com/randx/six.git') }
+ it { expect(submodule_url('invalid/path')).to eq(nil) }
- describe '#ref' do
- subject { super().ref }
- it { is_expected.to eq('master') }
- end
+ context 'uncommitted submodule dir' do
+ let(:ref) { 'fix-existing-submodule-dir' }
- describe '#filename' do
- subject { super().filename }
- it { is_expected.to eq('CHANGELOG') }
- end
+ it { expect(submodule_url('submodule-existing-dir')).to eq(nil) }
+ end
- describe '#startline' do
- subject { super().startline }
- it { is_expected.to eq(35) }
- end
+ context 'tags' do
+ let(:ref) { 'v1.2.1' }
- describe '#data' do
- subject { super().data }
- it { is_expected.to include "Ability to filter by multiple labels" }
- end
+ it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
+ end
+
+ context 'no submodules at commit' do
+ let(:ref) { '6d39438' }
+
+ it { expect(submodule_url('six')).to eq(nil) }
end
end
@@ -406,8 +361,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#commit_count' do
- it { expect(repository.commit_count("master")).to eq(25) }
- it { expect(repository.commit_count("feature")).to eq(9) }
+ shared_examples 'counting commits' do
+ it { expect(repository.commit_count("master")).to eq(25) }
+ it { expect(repository.commit_count("feature")).to eq(9) }
+ end
+
+ context 'when Gitaly commit_count feature is enabled' do
+ it_behaves_like 'counting commits'
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::CommitService, :commit_count do
+ subject { repository.commit_count('master') }
+ end
+ end
+
+ context 'when Gitaly commit_count feature is disabled', skip_gitaly_mock: true do
+ it_behaves_like 'counting commits'
+ end
end
describe "#reset" do
@@ -490,7 +458,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it "should refresh the repo's #heads collection" do
- head_names = @normal_repo.heads.map { |h| h.name }
+ head_names = @normal_repo.branches.map { |h| h.name }
expect(head_names).to include(new_branch)
end
@@ -511,7 +479,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
eq(normal_repo.rugged.branches["master"].target.oid)
)
- head_names = normal_repo.heads.map { |h| h.name }
+ head_names = normal_repo.branches.map { |h| h.name }
expect(head_names).not_to include(new_branch)
end
@@ -558,10 +526,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.rugged.branches["feature"]).to be_nil
end
- it "should update the repo's #heads collection" do
- expect(@repo.heads).not_to include("feature")
- end
-
after(:all) do
FileUtils.rm_rf(TEST_MUTABLE_REPO_PATH)
ensure_seeds
@@ -674,9 +638,9 @@ describe Gitlab::Git::Repository, seed_helper: true do
# Add new commits so that there's a renamed file in the commit history
repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH).rugged
- commit_with_old_name = new_commit_edit_old_file(repo)
- rename_commit = new_commit_move_file(repo)
- commit_with_new_name = new_commit_edit_new_file(repo)
+ commit_with_old_name = Gitlab::Git::Commit.decorate(new_commit_edit_old_file(repo))
+ rename_commit = Gitlab::Git::Commit.decorate(new_commit_move_file(repo))
+ commit_with_new_name = Gitlab::Git::Commit.decorate(new_commit_edit_new_file(repo))
end
after(:context) do
@@ -849,8 +813,8 @@ describe Gitlab::Git::Repository, seed_helper: true do
context "compare results between log_by_walk and log_by_shell" do
let(:options) { { ref: "master" } }
- let(:commits_by_walk) { repository.log(options).map(&:oid) }
- let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:oid) }
+ let(:commits_by_walk) { repository.log(options).map(&:id) }
+ let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) }
it { expect(commits_by_walk).to eq(commits_by_shell) }
@@ -893,7 +857,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.time >= options[:after] }
+ commits.all? { |commit| commit.committed_date >= options[:after] }
end
end
end
@@ -906,7 +870,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(commits.size).to be > 0
expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.time <= options[:before] }
+ commits.all? { |commit| commit.committed_date <= options[:before] }
end
end
end
@@ -915,7 +879,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
def commit_files(commit)
- commit.diff(commit.parent_ids.first).deltas.flat_map do |delta|
+ commit.diff_from_parent.deltas.flat_map do |delta|
[delta.old_file[:path], delta.new_file[:path]].uniq.compact
end
end
@@ -1261,21 +1225,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it 'gets the branches from GitalyClient' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
+ expect_any_instance_of(Gitlab::GitalyClient::RefService).to receive(:local_branches)
.and_return([])
@repo.local_branches
end
- it 'wraps GRPC not found' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
- .and_raise(GRPC::NotFound)
- expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
-
- it 'wraps GRPC exceptions' do
- expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
- .and_raise(GRPC::Unknown)
- expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :local_branches do
+ subject { @repo.local_branches }
end
end
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 78894ba9409..b051a088171 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Git::RevList, lib: true do
+describe Gitlab::Git::RevList do
let(:project) { create(:project, :repository) }
before do
@@ -11,7 +11,7 @@ describe Gitlab::Git::RevList, lib: true do
end
context "#new_refs" do
- let(:rev_list) { Gitlab::Git::RevList.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+ let(:rev_list) { described_class.new(newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
it 'calls out to `popen`' do
expect(Gitlab::Popen).to receive(:popen).with([
@@ -33,7 +33,7 @@ describe Gitlab::Git::RevList, lib: true do
end
context "#missed_ref" do
- let(:rev_list) { Gitlab::Git::RevList.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
+ let(:rev_list) { described_class.new(oldrev: 'oldrev', newrev: 'newrev', path_to_repo: project.repository.path_to_repo) }
it 'calls out to `popen`' do
expect(Gitlab::Popen).to receive(:popen).with([
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index 4b76a43e6b5..98ddd3c3664 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -1,8 +1,9 @@
require "spec_helper"
describe Gitlab::Git::Tree, seed_helper: true do
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
+
context :repo do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
it { expect(tree).to be_kind_of Array }
@@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(submodule.name).to eq('gitlab-shell') }
end
end
+
+ describe '#where' do
+ context 'with gitaly disabled' do
+ before do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
+ end
+
+ it 'calls #tree_entries_from_rugged' do
+ expect(described_class).to receive(:tree_entries_from_rugged)
+
+ described_class.where(repository, SeedRepo::Commit::ID, '/')
+ end
+ end
+
+ it 'gets the tree entries from GitalyClient' do
+ expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries)
+
+ described_class.where(repository, SeedRepo::Commit::ID, '/')
+ end
+ end
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 9a86cfa66e4..2d6ea37d0ac 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Gitlab::GitAccess, lib: true do
+describe Gitlab::GitAccess do
let(:pull_access_check) { access.check('git-upload-pack', '_any') }
let(:push_access_check) { access.check('git-receive-pack', '_any') }
- let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
+ let(:access) { described_class.new(actor, project, protocol, authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:actor) { user }
@@ -280,7 +280,7 @@ describe Gitlab::GitAccess, lib: true do
context 'when project is public' do
let(:public_project) { create(:project, :public, :repository) }
- let(:access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
+ let(:access) { described_class.new(nil, public_project, 'web', authentication_abilities: []) }
context 'when repository is enabled' do
it 'give access to download code' do
@@ -441,7 +441,7 @@ describe Gitlab::GitAccess, lib: true do
end
permissions_matrix[role].each do |action, allowed|
- context action do
+ context action.to_s do
subject { access.send(:check_push_access!, changes[action]) }
it do
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index 797ec8cb23e..0376b4ee783 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::GitAccessWiki, lib: true do
- let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
+describe Gitlab::GitAccessWiki do
+ let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:redirected_path) { nil }
diff --git a/spec/lib/gitlab/git_ref_validator_spec.rb b/spec/lib/gitlab/git_ref_validator_spec.rb
index cc8daa535d6..e1fa8ae03f8 100644
--- a/spec/lib/gitlab/git_ref_validator_spec.rb
+++ b/spec/lib/gitlab/git_ref_validator_spec.rb
@@ -1,25 +1,25 @@
require 'spec_helper'
-describe Gitlab::GitRefValidator, lib: true do
- it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy }
- it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy }
- it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy }
- it { expect(Gitlab::GitRefValidator.validate('#1')).to be_truthy }
- it { expect(Gitlab::GitRefValidator.validate('feature/refs/heads/foo')).to be_truthy }
- it { expect(Gitlab::GitRefValidator.validate('feature/~new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/^new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/:new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/?new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/*new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/[new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/new/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature/new.')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature\@{')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature\new')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature//new')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('feature new')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('refs/heads/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('refs/remotes/')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('refs/heads/feature')).to be_falsey }
- it { expect(Gitlab::GitRefValidator.validate('refs/remotes/origin')).to be_falsey }
+describe Gitlab::GitRefValidator do
+ it { expect(described_class.validate('feature/new')).to be_truthy }
+ it { expect(described_class.validate('implement_@all')).to be_truthy }
+ it { expect(described_class.validate('my_new_feature')).to be_truthy }
+ it { expect(described_class.validate('#1')).to be_truthy }
+ it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy }
+ it { expect(described_class.validate('feature/~new/')).to be_falsey }
+ it { expect(described_class.validate('feature/^new/')).to be_falsey }
+ it { expect(described_class.validate('feature/:new/')).to be_falsey }
+ it { expect(described_class.validate('feature/?new/')).to be_falsey }
+ it { expect(described_class.validate('feature/*new/')).to be_falsey }
+ it { expect(described_class.validate('feature/[new/')).to be_falsey }
+ it { expect(described_class.validate('feature/new/')).to be_falsey }
+ it { expect(described_class.validate('feature/new.')).to be_falsey }
+ it { expect(described_class.validate('feature\@{')).to be_falsey }
+ it { expect(described_class.validate('feature\new')).to be_falsey }
+ it { expect(described_class.validate('feature//new')).to be_falsey }
+ it { expect(described_class.validate('feature new')).to be_falsey }
+ it { expect(described_class.validate('refs/heads/')).to be_falsey }
+ it { expect(described_class.validate('refs/remotes/')).to be_falsey }
+ it { expect(described_class.validate('refs/heads/feature')).to be_falsey }
+ it { expect(described_class.validate('refs/remotes/origin')).to be_falsey }
end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index 36f0e6507c8..4702a978f19 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe Gitlab::Git, lib: true do
+describe Gitlab::Git do
let(:committer_email) { 'user@example.org' }
let(:committer_name) { 'John Doe' }
describe 'committer_hash' do
it "returns a hash containing the given email and name" do
- committer_hash = Gitlab::Git.committer_hash(email: committer_email, name: committer_name)
+ committer_hash = described_class.committer_hash(email: committer_email, name: committer_name)
expect(committer_hash[:email]).to eq(committer_email)
expect(committer_hash[:name]).to eq(committer_name)
@@ -15,7 +15,7 @@ describe Gitlab::Git, lib: true do
context 'when email is nil' do
it "returns nil" do
- committer_hash = Gitlab::Git.committer_hash(email: nil, name: committer_name)
+ committer_hash = described_class.committer_hash(email: nil, name: committer_name)
expect(committer_hash).to be_nil
end
@@ -23,7 +23,7 @@ describe Gitlab::Git, lib: true do
context 'when name is nil' do
it "returns nil" do
- committer_hash = Gitlab::Git.committer_hash(email: committer_email, name: nil)
+ committer_hash = described_class.committer_hash(email: committer_email, name: nil)
expect(committer_hash).to be_nil
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
new file mode 100644
index 00000000000..0868c793a33
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -0,0 +1,123 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::CommitService do
+ let(:project) { create(:project, :repository) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.path_with_namespace + '.git' }
+ let(:repository) { project.repository }
+ let(:repository_message) { repository.gitaly_repository }
+ let(:revision) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
+ let(:commit) { project.commit(revision) }
+ let(:client) { described_class.new(repository) }
+
+ describe '#diff_from_parent' do
+ context 'when a commit has a parent' do
+ it 'sends an RPC request with the parent ID as left commit' do
+ request = Gitaly::CommitDiffRequest.new(
+ repository: repository_message,
+ left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
+ right_commit_id: commit.id,
+ collapse_diffs: true,
+ enforce_limits: true,
+ **Gitlab::Git::DiffCollection.collection_limits.to_h
+ )
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
+
+ client.diff_from_parent(commit)
+ end
+ end
+
+ context 'when a commit does not have a parent' do
+ it 'sends an RPC request with empty tree ref as left commit' do
+ initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+ request = Gitaly::CommitDiffRequest.new(
+ repository: repository_message,
+ left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ right_commit_id: initial_commit.id,
+ collapse_diffs: true,
+ enforce_limits: true,
+ **Gitlab::Git::DiffCollection.collection_limits.to_h
+ )
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
+
+ client.diff_from_parent(initial_commit)
+ end
+ end
+
+ it 'returns a Gitlab::Git::DiffCollection' do
+ ret = client.diff_from_parent(commit)
+
+ expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
+ end
+
+ it 'passes options to Gitlab::Git::DiffCollection' do
+ options = { max_files: 31, max_lines: 13, from_gitaly: true }
+
+ expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
+
+ client.diff_from_parent(commit, options)
+ end
+ end
+
+ describe '#commit_deltas' do
+ context 'when a commit has a parent' do
+ it 'sends an RPC request with the parent ID as left commit' do
+ request = Gitaly::CommitDeltaRequest.new(
+ repository: repository_message,
+ left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
+ right_commit_id: commit.id
+ )
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
+
+ client.commit_deltas(commit)
+ end
+ end
+
+ context 'when a commit does not have a parent' do
+ it 'sends an RPC request with empty tree ref as left commit' do
+ initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+ request = Gitaly::CommitDeltaRequest.new(
+ repository: repository_message,
+ left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
+ right_commit_id: initial_commit.id
+ )
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
+
+ client.commit_deltas(initial_commit)
+ end
+ end
+ end
+
+ describe '#between' do
+ let(:from) { 'master' }
+ let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
+
+ it 'sends an RPC request' do
+ request = Gitaly::CommitsBetweenRequest.new(
+ repository: repository_message, from: from, to: to
+ )
+
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:commits_between)
+ .with(request, kind_of(Hash)).and_return([])
+
+ described_class.new(repository).between(from, to)
+ end
+ end
+
+ describe '#tree_entries' do
+ let(:path) { '/' }
+
+ it 'sends a get_tree_entries message' do
+ expect_any_instance_of(Gitaly::CommitService::Stub)
+ .to receive(:get_tree_entries)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_return([])
+
+ client.tree_entries(repository, revision, path)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_spec.rb
deleted file mode 100644
index dff5b25c712..00000000000
--- a/spec/lib/gitlab/gitaly_client/commit_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitalyClient::Commit do
- let(:diff_stub) { double('Gitaly::Diff::Stub') }
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
- let(:repository_message) { repository.gitaly_repository }
- let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
-
- describe '#diff_from_parent' do
- context 'when a commit has a parent' do
- it 'sends an RPC request with the parent ID as left commit' do
- request = Gitaly::CommitDiffRequest.new(
- repository: repository_message,
- left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
- right_commit_id: commit.id
- )
-
- expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
-
- described_class.new(repository).diff_from_parent(commit)
- end
- end
-
- context 'when a commit does not have a parent' do
- it 'sends an RPC request with empty tree ref as left commit' do
- initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
- request = Gitaly::CommitDiffRequest.new(
- repository: repository_message,
- left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
- right_commit_id: initial_commit.id
- )
-
- expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
-
- described_class.new(repository).diff_from_parent(initial_commit)
- end
- end
-
- it 'returns a Gitlab::Git::DiffCollection' do
- ret = described_class.new(repository).diff_from_parent(commit)
-
- expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
- end
-
- it 'passes options to Gitlab::Git::DiffCollection' do
- options = { max_files: 31, max_lines: 13 }
-
- expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
-
- described_class.new(repository).diff_from_parent(commit, options)
- end
- end
-
- describe '#commit_deltas' do
- context 'when a commit has a parent' do
- it 'sends an RPC request with the parent ID as left commit' do
- request = Gitaly::CommitDeltaRequest.new(
- repository: repository_message,
- left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660',
- right_commit_id: commit.id
- )
-
- expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
-
- described_class.new(repository).commit_deltas(commit)
- end
- end
-
- context 'when a commit does not have a parent' do
- it 'sends an RPC request with empty tree ref as left commit' do
- initial_commit = project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
- request = Gitaly::CommitDeltaRequest.new(
- repository: repository_message,
- left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904',
- right_commit_id: initial_commit.id
- )
-
- expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
-
- described_class.new(repository).commit_deltas(initial_commit)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/gitaly_client/diff_spec.rb b/spec/lib/gitlab/gitaly_client/diff_spec.rb
index 2960c9a79ad..00a31ac0b96 100644
--- a/spec/lib/gitlab/gitaly_client/diff_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Diff, lib: true do
+describe Gitlab::GitalyClient::Diff do
let(:diff_fields) do
{
to_path: ".gitmodules",
diff --git a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
index 07650013052..cd3242b9326 100644
--- a/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/diff_stitcher_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::DiffStitcher, lib: true do
+describe Gitlab::GitalyClient::DiffStitcher do
describe 'enumeration' do
it 'combines segregated diff messages together' do
diff_1 = OpenStruct.new(
diff --git a/spec/lib/gitlab/gitaly_client/notifications_spec.rb b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
index 7404ffe0f06..d9597c4aa78 100644
--- a/spec/lib/gitlab/gitaly_client/notifications_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Notifications do
+describe Gitlab::GitalyClient::NotificationService do
describe '#post_receive' do
let(:project) { create(:empty_project) }
let(:storage_name) { project.repository_storage }
@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Notifications do
subject { described_class.new(project.repository) }
it 'sends a post_receive message' do
- expect_any_instance_of(Gitaly::Notifications::Stub)
+ expect_any_instance_of(Gitaly::NotificationService::Stub)
.to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
subject.post_receive
diff --git a/spec/lib/gitlab/gitaly_client/ref_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index df22fcad902..1e8ed9d645b 100644
--- a/spec/lib/gitlab/gitaly_client/ref_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitalyClient::Ref do
+describe Gitlab::GitalyClient::RefService do
let(:project) { create(:empty_project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.path_with_namespace + '.git' }
@@ -8,7 +8,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#branch_names' do
it 'sends a find_all_branch_names message' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_all_branch_names)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([])
@@ -19,7 +19,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#tag_names' do
it 'sends a find_all_tag_names message' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_all_tag_names)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([])
@@ -30,7 +30,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#default_branch_name' do
it 'sends a find_default_branch_name message' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_default_branch_name)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(double(name: 'foo'))
@@ -41,7 +41,7 @@ describe Gitlab::GitalyClient::Ref do
describe '#local_branches' do
it 'sends a find_local_branches message' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([])
@@ -50,7 +50,7 @@ describe Gitlab::GitalyClient::Ref do
end
it 'parses and sends the sort parameter' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches)
.with(gitaly_request_with_params(sort_by: :UPDATED_DESC), kind_of(Hash))
.and_return([])
@@ -59,7 +59,7 @@ describe Gitlab::GitalyClient::Ref do
end
it 'translates known mismatches on sort param values' do
- expect_any_instance_of(Gitaly::Ref::Stub)
+ expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_local_branches)
.with(gitaly_request_with_params(sort_by: :NAME), kind_of(Hash))
.and_return([])
@@ -77,7 +77,7 @@ describe Gitlab::GitalyClient::Ref do
let(:client) { described_class.new(repository) }
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
- it { should be_utf8 }
- it { should eq('refs/heads/master') }
+ it { is_expected.to be_utf8 }
+ it { is_expected.to eq('refs/heads/master') }
end
end
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
new file mode 100644
index 00000000000..5a9f3fc130c
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::RepositoryService do
+ set(:project) { create(:empty_project) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.path_with_namespace + '.git' }
+ let(:client) { described_class.new(project.repository) }
+
+ describe '#exists?' do
+ it 'sends an exists message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:exists)
+ .with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
+ .and_call_original
+
+ client.exists?
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index ce7b18b784a..921e786a55c 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
-describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
+describe Gitlab::GitalyClient, skip_gitaly_mock: true do
describe '.stub' do
# Notice that this is referring to gRPC "stubs", not rspec stubs
before do
@@ -16,9 +16,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
'default' => { 'gitaly_address' => address }
})
- expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
+ expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
- described_class.stub(:commit, 'default')
+ described_class.stub(:commit_service, 'default')
end
end
@@ -31,9 +31,9 @@ describe Gitlab::GitalyClient, lib: true, skip_gitaly_mock: true do
'default' => { 'gitaly_address' => prefixed_address }
})
- expect(Gitaly::Commit::Stub).to receive(:new).with(address, any_args)
+ expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
- described_class.stub(:commit, 'default')
+ described_class.stub(:commit_service, 'default')
end
end
end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
index 3a31f93efa5..426b43f8b51 100644
--- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::BranchFormatter, lib: true do
+describe Gitlab::GithubImport::BranchFormatter do
let(:project) { create(:project, :repository) }
let(:commit) { create(:commit, project: project) }
let(:repo) { double }
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index 21f2a9e225b..66273255b6f 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Client, lib: true do
+describe Gitlab::GithubImport::Client do
let(:token) { '123456' }
let(:github_provider) { Settingslogic.new('app_id' => 'asd123', 'app_secret' => 'asd123', 'name' => 'github', 'args' => { 'client_options' => {} }) }
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
index cc38872e426..ef89634685a 100644
--- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::CommentFormatter, lib: true do
+describe Gitlab::GithubImport::CommentFormatter do
let(:client) { double }
let(:project) { create(:empty_project) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 9d5e20841b5..d00a2deaf7b 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::Importer, lib: true do
+describe Gitlab::GithubImport::Importer do
shared_examples 'Gitlab::GithubImport::Importer#execute' do
let(:expected_not_called) { [] }
diff --git a/spec/lib/gitlab/github_import/issuable_formatter_spec.rb b/spec/lib/gitlab/github_import/issuable_formatter_spec.rb
index 6bc5f98ed2c..05294d227bd 100644
--- a/spec/lib/gitlab/github_import/issuable_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::IssuableFormatter, lib: true do
+describe Gitlab::GithubImport::IssuableFormatter do
let(:raw_data) do
double(number: 42)
end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index a4089592cf2..39b15926193 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::IssueFormatter, lib: true do
+describe Gitlab::GithubImport::IssueFormatter do
let(:client) { double }
let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb
index 565435824fd..2cc7ac0b446 100644
--- a/spec/lib/gitlab/github_import/label_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::LabelFormatter, lib: true do
+describe Gitlab::GithubImport::LabelFormatter do
let(:project) { create(:empty_project) }
let(:raw) { double(name: 'improvements', color: 'e6e6e6') }
diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
index 6d38041c468..310e0536fd7 100644
--- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
+describe Gitlab::GithubImport::MilestoneFormatter do
let(:project) { create(:empty_project) }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index a73b1f4ff5d..948e7469a18 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ProjectCreator, lib: true do
+describe Gitlab::GithubImport::ProjectCreator do
let(:user) { create(:user) }
let(:namespace) { create(:group, owner: user) }
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index b7c59918a76..2e42f6239b7 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
+describe Gitlab::GithubImport::PullRequestFormatter do
let(:client) { double }
let(:project) { create(:project, :repository) }
let(:source_sha) { create(:commit, project: project).id }
diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb
index 13b15e669ab..1357cb636ae 100644
--- a/spec/lib/gitlab/github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::ReleaseFormatter, lib: true do
+describe Gitlab::GithubImport::ReleaseFormatter do
let!(:project) { create(:empty_project, namespace: create(:namespace, path: 'octocat')) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
diff --git a/spec/lib/gitlab/github_import/user_formatter_spec.rb b/spec/lib/gitlab/github_import/user_formatter_spec.rb
index db792233657..98e3a7c28b9 100644
--- a/spec/lib/gitlab/github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/user_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::UserFormatter, lib: true do
+describe Gitlab::GithubImport::UserFormatter do
let(:client) { double }
let(:octocat) { double(id: 123456, login: 'octocat', email: 'octocat@example.com') }
diff --git a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
index 1bd29b8a563..de50265bc14 100644
--- a/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/wiki_formatter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GithubImport::WikiFormatter, lib: true do
+describe Gitlab::GithubImport::WikiFormatter do
let(:project) do
create(:project,
namespace: create(:namespace, path: 'gitlabhq'),
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index cd8e805466a..50e8d7183ce 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Client, lib: true do
+describe Gitlab::GitlabImport::Client do
include ImportSpecHelper
let(:token) { '123456' }
- let(:client) { Gitlab::GitlabImport::Client.new(token) }
+ let(:client) { described_class.new(token) }
before do
stub_omniauth_provider('gitlab')
diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
index 4f588da0a83..16b14474b89 100644
--- a/spec/lib/gitlab/gitlab_import/importer_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::Importer, lib: true do
+describe Gitlab::GitlabImport::Importer do
include ImportSpecHelper
describe '#execute' do
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index 483f65cd053..da48d8f0670 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GitlabImport::ProjectCreator, lib: true do
+describe Gitlab::GitlabImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
{
@@ -23,7 +23,7 @@ describe Gitlab::GitlabImport::ProjectCreator, lib: true do
it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job)
- project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user, access_params)
+ project_creator = described_class.new(repo, namespace, user, access_params)
project = project_creator.execute
expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git")
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index 85949ae8dc4..37985c062b4 100644
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ b/spec/lib/gitlab/google_code_import/client_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Client, lib: true do
+describe Gitlab::GoogleCodeImport::Client do
let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
subject { described_class.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index 622a0f513f4..85f40825005 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe Gitlab::GoogleCodeImport::Importer, lib: true do
+describe Gitlab::GoogleCodeImport::Importer do
let(:mapped_user) { create(:user, username: "thilo123") }
let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index 499a896ee76..aad53938d52 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GoogleCodeImport::ProjectCreator, lib: true do
+describe Gitlab::GoogleCodeImport::ProjectCreator do
let(:user) { create(:user) }
let(:repo) do
Gitlab::GoogleCodeImport::Repository.new(
@@ -18,7 +18,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator, lib: true do
it 'creates project' do
allow_any_instance_of(Project).to receive(:add_import_job)
- project_creator = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, user)
+ project_creator = described_class.new(repo, namespace, user)
project = project_creator.execute
expect(project.import_url).to eq("https://vim.googlecode.com/git/")
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
new file mode 100644
index 00000000000..ddb8dd9f0f4
--- /dev/null
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -0,0 +1,127 @@
+require 'rails_helper'
+
+RSpec.describe Gitlab::Gpg::Commit do
+ describe '#signature' do
+ let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
+
+ context 'unisgned commit' do
+ it 'returns nil' do
+ expect(described_class.new(project.commit).signature).to be_nil
+ end
+ end
+
+ context 'known and verified public key' do
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: create(:user, email: GpgHelpers::User1.emails.first)
+ end
+
+ let!(:commit) do
+ raw_commit = double(:raw_commit, signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ], sha: commit_sha)
+ allow(raw_commit).to receive :save!
+
+ create :commit, git_commit: raw_commit, project: project
+ end
+
+ it 'returns a valid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ valid_signature: true
+ )
+ end
+
+ it 'returns the cached signature on second call' do
+ gpg_commit = described_class.new(commit)
+
+ expect(gpg_commit).to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+
+ # consecutive call
+ expect(gpg_commit).not_to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+ end
+ end
+
+ context 'known but unverified public key' do
+ let!(:gpg_key) { create :gpg_key, key: GpgHelpers::User1.public_key }
+
+ let!(:commit) do
+ raw_commit = double(:raw_commit, signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ], sha: commit_sha)
+ allow(raw_commit).to receive :save!
+
+ create :commit, git_commit: raw_commit, project: project
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ valid_signature: false
+ )
+ end
+
+ it 'returns the cached signature on second call' do
+ gpg_commit = described_class.new(commit)
+
+ expect(gpg_commit).to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+
+ # consecutive call
+ expect(gpg_commit).not_to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+ end
+ end
+
+ context 'unknown public key' do
+ let!(:commit) do
+ raw_commit = double(:raw_commit, signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ], sha: commit_sha)
+ allow(raw_commit).to receive :save!
+
+ create :commit,
+ git_commit: raw_commit,
+ project: project
+ end
+
+ it 'returns an invalid signature' do
+ expect(described_class.new(commit).signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: nil,
+ gpg_key_user_email: nil,
+ valid_signature: false
+ )
+ end
+
+ it 'returns the cached signature on second call' do
+ gpg_commit = described_class.new(commit)
+
+ expect(gpg_commit).to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+
+ # consecutive call
+ expect(gpg_commit).not_to receive(:using_keychain).and_call_original
+ gpg_commit.signature
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
new file mode 100644
index 00000000000..c4e04ee46a2
--- /dev/null
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -0,0 +1,173 @@
+require 'rails_helper'
+
+RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
+ describe '#run' do
+ let!(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
+ let!(:project) { create :project, :repository, path: 'sample-project' }
+ let!(:raw_commit) do
+ raw_commit = double(:raw_commit, signature: [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ], sha: commit_sha)
+
+ allow(raw_commit).to receive :save!
+
+ raw_commit
+ end
+
+ let!(:commit) do
+ create :commit, git_commit: raw_commit, project: project
+ end
+
+ before do
+ allow_any_instance_of(Project).to receive(:commit).and_return(commit)
+ end
+
+ context 'gpg signature did have an associated gpg key which was removed later' do
+ let!(:user) { create :user, email: GpgHelpers::User1.emails.first }
+
+ let!(:valid_gpg_signature) do
+ create :gpg_signature,
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: true
+ end
+
+ it 'assigns the gpg key to the signature when the missing gpg key is added' do
+ # InvalidGpgSignatureUpdater is called by the after_create hook
+ gpg_key = create :gpg_key,
+ key: GpgHelpers::User1.public_key,
+ user: user
+
+ expect(valid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: true
+ )
+ end
+
+ it 'does not assign the gpg key when an unrelated gpg key is added' do
+ # InvalidGpgSignatureUpdater is called by the after_create hook
+ create :gpg_key,
+ key: GpgHelpers::User2.public_key,
+ user: user
+
+ expect(valid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: true
+ )
+ end
+ end
+
+ context 'gpg signature did not have an associated gpg key' do
+ let!(:user) { create :user, email: GpgHelpers::User1.emails.first }
+
+ let!(:invalid_gpg_signature) do
+ create :gpg_signature,
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: false
+ end
+
+ it 'updates the signature to being valid when the missing gpg key is added' do
+ # InvalidGpgSignatureUpdater is called by the after_create hook
+ gpg_key = create :gpg_key,
+ key: GpgHelpers::User1.public_key,
+ user: user
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: true
+ )
+ end
+
+ it 'keeps the signature at being invalid when an unrelated gpg key is added' do
+ # InvalidGpgSignatureUpdater is called by the after_create hook
+ create :gpg_key,
+ key: GpgHelpers::User2.public_key,
+ user: user
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: false
+ )
+ end
+ end
+
+ context 'gpg signature did have an associated unverified gpg key' do
+ let!(:user) do
+ create(:user, email: 'unrelated@example.com').tap do |user|
+ user.skip_reconfirmation!
+ end
+ end
+
+ let!(:invalid_gpg_signature) do
+ create :gpg_signature,
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: nil,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: false
+ end
+
+ it 'updates the signature to being valid when the user updates the email address' do
+ gpg_key = create :gpg_key,
+ key: GpgHelpers::User1.public_key,
+ user: user
+
+ expect(invalid_gpg_signature.reload.valid_signature).to be_falsey
+
+ # InvalidGpgSignatureUpdater is called by the after_update hook
+ user.update_attributes!(email: GpgHelpers::User1.emails.first)
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: true
+ )
+ end
+
+ it 'keeps the signature at being invalid when the changed email address is still unrelated' do
+ gpg_key = create :gpg_key,
+ key: GpgHelpers::User1.public_key,
+ user: user
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: false
+ )
+
+ # InvalidGpgSignatureUpdater is called by the after_update hook
+ user.update_attributes!(email: 'still.unrelated@example.com')
+
+ expect(invalid_gpg_signature.reload).to have_attributes(
+ project: project,
+ commit_sha: commit_sha,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ valid_signature: false
+ )
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
new file mode 100644
index 00000000000..8041518117d
--- /dev/null
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -0,0 +1,83 @@
+require 'rails_helper'
+
+describe Gitlab::Gpg do
+ describe '.fingerprints_from_key' do
+ before do
+ # make sure that each method is using the temporary keychain
+ expect(described_class).to receive(:using_tmp_keychain).and_call_original
+ end
+
+ it 'returns CurrentKeyChain.fingerprints_from_key' do
+ expect(Gitlab::Gpg::CurrentKeyChain).to receive(:fingerprints_from_key).with(GpgHelpers::User1.public_key)
+
+ described_class.fingerprints_from_key(GpgHelpers::User1.public_key)
+ end
+ end
+
+ describe '.primary_keyids_from_key' do
+ it 'returns the keyid' do
+ expect(
+ described_class.primary_keyids_from_key(GpgHelpers::User1.public_key)
+ ).to eq [GpgHelpers::User1.primary_keyid]
+ end
+
+ it 'returns an empty array when the key is invalid' do
+ expect(
+ described_class.primary_keyids_from_key('bogus')
+ ).to eq []
+ end
+ end
+
+ describe '.user_infos_from_key' do
+ it 'returns the names and emails' do
+ user_infos = described_class.user_infos_from_key(GpgHelpers::User1.public_key)
+ expect(user_infos).to eq([{
+ name: GpgHelpers::User1.names.first,
+ email: GpgHelpers::User1.emails.first
+ }])
+ end
+
+ it 'returns an empty array when the key is invalid' do
+ expect(
+ described_class.user_infos_from_key('bogus')
+ ).to eq []
+ end
+ end
+end
+
+describe Gitlab::Gpg::CurrentKeyChain do
+ around do |example|
+ Gitlab::Gpg.using_tmp_keychain do
+ example.run
+ end
+ end
+
+ describe '.add' do
+ it 'stores the key in the keychain' do
+ expect(GPGME::Key.find(:public, GpgHelpers::User1.fingerprint)).to eq []
+
+ described_class.add(GpgHelpers::User1.public_key)
+
+ keys = GPGME::Key.find(:public, GpgHelpers::User1.fingerprint)
+ expect(keys.count).to eq 1
+ expect(keys.first).to have_attributes(
+ email: GpgHelpers::User1.emails.first,
+ fingerprint: GpgHelpers::User1.fingerprint
+ )
+ end
+ end
+
+ describe '.fingerprints_from_key' do
+ it 'returns the fingerprint' do
+ expect(
+ described_class.fingerprints_from_key(GpgHelpers::User1.public_key)
+ ).to eq [GpgHelpers::User1.fingerprint]
+ end
+
+ it 'returns an empty array when the key is invalid' do
+ expect(
+ described_class.fingerprints_from_key('bogus')
+ ).to eq []
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index abb5a26060f..3f9382a9143 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Graphs::Commits, lib: true do
+describe Gitlab::Graphs::Commits do
let!(:project) { create(:empty_project, :public) }
let!(:commit1) { create(:commit, git_commit: RepoHelpers.sample_commit, project: project, committed_date: Time.now) }
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index b333e162909..8abc4320c59 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::HealthChecks::FsShardsCheck do
def command_exists?(command)
_, status = Gitlab::Popen.popen(%W{ #{command} 1 echo })
- status == 0
+ status.zero?
rescue Errno::ENOENT
false
end
@@ -64,9 +64,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
it 'cleans up files used for testing' do
expect(described_class).to receive(:storage_write_test).with(any_args).and_call_original
- subject
-
- expect(Dir.entries(tmp_dir).count).to eq(2)
+ expect { subject }.not_to change(Dir.entries(tmp_dir), :count)
end
context 'read test fails' do
@@ -88,8 +86,6 @@ describe Gitlab::HealthChecks::FsShardsCheck do
end
describe '#metrics' do
- subject { described_class.metrics }
-
context 'storage points to not existing folder' do
let(:storages_paths) do
{
@@ -104,14 +100,15 @@ describe Gitlab::HealthChecks::FsShardsCheck do
end
it 'provides metrics' do
- expect(subject).to all(have_attributes(labels: { shard: :default }))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
-
- expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+ metrics = described_class.metrics
+
+ expect(metrics).to all(have_attributes(labels: { shard: :default }))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
end
end
@@ -121,15 +118,19 @@ describe Gitlab::HealthChecks::FsShardsCheck do
end
it 'provides metrics' do
- expect(subject).to all(have_attributes(labels: { shard: :default }))
-
- expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
+ metrics = described_class.metrics
+
+ expect(metrics).to all(have_attributes(labels: { shard: :default }))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
+ end
- expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+ it 'cleans up files used for metrics' do
+ expect { described_class.metrics }.not_to change(Dir.entries(tmp_dir), :count)
end
end
end
@@ -150,18 +151,16 @@ describe Gitlab::HealthChecks::FsShardsCheck do
end
describe '#metrics' do
- subject { described_class.metrics }
-
it 'provides metrics' do
- expect(subject).to all(have_attributes(labels: { shard: :default }))
-
- expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
-
- expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
- expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
+ metrics = described_class.metrics
+
+ expect(metrics).to all(have_attributes(labels: { shard: :default }))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
+ expect(metrics).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
end
end
end
diff --git a/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
new file mode 100644
index 00000000000..3693f52b51b
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/redis/cache_check_spec.rb
@@ -0,0 +1,6 @@
+require 'spec_helper'
+require_relative '../simple_check_shared'
+
+describe Gitlab::HealthChecks::Redis::CacheCheck do
+ include_examples 'simple_check', 'redis_cache_ping', 'RedisCache', 'PONG'
+end
diff --git a/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
new file mode 100644
index 00000000000..c69443d205d
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/redis/queues_check_spec.rb
@@ -0,0 +1,6 @@
+require 'spec_helper'
+require_relative '../simple_check_shared'
+
+describe Gitlab::HealthChecks::Redis::QueuesCheck do
+ include_examples 'simple_check', 'redis_queues_ping', 'RedisQueues', 'PONG'
+end
diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
new file mode 100644
index 00000000000..03afc1cd761
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
@@ -0,0 +1,6 @@
+require 'spec_helper'
+require_relative '../simple_check_shared'
+
+describe Gitlab::HealthChecks::Redis::RedisCheck do
+ include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
+end
diff --git a/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
new file mode 100644
index 00000000000..b72e152bbe2
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/redis/shared_state_check_spec.rb
@@ -0,0 +1,6 @@
+require 'spec_helper'
+require_relative '../simple_check_shared'
+
+describe Gitlab::HealthChecks::Redis::SharedStateCheck do
+ include_examples 'simple_check', 'redis_shared_state_ping', 'RedisSharedState', 'PONG'
+end
diff --git a/spec/lib/gitlab/health_checks/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis_check_spec.rb
deleted file mode 100644
index 734cdcb893e..00000000000
--- a/spec/lib/gitlab/health_checks/redis_check_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'spec_helper'
-require_relative './simple_check_shared'
-
-describe Gitlab::HealthChecks::RedisCheck do
- include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
-end
diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
index 3f871d66034..e2643458aca 100644
--- a/spec/lib/gitlab/health_checks/simple_check_shared.rb
+++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb
@@ -8,7 +8,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 1)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
- it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
+ it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
context 'Check is misbehaving' do
@@ -18,7 +18,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
- it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
+ it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
context 'Check is timeouting' do
@@ -28,7 +28,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 1)) }
- it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
+ it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
end
@@ -47,7 +47,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
allow(described_class).to receive(:check).and_return 'error!'
end
- it { is_expected.to have_attributes(success: false, message: "unexpected #{check_name} check result: error!") }
+ it { is_expected.to have_attributes(success: false, message: "unexpected #{described_class.human_name} check result: error!") }
end
context 'Check is timeouting' do
@@ -55,7 +55,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
allow(described_class).to receive(:check ).and_return Timeout::Error.new
end
- it { is_expected.to have_attributes(success: false, message: "#{check_name} check timed out") }
+ it { is_expected.to have_attributes(success: false, message: "#{described_class.human_name} check timed out") }
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 07687b470c5..29e61d15726 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Highlight, lib: true do
+describe Gitlab::Highlight do
include RepoHelpers
let(:project) { create(:project, :repository) }
@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do
let(:blob) { repository.blob_at_branch(branch, path) }
let(:highlighter) do
- Gitlab::Highlight.new(blob.path, blob.data, repository: repository)
+ described_class.new(blob.path, blob.data, repository: repository)
end
before do
@@ -42,7 +42,7 @@ describe Gitlab::Highlight, lib: true do
let(:path) { 'files/whitespace' }
let(:blob) { repository.blob_at_branch(branch, path) }
let(:lines) do
- Gitlab::Highlight.highlight(blob.path, blob.data, repository: repository).lines
+ described_class.highlight(blob.path, blob.data, repository: repository).lines
end
it 'strips extra LFs' do
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index 0dba4132101..785035d993f 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::I18n, lib: true do
+describe Gitlab::I18n do
let(:user) { create(:user, preferred_language: 'es') }
describe '.locale=' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a5f09f1856e..977174a5fd2 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -45,6 +45,7 @@ label:
- merge_requests
- priorities
milestone:
+- group
- project
- issues
- labels
@@ -88,7 +89,10 @@ merge_requests:
- head_pipeline
merge_request_diff:
- merge_request
+- merge_request_diff_commits
- merge_request_diff_files
+merge_request_diff_commits:
+- merge_request_diff
merge_request_diff_files:
- merge_request_diff
pipelines:
@@ -130,8 +134,11 @@ pipeline_schedules:
- owner
- pipelines
- last_pipeline
+- variables
pipeline_schedule:
- pipelines
+pipeline_schedule_variables:
+- pipeline_schedule
deploy_keys:
- user
- deploy_keys_projects
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 63bab0f0d0d..574748756bd 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AttributeCleaner, lib: true do
+describe Gitlab::ImportExport::AttributeCleaner do
let(:relation_class){ double('relation_class').as_null_object }
let(:unsafe_hash) do
{
diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
index e24d070706a..65f073b2df3 100644
--- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb
@@ -7,7 +7,7 @@ require 'spec_helper'
# to be included as part of the export, or blacklist them using the import_export.yml configuration file.
# Likewise, new models added to import_export.yml, will need to be added with their correspondent attributes
# to this spec.
-describe 'Import/Export attribute configuration', lib: true do
+describe 'Import/Export attribute configuration' do
include ConfigurationHelper
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index 08a42fd27a2..a7b292c8558 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarRestorer, lib: true do
+describe Gitlab::ImportExport::AvatarRestorer do
include UploadHelpers
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index 579a31ead58..814f85de03b 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::AvatarSaver, lib: true do
+describe Gitlab::ImportExport::AvatarSaver do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index b88b9c18c15..690c7625c52 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::FileImporter, lib: true do
+describe Gitlab::ImportExport::FileImporter do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index 70796781532..08588a76fe6 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe 'forked project import', services: true do
+describe 'forked project import' do
let(:user) { create(:user) }
- let!(:project_with_repo) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:empty_project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
diff --git a/spec/lib/gitlab/import_export/hash_util_spec.rb b/spec/lib/gitlab/import_export/hash_util_spec.rb
index 1c3a0b23ece..366582dece3 100644
--- a/spec/lib/gitlab/import_export/hash_util_spec.rb
+++ b/spec/lib/gitlab/import_export/hash_util_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::HashUtil, lib: true do
+describe Gitlab::ImportExport::HashUtil do
let(:stringified_array) { [{ 'test' => 1 }] }
let(:stringified_array_with_date) { [{ 'test_date' => '2016-04-06 06:17:44 +0200' }] }
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
index f3fd0d82875..07415d41f93 100644
--- a/spec/lib/gitlab/import_export/import_export_spec.rb
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport, services: true do
+describe Gitlab::ImportExport do
describe 'export filename' do
let(:group) { create(:group, :nested) }
let(:project) { create(:empty_project, :public, path: 'project-path', namespace: group) }
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 3e0291c9ae9..f66a2ab7dda 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::MembersMapper, services: true do
+describe Gitlab::ImportExport::MembersMapper do
describe 'map members' do
let(:user) { create(:admin) }
let(:project) { create(:empty_project, :public, name: 'searchable_project') }
diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
index 349be4596b6..f2b66c4421c 100644
--- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
+++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::MergeRequestParser do
let(:user) { create(:user) }
- let!(:project) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let(:forked_from_project) { create(:project) }
let(:fork_link) { create(:forked_project_link, forked_from_project: project) }
diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb
index 2ede5cdd2ad..5cb8f2589c8 100644
--- a/spec/lib/gitlab/import_export/model_configuration_spec.rb
+++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
# Part of the test security suite for the Import/Export feature
# Finds if a new model has been added that can potentially be part of the Import/Export
# If it finds a new model, it will show a +failure_message+ with the options available.
-describe 'Import/Export model configuration', lib: true do
+describe 'Import/Export model configuration' do
include ConfigurationHelper
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 98c117b4cd8..469a014e4d2 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2741,13 +2741,12 @@
"merge_request_diff": {
"id": 27,
"state": "collected",
- "st_commits": [
+ "merge_request_diff_commits": [
{
- "id": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
+ "merge_request_diff_id": 27,
+ "relative_order": 0,
+ "sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
"message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "5937ac0a7beb003549fc5fd26fc247adbce4a52e"
- ],
"authored_date": "2014-08-06T08:35:52.000+02:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2756,11 +2755,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
+ "merge_request_diff_id": 27,
+ "relative_order": 1,
+ "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
"message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "570e7b2abdd848b95f2f578043fc23bd6f6fd24d"
- ],
"authored_date": "2014-02-27T10:01:38.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2769,11 +2767,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
+ "merge_request_diff_id": 27,
+ "relative_order": 2,
+ "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
"message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- ],
"authored_date": "2014-02-27T09:57:31.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2782,11 +2779,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
+ "merge_request_diff_id": 27,
+ "relative_order": 3,
+ "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
"message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "d14d6c0abdd253381df51a723d58691b2ee1ab08"
- ],
"authored_date": "2014-02-27T09:54:21.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2795,11 +2791,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
+ "merge_request_diff_id": 27,
+ "relative_order": 4,
+ "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
"message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "c1acaa58bbcbc3eafe538cb8274ba387047b69f8"
- ],
"authored_date": "2014-02-27T09:49:50.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2808,11 +2803,10 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
},
{
- "id": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
+ "merge_request_diff_id": 27,
+ "relative_order": 5,
+ "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
"message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
- "parent_ids": [
- "ae73cb07c9eeaf35924a10f713b364d32b2dd34f"
- ],
"authored_date": "2014-02-27T09:48:32.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index c11b15a811b..d1ec0e45bbd 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
include ImportExport::CommonUtil
-describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
+describe Gitlab::ImportExport::ProjectTreeRestorer do
describe 'restore project tree' do
before(:context) do
@user = create(:user)
@@ -95,6 +95,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(MergeRequestDiffFile.where.not(diff: nil).count).to eq(9)
end
+ it 'has the correct data for merge request diff commits in serialised and table formats' do
+ expect(MergeRequestDiff.where.not(st_commits: nil).count).to eq(7)
+ expect(MergeRequestDiffCommit.count).to eq(6)
+ end
+
it 'has the correct time for merge request st_commits' do
st_commits = MergeRequestDiff.where.not(st_commits: nil).first.st_commits
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index e52f79513f1..0c7e733b01f 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
+describe Gitlab::ImportExport::ProjectTreeSaver do
describe 'saves the project tree into a json object' do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
@@ -87,6 +87,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_files']).not_to be_empty
end
+ it 'has merge request diff commits' do
+ expect(saved_project_json['merge_requests'].first['merge_request_diff']['merge_request_diff_commits']).not_to be_empty
+ end
+
it 'has merge requests comments' do
expect(saved_project_json['merge_requests'].first['notes']).not_to be_empty
end
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index d700af142be..e9f5273725d 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::Reader, lib: true do
+describe Gitlab::ImportExport::Reader do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
let(:test_config) { 'spec/support/import_export/import_export.yml' }
let(:project_tree_hash) do
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index 5417c7534ea..baa90af84f7 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RelationFactory, lib: true do
+describe Gitlab::ImportExport::RelationFactory do
let(:project) { create(:empty_project) }
let(:members_mapper) { double('members_mapper').as_null_object }
let(:user) { create(:admin) }
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 30b6a0d8845..d3be2965bf4 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoRestorer, services: true do
+describe Gitlab::ImportExport::RepoRestorer do
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
- let!(:project_with_repo) { create(:project, :test_repo, name: 'test-repo-restorer', path: 'test-repo-restorer') }
+ let!(:project_with_repo) { create(:project, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:empty_project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.path_with_namespace) }
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index a7f4e11271e..87af13e0beb 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::RepoSaver, services: true do
+describe Gitlab::ImportExport::RepoSaver do
describe 'bundle a project Git repo' do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index fadd3ad1330..11f4c16ff96 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -82,6 +82,7 @@ Milestone:
- id
- title
- project_id
+- group_id
- description
- due_date
- start_date
@@ -172,6 +173,17 @@ MergeRequestDiff:
- real_size
- head_commit_sha
- start_commit_sha
+MergeRequestDiffCommit:
+- merge_request_diff_id
+- relative_order
+- sha
+- authored_date
+- committed_date
+- author_name
+- author_email
+- committer_name
+- committer_email
+- message
MergeRequestDiffFile:
- merge_request_diff_id
- relative_order
@@ -183,6 +195,7 @@ MergeRequestDiffFile:
- a_mode
- b_mode
- too_large
+- binary
Ci::Pipeline:
- id
- project_id
@@ -383,6 +396,8 @@ Project:
- printing_merge_request_link_enabled
- build_allow_git_fetch
- last_repository_updated_at
+- ci_config_path
+- delete_error
Author:
- name
ProjectFeature:
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index 2405ac5abfe..e7d50f75682 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
include ImportExport::CommonUtil
-describe Gitlab::ImportExport::VersionChecker, services: true do
+describe Gitlab::ImportExport::VersionChecker do
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
describe 'bundle a project Git repo' do
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 071e5fac3f0..78137aeff5e 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ImportExport::WikiRepoSaver, services: true do
+describe Gitlab::ImportExport::WikiRepoSaver do
describe 'bundle a wiki Git repo' do
let(:user) { create(:user) }
let!(:project) { create(:empty_project, :public, name: 'searchable_project') }
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index 698bd72d0f8..c959add7a36 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe Gitlab::IncomingEmail, lib: true do
+describe Gitlab::IncomingEmail do
describe "self.enabled?" do
context "when reply by email is enabled" do
before do
diff --git a/spec/lib/gitlab/issuable_metadata_spec.rb b/spec/lib/gitlab/issuable_metadata_spec.rb
new file mode 100644
index 00000000000..2455969a183
--- /dev/null
+++ b/spec/lib/gitlab/issuable_metadata_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe Gitlab::IssuableMetadata do
+ let(:user) { create(:user) }
+ let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) }
+
+ subject { Class.new { include Gitlab::IssuableMetadata }.new }
+
+ it 'returns an empty Hash if an empty collection is provided' do
+ expect(subject.issuable_meta_data(Issue.none, 'Issue')).to eq({})
+ end
+
+ context 'issues' do
+ let!(:issue) { create(:issue, author: user, project: project) }
+ let!(:closed_issue) { create(:issue, state: :closed, author: user, project: project) }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: closed_issue) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: issue) }
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:closing_issues) { create(:merge_requests_closing_issues, issue: issue, merge_request: merge_request) }
+
+ it 'aggregates stats on issues' do
+ data = subject.issuable_meta_data(Issue.all, 'Issue')
+
+ expect(data.count).to eq(2)
+ expect(data[issue.id].upvotes).to eq(1)
+ expect(data[issue.id].downvotes).to eq(0)
+ expect(data[issue.id].notes_count).to eq(0)
+ expect(data[issue.id].merge_requests_count).to eq(1)
+
+ expect(data[closed_issue.id].upvotes).to eq(0)
+ expect(data[closed_issue.id].downvotes).to eq(1)
+ expect(data[closed_issue.id].notes_count).to eq(0)
+ expect(data[closed_issue.id].merge_requests_count).to eq(0)
+ end
+ end
+
+ context 'merge requests' do
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
+ let!(:merge_request_closed) { create(:merge_request, state: "closed", source_project: project, target_project: project, title: "Closed Test") }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
+ let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
+
+ it 'aggregates stats on merge requests' do
+ data = subject.issuable_meta_data(MergeRequest.all, 'MergeRequest')
+
+ expect(data.count).to eq(2)
+ expect(data[merge_request.id].upvotes).to eq(1)
+ expect(data[merge_request.id].downvotes).to eq(1)
+ expect(data[merge_request.id].notes_count).to eq(1)
+ expect(data[merge_request.id].merge_requests_count).to eq(0)
+
+ expect(data[merge_request_closed.id].upvotes).to eq(0)
+ expect(data[merge_request_closed.id].downvotes).to eq(0)
+ expect(data[merge_request_closed.id].notes_count).to eq(0)
+ expect(data[merge_request_closed.id].merge_requests_count).to eq(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb
index c9a434b2bcf..aeb32ef96d6 100644
--- a/spec/lib/gitlab/issuable_sorter_spec.rb
+++ b/spec/lib/gitlab/issuable_sorter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::IssuableSorter, lib: true do
+describe Gitlab::IssuableSorter do
let(:namespace1) { build(:namespace, id: 1) }
let(:project1) { build(:project, id: 1, namespace: namespace1) }
diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb
index d09f51f3bfc..d7bebaca675 100644
--- a/spec/lib/gitlab/key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/key_fingerprint_spec.rb
@@ -1,12 +1,12 @@
require "spec_helper"
-describe Gitlab::KeyFingerprint, lib: true do
+describe Gitlab::KeyFingerprint do
let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" }
let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" }
describe "#fingerprint" do
it "generates the key's fingerprint" do
- expect(Gitlab::KeyFingerprint.new(key).fingerprint).to eq(fingerprint)
+ expect(described_class.new(key).fingerprint).to eq(fingerprint)
end
end
end
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index e8c599a95ee..34b33772578 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -46,4 +46,28 @@ describe Gitlab::Kubernetes do
expect(filter_by_label(items, app: 'foo')).to eq(matching_items)
end
end
+
+ describe '#to_kubeconfig' do
+ subject do
+ to_kubeconfig(
+ url: 'https://kube.domain.com',
+ namespace: 'NAMESPACE',
+ token: 'TOKEN',
+ ca_pem: ca_pem)
+ end
+
+ context 'when CA PEM is provided' do
+ let(:ca_pem) { 'PEM' }
+ let(:path) { expand_fixture_path('config/kubeconfig.yml') }
+
+ it { is_expected.to eq(YAML.load_file(path)) }
+ end
+
+ context 'when CA PEM is not provided' do
+ let(:ca_pem) { nil }
+ let(:path) { expand_fixture_path('config/kubeconfig-without-ca.yml') }
+
+ it { is_expected.to eq(YAML.load_file(path)) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/lazy_spec.rb b/spec/lib/gitlab/lazy_spec.rb
index b5ca89dd242..37a3ac74316 100644
--- a/spec/lib/gitlab/lazy_spec.rb
+++ b/spec/lib/gitlab/lazy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Lazy, lib: true do
+describe Gitlab::Lazy do
let(:dummy) { double(:dummy) }
context 'when not calling any methods' do
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 756fcb0fcaf..6a47350be81 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::LDAP::Access, lib: true do
- let(:access) { Gitlab::LDAP::Access.new user }
+describe Gitlab::LDAP::Access do
+ let(:access) { described_class.new user }
let(:user) { create(:omniauth_user) }
describe '.allowed?' do
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index 9454878b057..d17d440d833 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::LDAP::Adapter, lib: true do
+describe Gitlab::LDAP::Adapter do
include LdapHelpers
let(:ldap) { double(:ldap) }
@@ -16,7 +16,7 @@ describe Gitlab::LDAP::Adapter, lib: true do
expect(adapter).to receive(:ldap_search) do |arg|
expect(arg[:filter].to_s).to eq('(uid=johndoe)')
expect(arg[:base]).to eq('dc=example,dc=com')
- expect(arg[:attributes]).to match(%w{uid cn mail dn})
+ expect(arg[:attributes]).to match(%w{uid cn dn uid userid sAMAccountName mail email userPrincipalName})
end.and_return({})
adapter.users('uid', 'johndoe')
@@ -26,7 +26,7 @@ describe Gitlab::LDAP::Adapter, lib: true do
expect(adapter).to receive(:ldap_search).with(
base: 'uid=johndoe,ou=users,dc=example,dc=com',
scope: Net::LDAP::SearchScope_BaseObject,
- attributes: %w{uid cn mail dn},
+ attributes: %w{uid cn dn uid userid sAMAccountName mail email userPrincipalName},
filter: nil
).and_return({})
@@ -63,7 +63,7 @@ describe Gitlab::LDAP::Adapter, lib: true do
it 'uses the right uid attribute when non-default' do
stub_ldap_config(uid: 'sAMAccountName')
expect(adapter).to receive(:ldap_search).with(
- hash_including(attributes: %w{sAMAccountName cn mail dn})
+ hash_including(attributes: %w{sAMAccountName cn dn uid userid sAMAccountName mail email userPrincipalName})
).and_return({})
adapter.users('sAMAccountName', 'johndoe')
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 7a2f774b948..57a91193004 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe Gitlab::LDAP::AuthHash, lib: true do
+describe Gitlab::LDAP::AuthHash do
let(:auth_hash) do
- Gitlab::LDAP::AuthHash.new(
+ described_class.new(
OmniAuth::AuthHash.new(
uid: '123456',
provider: 'ldapmain',
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index f689b47fec4..01b6282af0c 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::LDAP::Authentication, lib: true do
+describe Gitlab::LDAP::Authentication do
let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index cab2e9908ff..292ec064a67 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe Gitlab::LDAP::Config, lib: true do
+describe Gitlab::LDAP::Config do
include LdapHelpers
- let(:config) { Gitlab::LDAP::Config.new('ldapmain') }
+ let(:config) { described_class.new('ldapmain') }
- describe '#initalize' do
+ describe '#initialize' do
it 'requires a provider' do
- expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError
+ expect{ described_class.new }.to raise_error ArgumentError
end
it 'works' do
@@ -15,7 +15,7 @@ describe Gitlab::LDAP::Config, lib: true do
end
it 'raises an error if a unknown provider is used' do
- expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error(RuntimeError)
+ expect{ described_class.new 'unknown' }.to raise_error(RuntimeError)
end
end
@@ -23,9 +23,9 @@ describe Gitlab::LDAP::Config, lib: true do
it 'constructs basic options' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 386,
- 'method' => 'plain'
+ 'host' => 'ldap.example.com',
+ 'port' => 386,
+ 'encryption' => 'plain'
}
)
@@ -39,24 +39,140 @@ describe Gitlab::LDAP::Config, lib: true do
it 'includes authentication options when auth is configured' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 686,
- 'method' => 'ssl',
- 'bind_dn' => 'uid=admin,dc=example,dc=com',
- 'password' => 'super_secret'
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'bind_dn' => 'uid=admin,dc=example,dc=com',
+ 'password' => 'super_secret'
}
)
- expect(config.adapter_options).to eq(
- host: 'ldap.example.com',
- port: 686,
- encryption: :simple_tls,
+ expect(config.adapter_options).to include({
auth: {
method: :simple,
username: 'uid=admin,dc=example,dc=com',
password: 'super_secret'
}
+ })
+ end
+
+ it 'sets encryption method to simple_tls when configured as simple_tls' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls'
+ }
)
+
+ expect(config.adapter_options[:encryption]).to include({ method: :simple_tls })
+ end
+
+ it 'sets encryption method to start_tls when configured as start_tls' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'start_tls'
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({ method: :start_tls })
+ end
+
+ context 'when verify_certificates is enabled' do
+ it 'sets tls_options to OpenSSL defaults' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({ tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS })
+ end
+ end
+
+ context 'when verify_certificates is disabled' do
+ it 'sets verify_mode to OpenSSL VERIFY_NONE' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => false
+ }
+ )
+
+ expect(config.adapter_options[:encryption]).to include({
+ tls_options: {
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
+ }
+ })
+ end
+ end
+
+ context 'when ca_file is specified' do
+ it 'passes it through in tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ca_file' => '/etc/ca.pem'
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).to include({ ca_file: '/etc/ca.pem' })
+ end
+ end
+
+ context 'when ca_file is a blank string' do
+ it 'does not add the ca_file key to tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ca_file' => ' '
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ca_file)
+ end
+ end
+
+ context 'when ssl_version is specified' do
+ it 'passes it through in tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ssl_version' => 'TLSv1_2'
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).to include({ ssl_version: 'TLSv1_2' })
+ end
+ end
+
+ context 'when ssl_version is a blank string' do
+ it 'does not add the ssl_version key to tls_options' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'ssl_version' => ' '
+ }
+ )
+
+ expect(config.adapter_options[:encryption][:tls_options]).not_to have_key(:ssl_version)
+ end
end
end
@@ -64,11 +180,11 @@ describe Gitlab::LDAP::Config, lib: true do
it 'constructs basic options' do
stub_ldap_config(
options: {
- 'host' => 'ldap.example.com',
- 'port' => 386,
- 'base' => 'ou=users,dc=example,dc=com',
- 'method' => 'plain',
- 'uid' => 'uid'
+ 'host' => 'ldap.example.com',
+ 'port' => 386,
+ 'base' => 'ou=users,dc=example,dc=com',
+ 'encryption' => 'plain',
+ 'uid' => 'uid'
}
)
@@ -76,7 +192,7 @@ describe Gitlab::LDAP::Config, lib: true do
host: 'ldap.example.com',
port: 386,
base: 'ou=users,dc=example,dc=com',
- method: 'plain',
+ encryption: 'plain',
filter: '(uid=%{username})'
)
expect(config.omniauth_options.keys).not_to include(:bind_dn, :password)
@@ -98,6 +214,100 @@ describe Gitlab::LDAP::Config, lib: true do
password: 'super_secret'
)
end
+
+ context 'when verify_certificates is enabled' do
+ it 'specifies disable_verify_certificates as false' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true
+ }
+ )
+
+ expect(config.omniauth_options).to include({ disable_verify_certificates: false })
+ end
+ end
+
+ context 'when verify_certificates is disabled' do
+ it 'specifies disable_verify_certificates as true' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => false
+ }
+ )
+
+ expect(config.omniauth_options).to include({ disable_verify_certificates: true })
+ end
+ end
+
+ context 'when ca_file is present' do
+ it 'passes it through' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ca_file' => '/etc/ca.pem'
+ }
+ )
+
+ expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' })
+ end
+ end
+
+ context 'when ca_file is blank' do
+ it 'does not include the ca_file option' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ca_file' => ' '
+ }
+ )
+
+ expect(config.omniauth_options).not_to have_key(:ca_file)
+ end
+ end
+
+ context 'when ssl_version is present' do
+ it 'passes it through' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ssl_version' => 'TLSv1_2'
+ }
+ )
+
+ expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' })
+ end
+ end
+
+ context 'when ssl_version is blank' do
+ it 'does not include the ssl_version option' do
+ stub_ldap_config(
+ options: {
+ 'host' => 'ldap.example.com',
+ 'port' => 686,
+ 'encryption' => 'simple_tls',
+ 'verify_certificates' => true,
+ 'ssl_version' => ' '
+ }
+ )
+
+ expect(config.omniauth_options).not_to have_key(:ssl_version)
+ end
+ end
end
describe '#has_auth?' do
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index b796d8bf076..175ceec44d7 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::LDAP::User, lib: true do
- let(:ldap_user) { Gitlab::LDAP::User.new(auth_hash) }
+describe Gitlab::LDAP::User do
+ let(:ldap_user) { described_class.new(auth_hash) }
let(:gl_user) { ldap_user.gl_user }
let(:info) do
{
@@ -13,7 +13,7 @@ describe Gitlab::LDAP::User, lib: true do
let(:auth_hash) do
OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info)
end
- let(:ldap_user_upper_case) { Gitlab::LDAP::User.new(auth_hash_upper_case) }
+ let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) }
let(:info_upper_case) do
{
name: 'John',
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index e9c1163e22a..3a20dad16d0 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::LfsToken, lib: true do
+describe Gitlab::LfsToken do
describe '#token' do
shared_examples 'an LFS token generator' do
it 'returns a randomly generated token' do
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index 93b91b849f2..09e518ff989 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -1,40 +1,40 @@
require 'spec_helper'
-describe Gitlab::MarkupHelper, lib: true do
+describe Gitlab::MarkupHelper do
describe '#markup?' do
%w(textile rdoc org creole wiki
mediawiki rst adoc ad asciidoc mdown md markdown).each do |type|
it "returns true for #{type} files" do
- expect(Gitlab::MarkupHelper.markup?("README.#{type}")).to be_truthy
+ expect(described_class.markup?("README.#{type}")).to be_truthy
end
end
it 'returns false when given a non-markup filename' do
- expect(Gitlab::MarkupHelper.markup?('README.rb')).not_to be_truthy
+ expect(described_class.markup?('README.rb')).not_to be_truthy
end
end
describe '#gitlab_markdown?' do
%w(mdown mkd mkdn md markdown).each do |type|
it "returns true for #{type} files" do
- expect(Gitlab::MarkupHelper.gitlab_markdown?("README.#{type}")).to be_truthy
+ expect(described_class.gitlab_markdown?("README.#{type}")).to be_truthy
end
end
it 'returns false when given a non-markdown filename' do
- expect(Gitlab::MarkupHelper.gitlab_markdown?('README.rb')).not_to be_truthy
+ expect(described_class.gitlab_markdown?('README.rb')).not_to be_truthy
end
end
describe '#asciidoc?' do
%w(adoc ad asciidoc ADOC).each do |type|
it "returns true for #{type} files" do
- expect(Gitlab::MarkupHelper.asciidoc?("README.#{type}")).to be_truthy
+ expect(described_class.asciidoc?("README.#{type}")).to be_truthy
end
end
it 'returns false when given a non-asciidoc filename' do
- expect(Gitlab::MarkupHelper.asciidoc?('README.rb')).not_to be_truthy
+ expect(described_class.asciidoc?('README.rb')).not_to be_truthy
end
end
end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
index d07ce6f81af..0bc68d64276 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/influx_sampler_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Metrics::Sampler do
+describe Gitlab::Metrics::InfluxSampler do
let(:sampler) { described_class.new(5) }
after do
@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
end
describe '#start' do
- it 'gathers a sample at a given interval' do
- expect(sampler).to receive(:sleep).with(a_kind_of(Numeric))
- expect(sampler).to receive(:sample)
- expect(sampler).to receive(:loop).and_yield
+ it 'runs once and gathers a sample at a given interval' do
+ expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
+ expect(sampler).to receive(:sample).once
+ expect(sampler).to receive(:running).and_return(false, true, false)
sampler.start.join
end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
new file mode 100644
index 00000000000..461b1e4182a
--- /dev/null
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::RequestsRackMiddleware do
+ let(:app) { double('app') }
+ subject { described_class.new(app) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ describe '#call' do
+ let(:status) { 100 }
+ let(:env) { { 'REQUEST_METHOD' => 'GET' } }
+ let(:stack_result) { [status, {}, 'body'] }
+
+ before do
+ allow(app).to receive(:call).and_return(stack_result)
+ end
+
+ context '@app.call succeeds with 200' do
+ before do
+ allow(app).to receive(:call).and_return([200, nil, nil])
+ end
+
+ it 'increments requests count' do
+ expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
+
+ subject.call(env)
+ end
+
+ it 'measures execution time' do
+ execution_time = 10
+ allow(app).to receive(:call) do |*args|
+ Timecop.freeze(execution_time.seconds)
+ [200, nil, nil]
+ end
+
+ expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, execution_time)
+
+ subject.call(env)
+ end
+ end
+
+ context '@app.call throws exception' do
+ let(:http_request_duration_seconds) { double('http_request_duration_seconds') }
+
+ before do
+ allow(app).to receive(:call).and_raise(StandardError)
+ allow(described_class).to receive(:http_request_duration_seconds).and_return(http_request_duration_seconds)
+ end
+
+ it 'increments exceptions count' do
+ expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+
+ it 'increments requests count' do
+ expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+
+ it "does't measure request execution time" do
+ expect(described_class.http_request_duration_seconds).not_to receive(:increment)
+
+ expect { subject.call(env) }.to raise_error(StandardError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
new file mode 100644
index 00000000000..dc0d1f2e940
--- /dev/null
+++ b/spec/lib/gitlab/metrics/unicorn_sampler_spec.rb
@@ -0,0 +1,108 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::UnicornSampler do
+ subject { described_class.new(1.second) }
+
+ describe '#sample' do
+ let(:unicorn) { double('unicorn') }
+ let(:raindrops) { double('raindrops') }
+ let(:stats) { double('stats') }
+
+ before do
+ stub_const('Unicorn', unicorn)
+ stub_const('Raindrops::Linux', raindrops)
+ allow(raindrops).to receive(:unix_listener_stats).and_return({})
+ allow(raindrops).to receive(:tcp_listener_stats).and_return({})
+ end
+
+ context 'unicorn listens on unix sockets' do
+ let(:socket_address) { '/some/sock' }
+ let(:sockets) { [socket_address] }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return(sockets)
+ end
+
+ it 'samples socket data' do
+ expect(raindrops).to receive(:unix_listener_stats).with(sockets)
+
+ subject.sample
+ end
+
+ context 'stats collected' do
+ before do
+ allow(stats).to receive(:active).and_return('active')
+ allow(stats).to receive(:queued).and_return('queued')
+ allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
+ end
+
+ it 'updates metrics type unix and with addr' do
+ labels = { type: 'unix', address: socket_address }
+
+ expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+ expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+ subject.sample
+ end
+ end
+ end
+
+ context 'unicorn listens on tcp sockets' do
+ let(:tcp_socket_address) { '0.0.0.0:8080' }
+ let(:tcp_sockets) { [tcp_socket_address] }
+
+ before do
+ allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
+ end
+
+ it 'samples socket data' do
+ expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
+
+ subject.sample
+ end
+
+ context 'stats collected' do
+ before do
+ allow(stats).to receive(:active).and_return('active')
+ allow(stats).to receive(:queued).and_return('queued')
+ allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
+ end
+
+ it 'updates metrics type unix and with addr' do
+ labels = { type: 'tcp', address: tcp_socket_address }
+
+ expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
+ expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
+
+ subject.sample
+ end
+ end
+ end
+ end
+
+ describe '#start' do
+ context 'when enabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(true)
+ end
+
+ it 'creates new thread' do
+ expect(Thread).to receive(:new)
+
+ subject.start
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ allow(subject).to receive(:enabled?).and_return(false)
+ end
+
+ it "doesn't create new thread" do
+ expect(Thread).not_to receive(:new)
+
+ subject.start
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index c2ab015d5cb..6af1564da19 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Middleware::Go, lib: true do
+describe Gitlab::Middleware::Go do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
index 19ab17419fc..d5f4da3ce36 100644
--- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe Gitlab::OAuth::AuthHash, lib: true do
+describe Gitlab::OAuth::AuthHash do
let(:auth_hash) do
- Gitlab::OAuth::AuthHash.new(
+ described_class.new(
OmniAuth::AuthHash.new(
provider: provider_ascii,
uid: uid_ascii,
diff --git a/spec/lib/gitlab/o_auth/provider_spec.rb b/spec/lib/gitlab/o_auth/provider_spec.rb
index 1e2a1f8c039..30faf107e3f 100644
--- a/spec/lib/gitlab/o_auth/provider_spec.rb
+++ b/spec/lib/gitlab/o_auth/provider_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::OAuth::Provider, lib: true do
+describe Gitlab::OAuth::Provider do
describe '#config_for' do
context 'for an LDAP provider' do
context 'when the provider exists' do
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index ea29cb9caf1..47aa19d5fd9 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::OAuth::User, lib: true do
- let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) }
+describe Gitlab::OAuth::User do
+ let(:oauth_user) { described_class.new(auth_hash) }
let(:gl_user) { oauth_user.gl_user }
let(:uid) { 'my-uid' }
let(:provider) { 'my-provider' }
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index acce2be93f2..81f81d4f963 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::OptimisticLocking, lib: true do
+describe Gitlab::OptimisticLocking do
let!(:pipeline) { create(:ci_pipeline) }
let!(:pipeline2) { Ci::Pipeline.find(pipeline.id) }
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index c0f5fa9dc1f..e26f39e193e 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::OtherMarkup, lib: true do
+describe Gitlab::OtherMarkup do
let(:context) { {} }
context "XSS Checks" do
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 1eea710c80b..2f989397f7e 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -1,7 +1,7 @@
# coding: utf-8
require 'spec_helper'
-describe Gitlab::PathRegex, lib: true do
+describe Gitlab::PathRegex do
# Pass in a full path to remove the format segment:
# `/ci/lint(.:format)` -> `/ci/lint`
def without_format(path)
@@ -36,9 +36,12 @@ describe Gitlab::PathRegex, lib: true do
described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
end
- def failure_message(missing_words, constant_name, migration_helper)
+ def failure_message(constant_name, migration_helper, missing_words: [], additional_words: [])
missing_words = Array(missing_words)
- <<-MSG
+ additional_words = Array(additional_words)
+ message = ""
+ if missing_words.any?
+ message += <<-MISSING
Found new routes that could cause conflicts with existing namespaced routes
for groups or projects.
@@ -51,7 +54,18 @@ describe Gitlab::PathRegex, lib: true do
Make sure to make a note of the renamed records in the release blog post.
- MSG
+ MISSING
+ end
+
+ if additional_words.any?
+ message += <<-ADDITIONAL
+ Why are <#{additional_words.join(', ')}> in `#{constant_name}`?
+ If they are really required, update these specs to reflect that.
+
+ ADDITIONAL
+ end
+
+ message
end
let(:all_routes) do
@@ -68,9 +82,23 @@ describe Gitlab::PathRegex, lib: true do
let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
let(:top_level_words) do
- routes_not_starting_in_wildcard.map do |route|
+ words = routes_not_starting_in_wildcard.map do |route|
route.split('/')[1]
end.compact.uniq
+
+ words + ee_top_level_words + files_in_public + Array(API::API.prefix.to_s)
+ end
+
+ let(:ee_top_level_words) do
+ ['unsubscribes']
+ end
+
+ let(:files_in_public) do
+ git = Gitlab.config.git.bin_path
+ `cd #{Rails.root} && #{git} ls-files public`
+ .split("\n")
+ .map { |entry| entry.gsub('public/', '') }
+ .uniq
end
# All routes that start with a namespaced path, that have 1 or more
@@ -115,18 +143,29 @@ describe Gitlab::PathRegex, lib: true do
let(:paths_after_group_id) do
group_routes.map do |route|
route.gsub(STARTING_WITH_GROUP, '').split('/').first
- end.uniq
+ end.uniq + ee_paths_after_group_id
+ end
+
+ let(:ee_paths_after_group_id) do
+ %w(analytics
+ ldap
+ ldap_group_links
+ notification_setting
+ audit_events
+ pipeline_quota hooks)
end
describe 'TOP_LEVEL_ROUTES' do
it 'includes all the top level namespaces' do
failure_block = lambda do
missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
- failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+ additional_words = described_class::TOP_LEVEL_ROUTES - top_level_words
+ failure_message('TOP_LEVEL_ROUTES', 'rename_root_paths',
+ missing_words: missing_words, additional_words: additional_words)
end
expect(described_class::TOP_LEVEL_ROUTES)
- .to include(*top_level_words), failure_block
+ .to contain_exactly(*top_level_words), failure_block
end
end
@@ -134,11 +173,13 @@ describe Gitlab::PathRegex, lib: true do
it "don't contain a second wildcard" do
failure_block = lambda do
missing_words = paths_after_group_id - described_class::GROUP_ROUTES
- failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
+ additional_words = described_class::GROUP_ROUTES - paths_after_group_id
+ failure_message('GROUP_ROUTES', 'rename_child_paths',
+ missing_words: missing_words, additional_words: additional_words)
end
expect(described_class::GROUP_ROUTES)
- .to include(*paths_after_group_id), failure_block
+ .to contain_exactly(*paths_after_group_id), failure_block
end
end
@@ -147,7 +188,7 @@ describe Gitlab::PathRegex, lib: true do
aggregate_failures do
all_wildcard_paths.each do |path|
expect(wildcards_include?(path))
- .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
+ .to be(true), failure_message('PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths', missing_words: path)
end
end
end
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
new file mode 100644
index 00000000000..b8a2267f1a4
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -0,0 +1,92 @@
+require 'spec_helper'
+
+describe Gitlab::PerformanceBar do
+ shared_examples 'allowed user IDs are cached' do
+ before do
+ # Warm the Redis cache
+ described_class.enabled?(user)
+ end
+
+ it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
+ expect do
+ expect(described_class.enabled?(user)).to be_truthy
+ end.not_to exceed_query_limit(0)
+ end
+ end
+
+ describe '.enabled?' do
+ let(:user) { create(:user) }
+
+ before do
+ stub_application_setting(performance_bar_allowed_group_id: -1)
+ end
+
+ it 'returns false when given user is nil' do
+ expect(described_class.enabled?(nil)).to be_falsy
+ end
+
+ it 'returns false when allowed_group_id is nil' do
+ expect(described_class).to receive(:allowed_group_id).and_return(nil)
+
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+
+ context 'when allowed group ID does not exist' do
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+ end
+
+ context 'when allowed group exists' do
+ let!(:my_group) { create(:group, path: 'my-group') }
+
+ before do
+ stub_application_setting(performance_bar_allowed_group_id: my_group.id)
+ end
+
+ context 'when user is not a member of the allowed group' do
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+
+ it_behaves_like 'allowed user IDs are cached'
+ end
+
+ context 'when user is a member of the allowed group' do
+ before do
+ my_group.add_developer(user)
+ end
+
+ it 'returns true' do
+ expect(described_class.enabled?(user)).to be_truthy
+ end
+
+ it_behaves_like 'allowed user IDs are cached'
+ end
+ end
+
+ context 'when allowed group is nested', :nested_groups do
+ let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
+
+ before do
+ create(:group, path: 'my-group')
+ nested_my_group.add_developer(user)
+ stub_application_setting(performance_bar_allowed_group_id: nested_my_group.id)
+ end
+
+ it 'returns the nested group' do
+ expect(described_class.enabled?(user)).to be_truthy
+ end
+ end
+
+ context 'when a nested group has the same path', :nested_groups do
+ before do
+ create(:group, :nested, path: 'my-group').add_developer(user)
+ end
+
+ it 'returns false' do
+ expect(described_class.enabled?(user)).to be_falsy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb
index 5ea8ecb1c30..eb8e618156b 100644
--- a/spec/lib/gitlab/polling_interval_spec.rb
+++ b/spec/lib/gitlab/polling_interval_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::PollingInterval, lib: true do
+describe Gitlab::PollingInterval do
let(:polling_interval) { described_class }
describe '.set_header' do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index af50ecdb2ab..4567f220c11 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Gitlab::Popen', lib: true, no_db: true do
+describe 'Gitlab::Popen' do
let(:path) { Rails.root.join('tmp').to_s }
before do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 3d22784909d..d17b436b910 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ProjectSearchResults, lib: true do
+describe Gitlab::ProjectSearchResults do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:query) { 'hello world' }
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index e2d6b1b9ab7..10c5fb148cd 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Gitlab::ProjectTransfer, lib: true do
+describe Gitlab::ProjectTransfer do
before do
@root_dir = File.join(Rails.root, "public", "uploads")
- @project_transfer = Gitlab::ProjectTransfer.new
+ @project_transfer = described_class.new
allow(@project_transfer).to receive(:root_dir).and_return(@root_dir)
@project_path_was = "test_project_was"
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 61d48b05454..d7df4e35c31 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus::AdditionalMetricsParser, lib: true do
+describe Gitlab::Prometheus::AdditionalMetricsParser do
include Prometheus::MetricBuilders
let(:parser_error_class) { Gitlab::Prometheus::ParsingError }
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index 4909aec5a4d..e42e034f4fb 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery, lib: true do
+describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
include Prometheus::MetricBuilders
let(:client) { double('prometheus_client') }
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index 8e6e3bb5946..e9fd66d45fe 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery, lib: true do
+describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
include Prometheus::MetricBuilders
let(:client) { double('prometheus_client') }
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index d957dd932c4..ffe3ad85baa 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::DeploymentQuery, lib: true do
+describe Gitlab::Prometheus::Queries::DeploymentQuery do
let(:environment) { create(:environment, slug: 'environment-slug') }
let(:deployment) { create(:deployment, environment: environment) }
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
index d2796ab72da..2b488101496 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Prometheus::Queries::MatchedMetricsQuery, lib: true do
+describe Gitlab::Prometheus::Queries::MatchedMetricsQuery do
include Prometheus::MetricBuilders
let(:metric_group_class) { Gitlab::Prometheus::MetricGroup }
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 46eaadae206..de625324092 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::PrometheusClient, lib: true do
+describe Gitlab::PrometheusClient do
include PrometheusHelpers
subject { described_class.new(api_url: 'https://prometheus.example.com') }
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
new file mode 100644
index 00000000000..5a4f17cfcf6
--- /dev/null
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Gitlab::Redis::Cache do
+ let(:config_file_name) { "config/redis.cache.yml" }
+ let(:environment_config_file_name) { "GITLAB_REDIS_CACHE_CONFIG_FILE" }
+ let(:config_old_format_socket) { "spec/fixtures/config/redis_cache_old_format_socket.yml" }
+ let(:config_new_format_socket) { "spec/fixtures/config/redis_cache_new_format_socket.yml" }
+ let(:old_socket_path) {"/path/to/old/redis.cache.sock" }
+ let(:new_socket_path) {"/path/to/redis.cache.sock" }
+ let(:config_old_format_host) { "spec/fixtures/config/redis_cache_old_format_host.yml" }
+ let(:config_new_format_host) { "spec/fixtures/config/redis_cache_new_format_host.yml" }
+ let(:redis_port) { 6380 }
+ let(:redis_database) { 10 }
+ let(:sentinel_port) { redis_port + 20000 }
+ let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_cache_config_with_env.yml"}
+ let(:config_env_variable_url) {"TEST_GITLAB_REDIS_CACHE_URL"}
+ let(:class_redis_url) { Gitlab::Redis::Cache::DEFAULT_REDIS_CACHE_URL }
+
+ include_examples "redis_shared_examples"
+end
diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb
new file mode 100644
index 00000000000..01ca25635a9
--- /dev/null
+++ b/spec/lib/gitlab/redis/queues_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Gitlab::Redis::Queues do
+ let(:config_file_name) { "config/redis.queues.yml" }
+ let(:environment_config_file_name) { "GITLAB_REDIS_QUEUES_CONFIG_FILE" }
+ let(:config_old_format_socket) { "spec/fixtures/config/redis_queues_old_format_socket.yml" }
+ let(:config_new_format_socket) { "spec/fixtures/config/redis_queues_new_format_socket.yml" }
+ let(:old_socket_path) {"/path/to/old/redis.queues.sock" }
+ let(:new_socket_path) {"/path/to/redis.queues.sock" }
+ let(:config_old_format_host) { "spec/fixtures/config/redis_queues_old_format_host.yml" }
+ let(:config_new_format_host) { "spec/fixtures/config/redis_queues_new_format_host.yml" }
+ let(:redis_port) { 6381 }
+ let(:redis_database) { 11 }
+ let(:sentinel_port) { redis_port + 20000 }
+ let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_queues_config_with_env.yml"}
+ let(:config_env_variable_url) {"TEST_GITLAB_REDIS_QUEUES_URL"}
+ let(:class_redis_url) { Gitlab::Redis::Queues::DEFAULT_REDIS_QUEUES_URL }
+
+ include_examples "redis_shared_examples"
+end
diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb
new file mode 100644
index 00000000000..24b73745dc5
--- /dev/null
+++ b/spec/lib/gitlab/redis/shared_state_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Gitlab::Redis::SharedState do
+ let(:config_file_name) { "config/redis.shared_state.yml" }
+ let(:environment_config_file_name) { "GITLAB_REDIS_SHARED_STATE_CONFIG_FILE" }
+ let(:config_old_format_socket) { "spec/fixtures/config/redis_shared_state_old_format_socket.yml" }
+ let(:config_new_format_socket) { "spec/fixtures/config/redis_shared_state_new_format_socket.yml" }
+ let(:old_socket_path) {"/path/to/old/redis.shared_state.sock" }
+ let(:new_socket_path) {"/path/to/redis.shared_state.sock" }
+ let(:config_old_format_host) { "spec/fixtures/config/redis_shared_state_old_format_host.yml" }
+ let(:config_new_format_host) { "spec/fixtures/config/redis_shared_state_new_format_host.yml" }
+ let(:redis_port) { 6382 }
+ let(:redis_database) { 12 }
+ let(:sentinel_port) { redis_port + 20000 }
+ let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_shared_state_config_with_env.yml"}
+ let(:config_env_variable_url) {"TEST_GITLAB_REDIS_SHARED_STATE_URL"}
+ let(:class_redis_url) { Gitlab::Redis::SharedState::DEFAULT_REDIS_SHARED_STATE_URL }
+
+ include_examples "redis_shared_examples"
+end
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
new file mode 100644
index 00000000000..e1becd0a614
--- /dev/null
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Gitlab::Redis::Wrapper do
+ let(:config_file_name) { "config/resque.yml" }
+ let(:environment_config_file_name) { "GITLAB_REDIS_CONFIG_FILE" }
+ let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" }
+ let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
+ let(:old_socket_path) {"/path/to/old/redis.sock" }
+ let(:new_socket_path) {"/path/to/redis.sock" }
+ let(:config_old_format_host) { "spec/fixtures/config/redis_old_format_host.yml" }
+ let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
+ let(:redis_port) { 6379 }
+ let(:redis_database) { 99 }
+ let(:sentinel_port) { redis_port + 20000 }
+ let(:config_with_environment_variable_inside) { "spec/fixtures/config/redis_config_with_env.yml"}
+ let(:config_env_variable_url) {"TEST_GITLAB_REDIS_URL"}
+ let(:class_redis_url) { Gitlab::Redis::Wrapper::DEFAULT_REDIS_URL }
+
+ include_examples "redis_shared_examples"
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 84cfd934fa0..1a0357534f2 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe Gitlab::ReferenceExtractor, lib: true do
+describe Gitlab::ReferenceExtractor do
let(:project) { create(:empty_project) }
before do
project.team << [project.creator, :developer]
end
- subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
+ subject { described_class.new(project, project.creator) }
it 'accesses valid user objects' do
@u_foo = create(:user, username: 'foo')
@@ -183,11 +183,34 @@ describe Gitlab::ReferenceExtractor, lib: true do
context 'with an external issue tracker' do
let(:project) { create(:jira_project) }
+ let(:issue) { create(:issue, project: project) }
+
+ context 'when GitLab issues are enabled' do
+ it 'returns both JIRA and internal issues' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project),
+ issue]
+ end
+
+ it 'returns only JIRA issues if the internal one does not exists' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #999")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project)]
+ end
+ end
- it 'returns JIRA issues for a JIRA-integrated project' do
- subject.analyze('JIRA-123 and FOOBAR-4567')
- expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
- ExternalIssue.new('FOOBAR-4567', project)]
+ context 'when GitLab issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'returns only JIRA issues' do
+ subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
+ expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
+ ExternalIssue.new('FOOBAR-4567', project)]
+ end
end
end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 979f4fefcb6..68a57826647 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -1,7 +1,7 @@
# coding: utf-8
require 'spec_helper'
-describe Gitlab::Regex, lib: true do
+describe Gitlab::Regex do
describe '.project_name_regex' do
subject { described_class.project_name_regex }
@@ -14,12 +14,6 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('?gitlab') }
end
- describe '.file_name_regex' do
- subject { described_class.file_name_regex }
-
- it { is_expected.to match('foo@bar') }
- end
-
describe '.environment_slug_regex' do
subject { described_class.environment_name_regex }
@@ -44,4 +38,15 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') }
end
+
+ describe '.container_repository_name_regex' do
+ subject { described_class.container_repository_name_regex }
+
+ it { is_expected.to match('image') }
+ it { is_expected.to match('my/image') }
+ it { is_expected.to match('my/awesome/image-1') }
+ it { is_expected.to match('my/awesome/image.test') }
+ it { is_expected.not_to match('.my/image') }
+ it { is_expected.not_to match('my/image.') }
+ end
end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index a91c8655cdd..e272bdb9284 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
-describe Gitlab::RequestContext, lib: true do
+describe Gitlab::RequestContext do
describe '#client_ip' do
- subject { Gitlab::RequestContext.client_ip }
+ subject { described_class.client_ip }
let(:app) { -> (env) {} }
let(:env) { Hash.new }
@@ -16,7 +16,7 @@ describe Gitlab::RequestContext, lib: true do
before do
allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
- Gitlab::RequestContext.new(app).call(env)
+ described_class.new(app).call(env)
end
it { is_expected.to eq(ip) }
diff --git a/spec/lib/gitlab/request_forgery_protection_spec.rb b/spec/lib/gitlab/request_forgery_protection_spec.rb
new file mode 100644
index 00000000000..305de613866
--- /dev/null
+++ b/spec/lib/gitlab/request_forgery_protection_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
+ let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
+ let(:env) do
+ {
+ 'rack.input' => '',
+ 'rack.session' => {
+ _csrf_token: csrf_token
+ }
+ }
+ end
+
+ describe '.call' do
+ context 'when the request method is GET' do
+ before do
+ env['REQUEST_METHOD'] = 'GET'
+ end
+
+ it 'does not raise an exception' do
+ expect { described_class.call(env) }.not_to raise_exception
+ end
+ end
+
+ context 'when the request method is POST' do
+ before do
+ env['REQUEST_METHOD'] = 'POST'
+ end
+
+ context 'when the CSRF token is valid' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = csrf_token
+ end
+
+ it 'does not raise an exception' do
+ expect { described_class.call(env) }.not_to raise_exception
+ end
+ end
+
+ context 'when the CSRF token is invalid' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = 'foo'
+ end
+
+ it 'raises an ActionController::InvalidAuthenticityToken exception' do
+ expect { described_class.call(env) }.to raise_exception(ActionController::InvalidAuthenticityToken)
+ end
+ end
+ end
+ end
+
+ describe '.verified?' do
+ context 'when the request method is GET' do
+ before do
+ env['REQUEST_METHOD'] = 'GET'
+ end
+
+ it 'returns true' do
+ expect(described_class.verified?(env)).to be_truthy
+ end
+ end
+
+ context 'when the request method is POST' do
+ before do
+ env['REQUEST_METHOD'] = 'POST'
+ end
+
+ context 'when the CSRF token is valid' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = csrf_token
+ end
+
+ it 'returns true' do
+ expect(described_class.verified?(env)).to be_truthy
+ end
+ end
+
+ context 'when the CSRF token is invalid' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = 'foo'
+ end
+
+ it 'returns false' do
+ expect(described_class.verified?(env)).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index ae9c06ebb7d..fd8cbf39bce 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::RequestProfiler, lib: true do
+describe Gitlab::RequestProfiler do
describe '.profile_token' do
it 'returns a token' do
expect(described_class.profile_token).to be_present
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index 21c00c6e5b8..d672f7b5675 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::RouteMap, lib: true do
+describe Gitlab::RouteMap do
describe '#initialize' do
context 'when the data is not YAML' do
it 'raises an error' do
@@ -55,6 +55,19 @@ describe Gitlab::RouteMap, lib: true do
end
describe '#public_path_for_source_path' do
+ context 'malicious regexp' do
+ include_examples 'malicious regexp'
+
+ subject do
+ map = described_class.new(<<-"MAP".strip_heredoc)
+ - source: '#{malicious_regexp}'
+ public: '/'
+ MAP
+
+ map.public_path_for_source_path(malicious_text)
+ end
+ end
+
subject do
described_class.new(<<-'MAP'.strip_heredoc)
# Team data
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
index a4d2367b72a..2827a18515e 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Saml::User, lib: true do
+describe Gitlab::Saml::User do
let(:saml_user) { described_class.new(auth_hash) }
let(:gl_user) { saml_user.gl_user }
let(:uid) { 'my-uid' }
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 5b1b8f9516a..b90d8dede0f 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
require 'stringio'
-describe Gitlab::Shell, lib: true do
+describe Gitlab::Shell do
let(:project) { double('Project', id: 7, path: 'diaspora') }
- let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
before do
@@ -30,7 +30,7 @@ describe Gitlab::Shell, lib: true do
allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file)
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test')
FileUtils.mkdir('tmp/tests/shell-secret-test')
- Gitlab::Shell.ensure_secret_token!
+ described_class.ensure_secret_token!
end
after do
@@ -39,7 +39,7 @@ describe Gitlab::Shell, lib: true do
end
it 'creates and links the secret token file' do
- secret_token = Gitlab::Shell.secret_token
+ secret_token = described_class.secret_token
expect(File.exist?(secret_file)).to be(true)
expect(File.read(secret_file).chomp).to eq(secret_token)
@@ -59,7 +59,7 @@ describe Gitlab::Shell, lib: true do
end
end
- describe Gitlab::Shell::KeyAdder, lib: true do
+ describe Gitlab::Shell::KeyAdder do
describe '#add_key' do
it 'removes trailing garbage' do
io = spy(:io)
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index 2ae79b50e77..873ed14f804 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Collection, lib: true do
+describe Gitlab::Sherlock::Collection do
let(:collection) { described_class.new }
let(:transaction) do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index 4989d14def3..394421504e0 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::FileSample, lib: true do
+describe Gitlab::Sherlock::FileSample do
let(:sample) { described_class.new(__FILE__, [], 150.4, 2) }
describe '#id' do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index 39c6b2a4844..f2f8040fa0b 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineProfiler, lib: true do
+describe Gitlab::Sherlock::LineProfiler do
let(:profiler) { described_class.new }
describe '#profile' do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index f9b61f8684e..5f02f6a3213 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::LineSample, lib: true do
+describe Gitlab::Sherlock::LineSample do
let(:sample) { described_class.new(150.0, 4) }
describe '#duration' do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index 5739afa6b1e..b295a624b35 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Location, lib: true do
+describe Gitlab::Sherlock::Location do
let(:location) { described_class.new(__FILE__, 1) }
describe 'from_ruby_location' do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index b98ab0b14a2..2016023df06 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Middleware, lib: true do
+describe Gitlab::Sherlock::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index d97b5eef573..426071c7f92 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Query, lib: true do
+describe Gitlab::Sherlock::Query do
let(:started_at) { Time.utc(2015, 1, 1) }
let(:finished_at) { started_at + 5 }
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 6ae1aa20ea7..4a14dfbec56 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Sherlock::Transaction, lib: true do
+describe Gitlab::Sherlock::Transaction do
let(:transaction) { described_class.new('POST', '/cat_pictures') }
describe '#id' do
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 496e50fbae4..c2e77ef6b6c 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::SidekiqStatus do
- describe '.set', :redis do
+ describe '.set', :clean_gitlab_redis_shared_state do
it 'stores the job ID' do
described_class.set('123')
@@ -14,7 +14,7 @@ describe Gitlab::SidekiqStatus do
end
end
- describe '.unset', :redis do
+ describe '.unset', :clean_gitlab_redis_shared_state do
it 'removes the job ID' do
described_class.set('123')
described_class.unset('123')
@@ -27,7 +27,7 @@ describe Gitlab::SidekiqStatus do
end
end
- describe '.all_completed?', :redis do
+ describe '.all_completed?', :clean_gitlab_redis_shared_state do
it 'returns true if all jobs have been completed' do
expect(described_class.all_completed?(%w(123))).to eq(true)
end
@@ -39,7 +39,7 @@ describe Gitlab::SidekiqStatus do
end
end
- describe '.num_running', :redis do
+ describe '.num_running', :clean_gitlab_redis_shared_state do
it 'returns 0 if all jobs have been completed' do
expect(described_class.num_running(%w(123))).to eq(0)
end
@@ -52,7 +52,7 @@ describe Gitlab::SidekiqStatus do
end
end
- describe '.num_completed', :redis do
+ describe '.num_completed', :clean_gitlab_redis_shared_state do
it 'returns 1 if all jobs have been completed' do
expect(described_class.num_completed(%w(123))).to eq(1)
end
@@ -74,7 +74,7 @@ describe Gitlab::SidekiqStatus do
end
end
- describe 'completed', :redis do
+ describe 'completed', :clean_gitlab_redis_shared_state do
it 'returns the completed job' do
expect(described_class.completed_jids(%w(123))).to eq(['123'])
end
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index 28d7f9858c3..f0ecf59406a 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Command, service: true do
+describe Gitlab::SlashCommands::Command do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index d919f7260db..e52aaed7328 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::Deploy, service: true do
+describe Gitlab::SlashCommands::Deploy do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 4de50d4a8bb..5dfb1b506bc 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueNew, service: true do
+describe Gitlab::SlashCommands::IssueNew do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index 06fff0afc50..e5409fe2339 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueSearch, service: true do
+describe Gitlab::SlashCommands::IssueSearch do
describe '#execute' do
let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
index 1899f664ccd..f67a17c7922 100644
--- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SlashCommands::IssueShow, service: true do
+describe Gitlab::SlashCommands::IssueShow do
describe '#execute' do
let(:issue) { create(:issue, project: project) }
let(:project) { create(:empty_project) }
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
new file mode 100644
index 00000000000..f0bb4294d62
--- /dev/null
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::Glob do
+ describe '.to_like' do
+ it 'matches * as %' do
+ expect(glob('apple', '*')).to be(true)
+ expect(glob('apple', 'app*')).to be(true)
+ expect(glob('apple', 'apple*')).to be(true)
+ expect(glob('apple', '*pple')).to be(true)
+ expect(glob('apple', 'ap*le')).to be(true)
+
+ expect(glob('apple', '*a')).to be(false)
+ expect(glob('apple', 'app*a')).to be(false)
+ expect(glob('apple', 'ap*l')).to be(false)
+ end
+
+ it 'matches % literally' do
+ expect(glob('100%', '100%')).to be(true)
+
+ expect(glob('100%', '%')).to be(false)
+ end
+
+ it 'matches _ literally' do
+ expect(glob('^_^', '^_^')).to be(true)
+
+ expect(glob('^A^', '^_^')).to be(false)
+ end
+ end
+
+ def glob(string, pattern)
+ match(string, subject.to_like(quote(pattern)))
+ end
+
+ def match(string, pattern)
+ value = query("SELECT #{quote(string)} LIKE #{pattern}")
+ .rows.flatten.first
+
+ case value
+ when 't', 1
+ true
+ else
+ false
+ end
+ end
+
+ def query(sql)
+ ActiveRecord::Base.connection.select_all(sql)
+ end
+
+ def quote(string)
+ ActiveRecord::Base.connection.quote(string)
+ end
+end
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index 849edb09476..5346881444d 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SQL::Union, lib: true do
+describe Gitlab::SQL::Union do
let(:relation_1) { User.where(email: 'alice@example.com').select(:id) }
let(:relation_2) { User.where(email: 'bob@example.com').select(:id) }
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index 7c77772b3f6..abeaa7f0ddb 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::StringRangeMarker, lib: true do
+describe Gitlab::StringRangeMarker do
describe '#mark' do
context "when the rich text is html safe" do
let(:raw) { "abc <def>" }
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index 2f5cf6c6e3b..d715f9bd641 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::StringRegexMarker, lib: true do
+describe Gitlab::StringRegexMarker do
describe '#mark' do
let(:raw) { %{"name": "AFNetworking"} }
let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
new file mode 100644
index 00000000000..bed58d407ef
--- /dev/null
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe Gitlab::UntrustedRegexp do
+ describe '#initialize' do
+ subject { described_class.new(pattern) }
+
+ context 'invalid regexp' do
+ let(:pattern) { '[' }
+
+ it { expect { subject }.to raise_error(RegexpError) }
+ end
+ end
+
+ describe '#replace_all' do
+ it 'replaces all instances of the match in a string' do
+ result = described_class.new('foo').replace_all('foo bar foo', 'oof')
+
+ expect(result).to eq('oof bar oof')
+ end
+ end
+
+ describe '#replace' do
+ it 'replaces the first instance of the match in a string' do
+ result = described_class.new('foo').replace('foo bar foo', 'oof')
+
+ expect(result).to eq('oof bar foo')
+ end
+ end
+
+ describe '#===' do
+ it 'returns true for a match' do
+ result = described_class.new('foo') === 'a foo here'
+
+ expect(result).to be_truthy
+ end
+
+ it 'returns false for no match' do
+ result = described_class.new('foo') === 'a bar here'
+
+ expect(result).to be_falsy
+ end
+ end
+
+ describe '#scan' do
+ subject { described_class.new(regexp).scan(text) }
+ context 'malicious regexp' do
+ let(:text) { malicious_text }
+ let(:regexp) { malicious_regexp }
+
+ include_examples 'malicious regexp'
+ end
+
+ context 'empty regexp' do
+ let(:regexp) { '' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches' do
+ is_expected.to eq([nil, nil, nil, nil])
+ end
+ end
+
+ context 'empty capture group regexp' do
+ let(:regexp) { '()' }
+ let(:text) { 'foo' }
+
+ it 'returns an array of nil matches in an array' do
+ is_expected.to eq([[nil], [nil], [nil], [nil]])
+ end
+ end
+
+ context 'no capture group' do
+ let(:regexp) { '.+' }
+ let(:text) { 'foo' }
+
+ it 'returns the whole match' do
+ is_expected.to eq(['foo'])
+ end
+ end
+
+ context 'one capture group' do
+ let(:regexp) { '(f).+' }
+ let(:text) { 'foo' }
+
+ it 'returns the captured part' do
+ is_expected.to eq([%w[f]])
+ end
+ end
+
+ context 'two capture groups' do
+ let(:regexp) { '(f).(o)' }
+ let(:text) { 'foo' }
+
+ it 'returns the captured parts' do
+ is_expected.to eq([%w[f o]])
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index fcfd8d58b70..6106f13c774 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::Upgrader, lib: true do
- let(:upgrader) { Gitlab::Upgrader.new }
+describe Gitlab::Upgrader do
+ let(:upgrader) { described_class.new }
let(:current_version) { Gitlab::VERSION }
describe 'current_version_raw' do
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index a504d299307..f5b4882815f 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::UrlBlocker, lib: true do
+describe Gitlab::UrlBlocker do
describe '#blocked_url?' do
it 'allows imports from configured web host and port' do
import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git"
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index e9a6e273516..50422020823 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::UrlBuilder, lib: true do
+describe Gitlab::UrlBuilder do
describe '.build' do
context 'when passing a Commit' do
it 'returns a proper URL' do
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 6bce724a3f6..308b1a128be 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::UrlSanitizer, lib: true do
+describe Gitlab::UrlSanitizer do
let(:credentials) { { user: 'blah', password: 'password' } }
let(:url_sanitizer) do
described_class.new("https://github.com/me/project.git", credentials: credentials)
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index c6718827028..68429d792f2 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1,11 +1,19 @@
require 'spec_helper'
describe Gitlab::UsageData do
- let!(:project) { create(:empty_project) }
- let!(:project2) { create(:empty_project) }
- let!(:board) { create(:board, project: project) }
+ let(:projects) { create_list(:project, 3) }
+ let!(:board) { create(:board, project: projects[0]) }
describe '#data' do
+ before do
+ create(:jira_service, project: projects[0])
+ create(:jira_service, project: projects[1])
+ create(:prometheus_service, project: projects[1])
+ create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
+ create(:service, project: projects[1], type: 'SlackService', active: true)
+ create(:service, project: projects[2], type: 'SlackService', active: true)
+ end
+
subject { described_class.data }
it "gathers usage data" do
@@ -25,7 +33,7 @@ describe Gitlab::UsageData do
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
- expect(count_data[:projects]).to eq(2)
+ expect(count_data[:projects]).to eq(3)
expect(count_data.keys).to match_array(%i(
boards
@@ -48,6 +56,10 @@ describe Gitlab::UsageData do
milestones
notes
projects
+ projects_imported_from_github
+ projects_jira_active
+ projects_slack_notifications_active
+ projects_slack_slash_active
projects_prometheus_active
pages_domains
protected_branches
@@ -58,6 +70,16 @@ describe Gitlab::UsageData do
web_hooks
))
end
+
+ it 'gathers projects data correctly' do
+ count_data = subject[:counts]
+
+ expect(count_data[:projects]).to eq(3)
+ expect(count_data[:projects_prometheus_active]).to eq(1)
+ expect(count_data[:projects_jira_active]).to eq(2)
+ expect(count_data[:projects_slack_notifications_active]).to eq(2)
+ expect(count_data[:projects_slack_slash_active]).to eq(1)
+ end
end
describe '#license_usage_data' do
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 0d87cf25dbb..5ebaf6c1507 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::UserAccess, lib: true do
- let(:access) { Gitlab::UserAccess.new(user, project: project) }
+describe Gitlab::UserAccess do
+ let(:access) { described_class.new(user, project: project) }
let(:project) { create(:project) }
let(:user) { create(:user) }
@@ -28,7 +28,7 @@ describe Gitlab::UserAccess, lib: true do
describe 'push to empty project' do
let(:empty_project) { create(:project_empty_repo) }
- let(:project_access) { Gitlab::UserAccess.new(user, project: empty_project) }
+ let(:project_access) { described_class.new(user, project: empty_project) }
it 'returns true if user is master' do
empty_project.team << [user, :master]
diff --git a/spec/lib/gitlab/user_activities_spec.rb b/spec/lib/gitlab/user_activities_spec.rb
index 187d88c8c58..6bce2ee13cf 100644
--- a/spec/lib/gitlab/user_activities_spec.rb
+++ b/spec/lib/gitlab/user_activities_spec.rb
@@ -1,27 +1,27 @@
require 'spec_helper'
-describe Gitlab::UserActivities, :redis, lib: true do
+describe Gitlab::UserActivities, :clean_gitlab_redis_shared_state do
let(:now) { Time.now }
describe '.record' do
context 'with no time given' do
- it 'uses Time.now and records an activity in Redis' do
+ it 'uses Time.now and records an activity in SharedState' do
Timecop.freeze do
now # eager-load now
described_class.record(42)
end
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
end
end
end
context 'with a time given' do
- it 'uses the given time and records an activity in Redis' do
+ it 'uses the given time and records an activity in SharedState' do
described_class.record(42, now)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
end
end
@@ -31,30 +31,30 @@ describe Gitlab::UserActivities, :redis, lib: true do
describe '.delete' do
context 'with a single key' do
context 'and key exists' do
- it 'removes the pair from Redis' do
+ it 'removes the pair from SharedState' do
described_class.record(42, now)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
end
subject.delete(42)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
end
end
end
context 'and key does not exist' do
- it 'removes the pair from Redis' do
- Gitlab::Redis.with do |redis|
+ it 'removes the pair from SharedState' do
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
end
subject.delete(42)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
end
end
@@ -63,33 +63,33 @@ describe Gitlab::UserActivities, :redis, lib: true do
context 'with multiple keys' do
context 'and all keys exist' do
- it 'removes the pair from Redis' do
+ it 'removes the pair from SharedState' do
described_class.record(41, now)
described_class.record(42, now)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['41', now.to_i.to_s], ['42', now.to_i.to_s]]])
end
subject.delete(41, 42)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
end
end
end
context 'and some keys does not exist' do
- it 'removes the existing pair from Redis' do
+ it 'removes the existing pair from SharedState' do
described_class.record(42, now)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', [['42', now.to_i.to_s]]])
end
subject.delete(41, 42)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.hscan(described_class::KEY, 0)).to eq(['0', []])
end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 00941aec380..111c873f79c 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Utils, lib: true do
+describe Gitlab::Utils do
delegate :to_boolean, :boolean_to_yes_no, to: :described_class
describe '.to_boolean' do
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 706ee9bec58..e7e1a92ae54 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Gitlab::VersionInfo', lib: true, no_db: true do
+describe 'Gitlab::VersionInfo' do
before do
@unknown = Gitlab::VersionInfo.new
@v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index db9d2807be6..48a67773de9 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::VisibilityLevel, lib: true do
+describe Gitlab::VisibilityLevel do
describe '.level_value' do
it 'converts "public" to integer value' do
expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 493ff3bb5fb..654397ccffb 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Workhorse, lib: true do
+describe Gitlab::Workhorse do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
@@ -63,13 +63,13 @@ describe Gitlab::Workhorse, lib: true do
end
context 'without ca_pem' do
- subject { Gitlab::Workhorse.terminal_websocket(terminal) }
+ subject { described_class.terminal_websocket(terminal) }
it { is_expected.to eq(workhorse) }
end
context 'with ca_pem' do
- subject { Gitlab::Workhorse.terminal_websocket(terminal(ca_pem: "foo")) }
+ subject { described_class.terminal_websocket(terminal(ca_pem: "foo")) }
it { is_expected.to eq(workhorse(ca_pem: "foo")) }
end
@@ -237,7 +237,8 @@ describe Gitlab::Workhorse, lib: true do
context 'when action is not enabled by feature flag' do
it 'does not include Gitaly params in the returned value' do
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag).and_return(false)
+ status_opt_out = Gitlab::GitalyClient::MigrationStatus::OPT_OUT
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(feature_flag, status: status_opt_out).and_return(false)
expect(subject).not_to include(gitaly_params)
end
@@ -276,7 +277,7 @@ describe Gitlab::Workhorse, lib: true do
end
it 'set and notify' do
- expect_any_instance_of(Redis).to receive(:publish)
+ expect_any_instance_of(::Redis).to receive(:publish)
.with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value")
subject
@@ -310,11 +311,49 @@ describe Gitlab::Workhorse, lib: true do
end
it 'does not notify' do
- expect_any_instance_of(Redis).not_to receive(:publish)
+ expect_any_instance_of(::Redis).not_to receive(:publish)
subject
end
end
end
end
+
+ describe '.send_git_blob' do
+ include FakeBlobHelpers
+
+ let(:blob) { fake_blob }
+
+ subject { described_class.send_git_blob(repository, blob) }
+
+ context 'when Gitaly workhorse_raw_show feature is enabled' do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq('Gitlab-Workhorse-Send-Data')
+ expect(command).to eq('git-blob')
+ expect(params).to eq({
+ 'GitalyServer' => {
+ address: Gitlab::GitalyClient.address(project.repository_storage),
+ token: Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'GetBlobRequest' => {
+ repository: repository.gitaly_repository.to_h,
+ oid: blob.id,
+ limit: -1
+ }
+ }.deep_stringify_keys)
+ end
+ end
+
+ context 'when Gitaly workhorse_raw_show feature is disabled', skip_gitaly_mock: true do
+ it 'sets the header correctly' do
+ key, command, params = decode_workhorse_header(subject)
+
+ expect(key).to eq('Gitlab-Workhorse-Send-Data')
+ expect(command).to eq('git-blob')
+ expect(params).to eq('RepoPath' => repository.path_to_repo, 'BlobId' => blob.id)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index c4c107c9eea..f97136f0191 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Gitlab, lib: true do
+describe Gitlab do
describe '.com?' do
it 'is true when on GitLab.com' do
stub_config_setting(url: 'https://gitlab.com')
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index 5892f3481a4..0c15ba22bf2 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe RepositoryCache, lib: true do
+describe RepositoryCache do
let(:project) { create(:empty_project) }
let(:backend) { double('backend').as_null_object }
- let(:cache) { RepositoryCache.new('example', project.id, backend) }
+ let(:cache) { described_class.new('example', project.id, backend) }
describe '#cache_key' do
it 'includes the namespace' do
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index 795f11ee1f8..025ea2673b4 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck::SimpleExecutor, lib: true do
+describe SystemCheck::SimpleExecutor do
class SimpleCheck < SystemCheck::BaseCheck
set_name 'my simple check'
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
index 23d9beddb08..4d9e17fa6ec 100644
--- a/spec/lib/system_check_spec.rb
+++ b/spec/lib/system_check_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require 'rake_helper'
-describe SystemCheck, lib: true do
+describe SystemCheck do
class SimpleCheck < SystemCheck::BaseCheck
def check?
true
@@ -19,7 +19,7 @@ describe SystemCheck, lib: true do
end
describe '.run' do
- subject { SystemCheck }
+ subject { described_class }
it 'detects execution of SimpleCheck' do
is_expected.to execute_check(SimpleCheck)
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index 8c1c9bf135f..09e5094cf84 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -91,6 +91,36 @@ describe Emails::Profile do
end
end
+ describe 'user added gpg key' do
+ let(:gpg_key) { create(:gpg_key) }
+
+ subject { Notify.new_gpg_key_email(gpg_key.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the new user' do
+ is_expected.to deliver_to gpg_key.user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^GPG key was added to your account$/i
+ end
+
+ it 'contains the new gpg key title' do
+ is_expected.to have_body_text /#{gpg_key.fingerprint}/
+ end
+
+ it 'includes a link to gpg keys page' do
+ is_expected.to have_body_text /#{profile_gpg_keys_path}/
+ end
+
+ context 'with GPG key that does not exist' do
+ it { expect { Notify.new_gpg_key_email('foo') }.not_to raise_error }
+ end
+ end
+
describe 'user added email' do
let(:email) { create(:email) }
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 980b24370d0..683e893968b 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -52,7 +52,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(issue)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
@@ -99,7 +99,7 @@ describe Notify do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_html_escaped_body_text(previous_assignee.name)
is_expected.to have_html_escaped_body_text(assignee.name)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
end
@@ -125,7 +125,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text('foo, bar, and baz')
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
@@ -165,7 +165,7 @@ describe Notify do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(status)
is_expected.to have_html_escaped_body_text(current_user.name)
- is_expected.to have_body_text(namespace_project_issue_path project.namespace, project, issue)
+ is_expected.to have_body_text(project_issue_path project, issue)
end
end
end
@@ -185,13 +185,12 @@ describe Notify do
end
it 'has the correct subject and body' do
- new_issue_url = namespace_project_issue_path(new_issue.project.namespace,
- new_issue.project, new_issue)
+ new_issue_url = project_issue_path(new_issue.project, new_issue)
aggregate_failures do
is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(new_issue_url)
- is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
+ is_expected.to have_body_text(project_issue_path(project, issue))
end
end
end
@@ -216,7 +215,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
is_expected.to have_referable_subject(merge_request)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text(merge_request.source_branch)
is_expected.to have_body_text(merge_request.target_branch)
end
@@ -265,7 +264,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_html_escaped_body_text(previous_assignee.name)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_html_escaped_body_text(assignee.name)
end
end
@@ -291,7 +290,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('foo, bar, and baz')
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
@@ -316,7 +315,7 @@ describe Notify do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(status)
is_expected.to have_html_escaped_body_text(current_user.name)
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
end
@@ -341,7 +340,7 @@ describe Notify do
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text('merged')
- is_expected.to have_body_text(namespace_project_merge_request_path(project.namespace, project, merge_request))
+ is_expected.to have_body_text(project_merge_request_path(project, merge_request))
end
end
end
@@ -390,7 +389,7 @@ describe Notify do
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace
- is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
end
@@ -417,7 +416,7 @@ describe Notify do
is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
is_expected.to have_html_escaped_body_text project.name_with_namespace
- is_expected.to have_body_text namespace_project_project_members_url(project.namespace, project)
+ is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
end
@@ -609,7 +608,7 @@ describe Notify do
describe 'on a merge request' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
- let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+ let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(merge_request)
@@ -634,7 +633,7 @@ describe Notify do
describe 'on an issue' do
let(:issue) { create(:issue, project: project) }
- let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+ let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(issue)
@@ -725,7 +724,7 @@ describe Notify do
describe 'on a merge request' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) }
- let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") }
+ let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(merge_request)
@@ -752,7 +751,7 @@ describe Notify do
describe 'on an issue' do
let(:issue) { create(:issue, project: project) }
let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) }
- let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") }
+ let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") }
before do
allow(note).to receive(:noteable).and_return(issue)
@@ -1022,7 +1021,7 @@ describe Notify do
describe 'email on push for a created branch' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:tree_path) { namespace_project_tree_path(project.namespace, project, "empty-branch") }
+ let(:tree_path) { project_tree_path(project, "empty-branch") }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/empty-branch', action: :create) }
@@ -1048,7 +1047,7 @@ describe Notify do
describe 'email on push for a created tag' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
+ let(:tree_path) { project_tree_path(project, "v1.0") }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
@@ -1122,7 +1121,7 @@ describe Notify do
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
- let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
+ let(:diff_path) { project_compare_path(project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
let(:send_from_committer_email) { false }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
@@ -1216,7 +1215,7 @@ describe Notify do
let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
let(:compare) { Compare.decorate(raw_compare, project) }
let(:commits) { compare.commits }
- let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
+ let(:diff_path) { project_commit_path(project, commits.first) }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
subject { described_class.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, diff_refs: diff_refs) }
diff --git a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb
new file mode 100644
index 00000000000..d9ad9a585f0
--- /dev/null
+++ b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb')
+
+describe AddForeignKeyToMergeRequests, :migration do
+ let(:projects) { table(:projects) }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:pipelines) { table(:ci_pipelines) }
+
+ before do
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce')
+ pipelines.create!(project_id: projects.first.id,
+ ref: 'some-branch',
+ sha: 'abc12345')
+
+ # merge request without a pipeline
+ create_merge_request(head_pipeline_id: nil)
+
+ # merge request with non-existent pipeline
+ create_merge_request(head_pipeline_id: 1234)
+
+ # merge reqeust with existing pipeline assigned
+ create_merge_request(head_pipeline_id: pipelines.first.id)
+ end
+
+ it 'correctly adds a foreign key to head_pipeline_id' do
+ migrate!
+
+ expect(merge_requests.first.head_pipeline_id).to be_nil
+ expect(merge_requests.second.head_pipeline_id).to be_nil
+ expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id
+ end
+
+ def create_merge_request(**opts)
+ merge_requests.create!(source_project_id: projects.first.id,
+ target_project_id: projects.first.id,
+ source_branch: 'some-branch',
+ target_branch: 'master', **opts)
+ end
+end
diff --git a/spec/migrations/clean_appearance_symlinks_spec.rb b/spec/migrations/clean_appearance_symlinks_spec.rb
new file mode 100644
index 00000000000..9225dc0d894
--- /dev/null
+++ b/spec/migrations/clean_appearance_symlinks_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170613111224_clean_appearance_symlinks.rb')
+
+describe CleanAppearanceSymlinks do
+ let(:migration) { described_class.new }
+ let(:test_dir) { File.join(Rails.root, "tmp", "tests", "clean_appearance_test") }
+ let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
+ let(:new_uploads_dir) { File.join(uploads_dir, "system") }
+ let(:original_path) { File.join(new_uploads_dir, 'appearance') }
+ let(:symlink_path) { File.join(uploads_dir, 'appearance') }
+
+ before do
+ FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
+ FileUtils.mkdir_p(uploads_dir)
+ allow(migration).to receive(:base_directory).and_return(test_dir)
+ allow(migration).to receive(:say)
+ end
+
+ describe "#up" do
+ before do
+ FileUtils.mkdir_p(original_path)
+ FileUtils.ln_s(original_path, symlink_path)
+ end
+
+ it 'removes the symlink' do
+ migration.up
+
+ expect(File.symlink?(symlink_path)).to be(false)
+ end
+ end
+
+ describe '#down' do
+ before do
+ FileUtils.mkdir_p(File.join(original_path))
+ FileUtils.touch(File.join(original_path, 'dummy.file'))
+ end
+
+ it 'creates a symlink' do
+ expected_path = File.join(symlink_path, "dummy.file")
+ migration.down
+
+ expect(File.exist?(expected_path)).to be(true)
+ expect(File.symlink?(symlink_path)).to be(true)
+ end
+ end
+end
diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb
new file mode 100644
index 00000000000..9a581df28a2
--- /dev/null
+++ b/spec/migrations/clean_stage_id_reference_migration_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb')
+
+describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do
+ let(:migration_class) { 'MigrateBuildStageIdReference' }
+ let(:migration) { spy('migration') }
+
+ before do
+ allow(Gitlab::BackgroundMigration.const_get(migration_class))
+ .to receive(:new).and_return(migration)
+ end
+
+ context 'when there are pending background migrations' do
+ it 'processes pending jobs synchronously' do
+ Sidekiq::Testing.disable! do
+ BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1])
+ BackgroundMigrationWorker.perform_async(migration_class, [1, 1])
+
+ migrate!
+
+ expect(migration).to have_received(:perform).with(1, 1).twice
+ end
+ end
+ end
+ context 'when there are no background migrations pending' do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ migrate!
+
+ expect(migration).not_to have_received(:perform)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb
new file mode 100644
index 00000000000..3a9fa8c7113
--- /dev/null
+++ b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+require Rails.root.join("db", "post_migrate", "20170717111152_cleanup_move_system_upload_folder_symlink.rb")
+
+describe CleanupMoveSystemUploadFolderSymlink do
+ let(:migration) { described_class.new }
+ let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') }
+ let(:test_folder) { File.join(test_base, '-', 'system') }
+
+ before do
+ allow(migration).to receive(:base_directory).and_return(test_base)
+ FileUtils.rm_rf(test_base)
+ FileUtils.mkdir_p(test_folder)
+ allow(migration).to receive(:say)
+ end
+
+ describe '#up' do
+ before do
+ FileUtils.ln_s(test_folder, File.join(test_base, 'system'))
+ end
+
+ it 'removes the symlink' do
+ migration.up
+
+ expect(File.exist?(File.join(test_base, 'system'))).to be_falsey
+ end
+ end
+
+ describe '#down' do
+ it 'creates the symlink' do
+ migration.down
+
+ expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy
+ end
+ end
+end
diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
index 4223d2337a8..5b633dd349b 100644
--- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
+++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb
@@ -54,7 +54,7 @@ describe MigrateProcessCommitWorkerJobs do
end
end
- describe '#up', :redis do
+ describe '#up', :clean_gitlab_redis_shared_state do
let(:migration) { described_class.new }
def job_count
@@ -172,7 +172,7 @@ describe MigrateProcessCommitWorkerJobs do
end
end
- describe '#down', :redis do
+ describe '#down', :clean_gitlab_redis_shared_state do
let(:migration) { described_class.new }
def job_count
diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
new file mode 100644
index 00000000000..260378adaa7
--- /dev/null
+++ b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background')
+
+describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do
+ matcher :be_scheduled_migration do |delay, *expected|
+ match do |migration|
+ BackgroundMigrationWorker.jobs.any? do |job|
+ job['args'] == [migration, expected] &&
+ job['at'].to_i == (delay.to_i + Time.now.to_i)
+ end
+ end
+
+ failure_message do |migration|
+ "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
+ end
+ end
+
+ let(:jobs) { table(:ci_builds) }
+ let(:stages) { table(:ci_stages) }
+ let(:pipelines) { table(:ci_pipelines) }
+ let(:projects) { table(:projects) }
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 3)
+ stub_const("#{described_class.name}::RANGE_SIZE", 2)
+
+ projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1')
+ projects.create!(id: 345, name: 'gitlab2', path: 'gitlab2')
+
+ pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a')
+ pipelines.create!(id: 2, project_id: 345, ref: 'feature', sha: 'cdf43c3c')
+
+ jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build')
+ jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test')
+ jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy')
+ jobs.create!(id: 5, commit_id: 2, project_id: 345, stage_idx: 1, stage: 'test')
+
+ stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test')
+ stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build')
+ stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy')
+
+ jobs.create!(id: 6, commit_id: 2, project_id: 345, stage_id: 101, stage_idx: 1, stage: 'test')
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(2.minutes, 1, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(2.minutes, 3, 3)
+ expect(described_class::MIGRATION).to be_scheduled_migration(4.minutes, 4, 5)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.inline! do
+ expect(jobs.where(stage_id: nil).count).to eq 5
+
+ migrate!
+
+ expect(jobs.where(stage_id: nil).count).to eq 1
+ end
+ end
+end
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index e3b42b5eac8..063829be546 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -3,13 +3,13 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
-describe MigrateUserActivitiesToUsersLastActivityOn, :redis, :truncate do
+describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :truncate do
let(:migration) { described_class.new }
let!(:user_active_1) { create(:user) }
let!(:user_active_2) { create(:user) }
def record_activity(user, time)
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username)
end
end
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
new file mode 100644
index 00000000000..8505c7bf3e3
--- /dev/null
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -0,0 +1,180 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb')
+
+describe MovePersonalSnippetsFiles do
+ let(:migration) { described_class.new }
+ let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") }
+ let(:uploads_dir) { File.join(test_dir, 'uploads') }
+ let(:new_uploads_dir) { File.join(uploads_dir, 'system') }
+
+ before do
+ allow(CarrierWave).to receive(:root).and_return(test_dir)
+ allow(migration).to receive(:base_directory).and_return(test_dir)
+ FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
+ allow(migration).to receive(:say)
+ end
+
+ describe "#up" do
+ let(:snippet) do
+ snippet = create(:personal_snippet)
+ create_upload('picture.jpg', snippet)
+ snippet.update(description: markdown_linking_file('picture.jpg', snippet))
+ snippet
+ end
+
+ let(:snippet_with_missing_file) do
+ snippet = create(:snippet)
+ create_upload('picture.jpg', snippet, create_file: false)
+ snippet.update(description: markdown_linking_file('picture.jpg', snippet))
+ snippet
+ end
+
+ it 'moves the files' do
+ source_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet))
+ destination_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet))
+
+ migration.up
+
+ expect(File.exist?(source_path)).to be_falsy
+ expect(File.exist?(destination_path)).to be_truthy
+ end
+
+ describe 'updating the markdown' do
+ it 'includes the new path when the file exists' do
+ secret = "secret#{snippet.id}"
+ file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+
+ migration.up
+
+ expect(snippet.reload.description).to include(file_location)
+ end
+
+ it 'does not update the markdown when the file is missing' do
+ secret = "secret#{snippet_with_missing_file.id}"
+ file_location = "/uploads/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
+
+ migration.up
+
+ expect(snippet_with_missing_file.reload.description).to include(file_location)
+ end
+
+ it 'updates the note markdown' do
+ secret = "secret#{snippet.id}"
+ file_location = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+ markdown = markdown_linking_file('picture.jpg', snippet)
+ note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+
+ migration.up
+
+ expect(note.reload.note).to include(file_location)
+ end
+ end
+ end
+
+ describe "#down" do
+ let(:snippet) do
+ snippet = create(:personal_snippet)
+ create_upload('picture.jpg', snippet, in_new_path: true)
+ snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
+ snippet
+ end
+
+ let(:snippet_with_missing_file) do
+ snippet = create(:personal_snippet)
+ create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
+ snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
+ snippet
+ end
+
+ it 'moves the files' do
+ source_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet))
+ destination_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet))
+
+ migration.down
+
+ expect(File.exist?(source_path)).to be_falsey
+ expect(File.exist?(destination_path)).to be_truthy
+ end
+
+ describe 'updating the markdown' do
+ it 'includes the new path when the file exists' do
+ secret = "secret#{snippet.id}"
+ file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+
+ migration.down
+
+ expect(snippet.reload.description).to include(file_location)
+ end
+
+ it 'keeps the markdown as is when the file is missing' do
+ secret = "secret#{snippet_with_missing_file.id}"
+ file_location = "/uploads/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg"
+
+ migration.down
+
+ expect(snippet_with_missing_file.reload.description).to include(file_location)
+ end
+
+ it 'updates the note markdown' do
+ markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
+ secret = "secret#{snippet.id}"
+ file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
+ note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}")
+
+ migration.down
+
+ expect(note.reload.note).to include(file_location)
+ end
+ end
+ end
+
+ describe '#update_markdown' do
+ it 'escapes sql in the snippet description' do
+ migration.instance_variable_set('@source_relative_location', '/uploads/personal_snippet')
+ migration.instance_variable_set('@destination_relative_location', '/uploads/system/personal_snippet')
+
+ secret = '123456789'
+ filename = 'hello.jpg'
+ snippet = create(:personal_snippet)
+
+ path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
+ path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
+ description_before = "Hello world; ![image](#{path_before})'; select * from users;"
+ description_after = "Hello world; ![image](#{path_after})'; select * from users;"
+
+ migration.update_markdown(snippet.id, secret, filename, description_before)
+
+ expect(snippet.reload.description).to eq(description_after)
+ end
+ end
+
+ def create_upload(filename, snippet, create_file: true, in_new_path: false)
+ secret = "secret#{snippet.id}"
+ absolute_path = if in_new_path
+ File.join(new_uploads_dir, model_file_path(filename, snippet))
+ else
+ File.join(uploads_dir, model_file_path(filename, snippet))
+ end
+
+ if create_file
+ FileUtils.mkdir_p(File.dirname(absolute_path))
+ FileUtils.touch(absolute_path)
+ end
+
+ create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader)
+ end
+
+ def markdown_linking_file(filename, snippet, in_new_path: false)
+ markdown = "![#{filename.split('.')[0]}]"
+ markdown += '(/uploads'
+ markdown += '/system' if in_new_path
+ markdown += "/#{model_file_path(filename, snippet)})"
+ markdown
+ end
+
+ def model_file_path(filename, snippet)
+ secret = "secret#{snippet.id}"
+
+ File.join('personal_snippet', snippet.id.to_s, secret, filename)
+ end
+end
diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb
new file mode 100644
index 00000000000..b622b4e9536
--- /dev/null
+++ b/spec/migrations/move_system_upload_folder_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+require Rails.root.join("db", "migrate", "20170717074009_move_system_upload_folder.rb")
+
+describe MoveSystemUploadFolder do
+ let(:migration) { described_class.new }
+ let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') }
+
+ before do
+ allow(migration).to receive(:base_directory).and_return(test_base)
+ FileUtils.rm_rf(test_base)
+ FileUtils.mkdir_p(test_base)
+ allow(migration).to receive(:say)
+ end
+
+ describe '#up' do
+ let(:test_folder) { File.join(test_base, 'system') }
+ let(:test_file) { File.join(test_folder, 'file') }
+
+ before do
+ FileUtils.mkdir_p(test_folder)
+ FileUtils.touch(test_file)
+ end
+
+ it 'moves the related folder' do
+ migration.up
+
+ expect(File.exist?(File.join(test_base, '-', 'system', 'file'))).to be_truthy
+ end
+
+ it 'creates a symlink linking making the new folder available on the old path' do
+ migration.up
+
+ expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy
+ expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy
+ end
+ end
+
+ describe '#down' do
+ let(:test_folder) { File.join(test_base, '-', 'system') }
+ let(:test_file) { File.join(test_folder, 'file') }
+
+ before do
+ FileUtils.mkdir_p(test_folder)
+ FileUtils.touch(test_file)
+ end
+
+ it 'moves the system folder back to the old location' do
+ migration.down
+
+ expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy
+ end
+
+ it 'removes the symlink if it existed' do
+ FileUtils.ln_s(test_folder, File.join(test_base, 'system'))
+
+ migration.down
+
+ expect(File.directory?(File.join(test_base, 'system'))).to be_truthy
+ expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey
+ end
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index dc7a0d80752..aa019288700 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe Ability, lib: true do
+describe Ability do
context 'using a nil subject' do
it 'has no permissions' do
- expect(Ability.policy_for(nil, nil)).to be_banned
+ expect(described_class.policy_for(nil, nil)).to be_banned
end
end
@@ -98,7 +98,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project).to receive(:owner).twice.and_return(user1)
+ expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -109,7 +109,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project.team).to receive(:members).twice.and_return([user1])
+ expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -140,7 +140,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project).to receive(:owner).twice.and_return(user1)
+ expect(project).to receive(:owner).at_least(:once).and_return(user1)
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
@@ -151,7 +151,7 @@ describe Ability, lib: true do
user2 = build(:user, external: true)
users = [user1, user2]
- expect(project.team).to receive(:members).twice.and_return([user1])
+ expect(project.team).to receive(:members).at_least(:once).and_return([user1])
expect(described_class.users_that_can_read_project(users, project))
.to eq([user1])
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index c1bf5551fe0..d4da30b1641 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-RSpec.describe AbuseReport, type: :model do
+RSpec.describe AbuseReport do
subject { create(:abuse_report) }
let(:user) { create(:admin) }
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 1060bf3cbf4..7cd3a84d592 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-RSpec.describe Appearance, type: :model do
+RSpec.describe Appearance do
subject { build(:appearance) }
it { is_expected.to be_valid }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 166a4474abf..359753b600e 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe ApplicationSetting, models: true do
- let(:setting) { ApplicationSetting.create_from_defaults }
+describe ApplicationSetting do
+ let(:setting) { described_class.create_from_defaults }
it { expect(setting).to be_valid }
it { expect(setting.uuid).to be_present }
@@ -155,6 +155,18 @@ describe ApplicationSetting, models: true do
end
end
+ describe '.current' do
+ context 'redis unavailable' do
+ it 'returns an ApplicationSetting' do
+ allow(Rails.cache).to receive(:fetch).and_call_original
+ allow(described_class).to receive(:last).and_return(:last)
+ expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(ArgumentError)
+
+ expect(described_class.current).to eq(:last)
+ end
+ end
+ end
+
context 'restricted signup domains' do
it 'sets single domain' do
setting.domain_whitelist_raw = 'example.com'
@@ -214,6 +226,160 @@ describe ApplicationSetting, models: true do
end
end
+ describe 'performance bar settings' do
+ describe 'performance_bar_allowed_group_id=' do
+ context 'with a blank path' do
+ before do
+ setting.performance_bar_allowed_group_id = create(:group).full_path
+ end
+
+ it 'persists nil for a "" path and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = ''
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ end
+ end
+
+ context 'with an invalid path' do
+ it 'does not persist an invalid group path' do
+ setting.performance_bar_allowed_group_id = 'foo'
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ end
+ end
+
+ context 'with a path to an existing group' do
+ let(:group) { create(:group) }
+
+ it 'persists a valid group path and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = group.full_path
+
+ expect(setting.performance_bar_allowed_group_id).to eq(group.id)
+ end
+
+ context 'when the given path is the same' do
+ context 'with a blank path' do
+ before do
+ setting.performance_bar_allowed_group_id = nil
+ end
+
+ it 'clears the cached allowed user IDs' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = ''
+ end
+ end
+
+ context 'with a valid path' do
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'clears the cached allowed user IDs' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+ end
+ end
+ end
+ end
+
+ describe 'performance_bar_allowed_group' do
+ context 'with no performance_bar_allowed_group_id saved' do
+ it 'returns nil' do
+ expect(setting.performance_bar_allowed_group).to be_nil
+ end
+ end
+
+ context 'with a performance_bar_allowed_group_id saved' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'returns the group' do
+ expect(setting.performance_bar_allowed_group).to eq(group)
+ end
+ end
+ end
+
+ describe 'performance_bar_enabled' do
+ context 'with the Performance Bar is enabled' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ it 'returns true' do
+ expect(setting.performance_bar_enabled).to be_truthy
+ end
+ end
+ end
+
+ describe 'performance_bar_enabled=' do
+ context 'when the performance bar is enabled' do
+ let(:group) { create(:group) }
+
+ before do
+ setting.performance_bar_allowed_group_id = group.full_path
+ end
+
+ context 'when passing true' do
+ it 'does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = true
+
+ expect(setting.performance_bar_allowed_group_id).to eq(group.id)
+ expect(setting.performance_bar_enabled).to be_truthy
+ end
+ end
+
+ context 'when passing false' do
+ it 'disables the performance bar and clears allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = false
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+ end
+
+ context 'when the performance bar is disabled' do
+ context 'when passing true' do
+ it 'does nothing and does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = true
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+
+ context 'when passing false' do
+ it 'does nothing and does not clear allowed user IDs cache' do
+ expect(Gitlab::PerformanceBar).not_to receive(:expire_allowed_user_ids_cache)
+
+ setting.performance_bar_enabled = false
+
+ expect(setting.performance_bar_allowed_group_id).to be_nil
+ expect(setting.performance_bar_enabled).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
describe 'usage ping settings' do
context 'when the usage ping is disabled in gitlab.yml' do
before do
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 2a9a27752c1..87e60d9c16b 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe AwardEmoji, models: true do
+describe AwardEmoji do
describe 'Associations' do
it { is_expected.to belong_to(:awardable) }
it { is_expected.to belong_to(:user) }
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 574438838d8..e3640097dff 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::Base, model: true do
+describe BlobViewer::Base do
include FakeBlobHelpers
let(:project) { build(:empty_project) }
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
index 9066c5a05ac..db41eca0fc8 100644
--- a/spec/models/blob_viewer/changelog_spec.rb
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::Changelog, model: true do
+describe BlobViewer::Changelog do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index df4f1f4815c..82f6f7e5046 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::ComposerJson, model: true do
+describe BlobViewer::ComposerJson do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index 81e932de290..14cc5f3c0fd 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::Gemspec, model: true do
+describe BlobViewer::Gemspec do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index 0c6c24ece21..7a4f9866375 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::GitlabCiYml, model: true do
+describe BlobViewer::GitlabCiYml do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
index 944ddd32b92..222ed166ee0 100644
--- a/spec/models/blob_viewer/license_spec.rb
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::License, model: true do
+describe BlobViewer::License do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index 5c9a9c81963..96fb1b08c99 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::PackageJson, model: true do
+describe BlobViewer::PackageJson do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index 42a00940bc5..f510077a87b 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::PodspecJson, model: true do
+describe BlobViewer::PodspecJson do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 6c9f0f42d53..7c38083550c 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::Podspec, model: true do
+describe BlobViewer::Podspec do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
new file mode 100644
index 00000000000..926df21ffda
--- /dev/null
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe BlobViewer::Readme do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:blob) { fake_blob(path: 'README.md') }
+ subject { described_class.new(blob) }
+
+ describe '#render_error' do
+ context 'when there is no wiki' do
+ it 'returns :no_wiki' do
+ expect(subject.render_error).to eq(:no_wiki)
+ end
+ end
+
+ context 'when there is an external wiki' do
+ before do
+ project.has_external_wiki = true
+ end
+
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+
+ context 'when there is a local wiki' do
+ before do
+ project.wiki_enabled = true
+ end
+
+ context 'when the wiki is empty' do
+ it 'returns :no_wiki' do
+ expect(subject.render_error).to eq(:no_wiki)
+ end
+ end
+
+ context 'when the wiki is not empty' do
+ before do
+ WikiPages::CreateService.new(project, project.owner, title: 'home', content: 'Home page').execute
+ end
+
+ it 'returns nil' do
+ expect(subject.render_error).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index 4854e0262d9..115731b4970 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::RouteMap, model: true do
+describe BlobViewer::RouteMap do
include FakeBlobHelpers
let(:project) { build(:project) }
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
index f047953d540..2639eec9e84 100644
--- a/spec/models/blob_viewer/server_side_spec.rb
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BlobViewer::ServerSide, model: true do
+describe BlobViewer::ServerSide do
include FakeBlobHelpers
let(:project) { build(:empty_project) }
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 333f4139a96..a8ca1d110e4 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BroadcastMessage, models: true do
+describe BroadcastMessage do
subject { build(:broadcast_message) }
it { is_expected.to be_valid }
@@ -24,26 +24,26 @@ describe BroadcastMessage, models: true do
it 'returns message if time match' do
message = create(:broadcast_message)
- expect(BroadcastMessage.current).to include(message)
+ expect(described_class.current).to include(message)
end
it 'returns multiple messages if time match' do
message1 = create(:broadcast_message)
message2 = create(:broadcast_message)
- expect(BroadcastMessage.current).to contain_exactly(message1, message2)
+ expect(described_class.current).to contain_exactly(message1, message2)
end
it 'returns empty list if time not come' do
create(:broadcast_message, :future)
- expect(BroadcastMessage.current).to be_empty
+ expect(described_class.current).to be_empty
end
it 'returns empty list if time has passed' do
create(:broadcast_message, :expired)
- expect(BroadcastMessage.current).to be_empty
+ expect(described_class.current).to be_empty
end
end
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index b02971cab82..8581bcbb08b 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatName, models: true do
+describe ChatName do
subject { create(:chat_name) }
it { is_expected.to belong_to(:service) }
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 5283561a83f..e0e5f73e6fe 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatTeam, type: :model do
+describe ChatTeam do
subject { create(:chat_team) }
# Associations
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 968593d7e9b..a10a8af5303 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::ArtifactBlob, models: true do
+describe Ci::ArtifactBlob do
let(:build) { create(:ci_build, :artifacts) }
let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/another-subdirectory/banana_sample.gif') }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 488697f74eb..a18da3768d5 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Build, :models do
+describe Ci::Build do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -225,7 +225,7 @@ describe Ci::Build, :models do
it 'expects to have retried builds instead the original ones' do
project.add_developer(user)
- retried_rspec = Ci::Build.retry(rspec_test, user)
+ retried_rspec = described_class.retry(rspec_test, user)
expect(staging.depends_on_builds.map(&:id))
.to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
@@ -620,9 +620,9 @@ describe Ci::Build, :models do
describe '#first_pending' do
let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
- subject { Ci::Build.first_pending }
+ subject { described_class.first_pending }
- it { is_expected.to be_a(Ci::Build) }
+ it { is_expected.to be_a(described_class) }
it('returns with the first pending build') { is_expected.to eq(first) }
end
@@ -802,6 +802,47 @@ describe Ci::Build, :models do
end
end
+ describe 'build auto retry feature' do
+ describe '#retries_count' do
+ subject { create(:ci_build, name: 'test', pipeline: pipeline) }
+
+ context 'when build has been retried several times' do
+ before do
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ end
+
+ it 'reports a correct retry count value' do
+ expect(subject.retries_count).to eq 2
+ end
+ end
+
+ context 'when build has not been retried' do
+ it 'returns zero' do
+ expect(subject.retries_count).to eq 0
+ end
+ end
+ end
+
+ describe '#retries_max' do
+ context 'when max retries value is defined' do
+ subject { create(:ci_build, options: { retry: 1 }) }
+
+ it 'returns a number of configured max retries' do
+ expect(subject.retries_max).to eq 1
+ end
+ end
+
+ context 'when max retries value is not defined' do
+ subject { create(:ci_build) }
+
+ it 'returns zero' do
+ expect(subject.retries_max).to eq 0
+ end
+ end
+ end
+ end
+
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
@@ -863,7 +904,7 @@ describe Ci::Build, :models do
pipeline2 = create(:ci_pipeline, project: project)
@build2 = create(:ci_build, pipeline: pipeline2)
- allow(@merge_request).to receive(:commits_sha)
+ allow(@merge_request).to receive(:commit_shas)
.and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
@@ -904,7 +945,7 @@ describe Ci::Build, :models do
end
context 'when build is retried' do
- let!(:new_build) { Ci::Build.retry(build, user) }
+ let!(:new_build) { described_class.retry(build, user) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
@@ -912,7 +953,7 @@ describe Ci::Build, :models do
end
context 'when other build is retried' do
- let!(:retried_build) { Ci::Build.retry(other_build, user) }
+ let!(:retried_build) { described_class.retry(other_build, user) }
before do
retried_build.success
@@ -998,13 +1039,17 @@ describe Ci::Build, :models do
describe '#ref_slug' do
{
- 'master' => 'master',
- '1-foo' => '1-foo',
- 'fix/1-foo' => 'fix-1-foo',
- 'fix-1-foo' => 'fix-1-foo',
- 'a' * 63 => 'a' * 63,
- 'a' * 64 => 'a' * 63,
- 'FOO' => 'foo'
+ 'master' => 'master',
+ '1-foo' => '1-foo',
+ 'fix/1-foo' => 'fix-1-foo',
+ 'fix-1-foo' => 'fix-1-foo',
+ 'a' * 63 => 'a' * 63,
+ 'a' * 64 => 'a' * 63,
+ 'FOO' => 'foo',
+ '-' + 'a' * 61 + '-' => 'a' * 61,
+ '-' + 'a' * 62 + '-' => 'a' * 62,
+ '-' + 'a' * 63 + '-' => 'a' * 62,
+ 'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
@@ -1179,6 +1224,7 @@ describe Ci::Build, :models do
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+ { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
{ key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
{ key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
{ key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
@@ -1351,6 +1397,59 @@ describe Ci::Build, :models do
end
end
+ context 'when group secret variable is defined' do
+ let(:secret_variable) do
+ { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ end
+
+ let(:group) { create(:group, :access_requestable) }
+
+ before do
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ secret_variable.slice(:key, :value).merge(group: group))
+ end
+
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when group protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ let(:group) { create(:group, :access_requestable) }
+
+ before do
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(group: group))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
+ end
+
context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
@@ -1369,6 +1468,23 @@ describe Ci::Build, :models do
it { is_expected.to include(predefined_trigger_variable) }
end
+ context 'when a job was triggered by a pipeline schedule' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'SCHEDULE_VARIABLE_KEY',
+ pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ pipeline_schedule.pipelines << pipeline
+ pipeline_schedule.reload
+ end
+
+ it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) }
+ end
+
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
@@ -1469,6 +1585,16 @@ describe Ci::Build, :models do
it { is_expected.to include(deployment_variable) }
end
+ context 'when project has custom CI config path' do
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+
+ before do
+ project.update(ci_config_path: 'custom')
+ end
+
+ it { is_expected.to include(ci_config_path) }
+ end
+
context 'returns variables in valid order' do
let(:build_pre_var) { { key: 'build', value: 'value' } }
let(:project_pre_var) { { key: 'project', value: 'value' } }
@@ -1481,9 +1607,10 @@ describe Ci::Build, :models do
allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
- allow(project).to receive(:secret_variables_for).with(build.ref) do
- [create(:ci_variable, key: 'secret', value: 'value')]
- end
+ allow(project).to receive(:secret_variables_for)
+ .with(ref: 'master', environment: nil) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
end
it do
@@ -1497,7 +1624,7 @@ describe Ci::Build, :models do
end
end
- describe 'State transition: any => [:pending]' do
+ describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
it 'queues BuildQueueWorker' do
@@ -1506,4 +1633,35 @@ describe Ci::Build, :models do
build.enqueue
end
end
+
+ describe 'state transition when build fails' do
+ context 'when build is configured to be retried' do
+ subject { create(:ci_build, :running, options: { retry: 3 }) }
+
+ it 'retries builds and assigns a same user to it' do
+ expect(described_class).to receive(:retry)
+ .with(subject, subject.user)
+
+ subject.drop!
+ end
+ end
+
+ context 'when build is not configured to be retried' do
+ subject { create(:ci_build, :running) }
+
+ it 'does not retry build' do
+ expect(described_class).not_to receive(:retry)
+
+ subject.drop!
+ end
+
+ it 'does not count retries when not necessary' do
+ expect(described_class).not_to receive(:retry)
+ expect_any_instance_of(described_class)
+ .not_to receive(:retries_count)
+
+ subject.drop!
+ end
+ end
+ end
end
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 62e15093089..51123e73fe6 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Group, models: true do
+describe Ci::Group do
subject do
described_class.new('test', name: 'rspec', jobs: jobs)
end
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
new file mode 100644
index 00000000000..145189e7469
--- /dev/null
+++ b/spec/models/ci/group_variable_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Ci::GroupVariable do
+ subject { build(:ci_group_variable) }
+
+ it { is_expected.to include_module(HasVariable) }
+ it { is_expected.to include_module(Presentable) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id) }
+
+ describe '.unprotected' do
+ subject { described_class.unprotected }
+
+ context 'when variable is protected' do
+ before do
+ create(:ci_group_variable, :protected)
+ end
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when variable is not protected' do
+ let(:variable) { create(:ci_group_variable, protected: false) }
+
+ it 'returns the variable' do
+ is_expected.to contain_exactly(variable)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index d43c33d3807..0c33c1466b7 100644
--- a/spec/models/ci/legacy_stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::LegacyStage, :models do
+describe Ci::LegacyStage do
let(:stage) { build(:ci_stage) }
let(:pipeline) { stage.pipeline }
let(:stage_name) { stage.name }
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 56817baf79d..9a278212efc 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -1,10 +1,11 @@
require 'spec_helper'
-describe Ci::PipelineSchedule, models: true do
+describe Ci::PipelineSchedule do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:owner) }
it { is_expected.to have_many(:pipelines) }
+ it { is_expected.to have_many(:variables) }
it { is_expected.to respond_to(:ref) }
it { is_expected.to respond_to(:cron) }
@@ -45,7 +46,7 @@ describe Ci::PipelineSchedule, models: true do
end
it 'updates next_run_at automatically' do
- expect(Ci::PipelineSchedule.last.next_run_at).to eq(expected_next_run_at)
+ expect(described_class.last.next_run_at).to eq(expected_next_run_at)
end
end
@@ -60,7 +61,7 @@ describe Ci::PipelineSchedule, models: true do
it 'updates next_run_at automatically' do
pipeline_schedule.update!(cron: new_cron)
- expect(Ci::PipelineSchedule.last.next_run_at).to eq(expected_next_run_at)
+ expect(described_class.last.next_run_at).to eq(expected_next_run_at)
end
end
end
@@ -117,4 +118,20 @@ describe Ci::PipelineSchedule, models: true do
end
end
end
+
+ describe '#job_variables' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule) }
+
+ let!(:pipeline_schedule_variables) do
+ create_list(:ci_pipeline_schedule_variable, 2, pipeline_schedule: pipeline_schedule)
+ end
+
+ subject { pipeline_schedule.job_variables }
+
+ before do
+ pipeline_schedule.reload
+ end
+
+ it { is_expected.to contain_exactly(*pipeline_schedule_variables.map(&:to_runner_variable)) }
+ end
end
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
new file mode 100644
index 00000000000..dc8427f28bc
--- /dev/null
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -0,0 +1,7 @@
+require 'spec_helper'
+
+describe Ci::PipelineScheduleVariable do
+ subject { build(:ci_pipeline_schedule_variable) }
+
+ it { is_expected.to include_module(HasVariable) }
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 55d85a6e228..9461905c787 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Pipeline, models: true do
+describe Ci::Pipeline do
include EmailHelpers
let(:user) { create(:user) }
@@ -734,6 +734,8 @@ describe Ci::Pipeline, models: true do
context 'on failure and build retry' do
before do
+ stub_not_protect_default_branch
+
build.drop
project.add_developer(user)
@@ -748,6 +750,39 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#ci_yaml_file_path' do
+ subject { pipeline.ci_yaml_file_path }
+
+ it 'returns the path from project' do
+ allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
+
+ is_expected.to eq('custom/path')
+ end
+
+ it 'returns default when custom path is nil' do
+ allow(pipeline.project).to receive(:ci_config_path) { nil }
+
+ is_expected.to eq('.gitlab-ci.yml')
+ end
+
+ it 'returns default when custom path is empty' do
+ allow(pipeline.project).to receive(:ci_config_path) { '' }
+
+ is_expected.to eq('.gitlab-ci.yml')
+ end
+ end
+
+ describe '#ci_yaml_file' do
+ it 'reports error if the file is not found' do
+ allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
+
+ pipeline.ci_yaml_file
+
+ expect(pipeline.yaml_errors)
+ .to eq('Failed to load CI/CD config file at custom')
+ end
+ end
+
describe '#detailed_status' do
subject { pipeline.detailed_status(user) }
@@ -966,6 +1001,8 @@ describe Ci::Pipeline, models: true do
let(:latest_status) { pipeline.statuses.latest.pluck(:status) }
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 76ce558eea0..8d12a9c09ca 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Runner, models: true do
+describe Ci::Runner do
describe 'validation' do
context 'when runner is not allowed to pick untagged jobs' do
context 'when runner does not have tags' do
@@ -50,7 +50,7 @@ describe Ci::Runner, models: true do
end
describe '.online' do
- subject { Ci::Runner.online }
+ subject { described_class.online }
before do
@runner1 = FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.year.ago)
@@ -276,14 +276,14 @@ describe Ci::Runner, models: true do
it 'sets a new last_update value when it is called the first time' do
last_update = runner.ensure_runner_queue_value
- expect_value_in_redis.to eq(last_update)
+ expect_value_in_queues.to eq(last_update)
end
it 'does not change if it is not expired and called again' do
last_update = runner.ensure_runner_queue_value
expect(runner.ensure_runner_queue_value).to eq(last_update)
- expect_value_in_redis.to eq(last_update)
+ expect_value_in_queues.to eq(last_update)
end
context 'updates runner queue after changing editable value' do
@@ -294,7 +294,7 @@ describe Ci::Runner, models: true do
end
it 'sets a new last_update value' do
- expect_value_in_redis.not_to eq(last_update)
+ expect_value_in_queues.not_to eq(last_update)
end
end
@@ -306,12 +306,12 @@ describe Ci::Runner, models: true do
end
it 'has an old last_update value' do
- expect_value_in_redis.to eq(last_update)
+ expect_value_in_queues.to eq(last_update)
end
end
- def expect_value_in_redis
- Gitlab::Redis.with do |redis|
+ def expect_value_in_queues
+ Gitlab::Redis::Queues.with do |redis|
runner_queue_key = runner.send(:runner_queue_key)
expect(redis.get(runner_queue_key))
end
@@ -330,7 +330,7 @@ describe Ci::Runner, models: true do
end
it 'cleans up the queue' do
- Gitlab::Redis.with do |redis|
+ Gitlab::Redis::Queues.with do |redis|
expect(redis.get(queue_key)).to be_nil
end
end
@@ -352,13 +352,13 @@ describe Ci::Runner, models: true do
end
context 'does not give owned runner' do
- subject { Ci::Runner.assignable_for(project) }
+ subject { described_class.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does not give shared runner' do
- subject { Ci::Runner.assignable_for(another_project) }
+ subject { described_class.assignable_for(another_project) }
it { is_expected.to be_empty }
end
@@ -366,13 +366,13 @@ describe Ci::Runner, models: true do
context 'with unlocked runner' do
context 'does not give owned runner' do
- subject { Ci::Runner.assignable_for(project) }
+ subject { described_class.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does give a specific runner' do
- subject { Ci::Runner.assignable_for(another_project) }
+ subject { described_class.assignable_for(another_project) }
it { is_expected.to contain_exactly(runner) }
end
@@ -384,13 +384,13 @@ describe Ci::Runner, models: true do
end
context 'does not give owned runner' do
- subject { Ci::Runner.assignable_for(project) }
+ subject { described_class.assignable_for(project) }
it { is_expected.to be_empty }
end
context 'does not give a locked runner' do
- subject { Ci::Runner.assignable_for(another_project) }
+ subject { described_class.assignable_for(another_project) }
it { is_expected.to be_empty }
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 92c15c13c18..de51f4879fd 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Trigger, models: true do
+describe Ci::Trigger do
let(:project) { create :empty_project }
describe 'associations' do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 50f7c029af8..e4ff551151e 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,17 +1,12 @@
require 'spec_helper'
-describe Ci::Variable, models: true do
+describe Ci::Variable do
subject { build(:ci_variable) }
- let(:secret_value) { 'secret' }
-
describe 'validations' do
it { is_expected.to include_module(HasVariable) }
+ it { is_expected.to include_module(Presentable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) }
- it { is_expected.to validate_length_of(:key).is_at_most(255) }
- it { is_expected.to allow_value('foo').for(:key) }
- it { is_expected.not_to allow_value('foo bar').for(:key) }
- it { is_expected.not_to allow_value('foo/bar').for(:key) }
end
describe '.unprotected' do
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index ba9c3f66d21..07e10b44938 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CommitRange, models: true do
+describe CommitRange do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 6056d78da4e..2285c338599 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Commit, models: true do
+describe Commit do
let(:project) { create(:project, :public, :repository) }
let(:commit) { project.commit }
@@ -19,17 +19,15 @@ describe Commit, models: true do
expect(commit.author).to eq(user)
end
- it 'caches the author' do
- allow(RequestStore).to receive(:active?).and_return(true)
+ it 'caches the author', :request_store do
user = create(:user, email: commit.author_email)
- expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original
+ expect(User).to receive(:find_by_any_email).and_call_original
expect(commit.author).to eq(user)
- key = "commit_author:#{commit.author_email}"
+ key = "Commit:author:#{commit.author_email.downcase}"
expect(RequestStore.store[key]).to eq(user)
expect(commit.author).to eq(user)
- RequestStore.store.clear
end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 1e074c7ad26..6fb4794ea5f 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CommitStatus, :models do
+describe CommitStatus do
let(:project) { create(:project, :repository) }
let(:pipeline) do
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index da003dbf794..04f3cecae00 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Compare, models: true do
+describe Compare do
include RepoHelpers
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index a6fccb668e3..5c0dfaeb4d3 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CaseSensitivity, models: true do
+describe CaseSensitivity do
describe '.iwhere' do
let(:connection) { ActiveRecord::Base.connection }
let(:model) { Class.new { include CaseSensitivity } }
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index f3e148f95f0..2322eb206fb 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DiscussionOnDiff, model: true do
+describe DiscussionOnDiff do
subject { create(:diff_note_on_merge_request).to_discussion }
describe "#truncated_diff_lines" do
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
new file mode 100644
index 00000000000..951690a217b
--- /dev/null
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe EachBatch do
+ describe '.each_batch' do
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ include EachBatch
+
+ self.table_name = 'users'
+ end
+ end
+
+ before do
+ 5.times { create(:user, updated_at: 1.day.ago) }
+ end
+
+ it 'yields an ActiveRecord::Relation when a block is given' do
+ model.each_batch do |relation|
+ expect(relation).to be_a_kind_of(ActiveRecord::Relation)
+ end
+ end
+
+ it 'yields a batch index as the second argument' do
+ model.each_batch do |_, index|
+ expect(index).to eq(1)
+ end
+ end
+
+ it 'accepts a custom batch size' do
+ amount = 0
+
+ model.each_batch(of: 1) { amount += 1 }
+
+ expect(amount).to eq(5)
+ end
+
+ it 'does not include ORDER BYs in the yielded relations' do
+ model.each_batch do |relation|
+ expect(relation.to_sql).not_to include('ORDER BY')
+ end
+ end
+
+ it 'allows updating of the yielded relations' do
+ time = Time.now
+
+ model.each_batch do |relation|
+ relation.update_all(updated_at: time)
+ end
+
+ expect(model.where(updated_at: time).count).to eq(5)
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index ac9303370ab..505039c9d88 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -155,7 +155,7 @@ describe Issuable do
end
describe "#sort" do
- let(:project) { build_stubbed(:empty_project) }
+ let(:project) { create(:empty_project) }
context "by milestone due date" do
# Correct order is:
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index e2a29e0ae70..1ad811736af 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -174,25 +174,25 @@ describe Commit, 'Mentionable' do
it "is false when message doesn't reference anything" do
allow(commit.raw).to receive(:message).and_return "WIP: Do something"
- expect(commit.matches_cross_reference_regex?).to be false
+ expect(commit.matches_cross_reference_regex?).to be_falsey
end
it 'is true if issue #number mentioned in title' do
allow(commit.raw).to receive(:message).and_return "#1"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if references an MR' do
allow(commit.raw).to receive(:message).and_return "See merge request !12"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if references a commit' do
allow(commit.raw).to receive(:message).and_return "a1b2c3d4"
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
it 'is true if issue referenced by url' do
@@ -200,7 +200,7 @@ describe Commit, 'Mentionable' do
allow(commit.raw).to receive(:message).and_return Gitlab::UrlBuilder.build(issue)
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
context 'with external issue tracker' do
@@ -209,7 +209,13 @@ describe Commit, 'Mentionable' do
it 'is true if external issues referenced' do
allow(commit.raw).to receive(:message).and_return 'JIRA-123'
- expect(commit.matches_cross_reference_regex?).to be true
+ expect(commit.matches_cross_reference_regex?).to be_truthy
+ end
+
+ it 'is true if internal issues referenced' do
+ allow(commit.raw).to receive(:message).and_return '#123'
+
+ expect(commit.matches_cross_reference_regex?).to be_truthy
end
end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index bdae742ff1d..485a6e165a1 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Noteable, model: true do
+describe Noteable do
let!(:active_diff_note1) { create(:diff_note_on_merge_request) }
let(:project) { active_diff_note1.project }
subject { active_diff_note1.noteable }
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index a9f4ef9ee5e..431f1482615 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Participable, models: true do
+describe Participable do
let(:model) do
Class.new do
include Participable
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 808247ebfd5..5f9b7e0a367 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ReactiveCaching, caching: true do
+describe ReactiveCaching, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
class CacheTest
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 3934992c143..1616c2ea985 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Discussion, ResolvableDiscussion, models: true do
+describe Discussion, ResolvableDiscussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:discussion_note_on_merge_request) }
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index 1503ccdff11..53eaa6f8461 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Note, ResolvableNote, models: true do
+describe Note, ResolvableNote do
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project) }
subject { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 9e37c2b20c4..21893e0cbaa 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -13,15 +13,34 @@ describe ShaAttribute do
end
describe '#sha_attribute' do
- it 'defines a SHA attribute for a binary column' do
- expect(model).to receive(:attribute)
- .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+ context 'when the table exists' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(true)
+ end
- model.sha_attribute(:sha1)
+ it 'defines a SHA attribute for a binary column' do
+ expect(model).to receive(:attribute)
+ .with(:sha1, an_instance_of(Gitlab::Database::ShaAttribute))
+
+ model.sha_attribute(:sha1)
+ end
+
+ it 'raises ArgumentError when the column type is not :binary' do
+ expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+ end
end
- it 'raises ArgumentError when the column type is not :binary' do
- expect { model.sha_attribute(:name) }.to raise_error(ArgumentError)
+ context 'when the table does not exist' do
+ before do
+ allow(model).to receive(:table_exists?).and_return(false)
+ end
+
+ it 'does nothing' do
+ expect(model).not_to receive(:columns)
+ expect(model).not_to receive(:attribute)
+
+ model.sha_attribute(:name)
+ end
end
end
end
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
index 83187d732e4..914730718e7 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Uniquify, models: true do
+describe Uniquify do
let(:uniquify) { described_class.new }
describe "#string" do
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 9053485939e..f2f1928926c 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#code', feature: true do
+describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index fc7d18bd40e..985e1bf80be 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#issue', models: true do
+describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 4f33f3c6d69..6fbb2a2d102 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#plan', feature: true do
+describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 4744b9e05ea..f8681c0a2f9 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#production', feature: true do
+describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index febb18c9884..0ac58695b35 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#review', feature: true do
+describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index f78d7a23105..b66d5623910 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#staging', feature: true do
+describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index fd58bd1d6ad..690c09bc2dc 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CycleAnalytics#test', feature: true do
+describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration
let(:project) { create(:project, :repository) }
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 8ef8218cf74..2aece75b817 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeployKey, models: true do
+describe DeployKey do
include EmailHelpers
describe "Associations" do
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index aacc178a19e..f10b65ba9d8 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeployKeysProject, models: true do
+describe DeployKeysProject do
describe "Associations" do
it { is_expected.to belong_to(:deploy_key) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index bb84d3fc13d..6447095078b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Deployment, models: true do
+describe Deployment do
subject { build(:deployment) }
it { is_expected.to belong_to(:project) }
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 45b2f6e4beb..2704698f6c9 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DiffDiscussion, model: true do
+describe DiffDiscussion do
include RepoHelpers
subject { described_class.new([diff_note]) }
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 297c2108dc2..4aa9ec789a3 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DiffNote, models: true do
+describe DiffNote do
include RepoHelpers
let(:merge_request) { create(:merge_request) }
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index 3755f4a56f3..b26de3f3b97 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DiffViewer::Base, model: true do
+describe DiffViewer::Base do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb
index 2d926e06936..92e613f92de 100644
--- a/spec/models/diff_viewer/server_side_spec.rb
+++ b/spec/models/diff_viewer/server_side_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DiffViewer::ServerSide, model: true do
+describe DiffViewer::ServerSide do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 0221e23ced8..a46f7ed6507 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Discussion, model: true do
+describe Discussion do
subject { described_class.new([first_note, second_note, third_note]) }
let(:first_note) { create(:diff_note_on_merge_request) }
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index fe4de1b2afb..1d6fabe48b1 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Email, models: true do
+describe Email do
describe 'validations' do
it_behaves_like 'an object with email-formated attributes', :email do
subject { build(:email) }
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 0a2cd8c2957..ebf2c070116 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Environment, models: true do
+describe Environment do
set(:project) { create(:empty_project) }
subject(:environment) { create(:environment, project: project) }
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 10b9bf9f43a..4a4b84c9566 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Event, models: true do
+describe Event do
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:target) }
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index cd50bda8996..c8748daf46b 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ExternalIssue, models: true do
+describe ExternalIssue do
let(:project) { double('project', id: 1, to_reference: 'namespace1/project1') }
let(:issue) { described_class.new('EXT-1234', project) }
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 5c13cf584f9..38fbdd2536a 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -42,7 +42,7 @@ describe ForkedProjectLink, "add link on fork" do
describe '#forked?' do
let(:project_to) { create(:project, forked_project_link: forked_project_link) }
- let(:forked_project_link) { build(:forked_project_link) }
+ let(:forked_project_link) { create(:forked_project_link) }
before do
forked_project_link.forked_from_project = project_from
@@ -59,9 +59,9 @@ describe ForkedProjectLink, "add link on fork" do
end
it "project_to.destroy destroys fork_link" do
- expect(forked_project_link).to receive(:destroy)
-
project_to.destroy
+
+ expect(ForkedProjectLink.exists?(id: forked_project_link.id)).to eq(false)
end
end
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index 152e97e09bf..aedc74deb78 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GenericCommitStatus, models: true do
+describe GenericCommitStatus do
let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:external_url) { 'http://example.gitlab.com/status' }
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index a14efda3eda..5584a1a5a31 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GlobalMilestone, models: true do
+describe GlobalMilestone do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:group) { create(:group) }
@@ -72,7 +72,7 @@ describe GlobalMilestone, models: true do
project3
]
- @global_milestones = GlobalMilestone.build_collection(projects, {})
+ @global_milestones = described_class.build_collection(projects, {})
end
it 'has all project milestones' do
@@ -106,7 +106,7 @@ describe GlobalMilestone, models: true do
it 'returns the quantity of global milestones in each possible state' do
expected_count = { opened: 1, closed: 2, all: 2 }
- count = GlobalMilestone.states_count(Project.all)
+ count = described_class.states_count(Project.all)
expect(count).to eq(expected_count)
end
@@ -120,7 +120,7 @@ describe GlobalMilestone, models: true do
it 'returns 0 as the quantity of global milestones in each state' do
expected_count = { opened: 0, closed: 0, all: 0 }
- count = GlobalMilestone.states_count(Project.all)
+ count = described_class.states_count(Project.all)
expect(count).to eq(expected_count)
end
@@ -141,7 +141,7 @@ describe GlobalMilestone, models: true do
]
milestones_relation = Milestone.where(id: milestones.map(&:id))
- @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones_relation)
+ @global_milestone = described_class.new(milestone1_project1.title, milestones_relation)
end
it 'has exactly one group milestone' do
@@ -157,7 +157,7 @@ describe GlobalMilestone, models: true do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do
- global_milestone = GlobalMilestone.new(milestone.title, Milestone.where(id: milestone.id))
+ global_milestone = described_class.new(milestone.title, Milestone.where(id: milestone.id))
expect(global_milestone.safe_title).to eq('git-test')
end
@@ -171,7 +171,7 @@ describe GlobalMilestone, models: true do
create(:active_milestone, title: title),
create(:closed_milestone, title: title)
]
- global_milestone = GlobalMilestone.new(title, milestones)
+ global_milestone = described_class.new(title, milestones)
expect(global_milestone.state).to eq('active')
end
@@ -184,7 +184,7 @@ describe GlobalMilestone, models: true do
create(:closed_milestone, title: title),
create(:closed_milestone, title: title)
]
- global_milestone = GlobalMilestone.new(title, milestones)
+ global_milestone = described_class.new(title, milestones)
expect(global_milestone.state).to eq('closed')
end
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
new file mode 100644
index 00000000000..59c074199db
--- /dev/null
+++ b/spec/models/gpg_key_spec.rb
@@ -0,0 +1,157 @@
+require 'rails_helper'
+
+describe GpgKey do
+ describe "associations" do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe "validation" do
+ it { is_expected.to validate_presence_of(:user) }
+
+ it { is_expected.to validate_presence_of(:key) }
+ it { is_expected.to validate_uniqueness_of(:key) }
+
+ it { is_expected.to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey\n-----END PGP PUBLIC KEY BLOCK-----").for(:key) }
+
+ it { is_expected.not_to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey").for(:key) }
+ it { is_expected.not_to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey\n-----BEGIN PGP PUBLIC KEY BLOCK-----").for(:key) }
+ it { is_expected.not_to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK----------END PGP PUBLIC KEY BLOCK-----").for(:key) }
+ it { is_expected.not_to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----").for(:key) }
+ it { is_expected.not_to allow_value("-----END PGP PUBLIC KEY BLOCK-----").for(:key) }
+ it { is_expected.not_to allow_value("key\n-----END PGP PUBLIC KEY BLOCK-----").for(:key) }
+ it { is_expected.not_to allow_value('BEGIN PGP').for(:key) }
+ end
+
+ context 'callbacks' do
+ describe 'extract_fingerprint' do
+ it 'extracts the fingerprint from the gpg key' do
+ gpg_key = described_class.new(key: GpgHelpers::User1.public_key)
+ gpg_key.valid?
+ expect(gpg_key.fingerprint).to eq GpgHelpers::User1.fingerprint
+ end
+ end
+
+ describe 'extract_primary_keyid' do
+ it 'extracts the primary keyid from the gpg key' do
+ gpg_key = described_class.new(key: GpgHelpers::User1.public_key)
+ gpg_key.valid?
+ expect(gpg_key.primary_keyid).to eq GpgHelpers::User1.primary_keyid
+ end
+ end
+ end
+
+ describe '#key=' do
+ it 'strips white spaces' do
+ key = <<~KEY.strip
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1
+
+ mQENBFMOSOgBCADFCYxmnXFbrDhfvlf03Q/bQuT+nZu46BFGbo7XkUjDowFXJQhP
+ -----END PGP PUBLIC KEY BLOCK-----
+ KEY
+
+ expect(described_class.new(key: " #{key} ").key).to eq(key)
+ end
+
+ it 'does not strip when the key is nil' do
+ expect(described_class.new(key: nil).key).to be_nil
+ end
+ end
+
+ describe '#user_infos' do
+ it 'returns the user infos from the gpg key' do
+ gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key
+ expect(Gitlab::Gpg).to receive(:user_infos_from_key).with(gpg_key.key)
+
+ gpg_key.user_infos
+ end
+ end
+
+ describe '#verified_user_infos' do
+ it 'returns the user infos if it is verified' do
+ user = create :user, email: GpgHelpers::User1.emails.first
+ gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+
+ expect(gpg_key.verified_user_infos).to eq([{
+ name: GpgHelpers::User1.names.first,
+ email: GpgHelpers::User1.emails.first
+ }])
+ end
+
+ it 'returns an empty array if the user info is not verified' do
+ user = create :user, email: 'unrelated@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+
+ expect(gpg_key.verified_user_infos).to eq([])
+ end
+ end
+
+ describe '#emails_with_verified_status' do
+ it 'email is verified if the user has the matching email' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.emails_with_verified_status).to eq(
+ 'bette.cartwright@example.com' => true,
+ 'bette.cartwright@example.net' => false
+ )
+ end
+ end
+
+ describe '#verified?' do
+ it 'returns true one of the email addresses in the key belongs to the user' do
+ user = create :user, email: 'bette.cartwright@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_truthy
+ end
+
+ it 'returns false if one of the email addresses in the key does not belong to the user' do
+ user = create :user, email: 'someone.else@example.com'
+ gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key, user: user
+
+ expect(gpg_key.verified?).to be_falsey
+ end
+ end
+
+ describe 'notification' do
+ include EmailHelpers
+
+ let(:user) { create(:user) }
+
+ it 'sends a notification' do
+ perform_enqueued_jobs do
+ create(:gpg_key, user: user)
+ end
+
+ should_email(user)
+ end
+ end
+
+ describe '#revoke' do
+ it 'invalidates all associated gpg signatures and destroys the key' do
+ gpg_key = create :gpg_key
+ gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: gpg_key
+
+ unrelated_gpg_key = create :gpg_key, key: GpgHelpers::User2.public_key
+ unrelated_gpg_signature = create :gpg_signature, valid_signature: true, gpg_key: unrelated_gpg_key
+
+ gpg_key.revoke
+
+ expect(gpg_signature.reload).to have_attributes(
+ valid_signature: false,
+ gpg_key: nil
+ )
+
+ expect(gpg_key.destroyed?).to be true
+
+ # unrelated signature is left untouched
+ expect(unrelated_gpg_signature.reload).to have_attributes(
+ valid_signature: true,
+ gpg_key: unrelated_gpg_key
+ )
+
+ expect(unrelated_gpg_key.destroyed?).to be false
+ end
+ end
+end
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
new file mode 100644
index 00000000000..9a9b1900aa5
--- /dev/null
+++ b/spec/models/gpg_signature_spec.rb
@@ -0,0 +1,28 @@
+require 'rails_helper'
+
+RSpec.describe GpgSignature do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:gpg_key) }
+ end
+
+ describe 'validation' do
+ subject { described_class.new }
+ it { is_expected.to validate_presence_of(:commit_sha) }
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_presence_of(:gpg_key_primary_keyid) }
+ end
+
+ describe '#commit' do
+ it 'fetches the commit through the project' do
+ commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
+ project = create :project
+ commit = create :commit, project: project
+ gpg_signature = create :gpg_signature, commit_sha: commit_sha
+
+ expect_any_instance_of(Project).to receive(:commit).with(commit_sha).and_return(commit)
+
+ gpg_signature.commit
+ end
+ end
+end
diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb
index 555a876daeb..f7828059295 100644
--- a/spec/models/group_label_spec.rb
+++ b/spec/models/group_label_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupLabel, models: true do
+describe GroupLabel do
describe 'relationships' do
it { is_expected.to belong_to(:group) }
end
diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb
index 916afb7aaf5..ac76c927c39 100644
--- a/spec/models/group_milestone_spec.rb
+++ b/spec/models/group_milestone_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupMilestone, models: true do
+describe GroupMilestone do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
let(:project_milestone) do
@@ -9,7 +9,7 @@ describe GroupMilestone, models: true do
describe '.build' do
it 'returns milestone with group assigned' do
- milestone = GroupMilestone.build(
+ milestone = described_class.build(
group,
[project],
project_milestone.title
@@ -25,7 +25,7 @@ describe GroupMilestone, models: true do
end
it 'returns array of milestones, each with group assigned' do
- milestones = GroupMilestone.build_collection(group, [project], {})
+ milestones = described_class.build_collection(group, [project], {})
expect(milestones).to all(have_attributes(group: group))
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4de1683b21c..112bd605a64 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Group, models: true do
+describe Group do
let!(:group) { create(:group, :access_requestable) }
describe 'associations' do
@@ -13,6 +13,7 @@ describe Group, models: true do
it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
it { is_expected.to have_many(:labels).class_name('GroupLabel') }
+ it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_one(:chat_team) }
@@ -188,7 +189,7 @@ describe Group, models: true do
let!(:group) { create(:group, :access_requestable, :with_avatar) }
let(:user) { create(:user) }
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
- let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" }
+ let(:avatar_path) { "/uploads/-/system/group/avatar/#{group.id}/dk.png" }
context 'when avatar file is uploaded' do
before do
@@ -235,6 +236,7 @@ describe Group, models: true do
describe '#has_owner?' do
before do
@members = setup_group_members(group)
+ create(:group_member, :invited, :owner, group: group)
end
it { expect(group.has_owner?(@members[:owner])).to be_truthy }
@@ -243,11 +245,13 @@ describe Group, models: true do
it { expect(group.has_owner?(@members[:reporter])).to be_falsey }
it { expect(group.has_owner?(@members[:guest])).to be_falsey }
it { expect(group.has_owner?(@members[:requester])).to be_falsey }
+ it { expect(group.has_owner?(nil)).to be_falsey }
end
describe '#has_master?' do
before do
@members = setup_group_members(group)
+ create(:group_member, :invited, :master, group: group)
end
it { expect(group.has_master?(@members[:owner])).to be_falsey }
@@ -256,6 +260,7 @@ describe Group, models: true do
it { expect(group.has_master?(@members[:reporter])).to be_falsey }
it { expect(group.has_master?(@members[:guest])).to be_falsey }
it { expect(group.has_master?(@members[:requester])).to be_falsey }
+ it { expect(group.has_master?(nil)).to be_falsey }
end
describe '#lfs_enabled?' do
@@ -352,7 +357,7 @@ describe Group, models: true do
subject { build(:group, :nested) }
it { is_expected.to be_valid }
- it { expect(subject.parent).to be_kind_of(Group) }
+ it { expect(subject.parent).to be_kind_of(described_class) }
end
describe '#members_with_parents', :nested_groups do
@@ -418,4 +423,69 @@ describe Group, models: true do
expect(calls).to eq 2
end
end
+
+ describe '#secret_variables_for' do
+ let(:project) { create(:empty_project, group: group) }
+
+ let!(:secret_variable) do
+ create(:ci_group_variable, value: 'secret', group: group)
+ end
+
+ let!(:protected_variable) do
+ create(:ci_group_variable, :protected, value: 'protected', group: group)
+ end
+
+ subject { group.secret_variables_for('ref', project) }
+
+ shared_examples 'ref is protected' do
+ it 'contains all the variables' do
+ is_expected.to contain_exactly(secret_variable, protected_variable)
+ end
+ end
+
+ context 'when the ref is not protected' do
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
+
+ it 'contains only the secret variables' do
+ is_expected.to contain_exactly(secret_variable)
+ end
+ end
+
+ context 'when the ref is a protected branch' do
+ before do
+ create(:protected_branch, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when the ref is a protected tag' do
+ before do
+ create(:protected_tag, name: 'ref', project: project)
+ end
+
+ it_behaves_like 'ref is protected'
+ end
+
+ context 'when group has children', :postgresql do
+ let(:group_child) { create(:group, parent: group) }
+ let(:group_child_2) { create(:group, parent: group_child) }
+ let(:group_child_3) { create(:group, parent: group_child_2) }
+ let(:variable_child) { create(:ci_group_variable, group: group_child) }
+ let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
+ let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
+
+ it 'returns all variables belong to the group and parent groups' do
+ expected_array1 = [protected_variable, secret_variable]
+ expected_array2 = [variable_child, variable_child_2, variable_child_3]
+ got_array = group_child_3.secret_variables_for('ref', project).to_a
+
+ expect(got_array.shift(2)).to contain_exactly(*expected_array1)
+ expect(got_array).to eq(expected_array2)
+ end
+ end
+ end
end
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index c60bd7af958..0e9b94aac97 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Guest, lib: true do
+describe Guest do
let(:public_project) { build_stubbed(:empty_project, :public) }
let(:private_project) { build_stubbed(:empty_project, :private) }
let(:internal_project) { build_stubbed(:empty_project, :internal) }
@@ -8,13 +8,13 @@ describe Guest, lib: true do
describe '.can_pull?' do
context 'when project is private' do
it 'does not allow to pull the repo' do
- expect(Guest.can?(:download_code, private_project)).to eq(false)
+ expect(described_class.can?(:download_code, private_project)).to eq(false)
end
end
context 'when project is internal' do
it 'does not allow to pull the repo' do
- expect(Guest.can?(:download_code, internal_project)).to eq(false)
+ expect(described_class.can?(:download_code, internal_project)).to eq(false)
end
end
@@ -23,7 +23,7 @@ describe Guest, lib: true do
it 'does not allow to pull the repo' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
- expect(Guest.can?(:download_code, public_project)).to eq(false)
+ expect(described_class.can?(:download_code, public_project)).to eq(false)
end
end
@@ -31,13 +31,13 @@ describe Guest, lib: true do
it 'does not allow to pull the repo' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::PRIVATE)
- expect(Guest.can?(:download_code, public_project)).to eq(false)
+ expect(described_class.can?(:download_code, public_project)).to eq(false)
end
end
context 'when repository is enabled' do
it 'allows to pull the repo' do
- expect(Guest.can?(:download_code, public_project)).to eq(true)
+ expect(described_class.can?(:download_code, public_project)).to eq(true)
end
end
end
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 474ae62ccec..5dd31b1b5de 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,15 +1,19 @@
require 'spec_helper'
-describe ProjectHook, models: true do
- describe "Associations" do
+describe ProjectHook do
+ describe 'associations' do
it { is_expected.to belong_to :project }
end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ end
+
describe '.push_hooks' do
it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true)
create(:project_hook, push_events: false)
- expect(ProjectHook.push_hooks).to eq([hook])
+ expect(described_class.push_hooks).to eq([hook])
end
end
@@ -17,7 +21,7 @@ describe ProjectHook, models: true do
it 'returns hooks for tag push events only' do
hook = create(:project_hook, tag_push_events: true)
create(:project_hook, tag_push_events: false)
- expect(ProjectHook.tag_push_hooks).to eq([hook])
+ expect(described_class.tag_push_hooks).to eq([hook])
end
end
end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 57454d2a773..e32eaafc13f 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,10 +1,14 @@
require 'spec_helper'
-describe ServiceHook, models: true do
+describe ServiceHook do
describe 'associations' do
it { is_expected.to belong_to :service }
end
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:service) }
+ end
+
describe 'execute' do
let(:hook) { build(:service_hook) }
let(:data) { { key: 'value' } }
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 0d2b622132e..eadc232a989 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,14 +1,13 @@
require "spec_helper"
-describe SystemHook, models: true do
+describe SystemHook do
context 'default attributes' do
let(:system_hook) { build(:system_hook) }
it 'sets defined default parameters' do
attrs = {
push_events: false,
- repository_update_events: true,
- enable_ssl_verification: true
+ repository_update_events: true
}
expect(system_hook).to have_attributes(attrs)
end
@@ -123,7 +122,7 @@ describe SystemHook, models: true do
it 'returns hooks for repository update events only' do
hook = create(:system_hook, repository_update_events: true)
create(:system_hook, repository_update_events: false)
- expect(SystemHook.repository_update_hooks).to eq([hook])
+ expect(described_class.repository_update_hooks).to eq([hook])
end
end
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index c649cf3b589..19bc88b1333 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe WebHookLog, models: true do
+describe WebHookLog do
it { is_expected.to belong_to(:web_hook) }
it { is_expected.to serialize(:request_headers).as(Hash) }
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 53157c24477..388120160ab 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WebHook, models: true do
+describe WebHook do
let(:hook) { build(:project_hook) }
describe 'associations' do
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index b3aed66a5b6..4ca6556d0f4 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-RSpec.describe Identity, models: true do
+RSpec.describe Identity do
describe 'relations' do
it { is_expected.to belong_to(:user) }
end
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index 08712f2a768..6ceff7d24d4 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issue::Metrics, models: true do
+describe Issue::Metrics do
let(:project) { create(:empty_project) }
subject { create(:issue, project: project) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index bf97c6ececd..d72790eefe5 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issue, models: true do
+describe Issue do
describe "Associations" do
it { is_expected.to belong_to(:milestone) }
it { is_expected.to have_many(:assignees) }
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index f27920f9feb..d41717d0223 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Key, models: true do
+describe Key do
include EmailHelpers
describe "Associations" do
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index c18ed8574b1..e2b49bc2de7 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe LabelLink, models: true do
+describe LabelLink do
it { expect(build(:label_link)).to be_valid }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index d18c2f7949a..9dcb0f06b20 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe LabelPriority, models: true do
+describe LabelPriority do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:label) }
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 31190fe5685..8914845ea82 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Label, models: true do
+describe Label do
describe 'modules' do
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Subscribable) }
diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb
index 6eb4a2aaf39..dae97b69c84 100644
--- a/spec/models/legacy_diff_discussion_spec.rb
+++ b/spec/models/legacy_diff_discussion_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe LegacyDiffDiscussion, models: true do
+describe LegacyDiffDiscussion do
subject { create(:legacy_diff_note_on_merge_request).to_discussion }
describe '#reply_attributes' do
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index 7bc278e350f..8c74f1f9e86 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe LfsObjectsProject, models: true do
+describe LfsObjectsProject do
subject { create(:lfs_objects_project, project: project) }
let(:project) { create(:empty_project) }
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index db2c2619968..a6cc01bea5f 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -13,12 +13,6 @@ describe List do
it { is_expected.to validate_presence_of(:position) }
it { is_expected.to validate_numericality_of(:position).only_integer.is_greater_than_or_equal_to(0) }
- it 'validates uniqueness of label scoped to board_id' do
- create(:list)
-
- expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:board_id)
- end
-
context 'when list_type is set to closed' do
subject { described_class.new(list_type: :closed) }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 494a88368ba..8bfd70b8d46 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe Member, models: true do
+describe Member do
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
describe "Validation" do
- subject { Member.new(access_level: Member::GUEST) }
+ subject { described_class.new(access_level: Member::GUEST) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:source) }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 37014268a70..5a3b5b1f517 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupMember, models: true do
+describe GroupMember do
describe '.access_level_roles' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner)
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index cf9c701e8c5..025fb2bf441 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectMember, models: true do
+describe ProjectMember do
describe 'associations' do
it { is_expected.to belong_to(:project).with_foreign_key(:source_id) }
end
@@ -139,7 +139,7 @@ describe ProjectMember, models: true do
@project_1.team << [@user_1, :developer]
@project_2.team << [@user_2, :reporter]
- ProjectMember.truncate_teams([@project_1.id, @project_2.id])
+ described_class.truncate_teams([@project_1.id, @project_2.id])
end
it { expect(@project_1.users).to be_empty }
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 9afed311e27..9353d5c3c8a 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequest::Metrics, models: true do
+describe MergeRequest::Metrics do
subject { create(:merge_request) }
describe "when recording the default set of metrics on merge request save" do
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
new file mode 100644
index 00000000000..9d4a0ecf8c0
--- /dev/null
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe MergeRequestDiffCommit do
+ let(:merge_request) { create(:merge_request) }
+ subject { merge_request.commits.first }
+
+ describe '#to_hash' do
+ it 'returns the same results as Commit#to_hash, except for parent_ids' do
+ commit_from_repo = merge_request.project.repository.commit(subject.sha)
+ commit_from_repo_hash = commit_from_repo.to_hash.merge(parent_ids: [])
+
+ expect(subject.to_hash).to eq(commit_from_repo_hash)
+ end
+ end
+end
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index 7276f5b5061..faa47660a74 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -1,8 +1,33 @@
require 'rails_helper'
-describe MergeRequestDiffFile, type: :model do
+describe MergeRequestDiffFile do
+ describe '#diff' do
+ let(:unpacked) { 'unpacked' }
+ let(:packed) { [unpacked].pack('m0') }
+
+ before do
+ subject.diff = packed
+ end
+
+ context 'when the diff is marked as binary' do
+ before do
+ subject.binary = true
+ end
+
+ it 'unpacks from base 64' do
+ expect(subject.diff).to eq(unpacked)
+ end
+ end
+
+ context 'when the diff is not marked as binary' do
+ it 'returns the raw diff' do
+ expect(subject.diff).to eq(packed)
+ end
+ end
+ end
+
describe '#utf8_diff' do
- it 'does not raise error when a hash value is in binary' do
+ it 'does not raise error when the diff is binary' do
subject.diff = "\x05\x00\x68\x65\x6c\x6c\x6f"
expect { subject.utf8_diff }.not_to raise_error
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 4ad4abaa572..0cfaa17676e 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequestDiff, models: true do
+describe MergeRequestDiff do
describe 'create new record' do
subject { create(:merge_request).merge_request_diff }
@@ -98,23 +98,32 @@ describe MergeRequestDiff, models: true do
end
it 'saves empty state' do
- allow_any_instance_of(MergeRequestDiff).to receive(:commits)
+ allow_any_instance_of(described_class).to receive_message_chain(:compare, :commits)
.and_return([])
mr_diff = create(:merge_request).merge_request_diff
expect(mr_diff.empty?).to be_truthy
end
+
+ it 'saves binary diffs correctly' do
+ path = 'files/images/icn-time-tracking.pdf'
+ mr_diff = create(:merge_request, source_branch: 'add-pdf-text-binary', target_branch: 'master').merge_request_diff
+ diff_file = mr_diff.merge_request_diff_files.find_by(new_path: path)
+
+ expect(diff_file).to be_binary
+ expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [path]).to_a.first.diff)
+ end
end
- describe '#commits_sha' do
+ describe '#commit_shas' do
it 'returns all commits SHA using serialized commits' do
subject.st_commits = [
{ id: 'sha1' },
{ id: 'sha2' }
]
- expect(subject.commits_sha).to eq(%w(sha1 sha2))
+ expect(subject.commit_shas).to eq(%w(sha1 sha2))
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 587d4b83cb4..b2dd02553c1 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequest, models: true do
+describe MergeRequest do
include RepoHelpers
subject { create(:merge_request) }
@@ -10,7 +10,7 @@ describe MergeRequest, models: true do
it { is_expected.to belong_to(:source_project).class_name('Project') }
it { is_expected.to belong_to(:merge_user).class_name("User") }
it { is_expected.to belong_to(:assignee) }
- it { is_expected.to have_many(:merge_request_diffs).dependent(:destroy) }
+ it { is_expected.to have_many(:merge_request_diffs) }
end
describe 'modules' do
@@ -155,13 +155,53 @@ describe MergeRequest, models: true do
expect { subject.cache_merge_request_closes_issues!(subject.author) }.to change(subject.merge_requests_closing_issues, :count).by(1)
end
- it 'does not cache issues from external trackers' do
- subject.project.update_attribute(:has_external_issue_tracker, true)
- issue = ExternalIssue.new('JIRA-123', subject.project)
- commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
- allow(subject).to receive(:commits).and_return([commit])
+ context 'when both internal and external issue trackers are enabled' do
+ before do
+ subject.project.has_external_issue_tracker = true
+ subject.project.save!
+ end
+
+ it 'does not cache issues from external trackers' do
+ issue = ExternalIssue.new('JIRA-123', subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
- expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ end
+
+ it 'caches an internal issue' do
+ issue = create(:issue, project: subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }
+ .to change(subject.merge_requests_closing_issues, :count).by(1)
+ end
+ end
+
+ context 'when only external issue tracker enabled' do
+ before do
+ subject.project.has_external_issue_tracker = true
+ subject.project.issues_enabled = false
+ subject.project.save!
+ end
+
+ it 'does not cache issues from external trackers' do
+ issue = ExternalIssue.new('JIRA-123', subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
+ end
+
+ it 'does not cache an internal issue' do
+ issue = create(:issue, project: subject.project)
+ commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
+ allow(subject).to receive(:commits).and_return([commit])
+
+ expect { subject.cache_merge_request_closes_issues!(subject.author) }
+ .not_to change(subject.merge_requests_closing_issues, :count)
+ end
end
end
@@ -720,14 +760,14 @@ describe MergeRequest, models: true do
subject { create :merge_request, :simple }
end
- describe '#commits_sha' do
+ describe '#commit_shas' do
before do
- allow(subject.merge_request_diff).to receive(:commits_sha)
+ allow(subject.merge_request_diff).to receive(:commit_shas)
.and_return(['sha1'])
end
it 'delegates to merge request diff' do
- expect(subject.commits_sha).to eq ['sha1']
+ expect(subject.commit_shas).to eq ['sha1']
end
end
@@ -752,7 +792,7 @@ describe MergeRequest, models: true do
describe '#all_pipelines' do
shared_examples 'returning pipelines with proper ordering' do
let!(:all_pipelines) do
- subject.all_commits_sha.map do |sha|
+ subject.all_commit_shas.map do |sha|
create(:ci_empty_pipeline,
project: subject.source_project,
sha: sha,
@@ -794,16 +834,16 @@ describe MergeRequest, models: true do
end
end
- describe '#all_commits_sha' do
+ describe '#all_commit_shas' do
context 'when merge request is persisted' do
- let(:all_commits_sha) do
+ let(:all_commit_shas) do
subject.merge_request_diffs.flat_map(&:commits).map(&:sha).uniq
end
shared_examples 'returning all SHA' do
it 'returns all SHA from all merge_request_diffs' do
expect(subject.merge_request_diffs.size).to eq(2)
- expect(subject.all_commits_sha).to eq(all_commits_sha)
+ expect(subject.all_commit_shas).to match_array(all_commit_shas)
end
end
@@ -834,7 +874,7 @@ describe MergeRequest, models: true do
end
it 'returns commits from compare commits temporary data' do
- expect(subject.all_commits_sha).to eq [commit, commit]
+ expect(subject.all_commit_shas).to eq [commit, commit]
end
end
@@ -842,7 +882,7 @@ describe MergeRequest, models: true do
subject { build(:merge_request) }
it 'returns array with diff head sha element only' do
- expect(subject.all_commits_sha).to eq [subject.diff_head_sha]
+ expect(subject.all_commit_shas).to eq [subject.diff_head_sha]
end
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 45953023a36..aa376e242e8 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,14 +1,11 @@
require 'spec_helper'
-describe Milestone, models: true do
+describe Milestone do
describe "Validation" do
before do
allow(subject).to receive(:set_iid).and_return(false)
end
- it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to validate_presence_of(:project) }
-
describe 'start_date' do
it 'adds an error when start_date is greated then due_date' do
milestone = build(:milestone, start_date: Date.tomorrow, due_date: Date.yesterday)
@@ -37,17 +34,42 @@ describe Milestone, models: true do
end
end
- describe "unique milestone title per project" do
- it "does not accept the same title in a project twice" do
- new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
- expect(new_milestone).not_to be_valid
+ describe "unique milestone title" do
+ context "per project" do
+ it "does not accept the same title in a project twice" do
+ new_milestone = described_class.new(project: milestone.project, title: milestone.title)
+ expect(new_milestone).not_to be_valid
+ end
+
+ it "accepts the same title in another project" do
+ project = create(:empty_project)
+ new_milestone = described_class.new(project: project, title: milestone.title)
+
+ expect(new_milestone).to be_valid
+ end
end
- it "accepts the same title in another project" do
- project = build(:empty_project)
- new_milestone = Milestone.new(project: project, title: milestone.title)
+ context "per group" do
+ let(:group) { create(:group) }
+ let(:milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(group: group)
+ end
+
+ it "does not accept the same title in a group twice" do
+ new_milestone = described_class.new(group: group, title: milestone.title)
+
+ expect(new_milestone).not_to be_valid
+ end
- expect(new_milestone).to be_valid
+ it "does not accept the same title of a child project milestone" do
+ create(:milestone, project: group.projects.first)
+
+ new_milestone = described_class.new(group: group, title: milestone.title)
+
+ expect(new_milestone).not_to be_valid
+ end
end
end
@@ -192,7 +214,7 @@ describe Milestone, models: true do
# The call to `#try` is because this returns a relation with a Postgres DB,
# and an array of IDs with a MySQL DB.
- let(:milestone_ids) { Milestone.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
+ let(:milestone_ids) { described_class.upcoming_ids_by_projects(projects).map { |id| id.try(:id) || id } }
it 'returns the next upcoming open milestone ID for each project' do
expect(milestone_ids).to contain_exactly(current_milestone_project_1.id, current_milestone_project_2.id)
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 62c4ea01ce1..f12fe226e6b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Namespace, models: true do
+describe Namespace do
let!(:namespace) { create(:namespace) }
describe 'associations' do
@@ -44,7 +44,7 @@ describe Namespace, models: true do
end
context "is case insensitive" do
- let(:group) { build(:group, path: "System") }
+ let(:group) { build(:group, path: "Groups") }
it { expect(group).not_to be_valid }
end
@@ -63,6 +63,14 @@ describe Namespace, models: true do
it { is_expected.to respond_to(:has_parent?) }
end
+ describe 'inclusions' do
+ it { is_expected.to include_module(Gitlab::VisibilityLevel) }
+ end
+
+ describe '#visibility_level_field' do
+ it { expect(namespace.visibility_level_field).to eq(:visibility_level) }
+ end
+
describe '#to_param' do
it { expect(namespace.to_param).to eq(namespace.full_path) }
end
@@ -125,7 +133,7 @@ describe Namespace, models: true do
it "sums all project storage counters in the namespace" do
project1
project2
- statistics = Namespace.with_statistics.find(namespace.id)
+ statistics = described_class.with_statistics.find(namespace.id)
expect(statistics.storage_size).to eq 666
expect(statistics.repository_size).to eq 111
@@ -134,7 +142,7 @@ describe Namespace, models: true do
end
it "correctly handles namespaces without projects" do
- statistics = Namespace.with_statistics.find(namespace.id)
+ statistics = described_class.with_statistics.find(namespace.id)
expect(statistics.storage_size).to eq 0
expect(statistics.repository_size).to eq 0
@@ -143,7 +151,7 @@ describe Namespace, models: true do
end
end
- describe '#move_dir', repository: true do
+ describe '#move_dir' do
before do
@namespace = create :namespace
@project = create(:project_empty_repo, namespace: @namespace)
@@ -222,7 +230,7 @@ describe Namespace, models: true do
end
end
- describe '#rm_dir', 'callback', repository: true do
+ describe '#rm_dir', 'callback' do
let!(:project) { create(:project_empty_repo, namespace: namespace) }
let(:repository_storage_path) { Gitlab.config.repositories.storages.default['path'] }
let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) }
@@ -278,9 +286,9 @@ describe Namespace, models: true do
@namespace = create(:namespace, name: 'WoW', path: 'woW')
end
- it { expect(Namespace.find_by_path_or_name('wow')).to eq(@namespace) }
- it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) }
- it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) }
+ it { expect(described_class.find_by_path_or_name('wow')).to eq(@namespace) }
+ it { expect(described_class.find_by_path_or_name('WOW')).to eq(@namespace) }
+ it { expect(described_class.find_by_path_or_name('unknown')).to eq(nil) }
end
describe ".clean_path" do
@@ -288,8 +296,8 @@ describe Namespace, models: true do
let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") }
it "cleans the path and makes sure it's available" do
- expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
- expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
+ expect(described_class.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
+ expect(described_class.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
end
end
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index 0fe8a591a45..c364dd6643b 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Network::Graph, models: true do
+describe Network::Graph do
let(:project) { create(:project, :repository) }
let!(:note_on_commit) { create(:note_on_commit, project: project) }
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index e2b80cb6e61..cbe6d42ef53 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Note, models: true do
+describe Note do
include RepoHelpers
describe 'associations' do
@@ -525,7 +525,7 @@ describe Note, models: true do
it "has a discussion id" do
# The discussion_id is set in `after_initialize`, so `reload` won't work
- reloaded_note = Note.find(note.id)
+ reloaded_note = described_class.find(note.id)
expect(reloaded_note.discussion_id).not_to be_nil
expect(reloaded_note.discussion_id).to match(/\A\h{40}\z/)
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index cc235ad467e..07e296424ca 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -1,21 +1,28 @@
require 'rails_helper'
-RSpec.describe NotificationSetting, type: :model do
+RSpec.describe NotificationSetting do
describe "Associations" do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:source) }
end
describe "Validation" do
- subject { NotificationSetting.new(source_id: 1, source_type: 'Project') }
+ subject { described_class.new(source_id: 1, source_type: 'Project') }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:level) }
- it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) }
+
+ describe 'user_id' do
+ before do
+ subject.user = create(:user)
+ end
+
+ it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_type, :source_id]).with_message(/already exists in source/) }
+ end
context "events" do
let(:user) { create(:user) }
- let(:notification_setting) { NotificationSetting.new(source_id: 1, source_type: 'Project', user_id: user.id) }
+ let(:notification_setting) { described_class.new(source_id: 1, source_type: 'Project', user_id: user.id) }
before do
notification_setting.level = "custom"
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index f9d060d4e0e..7d835511dfb 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PagesDomain, models: true do
+describe PagesDomain do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
@@ -11,7 +11,7 @@ describe PagesDomain, models: true do
context 'is unique' do
let(:domain) { 'my.domain.com' }
- it { is_expected.to validate_uniqueness_of(:domain) }
+ it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
end
{
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index fa781195608..b2f2a3ce914 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PersonalAccessToken, models: true do
+describe PersonalAccessToken do
describe '.build' do
let(:personal_access_token) { build(:personal_access_token) }
let(:invalid_personal_access_token) { build(:personal_access_token, :invalid) }
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 4161b9158b1..d68d8b719cd 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe ProjectGroupLink do
describe "Associations" do
- it { should belong_to(:group) }
- it { should belong_to(:project) }
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:project) }
end
describe "Validation" do
@@ -12,10 +12,10 @@ describe ProjectGroupLink do
let(:project) { create(:project, group: group) }
let!(:project_group_link) { create(:project_group_link, project: project) }
- it { should validate_presence_of(:project_id) }
- it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
- it { should validate_presence_of(:group) }
- it { should validate_presence_of(:group_access) }
+ it { is_expected.to validate_presence_of(:project_id) }
+ it { is_expected.to validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) }
+ it { is_expected.to validate_presence_of(:group) }
+ it { is_expected.to validate_presence_of(:group_access) }
it "doesn't allow a project to be shared with the group it is in" do
project_group_link.group = group
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 9cdbfa44e5b..add7e85f388 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectLabel, models: true do
+describe ProjectLabel do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 95c35162d96..4684c970885 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe AsanaService, models: true do
+describe AsanaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -35,7 +35,7 @@ describe AsanaService, models: true do
end
before do
- @asana = AsanaService.new
+ @asana = described_class.new
allow(@asana).to receive_messages(
project: project,
project_id: project.id,
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 96f00af898e..5cb6d63659e 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe AssemblaService, models: true do
+describe AssemblaService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -11,7 +11,7 @@ describe AssemblaService, models: true do
let(:project) { create(:project, :repository) }
before do
- @assembla_service = AssemblaService.new
+ @assembla_service = described_class.new
allow(@assembla_service).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 7b1a554d1fb..82f02126de1 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BambooService, models: true, caching: true do
+describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index 5f17bbde390..43f7bcb1a19 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BugzillaService, models: true do
+describe BugzillaService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index dd529597067..3f7eb33e08a 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BuildkiteService, models: true, caching: true do
+describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
let(:project) { create(:empty_project) }
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index 56ff3596190..ed8347edffd 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CampfireService, models: true do
+describe CampfireService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -29,7 +29,7 @@ describe CampfireService, models: true do
let(:project) { create(:project, :repository) }
before do
- @campfire_service = CampfireService.new
+ @campfire_service = described_class.new
allow(@campfire_service).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index c159ab00ab1..4bb1db684e6 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatMessage::IssueMessage, models: true do
+describe ChatMessage::IssueMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 61f17031172..b600a36f578 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatMessage::MergeMessage, models: true do
+describe ChatMessage::MergeMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 7996536218a..a09c2f9935c 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatMessage::NoteMessage, models: true do
+describe ChatMessage::NoteMessage do
subject { described_class.new(args) }
let(:color) { '#345' }
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index c794f659c41..19c2862264f 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatMessage::PushMessage, models: true do
+describe ChatMessage::PushMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 17355c1e6f1..c4adee4f489 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatMessage::WikiPageMessage, models: true do
+describe ChatMessage::WikiPageMessage do
subject { described_class.new(args) }
let(:args) do
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 8fbe42248ae..413ceed73bf 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatNotificationService, models: true do
+describe ChatNotificationService do
describe 'Associations' do
before do
allow(subject).to receive(:activated?).and_return(true)
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index 9e574762232..7e1b1a4f2af 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CustomIssueTrackerService, models: true do
+describe CustomIssueTrackerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 1400175427f..5b0f24ce306 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DroneCiService, models: true, caching: true do
+describe DroneCiService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
describe 'associations' do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 291fc645a1c..22cd9d5e31e 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe ExternalWikiService, models: true do
+describe ExternalWikiService do
include ExternalWikiHelper
describe "Associations" do
- it { should belong_to :project }
- it { should have_one :service_hook }
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index 56ace04dd58..5e8e880985e 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe FlowdockService, models: true do
+describe FlowdockService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -29,7 +29,7 @@ describe FlowdockService, models: true do
let(:project) { create(:project, :repository) }
before do
- @flowdock_service = FlowdockService.new
+ @flowdock_service = described_class.new
allow(@flowdock_service).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 65c9e714bd1..4c61bc0af95 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GemnasiumService, models: true do
+describe GemnasiumService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -31,7 +31,7 @@ describe GemnasiumService, models: true do
let(:project) { create(:project, :repository) }
before do
- @gemnasium_service = GemnasiumService.new
+ @gemnasium_service = described_class.new
allow(@gemnasium_service).to receive_messages(
project_id: project.id,
project: project,
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index dcb70ee28a8..d19dab8fd39 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GitlabIssueTrackerService, models: true do
+describe GitlabIssueTrackerService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -23,38 +23,29 @@ describe GitlabIssueTrackerService, models: true do
describe 'project and issue urls' do
let(:project) { create(:empty_project) }
+ let(:service) { project.create_gitlab_issue_tracker_service(active: true) }
context 'with absolute urls' do
before do
- GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root"
- @service = project.create_gitlab_issue_tracker_service(active: true)
- end
-
- after do
- @service.destroy!
+ allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
end
it 'gives the correct path' do
- expect(@service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues")
- expect(@service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/new")
- expect(@service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/432")
+ expect(service.project_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues")
+ expect(service.new_issue_url).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/new")
+ expect(service.issue_url(432)).to eq("http://#{Gitlab.config.gitlab.host}/gitlab/root/#{project.path_with_namespace}/issues/432")
end
end
context 'with relative urls' do
before do
- GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root"
- @service = project.create_gitlab_issue_tracker_service(active: true)
- end
-
- after do
- @service.destroy!
+ allow(described_class).to receive(:default_url_options).and_return(script_name: "/gitlab/root")
end
it 'gives the correct path' do
- expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
- expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
- expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
+ expect(service.issue_tracker_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
+ expect(service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
+ expect(service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
end
end
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index c7c8e9651ab..7614bb897e8 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe HipchatService, models: true do
+describe HipchatService do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -25,7 +25,7 @@ describe HipchatService, models: true do
end
describe "Execute" do
- let(:hipchat) { HipchatService.new }
+ let(:hipchat) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index a5c4938b54e..cb9ca76fc3f 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
require 'socket'
require 'json'
-describe IrkerService, models: true do
+describe IrkerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -27,7 +27,7 @@ describe IrkerService, models: true do
end
describe 'Execute' do
- let(:irker) { IrkerService.new }
+ let(:irker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:sample_data) do
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index 869b25b933b..e6a1752576b 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe IssueTrackerService, models: true do
+describe IssueTrackerService do
describe 'Validations' do
let(:project) { create :project }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 4a1de76f099..c9e8d7e194d 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe JiraService, models: true do
- include Gitlab::Routing.url_helpers
+describe JiraService do
+ include Gitlab::Routing
describe "Associations" do
it { is_expected.to belong_to :project }
@@ -15,7 +15,6 @@ describe JiraService, models: true do
end
it { is_expected.to validate_presence_of(:url) }
- it { is_expected.to validate_presence_of(:project_key) }
it_behaves_like 'issue tracker service URL attribute', :url
end
@@ -34,7 +33,6 @@ describe JiraService, models: true do
active: true,
username: 'username',
password: 'test',
- project_key: 'TEST',
jira_issue_transition_id: 24,
url: 'http://jira.test.com'
)
@@ -80,7 +78,7 @@ describe JiraService, models: true do
let(:merge_request) { create(:merge_request) }
before do
- @jira_service = JiraService.new
+ @jira_service = described_class.new
allow(@jira_service).to receive_messages(
project_id: project.id,
project: project,
@@ -88,7 +86,6 @@ describe JiraService, models: true do
url: 'http://jira.example.com',
username: 'gitlab_jira_username',
password: 'gitlab_jira_password',
- project_key: 'GitLabProject',
jira_issue_transition_id: "custom-id"
)
@@ -170,7 +167,7 @@ describe JiraService, models: true do
stub_config_setting(relative_url_root: '/gitlab')
stub_config_setting(url: Settings.send(:build_gitlab_url))
- allow(JiraService).to receive(:default_url_options) do
+ allow(described_class).to receive(:default_url_options) do
{ script_name: '/gitlab' }
end
@@ -196,15 +193,14 @@ describe JiraService, models: true do
project: create(:project),
url: 'http://jira.example.com',
username: 'jira_username',
- password: 'jira_password',
- project_key: 'GitLabProject'
+ password: 'jira_password'
)
end
def test_settings(api_url)
- project_url = "http://#{api_url}/rest/api/2/project/GitLabProject"
+ test_url = "http://#{api_url}/rest/api/2/serverInfo"
- WebMock.stub_request(:get, project_url).with(basic_auth: %w(jira_username jira_password))
+ WebMock.stub_request(:get, test_url).with(basic_auth: %w(jira_username jira_password)).to_return(body: { url: 'http://url' }.to_json )
jira_service.test_settings
end
@@ -224,7 +220,7 @@ describe JiraService, models: true do
context "when a password was previously set" do
before do
- @jira_service = JiraService.create!(
+ @jira_service = described_class.create!(
project: project,
properties: {
url: 'http://jira.example.com/web',
@@ -305,7 +301,7 @@ describe JiraService, models: true do
context 'when no password was previously set' do
before do
- @jira_service = JiraService.create(
+ @jira_service = described_class.create(
project: project,
properties: {
url: 'http://jira.example.com/rest/api/2',
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 858ad595dbf..55b96a0c12e 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe KubernetesService, models: true, caching: true do
+describe KubernetesService, :use_clean_rails_memory_store_caching do
include KubernetesHelpers
include ReactiveCachingHelpers
@@ -129,7 +129,7 @@ describe KubernetesService, models: true, caching: true do
it "returns the default namespace" do
is_expected.to eq(service.send(:default_namespace))
end
-
+
context 'when namespace is specified' do
before do
service.namespace = 'my-namespace'
@@ -201,6 +201,22 @@ describe KubernetesService, models: true, caching: true do
end
describe '#predefined_variables' do
+ let(:kubeconfig) do
+ config =
+ YAML.load(File.read(expand_fixture_path('config/kubeconfig.yml')))
+
+ config.dig('users', 0, 'user')['token'] =
+ 'token'
+
+ config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
+ Base64.encode64('CA PEM DATA')
+
+ config.dig('contexts', 0, 'context')['namespace'] =
+ namespace
+
+ YAML.dump(config)
+ end
+
before do
subject.api_url = 'https://kube.domain.com'
subject.token = 'token'
@@ -208,32 +224,34 @@ describe KubernetesService, models: true, caching: true do
subject.project = project
end
- context 'namespace is provided' do
- before do
- subject.namespace = 'my-project'
- end
-
+ shared_examples 'setting variables' do
it 'sets the variables' do
expect(subject.predefined_variables).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false },
- { key: 'KUBE_NAMESPACE', value: 'my-project', public: true },
+ { key: 'KUBE_NAMESPACE', value: namespace, public: true },
+ { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
{ key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
)
end
end
- context 'no namespace provided' do
- it 'sets the variables' do
- expect(subject.predefined_variables).to include(
- { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
- { key: 'KUBE_TOKEN', value: 'token', public: false },
- { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true },
- { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }
- )
+ context 'namespace is provided' do
+ let(:namespace) { 'my-project' }
+
+ before do
+ subject.namespace = namespace
end
+ it_behaves_like 'setting variables'
+ end
+
+ context 'no namespace provided' do
+ let(:namespace) { subject.actual_namespace }
+
+ it_behaves_like 'setting variables'
+
it 'sets the KUBE_NAMESPACE' do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
index 490d6aedffc..10c62ca55a7 100644
--- a/spec/models/project_services/mattermost_service_spec.rb
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -1,5 +1,5 @@
require 'spec_helper'
-describe MattermostService, models: true do
+describe MattermostService do
it_behaves_like "slack or mattermost notifications"
end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index fa38d23e82f..4c21c8b88bd 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MattermostSlashCommandsService, :models do
+describe MattermostSlashCommandsService do
it_behaves_like "chat slash commands service"
context 'Mattermost API' do
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index bd50a2d1470..f89be20ad78 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MicrosoftTeamsService, models: true do
+describe MicrosoftTeamsService do
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
@@ -112,7 +112,7 @@ describe MicrosoftTeamsService, models: true do
let(:wiki_page_sample_data) do
service = WikiPages::CreateService.new(project, user, opts)
wiki_page = service.execute
- service.hook_data(wiki_page, 'create')
+ Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create')
end
it "calls Microsoft Teams API" do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index f4c1a9c94b6..f7d2372eca2 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PivotaltrackerService, models: true do
+describe PivotaltrackerService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -26,7 +26,7 @@ describe PivotaltrackerService, models: true do
describe 'Execute' do
let(:service) do
- PivotaltrackerService.new.tap do |service|
+ described_class.new.tap do |service|
service.token = 'secret_api_token'
end
end
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 37f23b1243c..bf39e8d7a39 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PrometheusService, models: true, caching: true do
+describe PrometheusService, :use_clean_rails_memory_store_caching do
include PrometheusHelpers
include ReactiveCachingHelpers
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 9171d9604ee..54b8c658ff6 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PushoverService, models: true do
+describe PushoverService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -29,7 +29,7 @@ describe PushoverService, models: true do
end
describe 'Execute' do
- let(:pushover) { PushoverService.new }
+ let(:pushover) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:sample_data) do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 441b3f896ca..2ac14eab5e1 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe RedmineService, models: true do
+describe RedmineService do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 9a3ecc66d83..13cf4d1915e 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -1,5 +1,5 @@
require 'spec_helper'
-describe SlackService, models: true do
+describe SlackService do
it_behaves_like "slack or mattermost notifications"
end
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 5766aa340e2..aea674c4f6b 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SlackSlashCommandsService, :models do
+describe SlackSlashCommandsService do
it_behaves_like "chat slash commands service"
describe '#trigger' do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 6b004098510..47fd0c79e0b 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe TeamcityService, models: true, caching: true do
+describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 5fe4885eeb4..1b439bcfad1 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectSnippet, models: true do
+describe ProjectSnippet do
describe "Associations" do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ad98b4b669f..473b7a88d61 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,56 +1,56 @@
require 'spec_helper'
-describe Project, models: true do
+describe Project do
describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:creator).class_name('User') }
it { is_expected.to have_many(:users) }
it { is_expected.to have_many(:services) }
- it { is_expected.to have_many(:events).dependent(:destroy) }
- it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
- it { is_expected.to have_many(:issues).dependent(:destroy) }
- it { is_expected.to have_many(:milestones).dependent(:destroy) }
- it { is_expected.to have_many(:project_members).dependent(:destroy) }
+ it { is_expected.to have_many(:events) }
+ it { is_expected.to have_many(:merge_requests) }
+ it { is_expected.to have_many(:issues) }
+ it { is_expected.to have_many(:milestones) }
+ it { is_expected.to have_many(:project_members).dependent(:delete_all) }
it { is_expected.to have_many(:users).through(:project_members) }
- it { is_expected.to have_many(:requesters).dependent(:destroy) }
- it { is_expected.to have_many(:notes).dependent(:destroy) }
- it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
- it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) }
+ it { is_expected.to have_many(:requesters).dependent(:delete_all) }
+ it { is_expected.to have_many(:notes) }
+ it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') }
+ it { is_expected.to have_many(:deploy_keys_projects) }
it { is_expected.to have_many(:deploy_keys) }
- it { is_expected.to have_many(:hooks).dependent(:destroy) }
- it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
- it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
- it { is_expected.to have_one(:slack_service).dependent(:destroy) }
- it { is_expected.to have_one(:microsoft_teams_service).dependent(:destroy) }
- it { is_expected.to have_one(:mattermost_service).dependent(:destroy) }
- it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
- it { is_expected.to have_one(:asana_service).dependent(:destroy) }
- it { is_expected.to have_many(:boards).dependent(:destroy) }
- it { is_expected.to have_one(:campfire_service).dependent(:destroy) }
- it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) }
- it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
- it { is_expected.to have_one(:pipelines_email_service).dependent(:destroy) }
- it { is_expected.to have_one(:irker_service).dependent(:destroy) }
- it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
- it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
- it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
- it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) }
- it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) }
- it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
- it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
- it { is_expected.to have_one(:bamboo_service).dependent(:destroy) }
- it { is_expected.to have_one(:teamcity_service).dependent(:destroy) }
- it { is_expected.to have_one(:jira_service).dependent(:destroy) }
- it { is_expected.to have_one(:redmine_service).dependent(:destroy) }
- it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) }
- it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
- it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
- it { is_expected.to have_one(:project_feature).dependent(:destroy) }
- it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) }
- it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:delete) }
+ it { is_expected.to have_many(:hooks) }
+ it { is_expected.to have_many(:protected_branches) }
+ it { is_expected.to have_one(:forked_project_link) }
+ it { is_expected.to have_one(:slack_service) }
+ it { is_expected.to have_one(:microsoft_teams_service) }
+ it { is_expected.to have_one(:mattermost_service) }
+ it { is_expected.to have_one(:pushover_service) }
+ it { is_expected.to have_one(:asana_service) }
+ it { is_expected.to have_many(:boards) }
+ it { is_expected.to have_one(:campfire_service) }
+ it { is_expected.to have_one(:drone_ci_service) }
+ it { is_expected.to have_one(:emails_on_push_service) }
+ it { is_expected.to have_one(:pipelines_email_service) }
+ it { is_expected.to have_one(:irker_service) }
+ it { is_expected.to have_one(:pivotaltracker_service) }
+ it { is_expected.to have_one(:hipchat_service) }
+ it { is_expected.to have_one(:flowdock_service) }
+ it { is_expected.to have_one(:assembla_service) }
+ it { is_expected.to have_one(:slack_slash_commands_service) }
+ it { is_expected.to have_one(:mattermost_slash_commands_service) }
+ it { is_expected.to have_one(:gemnasium_service) }
+ it { is_expected.to have_one(:buildkite_service) }
+ it { is_expected.to have_one(:bamboo_service) }
+ it { is_expected.to have_one(:teamcity_service) }
+ it { is_expected.to have_one(:jira_service) }
+ it { is_expected.to have_one(:redmine_service) }
+ it { is_expected.to have_one(:custom_issue_tracker_service) }
+ it { is_expected.to have_one(:bugzilla_service) }
+ it { is_expected.to have_one(:gitlab_issue_tracker_service) }
+ it { is_expected.to have_one(:external_wiki_service) }
+ it { is_expected.to have_one(:project_feature) }
+ it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
+ it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
it { is_expected.to have_many(:commit_statuses) }
@@ -62,22 +62,22 @@ describe Project, models: true do
it { is_expected.to have_many(:variables) }
it { is_expected.to have_many(:triggers) }
it { is_expected.to have_many(:pages_domains) }
- it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) }
- it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
- it { is_expected.to have_many(:environments).dependent(:destroy) }
- it { is_expected.to have_many(:deployments).dependent(:destroy) }
- it { is_expected.to have_many(:todos).dependent(:destroy) }
- it { is_expected.to have_many(:releases).dependent(:destroy) }
- it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) }
- it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
- it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
+ it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
+ it { is_expected.to have_many(:users_star_projects) }
+ it { is_expected.to have_many(:environments) }
+ it { is_expected.to have_many(:deployments) }
+ it { is_expected.to have_many(:todos) }
+ it { is_expected.to have_many(:releases) }
+ it { is_expected.to have_many(:lfs_objects_projects) }
+ it { is_expected.to have_many(:project_group_links) }
+ it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
it { is_expected.to have_many(:forks).through(:forked_project_links) }
it { is_expected.to have_many(:uploads).dependent(:destroy) }
- it { is_expected.to have_many(:pipeline_schedules).dependent(:destroy) }
+ it { is_expected.to have_many(:pipeline_schedules) }
context 'after initialized' do
it "has a project_feature" do
- expect(Project.new.project_feature).to be_present
+ expect(described_class.new.project_feature).to be_present
end
end
@@ -143,6 +143,10 @@ describe Project, models: true do
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
+ it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
+ it { is_expected.to allow_value('').for(:ci_config_path) }
+ it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
+
it { is_expected.to validate_presence_of(:creator) }
it { is_expected.to validate_presence_of(:namespace) }
@@ -305,10 +309,14 @@ describe Project, models: true do
end
describe 'delegation' do
- it { is_expected.to delegate_method(:add_guest).to(:team) }
- it { is_expected.to delegate_method(:add_reporter).to(:team) }
- it { is_expected.to delegate_method(:add_developer).to(:team) }
- it { is_expected.to delegate_method(:add_master).to(:team) }
+ [:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method|
+ it { is_expected.to delegate_method(method).to(:team) }
+ end
+
+ it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
+ it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
+ it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) }
+ it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
end
describe '#to_reference' do
@@ -430,7 +438,7 @@ describe Project, models: true do
end
it 'returns valid url to repo' do
- project = Project.new(path: 'somewhere')
+ project = described_class.new(path: 'somewhere')
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
@@ -525,15 +533,48 @@ describe Project, models: true do
end
context 'with external issues tracker' do
+ let!(:internal_issue) { create(:issue, project: project) }
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ allow(project).to receive(:external_issue_tracker).and_return(true)
end
- it 'returns an ExternalIssue' do
- issue = project.get_issue('FOO-1234', user)
- expect(issue).to be_kind_of(ExternalIssue)
- expect(issue.iid).to eq 'FOO-1234'
- expect(issue.project).to eq project
+ context 'when internal issues are enabled' do
+ it 'returns interlan issue' do
+ issue = project.get_issue(internal_issue.iid, user)
+
+ expect(issue).to be_kind_of(Issue)
+ expect(issue.iid).to eq(internal_issue.iid)
+ expect(issue.project).to eq(project)
+ end
+
+ it 'returns an ExternalIssue when internal issue does not exists' do
+ issue = project.get_issue('FOO-1234', user)
+
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq('FOO-1234')
+ expect(issue.project).to eq(project)
+ end
+ end
+
+ context 'when internal issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+
+ it 'returns always an External issues' do
+ issue = project.get_issue(internal_issue.iid, user)
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq(internal_issue.iid.to_s)
+ expect(issue.project).to eq(project)
+ end
+
+ it 'returns an ExternalIssue when internal issue does not exists' do
+ issue = project.get_issue('FOO-1234', user)
+ expect(issue).to be_kind_of(ExternalIssue)
+ expect(issue.iid).to eq('FOO-1234')
+ expect(issue.project).to eq(project)
+ end
end
end
end
@@ -803,7 +844,7 @@ describe Project, models: true do
context 'when avatar file is uploaded' do
let(:project) { create(:empty_project, :with_avatar) }
- let(:avatar_path) { "/uploads/system/project/avatar/#{project.id}/dk.png" }
+ let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" }
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
it 'shows correct url' do
@@ -823,13 +864,13 @@ describe Project, models: true do
let(:avatar_path) { "/#{project.full_path}/avatar" }
- it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
+ it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
end
context 'when git repo is empty' do
let(:project) { create(:empty_project) }
- it { should eq nil }
+ it { is_expected.to eq nil }
end
end
@@ -876,7 +917,7 @@ describe Project, models: true do
end
describe '.with_shared_runners' do
- subject { Project.with_shared_runners }
+ subject { described_class.with_shared_runners }
context 'when shared runners are enabled for project' do
let!(:project) { create(:empty_project, shared_runners_enabled: true) }
@@ -895,16 +936,16 @@ describe Project, models: true do
end
end
- describe '.cached_count', caching: true do
+ describe '.cached_count', :use_clean_rails_memory_store_caching do
let(:group) { create(:group, :public) }
let!(:project1) { create(:empty_project, :public, group: group) }
let!(:project2) { create(:empty_project, :public, group: group) }
it 'returns total project count' do
- expect(Project).to receive(:count).once.and_call_original
+ expect(described_class).to receive(:count).once.and_call_original
3.times do
- expect(Project.cached_count).to eq(2)
+ expect(described_class.cached_count).to eq(2)
end
end
end
@@ -949,7 +990,7 @@ describe Project, models: true do
user1.toggle_star(project1)
user2.toggle_star(project2)
- expect(Project.starred_by(user1)).to contain_exactly(project1)
+ expect(described_class.starred_by(user1)).to contain_exactly(project1)
end
end
@@ -1232,7 +1273,7 @@ describe Project, models: true do
subject { project.rename_repo }
- it { expect{subject}.to raise_error(Exception) }
+ it { expect{subject}.to raise_error(StandardError) }
end
end
@@ -1504,6 +1545,28 @@ describe Project, models: true do
end
end
+ describe '#ci_config_path=' do
+ let(:project) { create(:empty_project) }
+
+ it 'sets nil' do
+ project.update!(ci_config_path: nil)
+
+ expect(project.ci_config_path).to be_nil
+ end
+
+ it 'sets a string' do
+ project.update!(ci_config_path: 'foo/.gitlab_ci.yml')
+
+ expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
+ end
+
+ it 'sets a string but removes all leading slashes and null characters' do
+ project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml")
+
+ expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
+ end
+ end
+
describe 'Project import job' do
let(:project) { create(:empty_project, import_url: generate(:url)) }
@@ -1849,7 +1912,12 @@ describe Project, models: true do
create(:ci_variable, :protected, value: 'protected', project: project)
end
- subject { project.secret_variables_for('ref') }
+ subject { project.secret_variables_for(ref: 'ref') }
+
+ before do
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+ end
shared_examples 'ref is protected' do
it 'contains all the variables' do
@@ -1858,11 +1926,6 @@ describe Project, models: true do
end
context 'when the ref is not protected' do
- before do
- stub_application_setting(
- default_branch_protection: Gitlab::Access::PROTECTION_NONE)
- end
-
it 'contains only the secret variables' do
is_expected.to contain_exactly(secret_variable)
end
@@ -1949,7 +2012,7 @@ describe Project, models: true do
let!(:path) { project1.namespace.full_path }
it 'returns correct project' do
- expect(Project.inside_path(path)).to eq([project1])
+ expect(described_class.inside_path(path)).to eq([project1])
end
end
@@ -2153,7 +2216,7 @@ describe Project, models: true do
context 'with a user' do
let(:projects) do
- Project.all.public_or_visible_to_user(user)
+ described_class.all.public_or_visible_to_user(user)
end
it 'includes projects the user has access to' do
@@ -2167,10 +2230,27 @@ describe Project, models: true do
context 'without a user' do
it 'only includes public projects' do
- projects = Project.all.public_or_visible_to_user
+ projects = described_class.all.public_or_visible_to_user
expect(projects).to eq([public_project])
end
end
end
+
+ describe '#remove_private_deploy_keys' do
+ it 'removes the private deploy keys of a project' do
+ project = create(:empty_project)
+
+ private_key = create(:deploy_key, public: false)
+ public_key = create(:deploy_key, public: true)
+
+ create(:deploy_keys_project, deploy_key: private_key, project: project)
+ create(:deploy_keys_project, deploy_key: public_key, project: project)
+
+ project.remove_private_deploy_keys
+
+ expect(project.deploy_keys.where(public: false).any?).to eq(false)
+ expect(project.deploy_keys.where(public: true).any?).to eq(true)
+ end
+ end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index c5ffbda9821..be1b37730b1 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe ProjectStatistics, models: true do
+describe ProjectStatistics do
let(:project) { create :empty_project }
let(:statistics) { project.statistics }
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 49f2f8c0ad1..68228a038a8 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe ProjectTeam, models: true do
+describe ProjectTeam do
let(:master) { create(:user) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 1f314791479..7fcbeb459e0 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,11 +1,11 @@
require "spec_helper"
-describe ProjectWiki, models: true do
+describe ProjectWiki do
let(:project) { create(:empty_project) }
let(:repository) { project.repository }
let(:user) { project.owner }
let(:gitlab_shell) { Gitlab::Shell.new }
- let(:project_wiki) { ProjectWiki.new(project, user) }
+ let(:project_wiki) { described_class.new(project, user) }
subject { project_wiki }
@@ -21,7 +21,7 @@ describe ProjectWiki, models: true do
describe '#web_url' do
it 'returns the full web URL to the wiki' do
- expect(subject.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/wikis/home")
+ expect(subject.web_url).to match("https?://[^\/]+/#{project.path_with_namespace}/wikis/home")
end
end
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index 4c9bade592b..5c5dcd9f5c9 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectableDropdown, models: true do
+describe ProtectableDropdown do
let(:project) { create(:project, :repository) }
let(:subject) { described_class.new(project, :branches) }
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 1e7242e9fa8..f70503eadbc 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -1,5 +1,5 @@
require 'spec_helper'
-describe ProtectedBranch::MergeAccessLevel, :models do
+describe ProtectedBranch::MergeAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index de68351198c..f161f345761 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -1,5 +1,5 @@
require 'spec_helper'
-describe ProtectedBranch::PushAccessLevel, :models do
+describe ProtectedBranch::PushAccessLevel do
it { is_expected.to validate_inclusion_of(:access_level).in_array([Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS]) }
end
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index ca347cf92c9..a54af3bfe59 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedBranch, models: true do
+describe ProtectedBranch do
subject { build_stubbed(:protected_branch) }
describe 'Associations' do
@@ -101,17 +101,17 @@ describe ProtectedBranch, models: true do
production = create(:protected_branch, name: "production")
staging = create(:protected_branch, name: "staging")
- expect(ProtectedBranch.matching("production")).to include(production)
- expect(ProtectedBranch.matching("production")).not_to include(staging)
+ expect(described_class.matching("production")).to include(production)
+ expect(described_class.matching("production")).not_to include(staging)
end
it "accepts a list of protected branches to search from, so as to avoid a DB call" do
production = build(:protected_branch, name: "production")
staging = build(:protected_branch, name: "staging")
- expect(ProtectedBranch.matching("production")).to be_empty
- expect(ProtectedBranch.matching("production", protected_refs: [production, staging])).to include(production)
- expect(ProtectedBranch.matching("production", protected_refs: [production, staging])).not_to include(staging)
+ expect(described_class.matching("production")).to be_empty
+ expect(described_class.matching("production", protected_refs: [production, staging])).to include(production)
+ expect(described_class.matching("production", protected_refs: [production, staging])).not_to include(staging)
end
end
@@ -120,17 +120,17 @@ describe ProtectedBranch, models: true do
production = create(:protected_branch, name: "production/*")
staging = create(:protected_branch, name: "staging/*")
- expect(ProtectedBranch.matching("production/some-branch")).to include(production)
- expect(ProtectedBranch.matching("production/some-branch")).not_to include(staging)
+ expect(described_class.matching("production/some-branch")).to include(production)
+ expect(described_class.matching("production/some-branch")).not_to include(staging)
end
it "accepts a list of protected branches to search from, so as to avoid a DB call" do
production = build(:protected_branch, name: "production/*")
staging = build(:protected_branch, name: "staging/*")
- expect(ProtectedBranch.matching("production/some-branch")).to be_empty
- expect(ProtectedBranch.matching("production/some-branch", protected_refs: [production, staging])).to include(production)
- expect(ProtectedBranch.matching("production/some-branch", protected_refs: [production, staging])).not_to include(staging)
+ expect(described_class.matching("production/some-branch")).to be_empty
+ expect(described_class.matching("production/some-branch", protected_refs: [production, staging])).to include(production)
+ expect(described_class.matching("production/some-branch", protected_refs: [production, staging])).not_to include(staging)
end
end
end
@@ -142,23 +142,23 @@ describe ProtectedBranch, models: true do
it 'returns true when the branch matches a protected branch via direct match' do
create(:protected_branch, project: project, name: "foo")
- expect(ProtectedBranch.protected?(project, 'foo')).to eq(true)
+ expect(described_class.protected?(project, 'foo')).to eq(true)
end
it 'returns true when the branch matches a protected branch via wildcard match' do
create(:protected_branch, project: project, name: "production/*")
- expect(ProtectedBranch.protected?(project, 'production/some-branch')).to eq(true)
+ expect(described_class.protected?(project, 'production/some-branch')).to eq(true)
end
it 'returns false when the branch does not match a protected branch via direct match' do
- expect(ProtectedBranch.protected?(project, 'foo')).to eq(false)
+ expect(described_class.protected?(project, 'foo')).to eq(false)
end
it 'returns false when the branch does not match a protected branch via wildcard match' do
create(:protected_branch, project: project, name: "production/*")
- expect(ProtectedBranch.protected?(project, 'staging/some-branch')).to eq(false)
+ expect(described_class.protected?(project, 'staging/some-branch')).to eq(false)
end
end
@@ -168,25 +168,25 @@ describe ProtectedBranch, models: true do
it 'returns false when default_protected_branch is unprotected' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
- expect(ProtectedBranch.protected?(project, 'master')).to be false
+ expect(described_class.protected?(project, 'master')).to be false
end
it 'returns false when default_protected_branch lets developers push' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
- expect(ProtectedBranch.protected?(project, 'master')).to be false
+ expect(described_class.protected?(project, 'master')).to be false
end
it 'returns true when default_branch_protection does not let developers push but let developer merge branches' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
- expect(ProtectedBranch.protected?(project, 'master')).to be true
+ expect(described_class.protected?(project, 'master')).to be true
end
it 'returns true when default_branch_protection is in full protection' do
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
- expect(ProtectedBranch.protected?(project, 'master')).to be true
+ expect(described_class.protected?(project, 'master')).to be true
end
end
end
diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb
index 51353852a93..e5a0f6ec23f 100644
--- a/spec/models/protected_tag_spec.rb
+++ b/spec/models/protected_tag_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedTag, models: true do
+describe ProtectedTag do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 71827421dd7..80943877095 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe RedirectRoute, models: true do
+describe RedirectRoute do
let(:group) { create(:group) }
let!(:redirect_route) { group.redirect_routes.create(path: 'gitlabb') }
@@ -11,7 +11,7 @@ describe RedirectRoute, models: true do
describe 'validations' do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
end
describe '.matching_path_and_descendants' do
@@ -21,7 +21,7 @@ describe RedirectRoute, models: true do
let!(:redirect5) { group.redirect_routes.create(path: 'gitlabb/test/baz') }
it 'returns correct routes' do
- expect(RedirectRoute.matching_path_and_descendants('gitlabb/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
+ expect(described_class.matching_path_and_descendants('gitlabb/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
end
end
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 527005b2b69..3f86347c3ae 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-RSpec.describe Release, type: :model do
+RSpec.describe Release do
let(:release) { create(:release) }
it { expect(release).to be_valid }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index c69f0a495db..07ed66e127a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Repository, models: true do
+describe Repository do
include RepoHelpers
TestBlob = Struct.new(:path)
@@ -347,6 +347,17 @@ describe Repository, models: true do
expect(blob.data).to eq('Changelog!')
end
+ it 'creates new file and dir when file_path has a forward slash' do
+ expect do
+ repository.create_file(user, 'new_dir/new_file.txt', 'File!',
+ message: 'Create new_file with new_dir',
+ branch_name: 'master')
+ end.to change { repository.commits('master').count }.by(1)
+
+ expect(repository.tree('master', 'new_dir').path).to eq('new_dir')
+ expect(repository.blob_at('master', 'new_dir/new_file.txt').data).to eq('File!')
+ end
+
it 'respects the autocrlf setting' do
repository.create_file(user, 'hello.txt', "Hello,\r\nWorld",
message: 'Add hello world',
@@ -550,7 +561,7 @@ describe Repository, models: true do
end
end
- describe "#changelog", caching: true do
+ describe "#changelog", :use_clean_rails_memory_store_caching do
it 'accepts changelog' do
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])
@@ -582,7 +593,7 @@ describe Repository, models: true do
end
end
- describe "#license_blob", caching: true do
+ describe "#license_blob", :use_clean_rails_memory_store_caching do
before do
repository.delete_file(
user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
@@ -627,7 +638,7 @@ describe Repository, models: true do
end
end
- describe '#license_key', caching: true do
+ describe '#license_key', :use_clean_rails_memory_store_caching do
before do
repository.delete_file(user, 'LICENSE',
message: 'Remove LICENSE', branch_name: 'master')
@@ -692,7 +703,7 @@ describe Repository, models: true do
end
end
- describe "#gitlab_ci_yml", caching: true do
+ describe "#gitlab_ci_yml", :use_clean_rails_memory_store_caching do
it 'returns valid file' do
files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
expect(repository.tree).to receive(:blobs).and_return(files)
@@ -945,21 +956,25 @@ describe Repository, models: true do
end
end
- describe '#exists?' do
+ shared_examples 'repo exists check' do
it 'returns true when a repository exists' do
expect(repository.exists?).to eq(true)
end
- it 'returns false when a repository does not exist' do
- allow(repository).to receive(:refs_directory_exists?).and_return(false)
+ it 'returns false if no full path can be constructed' do
+ allow(repository).to receive(:path_with_namespace).and_return(nil)
expect(repository.exists?).to eq(false)
end
+ end
- it 'returns false when there is no namespace' do
- allow(repository).to receive(:path_with_namespace).and_return(nil)
+ describe '#exists?' do
+ context 'when repository_exists is disabled' do
+ it_behaves_like 'repo exists check'
+ end
- expect(repository.exists?).to eq(false)
+ context 'when repository_exists is enabled', skip_gitaly_mock: true do
+ it_behaves_like 'repo exists check'
end
end
@@ -1600,7 +1615,7 @@ describe Repository, models: true do
end
end
- describe '#contribution_guide', caching: true do
+ describe '#contribution_guide', :use_clean_rails_memory_store_caching do
it 'returns and caches the output' do
expect(repository).to receive(:file_on_head)
.with(:contributing)
@@ -1614,7 +1629,7 @@ describe Repository, models: true do
end
end
- describe '#gitignore', caching: true do
+ describe '#gitignore', :use_clean_rails_memory_store_caching do
it 'returns and caches the output' do
expect(repository).to receive(:file_on_head)
.with(:gitignore)
@@ -1627,7 +1642,7 @@ describe Repository, models: true do
end
end
- describe '#koding_yml', caching: true do
+ describe '#koding_yml', :use_clean_rails_memory_store_caching do
it 'returns and caches the output' do
expect(repository).to receive(:file_on_head)
.with(:koding)
@@ -1640,7 +1655,7 @@ describe Repository, models: true do
end
end
- describe '#readme', caching: true do
+ describe '#readme', :use_clean_rails_memory_store_caching do
context 'with a non-existing repository' do
it 'returns nil' do
allow(repository).to receive(:tree).with(:head).and_return(nil)
@@ -1811,7 +1826,7 @@ describe Repository, models: true do
end
end
- describe '#cache_method_output', caching: true do
+ describe '#cache_method_output', :use_clean_rails_memory_store_caching do
context 'with a non-existing repository' do
let(:value) do
repository.cache_method_output(:cats, fallback: 10) do
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 1754253e0f2..bdacc60fb53 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Route, models: true do
+describe Route do
let(:group) { create(:group, path: 'git_lab', name: 'git_lab') }
let(:route) { group.route }
@@ -15,7 +15,7 @@ describe Route, models: true do
it { is_expected.to validate_presence_of(:source) }
it { is_expected.to validate_presence_of(:path) }
- it { is_expected.to validate_uniqueness_of(:path) }
+ it { is_expected.to validate_uniqueness_of(:path).case_insensitive }
end
describe 'callbacks' do
@@ -34,7 +34,7 @@ describe Route, models: true do
context 'after create' do
it 'calls #delete_conflicting_redirects' do
route.destroy
- new_route = Route.new(source: group, path: group.path)
+ new_route = described_class.new(source: group, path: group.path)
expect(new_route).to receive(:delete_conflicting_redirects)
new_route.save!
end
@@ -49,7 +49,7 @@ describe Route, models: true do
let!(:another_group_nested) { create(:group, path: 'another', name: 'another', parent: similar_group) }
it 'returns correct routes' do
- expect(Route.inside_path('git_lab')).to match_array([nested_group.route, deep_nested_group.route])
+ expect(described_class.inside_path('git_lab')).to match_array([nested_group.route, deep_nested_group.route])
end
end
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 5710edbc9e0..8b6b02916ae 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SentNotification, model: true do
+describe SentNotification do
describe 'validation' do
describe 'note validity' do
context "when the project doesn't match the noteable's project" do
@@ -38,7 +38,7 @@ describe SentNotification, model: true do
let(:issue) { create(:issue) }
it 'creates a new SentNotification' do
- expect { described_class.record(issue, user.id) }.to change { SentNotification.count }.by(1)
+ expect { described_class.record(issue, user.id) }.to change { described_class.count }.by(1)
end
end
@@ -47,7 +47,7 @@ describe SentNotification, model: true do
let(:note) { create(:diff_note_on_merge_request) }
it 'creates a new SentNotification' do
- expect { described_class.record_note(note, user.id) }.to change { SentNotification.count }.by(1)
+ expect { described_class.record_note(note, user.id) }.to change { described_class.count }.by(1)
end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 134882648b9..dba9fc4ac9b 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Service, models: true do
+describe Service do
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb
index 120b390586b..7c71c458fcc 100644
--- a/spec/models/snippet_blob_spec.rb
+++ b/spec/models/snippet_blob_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SnippetBlob, models: true do
+describe SnippetBlob do
let(:snippet) { create(:snippet) }
subject { described_class.new(snippet) }
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 1e5c96fe593..904bf2c6144 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Snippet, models: true do
+describe Snippet do
describe 'modules' do
subject { described_class }
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index 838fba6c92d..0d6b4384ada 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SpamLog, models: true do
+describe SpamLog do
let(:admin) { create(:admin) }
describe 'associations' do
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index 9ab112bb2ee..9e4c2620d82 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Subscription, models: true do
+describe Subscription do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:subscribable) }
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index d238e28209a..1e3f587e460 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SystemNoteMetadata, models: true do
+describe SystemNoteMetadata do
describe 'associations' do
it { is_expected.to belong_to(:note) }
end
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index ebc694213b6..6e30798356c 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-RSpec.describe Timelog, type: :model do
+RSpec.describe Timelog do
subject { build(:timelog) }
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 3f80e1ac534..3e8f3848eca 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Todo, models: true do
+describe Todo do
let(:issue) { create(:issue) }
describe 'relationships' do
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index a87983b7492..6bdb62a0864 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Tree, models: true do
+describe Tree do
let(:repository) { create(:project, :repository).repository }
let(:sha) { repository.root_ref }
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 2dea2c6015f..345382ea8c7 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe Upload, type: :model do
+describe Upload do
describe 'assocations' do
it { is_expected.to belong_to(:model) }
end
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index a8c25766e73..b4669f8c1c2 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe UserAgentDetail, type: :model do
+describe UserAgentDetail do
describe '.submittable?' do
it 'is submittable when not already submitted' do
detail = build(:user_agent_detail)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 448555d2190..ec98a3f3498 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe User, models: true do
+describe User do
include Gitlab::CurrentSettings
describe 'modules' do
@@ -114,7 +114,9 @@ describe User, models: true do
end
it 'validates uniqueness' do
- expect(subject).to validate_uniqueness_of(:username).case_insensitive
+ user = build(:user)
+
+ expect(user).to validate_uniqueness_of(:username).case_insensitive
end
end
@@ -259,7 +261,7 @@ describe User, models: true do
it "returns users with 2fa enabled via OTP" do
user_with_2fa = create(:user, :two_factor_via_otp)
user_without_2fa = create(:user)
- users_with_two_factor = User.with_two_factor.pluck(:id)
+ users_with_two_factor = described_class.with_two_factor.pluck(:id)
expect(users_with_two_factor).to include(user_with_2fa.id)
expect(users_with_two_factor).not_to include(user_without_2fa.id)
@@ -268,7 +270,7 @@ describe User, models: true do
it "returns users with 2fa enabled via U2F" do
user_with_2fa = create(:user, :two_factor_via_u2f)
user_without_2fa = create(:user)
- users_with_two_factor = User.with_two_factor.pluck(:id)
+ users_with_two_factor = described_class.with_two_factor.pluck(:id)
expect(users_with_two_factor).to include(user_with_2fa.id)
expect(users_with_two_factor).not_to include(user_without_2fa.id)
@@ -277,7 +279,7 @@ describe User, models: true do
it "returns users with 2fa enabled via OTP and U2F" do
user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
user_without_2fa = create(:user)
- users_with_two_factor = User.with_two_factor.pluck(:id)
+ users_with_two_factor = described_class.with_two_factor.pluck(:id)
expect(users_with_two_factor).to eq([user_with_2fa.id])
expect(users_with_two_factor).not_to include(user_without_2fa.id)
@@ -288,7 +290,7 @@ describe User, models: true do
it "excludes users with 2fa enabled via OTP" do
user_with_2fa = create(:user, :two_factor_via_otp)
user_without_2fa = create(:user)
- users_without_two_factor = User.without_two_factor.pluck(:id)
+ users_without_two_factor = described_class.without_two_factor.pluck(:id)
expect(users_without_two_factor).to include(user_without_2fa.id)
expect(users_without_two_factor).not_to include(user_with_2fa.id)
@@ -297,7 +299,7 @@ describe User, models: true do
it "excludes users with 2fa enabled via U2F" do
user_with_2fa = create(:user, :two_factor_via_u2f)
user_without_2fa = create(:user)
- users_without_two_factor = User.without_two_factor.pluck(:id)
+ users_without_two_factor = described_class.without_two_factor.pluck(:id)
expect(users_without_two_factor).to include(user_without_2fa.id)
expect(users_without_two_factor).not_to include(user_with_2fa.id)
@@ -306,7 +308,7 @@ describe User, models: true do
it "excludes users with 2fa enabled via OTP and U2F" do
user_with_2fa = create(:user, :two_factor_via_otp, :two_factor_via_u2f)
user_without_2fa = create(:user)
- users_without_two_factor = User.without_two_factor.pluck(:id)
+ users_without_two_factor = described_class.without_two_factor.pluck(:id)
expect(users_without_two_factor).to include(user_without_2fa.id)
expect(users_without_two_factor).not_to include(user_with_2fa.id)
@@ -322,8 +324,8 @@ describe User, models: true do
create(:todo, user: current_user, author: user_2, state: :done)
create(:todo, user: current_user, author: user_3, state: :pending)
- expect(User.todo_authors(current_user.id, 'pending')).to eq [user_3]
- expect(User.todo_authors(current_user.id, 'done')).to eq [user_2]
+ expect(described_class.todo_authors(current_user.id, 'pending')).to eq [user_3]
+ expect(described_class.todo_authors(current_user.id, 'done')).to eq [user_2]
end
end
end
@@ -348,7 +350,27 @@ describe User, models: true do
end
end
- describe '#update_tracked_fields!', :redis do
+ describe 'after update hook' do
+ describe '.update_invalid_gpg_signatures' do
+ let(:user) do
+ create(:user, email: 'tula.torphy@abshire.ca').tap do |user|
+ user.skip_reconfirmation!
+ end
+ end
+
+ it 'does nothing when the name is updated' do
+ expect(user).not_to receive(:update_invalid_gpg_signatures)
+ user.update_attributes!(name: 'Bette')
+ end
+
+ it 'synchronizes the gpg keys when the email is updated' do
+ expect(user).to receive(:update_invalid_gpg_signatures)
+ user.update_attributes!(email: 'shawnee.ritchie@denesik.com')
+ end
+ end
+ end
+
+ describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do
let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
let(:user) { create(:user) }
@@ -607,39 +629,39 @@ describe User, models: true do
let(:user) { double }
it 'filters by active users by default' do
- expect(User).to receive(:active).and_return([user])
+ expect(described_class).to receive(:active).and_return([user])
- expect(User.filter(nil)).to include user
+ expect(described_class.filter(nil)).to include user
end
it 'filters by admins' do
- expect(User).to receive(:admins).and_return([user])
+ expect(described_class).to receive(:admins).and_return([user])
- expect(User.filter('admins')).to include user
+ expect(described_class.filter('admins')).to include user
end
it 'filters by blocked' do
- expect(User).to receive(:blocked).and_return([user])
+ expect(described_class).to receive(:blocked).and_return([user])
- expect(User.filter('blocked')).to include user
+ expect(described_class.filter('blocked')).to include user
end
it 'filters by two_factor_disabled' do
- expect(User).to receive(:without_two_factor).and_return([user])
+ expect(described_class).to receive(:without_two_factor).and_return([user])
- expect(User.filter('two_factor_disabled')).to include user
+ expect(described_class.filter('two_factor_disabled')).to include user
end
it 'filters by two_factor_enabled' do
- expect(User).to receive(:with_two_factor).and_return([user])
+ expect(described_class).to receive(:with_two_factor).and_return([user])
- expect(User.filter('two_factor_enabled')).to include user
+ expect(described_class.filter('two_factor_enabled')).to include user
end
it 'filters by wop' do
- expect(User).to receive(:without_projects).and_return([user])
+ expect(described_class).to receive(:without_projects).and_return([user])
- expect(User.filter('wop')).to include user
+ expect(described_class.filter('wop')).to include user
end
end
@@ -660,9 +682,9 @@ describe User, models: true do
project.request_access(user_without_project2)
end
- it { expect(User.without_projects).not_to include user }
- it { expect(User.without_projects).to include user_without_project }
- it { expect(User.without_projects).to include user_without_project2 }
+ it { expect(described_class.without_projects).not_to include user }
+ it { expect(described_class.without_projects).to include user_without_project }
+ it { expect(described_class.without_projects).to include user_without_project2 }
end
describe 'user creation' do
@@ -678,7 +700,7 @@ describe User, models: true do
end
describe 'with defaults' do
- let(:user) { User.new }
+ let(:user) { described_class.new }
it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
@@ -688,7 +710,7 @@ describe User, models: true do
end
describe 'with default overrides' do
- let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true) }
+ let(:user) { described_class.new(projects_limit: 123, can_create_group: false, can_create_team: true) }
it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
@@ -738,18 +760,18 @@ describe User, models: true do
it 'finds by primary email' do
user = create(:user, email: 'foo@example.com')
- expect(User.find_by_any_email(user.email)).to eq user
+ expect(described_class.find_by_any_email(user.email)).to eq user
end
it 'finds by secondary email' do
email = create(:email, email: 'foo@example.com')
user = email.user
- expect(User.find_by_any_email(email.email)).to eq user
+ expect(described_class.find_by_any_email(email.email)).to eq user
end
it 'returns nil when nothing found' do
- expect(User.find_by_any_email('')).to be_nil
+ expect(described_class.find_by_any_email('')).to be_nil
end
end
@@ -763,7 +785,7 @@ describe User, models: true do
end
it 'returns users with a partially matching name' do
- expect(described_class.search(user.name[0..2])).to eq([user2, user])
+ expect(described_class.search(user.name[0..2])).to eq([user, user2])
end
it 'returns users with a matching name regardless of the casing' do
@@ -777,7 +799,7 @@ describe User, models: true do
end
it 'returns users with a partially matching Email' do
- expect(described_class.search(user.email[0..2])).to eq([user2, user])
+ expect(described_class.search(user.email[0..2])).to eq([user, user2])
end
it 'returns users with a matching Email regardless of the casing' do
@@ -791,7 +813,7 @@ describe User, models: true do
end
it 'returns users with a partially matching username' do
- expect(described_class.search(user.username[0..2])).to eq([user2, user])
+ expect(described_class.search(user.username[0..2])).to eq([user, user2])
end
it 'returns users with a matching username regardless of the casing' do
@@ -897,12 +919,12 @@ describe User, models: true do
let!(:user) { create(:user, username: username) }
it 'gets the correct user' do
- expect(User.by_login(user.email.upcase)).to eq user
- expect(User.by_login(user.email)).to eq user
- expect(User.by_login(username.downcase)).to eq user
- expect(User.by_login(username)).to eq user
- expect(User.by_login(nil)).to be_nil
- expect(User.by_login('')).to be_nil
+ expect(described_class.by_login(user.email.upcase)).to eq user
+ expect(described_class.by_login(user.email)).to eq user
+ expect(described_class.by_login(username.downcase)).to eq user
+ expect(described_class.by_login(username)).to eq user
+ expect(described_class.by_login(nil)).to be_nil
+ expect(described_class.by_login('')).to be_nil
end
end
@@ -936,12 +958,12 @@ describe User, models: true do
let!(:route) { user.namespace.route }
it 'returns the user' do
- expect(User.find_by_full_path(route.path)).to eq(user)
+ expect(described_class.find_by_full_path(route.path)).to eq(user)
end
it 'is case-insensitive' do
- expect(User.find_by_full_path(route.path.upcase)).to eq(user)
- expect(User.find_by_full_path(route.path.downcase)).to eq(user)
+ expect(described_class.find_by_full_path(route.path.upcase)).to eq(user)
+ expect(described_class.find_by_full_path(route.path.downcase)).to eq(user)
end
end
@@ -950,18 +972,18 @@ describe User, models: true do
context 'without the follow_redirects option' do
it 'returns nil' do
- expect(User.find_by_full_path(redirect_route.path)).to eq(nil)
+ expect(described_class.find_by_full_path(redirect_route.path)).to eq(nil)
end
end
context 'with the follow_redirects option set to true' do
it 'returns the user' do
- expect(User.find_by_full_path(redirect_route.path, follow_redirects: true)).to eq(user)
+ expect(described_class.find_by_full_path(redirect_route.path, follow_redirects: true)).to eq(user)
end
it 'is case-insensitive' do
- expect(User.find_by_full_path(redirect_route.path.upcase, follow_redirects: true)).to eq(user)
- expect(User.find_by_full_path(redirect_route.path.downcase, follow_redirects: true)).to eq(user)
+ expect(described_class.find_by_full_path(redirect_route.path.upcase, follow_redirects: true)).to eq(user)
+ expect(described_class.find_by_full_path(redirect_route.path.downcase, follow_redirects: true)).to eq(user)
end
end
end
@@ -969,12 +991,12 @@ describe User, models: true do
context 'without a route or a redirect route matching the given path' do
context 'without the follow_redirects option' do
it 'returns nil' do
- expect(User.find_by_full_path('unknown')).to eq(nil)
+ expect(described_class.find_by_full_path('unknown')).to eq(nil)
end
end
context 'with the follow_redirects option set to true' do
it 'returns nil' do
- expect(User.find_by_full_path('unknown', follow_redirects: true)).to eq(nil)
+ expect(described_class.find_by_full_path('unknown', follow_redirects: true)).to eq(nil)
end
end
end
@@ -984,7 +1006,7 @@ describe User, models: true do
let!(:group) { create(:group, path: 'group_path', owner: user) }
it 'returns nil' do
- expect(User.find_by_full_path('group_path')).to eq(nil)
+ expect(described_class.find_by_full_path('group_path')).to eq(nil)
end
end
@@ -992,7 +1014,7 @@ describe User, models: true do
let!(:group) { create(:group, path: 'group_path') }
it 'returns nil' do
- expect(User.find_by_full_path('group_path')).to eq(nil)
+ expect(described_class.find_by_full_path('group_path')).to eq(nil)
end
end
end
@@ -1028,7 +1050,7 @@ describe User, models: true do
context 'when avatar file is uploaded' do
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
- let(:avatar_path) { "/uploads/system/user/avatar/#{user.id}/dk.png" }
+ let(:avatar_path) { "/uploads/-/system/user/avatar/#{user.id}/dk.png" }
it 'shows correct avatar url' do
expect(user.avatar_url).to eq(avatar_path)
@@ -1042,7 +1064,7 @@ describe User, models: true do
end
describe '#requires_ldap_check?' do
- let(:user) { User.new }
+ let(:user) { described_class.new }
it 'is false when LDAP is disabled' do
# Create a condition which would otherwise cause 'true' to be returned
@@ -1159,6 +1181,18 @@ describe User, models: true do
end
end
+ describe '#sanitize_attrs' do
+ let(:user) { build(:user, name: 'test & user', skype: 'test&user') }
+
+ it 'encodes HTML entities in the Skype attribute' do
+ expect { user.sanitize_attrs }.to change { user.skype }.to('test&amp;user')
+ end
+
+ it 'does not encode HTML entities in the name attribute' do
+ expect { user.sanitize_attrs }.not_to change { user.name }
+ end
+ end
+
describe '#starred?' do
it 'determines if user starred a project' do
user = create :user
@@ -1201,7 +1235,7 @@ describe User, models: true do
describe '#sort' do
before do
- User.delete_all
+ described_class.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
@user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta'
@@ -1209,34 +1243,34 @@ describe User, models: true do
context 'when sort by recent_sign_in' do
it 'sorts users by the recent sign-in time' do
- expect(User.sort('recent_sign_in').first).to eq(@user)
+ expect(described_class.sort('recent_sign_in').first).to eq(@user)
end
it 'pushes users who never signed in to the end' do
- expect(User.sort('recent_sign_in').third).to eq(@user2)
+ expect(described_class.sort('recent_sign_in').third).to eq(@user2)
end
end
context 'when sort by oldest_sign_in' do
it 'sorts users by the oldest sign-in time' do
- expect(User.sort('oldest_sign_in').first).to eq(@user1)
+ expect(described_class.sort('oldest_sign_in').first).to eq(@user1)
end
it 'pushes users who never signed in to the end' do
- expect(User.sort('oldest_sign_in').third).to eq(@user2)
+ expect(described_class.sort('oldest_sign_in').third).to eq(@user2)
end
end
it 'sorts users in descending order by their creation time' do
- expect(User.sort('created_desc').first).to eq(@user)
+ expect(described_class.sort('created_desc').first).to eq(@user)
end
it 'sorts users in ascending order by their creation time' do
- expect(User.sort('created_asc').first).to eq(@user2)
+ expect(described_class.sort('created_asc').first).to eq(@user2)
end
it 'sorts users by id in descending order when nil is passed' do
- expect(User.sort(nil).first).to eq(@user2)
+ expect(described_class.sort(nil).first).to eq(@user2)
end
end
@@ -1684,7 +1718,7 @@ describe User, models: true do
end
end
- describe '#refresh_authorized_projects', redis: true do
+ describe '#refresh_authorized_projects', clean_gitlab_redis_shared_state: true do
let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) }
let(:user) { create(:user) }
@@ -1756,7 +1790,7 @@ describe User, models: true do
describe '.ghost' do
it "creates a ghost user if one isn't already present" do
- ghost = User.ghost
+ ghost = described_class.ghost
expect(ghost).to be_ghost
expect(ghost).to be_persisted
@@ -1764,16 +1798,16 @@ describe User, models: true do
it "does not create a second ghost user if one is already present" do
expect do
- User.ghost
- User.ghost
- end.to change { User.count }.by(1)
- expect(User.ghost).to eq(User.ghost)
+ described_class.ghost
+ described_class.ghost
+ end.to change { described_class.count }.by(1)
+ expect(described_class.ghost).to eq(described_class.ghost)
end
context "when a regular user exists with the username 'ghost'" do
it "creates a ghost user with a non-conflicting username" do
create(:user, username: 'ghost')
- ghost = User.ghost
+ ghost = described_class.ghost
expect(ghost).to be_persisted
expect(ghost.username).to eq('ghost1')
@@ -1783,7 +1817,7 @@ describe User, models: true do
context "when a regular user exists with the email 'ghost@example.com'" do
it "creates a ghost user with a non-conflicting email" do
create(:user, email: 'ghost@example.com')
- ghost = User.ghost
+ ghost = described_class.ghost
expect(ghost).to be_persisted
expect(ghost.email).to eq('ghost1@example.com')
@@ -1796,7 +1830,7 @@ describe User, models: true do
end
it 'creates a ghost user' do
- expect(User.ghost).to be_persisted
+ expect(described_class.ghost).to be_persisted
end
end
end
@@ -1875,13 +1909,13 @@ describe User, models: true do
context '.active' do
before do
- User.ghost
+ described_class.ghost
create(:user, name: 'user', state: 'active')
create(:user, name: 'user', state: 'blocked')
end
it 'only counts active and non internal users' do
- expect(User.active.count).to eq(1)
+ expect(described_class.active.count).to eq(1)
end
end
@@ -1920,4 +1954,26 @@ describe User, models: true do
user.invalidate_merge_request_cache_counts
end
end
+
+ describe '#allow_password_authentication?' do
+ context 'regular user' do
+ let(:user) { build(:user) }
+
+ it 'returns true when sign-in is enabled' do
+ expect(user.allow_password_authentication?).to be_truthy
+ end
+
+ it 'returns false when sign-in is disabled' do
+ stub_application_setting(password_authentication_enabled: false)
+
+ expect(user.allow_password_authentication?).to be_falsey
+ end
+ end
+
+ it 'returns false for ldap user' do
+ user = create(:omniauth_user, provider: 'ldapmain')
+
+ expect(user.allow_password_authentication?).to be_falsey
+ end
+ end
end
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
index 1caaa557085..fb8575cfe2b 100644
--- a/spec/models/wiki_directory_spec.rb
+++ b/spec/models/wiki_directory_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-RSpec.describe WikiDirectory, models: true do
+RSpec.describe WikiDirectory do
describe 'validations' do
subject { build(:wiki_directory) }
@@ -10,7 +10,7 @@ RSpec.describe WikiDirectory, models: true do
describe '#initialize' do
context 'when there are pages' do
let(:pages) { [build(:wiki_page)] }
- let(:directory) { WikiDirectory.new('/path_up_to/dir', pages) }
+ let(:directory) { described_class.new('/path_up_to/dir', pages) }
it 'sets the slug attribute' do
expect(directory.slug).to eq('/path_up_to/dir')
@@ -22,7 +22,7 @@ RSpec.describe WikiDirectory, models: true do
end
context 'when there are no pages' do
- let(:directory) { WikiDirectory.new('/path_up_to/dir') }
+ let(:directory) { described_class.new('/path_up_to/dir') }
it 'sets the slug attribute' do
expect(directory.slug).to eq('/path_up_to/dir')
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 4a73552b8a6..90ad3cdeb93 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -1,17 +1,17 @@
require "spec_helper"
-describe WikiPage, models: true do
+describe WikiPage do
let(:project) { create(:empty_project) }
let(:user) { project.owner }
let(:wiki) { ProjectWiki.new(project, user) }
- subject { WikiPage.new(wiki) }
+ subject { described_class.new(wiki) }
describe '.group_by_directory' do
context 'when there are no pages' do
it 'returns an empty array' do
- expect(WikiPage.group_by_directory(nil)).to eq([])
- expect(WikiPage.group_by_directory([])).to eq([])
+ expect(described_class.group_by_directory(nil)).to eq([])
+ expect(described_class.group_by_directory([])).to eq([])
end
end
@@ -39,7 +39,7 @@ describe WikiPage, models: true do
it 'returns an array with pages and directories' do
expected_grouped_entries = [page_1, dir_1, dir_1_1, dir_2]
- grouped_entries = WikiPage.group_by_directory(wiki.pages)
+ grouped_entries = described_class.group_by_directory(wiki.pages)
grouped_entries.each_with_index do |page_or_dir, i|
expected_page_or_dir = expected_grouped_entries[i]
@@ -56,7 +56,7 @@ describe WikiPage, models: true do
expected_order = ['page_1', 'dir_1/page_2', 'dir_1/dir_1_1/page_3',
'dir_2/page_4', 'dir_2/page_5']
- grouped_entries = WikiPage.group_by_directory(wiki.pages)
+ grouped_entries = described_class.group_by_directory(wiki.pages)
actual_order =
grouped_entries.map do |page_or_dir|
@@ -72,7 +72,7 @@ describe WikiPage, models: true do
it 'removes hyphens from a name' do
name = 'a-name--with-hyphens'
- expect(WikiPage.unhyphenize(name)).to eq('a name with hyphens')
+ expect(described_class.unhyphenize(name)).to eq('a name with hyphens')
end
end
@@ -81,7 +81,7 @@ describe WikiPage, models: true do
before do
create_page("test page", "test content")
@page = wiki.wiki.paged("test page")
- @wiki_page = WikiPage.new(wiki, @page, true)
+ @wiki_page = described_class.new(wiki, @page, true)
end
it "sets the slug attribute" do
diff --git a/spec/policies/base_policy_spec.rb b/spec/policies/base_policy_spec.rb
index e1963091a72..c03d95b34db 100644
--- a/spec/policies/base_policy_spec.rb
+++ b/spec/policies/base_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BasePolicy, models: true do
+describe BasePolicy do
describe '.class_for' do
it 'detects policy class based on the subject ancestors' do
expect(DeclarativePolicy.class_for(GenericCommitStatus.new)).to eq(CommitStatusPolicy)
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
index ace95ac7067..a83a83a7349 100644
--- a/spec/policies/ci/build_policy_spec.rb
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::BuildPolicy, :models do
+describe Ci::BuildPolicy do
let(:user) { create(:user) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
@@ -96,55 +96,57 @@ describe Ci::BuildPolicy, :models do
end
end
- describe 'rules for manual actions' do
+ describe 'rules for protected ref' do
let(:project) { create(:project) }
+ let(:build) { create(:ci_build, ref: 'some-ref', pipeline: pipeline) }
before do
project.add_developer(user)
end
- context 'when branch build is assigned to is protected' do
+ context 'when no one can push or merge to the branch' do
before do
create(:protected_branch, :no_one_can_push,
- name: 'some-ref', project: project)
+ name: build.ref, project: project)
end
- context 'when build is a manual action' do
- let(:build) do
- create(:ci_build, :manual, ref: 'some-ref', pipeline: pipeline)
- end
-
- it 'does not include ability to update build' do
- expect(policy).to be_disallowed :update_build
- end
+ it 'does not include ability to update build' do
+ expect(policy).to be_disallowed :update_build
end
+ end
- context 'when build is not a manual action' do
- let(:build) do
- create(:ci_build, ref: 'some-ref', pipeline: pipeline)
- end
+ context 'when developers can push to the branch' do
+ before do
+ create(:protected_branch, :developers_can_merge,
+ name: build.ref, project: project)
+ end
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
+ it 'includes ability to update build' do
+ expect(policy).to be_allowed :update_build
end
end
- context 'when branch build is assigned to is not protected' do
- context 'when build is a manual action' do
- let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+ context 'when no one can create the tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: build.ref, project: project)
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
+ build.update(tag: true)
+ end
+
+ it 'does not include ability to update build' do
+ expect(policy).to be_disallowed :update_build
end
+ end
- context 'when build is not a manual action' do
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ context 'when no one can create the tag but it is not a tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: build.ref, project: project)
+ end
- it 'includes ability to update build' do
- expect(policy).to be_allowed :update_build
- end
+ it 'includes ability to update build' do
+ expect(policy).to be_allowed :update_build
end
end
end
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
new file mode 100644
index 00000000000..b11b06d301f
--- /dev/null
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Ci::PipelinePolicy, :models do
+ let(:user) { create(:user) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+ let(:policy) do
+ described_class.new(user, pipeline)
+ end
+
+ describe 'rules' do
+ describe 'rules for protected ref' do
+ let(:project) { create(:project) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when no one can push or merge to the branch' do
+ before do
+ create(:protected_branch, :no_one_can_push,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'does not include ability to update pipeline' do
+ expect(policy).to be_disallowed :update_pipeline
+ end
+ end
+
+ context 'when developers can push to the branch' do
+ before do
+ create(:protected_branch, :developers_can_merge,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'includes ability to update pipeline' do
+ expect(policy).to be_allowed :update_pipeline
+ end
+ end
+
+ context 'when no one can create the tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: pipeline.ref, project: project)
+
+ pipeline.update(tag: true)
+ end
+
+ it 'does not include ability to update pipeline' do
+ expect(policy).to be_disallowed :update_pipeline
+ end
+ end
+
+ context 'when no one can create the tag but it is not a tag' do
+ before do
+ create(:protected_tag, :no_one_can_create,
+ name: pipeline.ref, project: project)
+ end
+
+ it 'includes ability to update pipeline' do
+ expect(policy).to be_allowed :update_pipeline
+ end
+ end
+ end
+ end
+end
diff --git a/spec/policies/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index ed4010e723b..3d3e3d3755b 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::TriggerPolicy, :models do
+describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
diff --git a/spec/policies/deploy_key_policy_spec.rb b/spec/policies/deploy_key_policy_spec.rb
index f15f4a11f02..ca7b7fe7ef7 100644
--- a/spec/policies/deploy_key_policy_spec.rb
+++ b/spec/policies/deploy_key_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeployKeyPolicy, models: true do
+describe DeployKeyPolicy do
subject { described_class.new(current_user, deploy_key) }
describe 'updating a deploy_key' do
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index bb0fa0c0e9c..a6bf70c1e09 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe GlobalPolicy, models: true do
+describe GlobalPolicy do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
- subject { GlobalPolicy.new(current_user, [user]) }
+ subject { described_class.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
@@ -30,5 +30,25 @@ describe GlobalPolicy, models: true do
it { is_expected.to be_allowed(:read_users_list) }
end
end
+
+ context "for an admin" do
+ let(:current_user) { create(:admin) }
+
+ context "when the public level is restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+
+ context "when the public level is not restricted" do
+ before do
+ stub_application_setting(restricted_visibility_levels: [])
+ end
+
+ it { is_expected.to be_allowed(:read_users_list) }
+ end
+ end
end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 06db0ea56e3..b17a93e3fbe 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GroupPolicy, models: true do
+describe GroupPolicy do
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
let(:developer) { create(:user) }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index c978cbd6185..279b96fb2af 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe IssuePolicy, models: true do
+describe IssuePolicy do
let(:guest) { create(:user) }
let(:author) { create(:user) }
let(:assignee) { create(:user) }
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 4d6350fc653..b70c8646a3d 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe PersonalSnippetPolicy, models: true do
+describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:admin_user) { create(:user, :admin) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index ca435dd0218..1f51ced1beb 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectPolicy, models: true do
+describe ProjectPolicy do
let(:guest) { create(:user) }
let(:reporter) { create(:user) }
let(:dev) { create(:user) }
@@ -103,6 +103,48 @@ describe ProjectPolicy, models: true do
end
end
+ context 'issues feature' do
+ subject { described_class.new(owner, project) }
+
+ context 'when the feature is disabled' do
+ it 'does not include the issues permissions' do
+ project.issues_enabled = false
+ project.save!
+
+ expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+ end
+ end
+
+ context 'when the feature is disabled and external tracker configured' do
+ it 'does not include the issues permissions' do
+ create(:jira_service, project: project)
+
+ project.issues_enabled = false
+ project.save!
+
+ expect_disallowed :read_issue, :create_issue, :update_issue, :admin_issue
+ end
+ end
+ end
+
+ context 'when a project has pending invites, and the current user is anonymous' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:empty_project, :public, namespace: group) }
+ let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
+
+ subject { described_class.new(nil, project) }
+
+ before do
+ create(:group_member, :invited, group: group)
+ end
+
+ it 'does not grant owner access' do
+ expect_allowed(*anonymous_permissions)
+ expect_disallowed(*user_permissions)
+ end
+ end
+
context 'abilities for non-public projects' do
let(:project) { create(:empty_project, namespace: owner.namespace) }
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index 2799f03fb9b..bae35ee31c6 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProjectSnippetPolicy, models: true do
+describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:project) { create(:empty_project, :public) }
diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb
index 0251d5dcf1c..6593a6ca3b9 100644
--- a/spec/policies/user_policy_spec.rb
+++ b/spec/policies/user_policy_spec.rb
@@ -1,10 +1,10 @@
require 'spec_helper'
-describe UserPolicy, models: true do
+describe UserPolicy do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
- subject { UserPolicy.new(current_user, user) }
+ subject { described_class.new(current_user, user) }
describe "reading a user's information" do
it { is_expected.to be_allowed(:read_user) }
diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb
index 518e97d17a1..f05d5c7fce5 100644
--- a/spec/presenters/ci/build_presenter_spec.rb
+++ b/spec/presenters/ci/build_presenter_spec.rb
@@ -85,7 +85,7 @@ describe Ci::BuildPresenter do
describe 'quack like a Ci::Build permission-wise' do
context 'user is not allowed' do
- let(:project) { build_stubbed(:empty_project, public_builds: false) }
+ let(:project) { create(:empty_project, public_builds: false) }
it 'returns false' do
expect(presenter.can?(nil, :read_build)).to be_falsy
@@ -93,7 +93,7 @@ describe Ci::BuildPresenter do
end
context 'user is allowed' do
- let(:project) { build_stubbed(:empty_project, :public) }
+ let(:project) { create(:empty_project, :public) }
it 'returns true' do
expect(presenter.can?(nil, :read_build)).to be_truthy
diff --git a/spec/presenters/ci/group_variable_presenter_spec.rb b/spec/presenters/ci/group_variable_presenter_spec.rb
new file mode 100644
index 00000000000..d404028405b
--- /dev/null
+++ b/spec/presenters/ci/group_variable_presenter_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Ci::GroupVariablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:group) { create(:group) }
+ let(:variable) { create(:ci_group_variable, group: group) }
+
+ subject(:presenter) do
+ described_class.new(variable)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+
+ describe '#initialize' do
+ it 'takes a variable and optional params' do
+ expect { presenter }.not_to raise_error
+ end
+
+ it 'exposes variable' do
+ expect(presenter.variable).to eq(variable)
+ end
+
+ it 'forwards missing methods to variable' do
+ expect(presenter.key).to eq(variable.key)
+ end
+ end
+
+ describe '#placeholder' do
+ subject { described_class.new(variable).placeholder }
+
+ it { is_expected.to eq('GROUP_VARIABLE') }
+ end
+
+ describe '#form_path' do
+ context 'when variable is persisted' do
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+
+ context 'when variable is not persisted' do
+ let(:variable) { build(:ci_group_variable, group: group) }
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(group_variables_path(group)) }
+ end
+ end
+
+ describe '#edit_path' do
+ subject { described_class.new(variable).edit_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+
+ describe '#delete_path' do
+ subject { described_class.new(variable).delete_path }
+
+ it { is_expected.to eq(group_variable_path(group, variable)) }
+ end
+end
diff --git a/spec/presenters/ci/variable_presenter_spec.rb b/spec/presenters/ci/variable_presenter_spec.rb
new file mode 100644
index 00000000000..9e6aae7bcad
--- /dev/null
+++ b/spec/presenters/ci/variable_presenter_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Ci::VariablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:project) { create(:empty_project) }
+ let(:variable) { create(:ci_variable, project: project) }
+
+ subject(:presenter) do
+ described_class.new(variable)
+ end
+
+ it 'inherits from Gitlab::View::Presenter::Delegated' do
+ expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
+ end
+
+ describe '#initialize' do
+ it 'takes a variable and optional params' do
+ expect { presenter }.not_to raise_error
+ end
+
+ it 'exposes variable' do
+ expect(presenter.variable).to eq(variable)
+ end
+
+ it 'forwards missing methods to variable' do
+ expect(presenter.key).to eq(variable.key)
+ end
+ end
+
+ describe '#placeholder' do
+ subject { described_class.new(variable).placeholder }
+
+ it { is_expected.to eq('PROJECT_VARIABLE') }
+ end
+
+ describe '#form_path' do
+ context 'when variable is persisted' do
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+
+ context 'when variable is not persisted' do
+ let(:variable) { build(:ci_variable, project: project) }
+ subject { described_class.new(variable).form_path }
+
+ it { is_expected.to eq(project_variables_path(project)) }
+ end
+ end
+
+ describe '#edit_path' do
+ subject { described_class.new(variable).edit_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+
+ describe '#delete_path' do
+ subject { described_class.new(variable).delete_path }
+
+ it { is_expected.to eq(project_variable_path(project, variable)) }
+ end
+end
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index f5a14b1d04d..c1a0313b13c 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -332,7 +332,31 @@ describe MergeRequestPresenter do
end
end
- context 'when target branch does not exists' do
+ context 'when target branch does not exist' do
+ it 'returns nil' do
+ allow(resource).to receive(:target_branch_exists?) { false }
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#target_branch_tree_path' do
+ subject do
+ described_class.new(resource, current_user: user)
+ .target_branch_tree_path
+ end
+
+ context 'when target branch exists' do
+ it 'returns path' do
+ allow(resource).to receive(:target_branch_exists?) { true }
+
+ is_expected
+ .to eq("/#{resource.target_project.full_path}/tree/#{resource.target_branch}")
+ end
+ end
+
+ context 'when target branch does not exist' do
it 'returns nil' do
allow(resource).to receive(:target_branch_exists?) { false }
@@ -355,7 +379,7 @@ describe MergeRequestPresenter do
end
end
- context 'when source branch does not exists' do
+ context 'when source branch does not exist' do
it 'returns nil' do
allow(resource).to receive(:source_branch_exists?) { false }
@@ -363,4 +387,17 @@ describe MergeRequestPresenter do
end
end
end
+
+ describe '#source_branch_with_namespace_link' do
+ subject do
+ described_class.new(resource, current_user: user).source_branch_with_namespace_link
+ end
+
+ it 'returns link' do
+ allow(resource).to receive(:source_branch_exists?) { true }
+
+ is_expected
+ .to eq("<a href=\"/#{resource.source_project.full_path}/tree/#{resource.source_branch}\">#{resource.source_branch}</a>")
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index c64499fc8c0..5a2e1b2cf2d 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -1,25 +1,31 @@
require 'spec_helper'
-require 'mime/types'
describe API::Branches do
let(:user) { create(:user) }
- let!(:project) { create(:project, :repository, creator: user) }
- let!(:master) { create(:project_member, :master, user: user, project: project) }
- let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
- let!(:branch_name) { 'feature' }
- let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
- let(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master")[:branch] }
+ let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
+ let(:branch_name) { 'feature' }
+ let(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+ let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
+ let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
+
+ let(:project_id) { project.id }
+ let(:current_user) { nil }
+
+ before do
+ project.add_master(user)
+ end
describe "GET /projects/:id/repository/branches" do
- let(:route) { "/projects/#{project.id}/repository/branches" }
+ let(:route) { "/projects/#{project_id}/repository/branches" }
shared_examples_for 'repository branches' do
it 'returns the repository branches' do
get api(route, current_user), per_page: 100
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branches')
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
end
@@ -34,10 +40,9 @@ describe API::Branches do
end
context 'when unauthenticated', 'and project is public' do
- it_behaves_like 'repository branches' do
- let(:project) { create(:project, :public, :repository) }
- let(:current_user) { nil }
- end
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'repository branches'
end
context 'when unauthenticated', 'and project is private' do
@@ -47,9 +52,15 @@ describe API::Branches do
end
end
- context 'when authenticated', 'as a developer' do
- it_behaves_like 'repository branches' do
- let(:current_user) { user }
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ it_behaves_like 'repository branches'
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository branches'
end
end
@@ -61,31 +72,15 @@ describe API::Branches do
end
describe "GET /projects/:id/repository/branches/:branch" do
- let(:route) { "/projects/#{project.id}/repository/branches/#{branch_name}" }
+ let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}" }
- shared_examples_for 'repository branch' do |merged: false|
+ shared_examples_for 'repository branch' do
it 'returns the repository branch' do
get api(route, current_user)
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['merged']).to eq(merged)
- expect(json_response['protected']).to eq(false)
- expect(json_response['developers_can_push']).to eq(false)
- expect(json_response['developers_can_merge']).to eq(false)
-
- json_commit = json_response['commit']
- expect(json_commit['id']).to eq(branch_sha)
- expect(json_commit).to have_key('short_id')
- expect(json_commit).to have_key('title')
- expect(json_commit).to have_key('message')
- expect(json_commit).to have_key('author_name')
- expect(json_commit).to have_key('author_email')
- expect(json_commit).to have_key('authored_date')
- expect(json_commit).to have_key('committer_name')
- expect(json_commit).to have_key('committer_email')
- expect(json_commit).to have_key('committed_date')
- expect(json_commit).to have_key('parent_ids')
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
end
context 'when branch does not exist' do
@@ -107,10 +102,9 @@ describe API::Branches do
end
context 'when unauthenticated', 'and project is public' do
- it_behaves_like 'repository branch' do
- let(:project) { create(:project, :public, :repository) }
- let(:current_user) { nil }
- end
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'repository branch'
end
context 'when unauthenticated', 'and project is private' do
@@ -120,22 +114,41 @@ describe API::Branches do
end
end
- context 'when authenticated', 'as a developer' do
+ context 'when authenticated', 'as a master' do
let(:current_user) { user }
+
it_behaves_like 'repository branch'
context 'when branch contains a dot' do
let(:branch_name) { branch_with_dot.name }
- let(:branch_sha) { project.commit('master').sha }
it_behaves_like 'repository branch'
end
- context 'when branch is merged' do
- let(:branch_name) { 'merge-test' }
- let(:branch_sha) { project.commit('merge-test').sha }
+ context 'when branch contains a slash' do
+ let(:branch_name) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'when branch contains an escaped slash' do
+ let(:branch_name) { CGI.escape(branch_with_slash.name) }
+
+ it_behaves_like 'repository branch'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository branch'
- it_behaves_like 'repository branch', merged: true
+ context 'when branch contains a dot' do
+ let(:branch_name) { branch_with_dot.name }
+
+ it_behaves_like 'repository branch'
+ end
end
end
@@ -147,268 +160,348 @@ describe API::Branches do
end
describe 'PUT /projects/:id/repository/branches/:branch/protect' do
- context "when a protected branch doesn't already exist" do
- it 'protects a single branch' do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
+ let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/protect" }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(false)
- expect(json_response['developers_can_merge']).to eq(false)
- end
-
- it "protects a single branch with dots in the name" do
- put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/protect", user)
+ shared_examples_for 'repository new protected branch' do
+ it 'protects a single branch' do
+ put api(route, current_user)
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_with_dot.name)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
expect(json_response['protected']).to eq(true)
end
it 'protects a single branch and developers can push' do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
- developers_can_push: true
+ put api(route, current_user), developers_can_push: true
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(false)
end
it 'protects a single branch and developers can merge' do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
- developers_can_merge: true
+ put api(route, current_user), developers_can_merge: true
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(false)
expect(json_response['developers_can_merge']).to eq(true)
end
it 'protects a single branch and developers can push and merge' do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
- developers_can_push: true, developers_can_merge: true
+ put api(route, current_user), developers_can_push: true, developers_can_merge: true
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
expect(json_response['protected']).to eq(true)
expect(json_response['developers_can_push']).to eq(true)
expect(json_response['developers_can_merge']).to eq(true)
end
+
+ context 'when branch does not exist' do
+ let(:branch_name) { 'unknown' }
+
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user) }
+ let(:message) { '404 Branch Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { put api(route, current_user) }
+ end
+ end
end
- context 'for an existing protected branch' do
- before do
- project.repository.add_branch(user, protected_branch.name, 'master')
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { put api(route) }
+ let(:message) { '404 Project Not Found' }
end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { put api(route, guest) }
+ end
+ end
+
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- context "when developers can push and merge" do
- let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') }
+ context "when a protected branch doesn't already exist" do
+ it_behaves_like 'repository new protected branch'
- it 'updates that a developer cannot push or merge' do
- put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_push: false, developers_can_merge: false
+ context 'when branch contains a dot' do
+ let(:branch_name) { branch_with_dot.name }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(protected_branch.name)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(false)
- expect(json_response['developers_can_merge']).to eq(false)
+ it_behaves_like 'repository new protected branch'
end
- it "doesn't result in 0 access levels when 'developers_can_push' is switched off" do
- put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_push: false
+ context 'when branch contains a slash' do
+ let(:branch_name) { branch_with_slash.name }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(protected_branch.name)
- expect(protected_branch.reload.push_access_levels.first).to be_present
- expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user) }
+ end
end
- it "doesn't result in 0 access levels when 'developers_can_merge' is switched off" do
- put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_merge: false
+ context 'when branch contains an escaped slash' do
+ let(:branch_name) { CGI.escape(branch_with_slash.name) }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(protected_branch.name)
- expect(protected_branch.reload.merge_access_levels.first).to be_present
- expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
+ it_behaves_like 'repository new protected branch'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository new protected branch'
+
+ context 'when branch contains a dot' do
+ let(:branch_name) { branch_with_dot.name }
+
+ it_behaves_like 'repository new protected branch'
+ end
end
end
- context "when developers cannot push or merge" do
- let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') }
+ context 'when protected branch already exists' do
+ before do
+ project.repository.add_branch(user, protected_branch.name, 'master')
+ end
+
+ context 'when developers can push and merge' do
+ let(:protected_branch) { create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: 'protected_branch') }
+
+ it 'updates that a developer cannot push or merge' do
+ put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
+ developers_can_push: false, developers_can_merge: false
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(protected_branch.name)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(false)
+ expect(protected_branch.reload.push_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
+ expect(protected_branch.reload.merge_access_levels.first.access_level).to eq(Gitlab::Access::MASTER)
+ end
+ end
+
+ context 'when developers cannot push or merge' do
+ let(:protected_branch) { create(:protected_branch, project: project, name: 'protected_branch') }
- it 'updates that a developer can push and merge' do
- put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_push: true, developers_can_merge: true
+ it 'updates that a developer can push and merge' do
+ put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
+ developers_can_push: true, developers_can_merge: true
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(protected_branch.name)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(true)
- expect(json_response['developers_can_merge']).to eq(true)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(protected_branch.name)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(true)
+ expect(json_response['developers_can_merge']).to eq(true)
+ end
end
end
end
+ end
- context "multiple API calls" do
- it "returns success when `protect` is called twice" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
+ describe 'PUT /projects/:id/repository/branches/:branch/unprotect' do
+ let(:route) { "/projects/#{project_id}/repository/branches/#{branch_name}/unprotect" }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(false)
- expect(json_response['developers_can_merge']).to eq(false)
+ shared_examples_for 'repository unprotected branch' do
+ it 'unprotects a single branch' do
+ put api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq(CGI.unescape(branch_name))
+ expect(json_response['protected']).to eq(false)
end
- it "returns success when `protect` is called twice with `developers_can_push` turned on" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true
+ context 'when branch does not exist' do
+ let(:branch_name) { 'unknown' }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(true)
- expect(json_response['developers_can_merge']).to eq(false)
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user) }
+ let(:message) { '404 Branch Not Found' }
+ end
end
- it "returns success when `protect` is called twice with `developers_can_merge` turned on" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_merge: true
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['protected']).to eq(true)
- expect(json_response['developers_can_push']).to eq(false)
- expect(json_response['developers_can_merge']).to eq(true)
+ it_behaves_like '403 response' do
+ let(:request) { put api(route, current_user) }
+ end
end
end
- it "returns a 404 error if branch not found" do
- put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { put api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
end
- it "returns a 403 error if guest" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", guest)
- expect(response).to have_http_status(403)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { put api(route, guest) }
+ end
end
- end
- describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
- it "unprotects a single branch" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
- expect(response).to have_http_status(200)
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
+
+ context "when a protected branch doesn't already exist" do
+ it_behaves_like 'repository unprotected branch'
+
+ context 'when branch contains a dot' do
+ let(:branch_name) { branch_with_dot.name }
+
+ it_behaves_like 'repository unprotected branch'
+ end
+
+ context 'when branch contains a slash' do
+ let(:branch_name) { branch_with_slash.name }
+
+ it_behaves_like '404 response' do
+ let(:request) { put api(route, current_user) }
+ end
+ end
+
+ context 'when branch contains an escaped slash' do
+ let(:branch_name) { CGI.escape(branch_with_slash.name) }
- expect(json_response['name']).to eq(branch_name)
- expect(json_response['commit']['id']).to eq(branch_sha)
- expect(json_response['protected']).to eq(false)
+ it_behaves_like 'repository unprotected branch'
+ end
+
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
+
+ it_behaves_like 'repository unprotected branch'
+
+ context 'when branch contains a dot' do
+ let(:branch_name) { branch_with_dot.name }
+
+ it_behaves_like 'repository unprotected branch'
+ end
+ end
+ end
end
+ end
- it "update branches with dots in branch name" do
- put api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}/unprotect", user)
+ describe 'POST /projects/:id/repository/branches' do
+ let(:route) { "/projects/#{project_id}/repository/branches" }
- expect(response).to have_http_status(200)
- expect(json_response['name']).to eq(branch_with_dot.name)
- expect(json_response['protected']).to eq(false)
+ shared_examples_for 'repository new branch' do
+ it 'creates a new branch' do
+ post api(route, current_user), branch: 'feature1', ref: branch_sha
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(response).to match_response_schema('public_api/v4/branch')
+ expect(json_response['name']).to eq('feature1')
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, current_user) }
+ end
+ end
end
- it "returns success when unprotect branch" do
- put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
end
- it "returns success when unprotect branch again" do
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
- put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
- expect(response).to have_http_status(200)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { post api(route, guest) }
+ end
end
- end
- describe "POST /projects/:id/repository/branches" do
- it "creates a new branch" do
- post api("/projects/#{project.id}/repository/branches", user),
- branch: 'feature1',
- ref: branch_sha
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- expect(response).to have_http_status(201)
+ context "when a protected branch doesn't already exist" do
+ it_behaves_like 'repository new branch'
- expect(json_response['name']).to eq('feature1')
- expect(json_response['commit']['id']).to eq(branch_sha)
- end
+ context 'requesting with the escaped project full path' do
+ let(:project_id) { CGI.escape(project.full_path) }
- it "denies for user without push access" do
- post api("/projects/#{project.id}/repository/branches", guest),
- branch: branch_name,
- ref: branch_sha
- expect(response).to have_http_status(403)
+ it_behaves_like 'repository new branch'
+ end
+ end
end
it 'returns 400 if branch name is invalid' do
- post api("/projects/#{project.id}/repository/branches", user),
- branch: 'new design',
- ref: branch_sha
- expect(response).to have_http_status(400)
+ post api(route, user), branch: 'new design', ref: branch_sha
+
+ expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Branch name is invalid')
end
it 'returns 400 if branch already exists' do
- post api("/projects/#{project.id}/repository/branches", user),
- branch: 'new_design1',
- ref: branch_sha
- expect(response).to have_http_status(201)
-
- post api("/projects/#{project.id}/repository/branches", user),
- branch: 'new_design1',
- ref: branch_sha
- expect(response).to have_http_status(400)
+ post api(route, user), branch: 'new_design1', ref: branch_sha
+
+ expect(response).to have_gitlab_http_status(201)
+
+ post api(route, user), branch: 'new_design1', ref: branch_sha
+
+ expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Branch already exists')
end
it 'returns 400 if ref name is invalid' do
- post api("/projects/#{project.id}/repository/branches", user),
- branch: 'new_design3',
- ref: 'foo'
- expect(response).to have_http_status(400)
+ post api(route, user), branch: 'new_design3', ref: 'foo'
+
+ expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Invalid reference name')
end
end
- describe "DELETE /projects/:id/repository/branches/:branch" do
+ describe 'DELETE /projects/:id/repository/branches/:branch' do
before do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
- it "removes branch" do
+ it 'removes branch' do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
- expect(response).to have_http_status(204)
+ expect(response).to have_gitlab_http_status(204)
end
- it "removes a branch with dots in the branch name" do
+ it 'removes a branch with dots in the branch name' do
delete api("/projects/#{project.id}/repository/branches/#{branch_with_dot.name}", user)
- expect(response).to have_http_status(204)
+ expect(response).to have_gitlab_http_status(204)
end
it 'returns 404 if branch not exists' do
delete api("/projects/#{project.id}/repository/branches/foobar", user)
- expect(response).to have_http_status(404)
+
+ expect(response).to have_gitlab_http_status(404)
end
end
- describe "DELETE /projects/:id/repository/merged_branches" do
+ describe 'DELETE /projects/:id/repository/merged_branches' do
before do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
@@ -416,13 +509,14 @@ describe API::Branches do
it 'returns 202 with json body' do
delete api("/projects/#{project.id}/repository/merged_branches", user)
- expect(response).to have_http_status(202)
+ expect(response).to have_gitlab_http_status(202)
expect(json_response['message']).to eql('202 Accepted')
end
it 'returns a 403 error if guest' do
delete api("/projects/#{project.id}/repository/merged_branches", guest)
- expect(response).to have_http_status(403)
+
+ expect(response).to have_gitlab_http_status(403)
end
end
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index cdb60fc0d1a..8b62aa268d9 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -237,6 +237,28 @@ describe API::CommitStatuses do
end
end
+ context 'when retrying a commit status' do
+ before do
+ post api(post_url, developer),
+ { state: 'failed', name: 'test', ref: 'master' }
+
+ post api(post_url, developer),
+ { state: 'success', name: 'test', ref: 'master' }
+ end
+
+ it 'correctly posts a new commit status' do
+ expect(response).to have_http_status(201)
+ expect(json_response['sha']).to eq(commit.id)
+ expect(json_response['status']).to eq('success')
+ end
+
+ it 'retries a commit status' do
+ expect(CommitStatus.count).to eq 2
+ expect(CommitStatus.first).to be_retried
+ expect(CommitStatus.last.pipeline).to be_success
+ end
+ end
+
context 'when status is invalid' do
before do
post api(post_url, developer), state: 'invalid'
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index a19870a95e8..1754ba66a96 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::Events, api: true do
+describe API::Events do
include ApiHelpers
let(:user) { create(:user) }
let(:non_member) { create(:user) }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 1d8aaeea8f2..7e21006b254 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -113,6 +113,20 @@ describe API::Features do
{ 'key' => 'actors', 'value' => ["User:#{user.id}"] }
])
end
+
+ it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do
+ post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team'
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'groups', 'value' => ['perf_team'] },
+ { 'key' => 'actors', 'value' => ["User:#{user.id}"] }
+ ])
+ end
end
it 'creates a feature with the given percentage if passed an integer' do
diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb
new file mode 100644
index 00000000000..9b24658771f
--- /dev/null
+++ b/spec/requests/api/group_milestones_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe API::GroupMilestones do
+ let(:user) { create(:user) }
+ let(:group) { create(:group, :private) }
+ let(:project) { create(:empty_project, namespace: group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let!(:closed_milestone) { create(:closed_milestone, group: group, title: 'version1', description: 'closed milestone') }
+ let!(:milestone) { create(:milestone, group: group, title: 'version2', description: 'open milestone') }
+
+ it_behaves_like 'group and project milestones', "/groups/:id/milestones" do
+ let(:route) { "/groups/#{group.id}/milestones" }
+ end
+
+ def setup_for_group
+ context_group.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ context_group.add_developer(user)
+ public_project.update(namespace: context_group)
+ context_group.reload
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 656f098aea8..1d7adc6ac45 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -510,7 +510,7 @@ describe API::Groups do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) }
- let(:project_path) { project.full_path.gsub('/', '%2F') }
+ let(:project_path) { CGI.escape(project.full_path) }
before(:each) do
allow_any_instance_of(Projects::TransferService)
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 191c60aba31..7a1bd76af7a 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -10,10 +10,22 @@ describe API::Helpers do
let(:key) { create(:key, user: user) }
let(:params) { {} }
- let(:env) { { 'REQUEST_METHOD' => 'GET' } }
- let(:request) { Rack::Request.new(env) }
+ let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) }
+ let(:env) do
+ {
+ 'rack.input' => '',
+ 'rack.session' => {
+ _csrf_token: csrf_token
+ },
+ 'REQUEST_METHOD' => 'GET'
+ }
+ end
let(:header) { }
+ before do
+ allow_any_instance_of(self.class).to receive(:options).and_return({})
+ end
+
def set_env(user_or_token, identifier)
clear_env
clear_param
@@ -54,7 +66,7 @@ describe API::Helpers do
describe ".current_user" do
subject { current_user }
- describe "Warden authentication" do
+ describe "Warden authentication", :allow_forgery_protection do
before do
doorkeeper_guard_returns false
end
@@ -95,7 +107,17 @@ describe API::Helpers do
env['REQUEST_METHOD'] = 'PUT'
end
- it { is_expected.to be_nil }
+ context 'without CSRF token' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with CSRF token' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = csrf_token
+ end
+
+ it { is_expected.to eq(user) }
+ end
end
context "POST request" do
@@ -103,7 +125,17 @@ describe API::Helpers do
env['REQUEST_METHOD'] = 'POST'
end
- it { is_expected.to be_nil }
+ context 'without CSRF token' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with CSRF token' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = csrf_token
+ end
+
+ it { is_expected.to eq(user) }
+ end
end
context "DELETE request" do
@@ -111,7 +143,17 @@ describe API::Helpers do
env['REQUEST_METHOD'] = 'DELETE'
end
- it { is_expected.to be_nil }
+ context 'without CSRF token' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with CSRF token' do
+ before do
+ env['HTTP_X_CSRF_TOKEN'] = csrf_token
+ end
+
+ it { is_expected.to eq(user) }
+ end
end
end
end
@@ -167,7 +209,6 @@ describe API::Helpers do
it "returns nil for a token without the appropriate scope" do
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
- allow_access_with_scope('write_user')
expect(current_user).to be_nil
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6deaea956e0..ce9b9ac1eb3 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -6,7 +6,7 @@ describe API::Internal do
let(:project) { create(:project, :repository) }
let(:secret_token) { Gitlab::Shell.secret_token }
- describe "GET /internal/check", no_db: true do
+ describe "GET /internal/check" do
it do
get api("/internal/check"), secret_token: secret_token
@@ -35,6 +35,17 @@ describe API::Internal do
expect(json_response).to be_empty
end
end
+
+ context 'nil broadcast message' do
+ it 'returns nothing' do
+ allow(BroadcastMessage).to receive(:current).and_return(nil)
+
+ get api('/internal/broadcast_message'), secret_token: secret_token
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_empty
+ end
+ end
end
describe 'GET /internal/broadcast_messages' do
@@ -168,7 +179,7 @@ describe API::Internal do
end
end
- describe "POST /internal/allowed", :redis do
+ describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
before do
project.team << [user, :developer]
@@ -220,26 +231,72 @@ describe API::Internal do
end
context "git pull" do
- it do
- pull(key, project)
+ context "gitaly disabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(false)
+ pull(key, project)
- expect(response).to have_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(user).to have_an_activity_record
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).to be_nil
+ expect(user).to have_an_activity_record
+ end
+ end
+
+ context "gitaly enabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_upload_pack).and_return(true)
+ pull(key, project)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).to have_an_activity_record
+ end
end
end
context "git push" do
- it do
- push(key, project)
+ context "gitaly disabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(false)
+ push(key, project)
- expect(response).to have_http_status(200)
- expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
- expect(json_response["gl_repository"]).to eq("project-#{project.id}")
- expect(user).not_to have_an_activity_record
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).to be_nil
+ expect(user).not_to have_an_activity_record
+ end
+ end
+
+ context "gitaly enabled" do
+ it "has the correct payload" do
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:ssh_receive_pack).and_return(true)
+ push(key, project)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.repository.path_to_repo)
+ expect(json_response["gl_repository"]).to eq("project-#{project.id}")
+ expect(json_response["gitaly"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]).not_to be_nil
+ expect(json_response["gitaly"]["repository"]["storage_name"]).to eq(project.repository.gitaly_repository.storage_name)
+ expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
+ expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
+ expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
+ expect(user).not_to have_an_activity_record
+ end
end
context 'project as /namespace/project' do
@@ -537,10 +594,10 @@ describe API::Internal do
# end
#
# it "calls the Gitaly client with the project's repository" do
- # expect(Gitlab::GitalyClient::Notifications).
+ # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
# and_call_original
- # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive)
#
# post api("/internal/notify_post_receive"), valid_params
@@ -549,10 +606,10 @@ describe API::Internal do
# end
#
# it "calls the Gitaly client with the wiki's repository if it's a wiki" do
- # expect(Gitlab::GitalyClient::Notifications).
+ # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
# and_call_original
- # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive)
#
# post api("/internal/notify_post_receive"), valid_wiki_params
@@ -561,7 +618,7 @@ describe API::Internal do
# end
#
# it "returns 500 if the gitaly call fails" do
- # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive).and_raise(GRPC::Unavailable)
#
# post api("/internal/notify_post_receive"), valid_params
@@ -579,10 +636,10 @@ describe API::Internal do
# end
#
# it "calls the Gitaly client with the project's repository" do
- # expect(Gitlab::GitalyClient::Notifications).
+ # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
# and_call_original
- # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive)
#
# post api("/internal/notify_post_receive"), valid_params
@@ -591,10 +648,10 @@ describe API::Internal do
# end
#
# it "calls the Gitaly client with the wiki's repository if it's a wiki" do
- # expect(Gitlab::GitalyClient::Notifications).
+ # expect(Gitlab::GitalyClient::NotificationService).
# to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
# and_call_original
- # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # expect_any_instance_of(Gitlab::GitalyClient::NotificationService).
# to receive(:post_receive)
#
# post api("/internal/notify_post_receive"), valid_wiki_params
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 79cac721202..33cea02153e 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -71,7 +71,6 @@ describe API::Issues do
expect(response).to have_http_status(401)
end
end
-
context "when authenticated" do
let(:first_issue) { json_response.first }
@@ -105,6 +104,42 @@ describe API::Issues do
expect(json_response.second['id']).to eq(closed_issue.id)
end
+ it 'returns issues assigned to me' do
+ issue2 = create(:issue, assignees: [user2], project: project)
+
+ get api('/issues', user2), scope: 'assigned-to-me'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues authored by the given author id' do
+ issue2 = create(:issue, author: user2, project: project)
+
+ get api('/issues', user), author_id: user2.id, scope: 'all'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues assigned to the given assignee id' do
+ issue2 = create(:issue, assignees: [user2], project: project)
+
+ get api('/issues', user), assignee_id: user2.id, scope: 'all'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues authored by the given author id and assigned to the given assignee id' do
+ issue2 = create(:issue, author: user2, assignees: [user2], project: project)
+
+ get api('/issues', user), author_id: user2.id, assignee_id: user2.id, scope: 'all'
+
+ expect_paginated_array_response(size: 1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
it 'returns issues matching given search string for title' do
get api("/issues", user), search: issue.title
@@ -693,6 +728,19 @@ describe API::Issues do
expect(json_response['confidential']).to be_falsy
end
+ context 'links exposure' do
+ it 'exposes related resources full URIs' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}", user)
+
+ links = json_response['_links']
+
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}")
+ expect(links['notes']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/notes")
+ expect(links['award_emoji']).to end_with("/api/v4/projects/#{project.id}/issues/#{issue.iid}/award_emoji")
+ expect(links['project']).to end_with("/api/v4/projects/#{project.id}")
+ end
+ end
+
it "returns a project issue by internal id" do
get api("/projects/#{project.id}/issues/#{issue.iid}", user)
@@ -772,7 +820,7 @@ describe API::Issues do
end
end
- context 'CE restrictions' do
+ context 'single assignee restrictions' do
it 'creates a new project issue with no more than one assignee' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', assignee_ids: [user2.id, guest.id]
@@ -1123,7 +1171,7 @@ describe API::Issues do
expect(json_response['assignees'].first['name']).to eq(user2.name)
end
- context 'CE restrictions' do
+ context 'single assignee restrictions' do
it 'updates an issue with several assignees but only one has been applied' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
assignee_ids: [user2.id, guest.id]
@@ -1462,6 +1510,25 @@ describe API::Issues do
end
end
+ describe "GET /projects/:id/issues/:issue_iid/user_agent_detail" do
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: issue) }
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
def expect_paginated_array_response(size: nil)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 8d647eb1c7e..f56baf9663d 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::Jobs, :api do
+describe API::Jobs do
let!(:project) do
create(:project, :repository, public_builds: false)
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 4d0bd67c571..35b6522ea98 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -16,7 +16,11 @@ describe API::MergeRequests do
let!(:label) do
create(:label, title: 'label', color: '#FFAABB', project: project)
end
+ let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
let!(:label_link) { create(:label_link, label: label, target: merge_request) }
+ let!(:label_link2) { create(:label_link, label: label2, target: merge_request) }
+ let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
+ let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
before do
project.team << [user, :reporter]
@@ -32,6 +36,18 @@ describe API::MergeRequests do
end
context "when authenticated" do
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.count
+
+ create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time)
+
+ expect do
+ get api("/projects/#{project.id}/merge_requests", user)
+ end.not_to exceed_query_limit(control_count)
+ end
+
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
@@ -44,12 +60,31 @@ describe API::MergeRequests do
expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
expect(json_response.last['merge_commit_sha']).to be_nil
expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.last['downvotes']).to eq(1)
+ expect(json_response.last['upvotes']).to eq(1)
+ expect(json_response.last['labels']).to eq([label2.title, label.title])
expect(json_response.first['title']).to eq(merge_request_merged.title)
expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
expect(json_response.first['merge_commit_sha']).not_to be_nil
expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
end
+ it "returns an array of all merge_requests using simple mode" do
+ get api("/projects/#{project.id}/merge_requests?view=simple", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(3)
+ expect(json_response.last['iid']).to eq(merge_request.iid)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.first['iid']).to eq(merge_request_merged.iid)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first).to have_key('web_url')
+ end
+
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
@@ -145,7 +180,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
end
it 'returns an array of labeled merge requests where all labels match' do
@@ -236,8 +271,8 @@ describe API::MergeRequests do
expect(json_response['author']).to be_a Hash
expect(json_response['target_branch']).to eq(merge_request.target_branch)
expect(json_response['source_branch']).to eq(merge_request.source_branch)
- expect(json_response['upvotes']).to eq(0)
- expect(json_response['downvotes']).to eq(0)
+ expect(json_response['upvotes']).to eq(1)
+ expect(json_response['downvotes']).to eq(1)
expect(json_response['source_project_id']).to eq(merge_request.source_project.id)
expect(json_response['target_project_id']).to eq(merge_request.target_project.id)
expect(json_response['work_in_progress']).to be_falsy
@@ -759,18 +794,24 @@ describe API::MergeRequests do
it 'handles external issues' do
jira_project = create(:jira_project, :public, name: 'JIR_EXT1')
- issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
- merge_request = create(:merge_request, :simple, author: user, assignee: user, source_project: jira_project)
- merge_request.update_attribute(:description, "Closes #{issue.to_reference(jira_project)}")
+ ext_issue = ExternalIssue.new("#{jira_project.name}-123", jira_project)
+ issue = create(:issue, project: jira_project)
+ description = "Closes #{ext_issue.to_reference(jira_project)}\ncloses #{issue.to_reference}"
+ merge_request = create(:merge_request,
+ :simple, author: user, assignee: user, source_project: jira_project, description: description)
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
+ expect(json_response.second['title']).to eq(ext_issue.title)
+ expect(json_response.second['id']).to eq(ext_issue.id)
+ expect(json_response.second['confidential']).to be_nil
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.first['id']).to eq(issue.id)
+ expect(json_response.first['confidential']).not_to be_nil
end
it 'returns 403 if the user has no access to the merge request' do
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 85d11deb26f..b34555d2815 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -279,6 +279,8 @@ describe API::PipelineSchedules do
end
context 'authenticated user with invalid permissions' do
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: master) }
+
it 'does not delete pipeline_schedule' do
delete api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
new file mode 100644
index 00000000000..fe8fdbfd7e4
--- /dev/null
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe API::ProjectMilestones do
+ let(:user) { create(:user) }
+ let!(:project) { create(:empty_project, namespace: user.namespace ) }
+ let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
+ let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ it_behaves_like 'group and project milestones', "/projects/:id/milestones" do
+ let(:route) { "/projects/#{project.id}/milestones" }
+ end
+
+ describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
+ it 'creates an activity event when an milestone is closed' do
+ expect(Event).to receive(:create)
+
+ put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ state_event: 'close'
+ end
+ end
+end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 518639f45a2..f220972bae3 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -5,6 +5,26 @@ describe API::ProjectSnippets do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
+ describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
+ let(:snippet) { create(:project_snippet, :public, project: project) }
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
+
+ it 'exposes known attributes' do
+ get api("/projects/#{project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
describe 'GET /projects/:project_id/snippets/' do
let(:user) { create(:user) }
@@ -20,7 +40,7 @@ describe API::ProjectSnippets do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(3)
- expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+ expect(json_response.map { |snippet| snippet['id'] }).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
end
@@ -38,7 +58,7 @@ describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id' do
let(:user) { create(:user) }
- let(:snippet) { create(:project_snippet, :public, project: project) }
+ let(:snippet) { create(:project_snippet, :public, project: project) }
it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 14dec3d45b1..6ed68fcff09 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -52,6 +52,24 @@ describe API::Projects do
end
end
+ shared_examples_for 'projects response without N + 1 queries' do
+ it 'avoids N + 1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ get api('/projects', current_user)
+ end.count
+
+ if defined?(additional_project)
+ additional_project
+ else
+ create(:empty_project, :public)
+ end
+
+ expect do
+ get api('/projects', current_user)
+ end.not_to exceed_query_limit(control_count + 8)
+ end
+ end
+
let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
before do
project
@@ -62,9 +80,13 @@ describe API::Projects do
context 'when unauthenticated' do
it_behaves_like 'projects response' do
- let(:filter) { {} }
+ let(:filter) { { search: project.name } }
+ let(:current_user) { user }
+ let(:projects) { [project] }
+ end
+
+ it_behaves_like 'projects response without N + 1 queries' do
let(:current_user) { nil }
- let(:projects) { [public_project] }
end
end
@@ -75,6 +97,21 @@ describe API::Projects do
let(:projects) { [public_project, project, project2, project3] }
end
+ it_behaves_like 'projects response without N + 1 queries' do
+ let(:current_user) { user }
+ end
+
+ context 'when some projects are in a group' do
+ before do
+ create(:empty_project, :public, group: create(:group))
+ end
+
+ it_behaves_like 'projects response without N + 1 queries' do
+ let(:current_user) { user }
+ let(:additional_project) { create(:empty_project, :public, group: create(:group)) }
+ end
+ end
+
it 'includes the project labels as the tag_list' do
get api('/projects', user)
@@ -122,6 +159,31 @@ describe API::Projects do
expect(json_response.first).to include 'statistics'
end
+ context 'when external issue tracker is enabled' do
+ let!(:jira_service) { create(:jira_service, project: project) }
+
+ it 'includes open_issues_count' do
+ get api('/projects', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to include('open_issues_count')
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count')
+ end
+
+ it 'does not include open_issues_count if issues are disabled' do
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+
+ get api('/projects', user)
+
+ expect(response.status).to eq 200
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
+ end
+ end
+
context 'and with simple=true' do
it 'returns a simplified version of all the projects' do
expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
@@ -347,7 +409,8 @@ describe API::Projects do
wiki_enabled: false,
only_allow_merge_if_pipeline_succeeds: false,
request_access_enabled: true,
- only_allow_merge_if_all_discussions_are_resolved: false
+ only_allow_merge_if_all_discussions_are_resolved: false,
+ ci_config_path: 'a/custom/path'
})
post api('/projects', user), project
@@ -404,7 +467,7 @@ describe API::Projects do
post api('/projects', user), project
project_id = json_response['id']
- expect(json_response['avatar_url']).to eq("http://localhost/uploads/system/project/avatar/#{project_id}/banana_sample.gif")
+ expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
end
it 'sets a project as allowing merge even if build fails' do
@@ -475,6 +538,26 @@ describe API::Projects do
end
end
+ describe 'GET /users/:user_id/projects/' do
+ let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
+
+ it 'returns error when user not found' do
+ get api('/users/9999/projects/')
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns projects filtered by user' do
+ get api("/users/#{user4.id}/projects/", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
+ end
+ end
+
describe 'POST /projects/user/:id' do
before do
expect(project).to be_persisted
@@ -653,6 +736,7 @@ describe API::Projects do
expect(json_response['star_count']).to be_present
expect(json_response['forks_count']).to be_present
expect(json_response['public_jobs']).to be_present
+ expect(json_response['ci_config_path']).to be_nil
expect(json_response['shared_with_groups']).to be_an Array
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
@@ -684,7 +768,7 @@ describe API::Projects do
dot_user = create(:user, username: 'dot.user')
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
- get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
+ get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(project.name)
end
@@ -731,6 +815,38 @@ describe API::Projects do
expect(json_response).not_to include("import_error")
end
+ context 'links exposure' do
+ it 'exposes related resources full URIs' do
+ get api("/projects/#{project.id}", user)
+
+ links = json_response['_links']
+
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
+ expect(links['issues']).to end_with("/api/v4/projects/#{project.id}/issues")
+ expect(links['merge_requests']).to end_with("/api/v4/projects/#{project.id}/merge_requests")
+ expect(links['repo_branches']).to end_with("/api/v4/projects/#{project.id}/repository/branches")
+ expect(links['labels']).to end_with("/api/v4/projects/#{project.id}/labels")
+ expect(links['events']).to end_with("/api/v4/projects/#{project.id}/events")
+ expect(links['members']).to end_with("/api/v4/projects/#{project.id}/members")
+ end
+
+ it 'filters related URIs when their feature is not enabled' do
+ project = create(:empty_project, :public,
+ :merge_requests_disabled,
+ :issues_disabled,
+ creator_id: user.id,
+ namespace: user.namespace)
+
+ get api("/projects/#{project.id}", user)
+
+ links = json_response['_links']
+
+ expect(links.has_key?('merge_requests')).to be_falsy
+ expect(links.has_key?('issues')).to be_falsy
+ expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
+ end
+ end
+
describe 'permissions' do
context 'all projects' do
before do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 339a57a1f20..ca5d98c78ef 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -351,7 +351,8 @@ describe API::Runner do
let(:expected_cache) do
[{ 'key' => 'cache_key',
'untracked' => false,
- 'paths' => ['vendor/*'] }]
+ 'paths' => ['vendor/*'],
+ 'policy' => 'pull-push' }]
end
it 'picks a job' do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index ede48b1c888..b71ac6c30b5 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -10,7 +10,7 @@ describe API::Settings, 'Settings' do
expect(response).to have_http_status(200)
expect(json_response).to be_an Hash
expect(json_response['default_projects_limit']).to eq(42)
- expect(json_response['signin_enabled']).to be_truthy
+ expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['repository_storage']).to eq('default')
expect(json_response['koding_enabled']).to be_falsey
expect(json_response['koding_url']).to be_nil
@@ -32,7 +32,7 @@ describe API::Settings, 'Settings' do
it "updates application settings" do
put api("/application/settings", admin),
default_projects_limit: 3,
- signin_enabled: false,
+ password_authentication_enabled: false,
repository_storage: 'custom',
koding_enabled: true,
koding_url: 'http://koding.example.com',
@@ -46,7 +46,7 @@ describe API::Settings, 'Settings' do
help_page_support_url: 'http://example.com/help'
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
- expect(json_response['signin_enabled']).to be_falsey
+ expect(json_response['password_authentication_enabled']).to be_falsey
expect(json_response['repository_storage']).to eq('custom')
expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['koding_enabled']).to be_truthy
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index b20a187acfe..373fab4d98a 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -271,4 +271,25 @@ describe API::Snippets do
expect(json_response['message']).to eq('404 Snippet Not Found')
end
end
+
+ describe "GET /snippets/:id/user_agent_detail" do
+ let(:admin) { create(:admin) }
+ let(:snippet) { create(:personal_snippet, :public, author: user) }
+ let!(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
+
+ it 'exposes known attributes' do
+ get api("/snippets/#{snippet.id}/user_agent_detail", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
+ expect(json_response['ip_address']).to eq(user_agent_detail.ip_address)
+ expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
+ end
+
+ it "returns unautorized for non-admin users" do
+ get api("/snippets/#{snippet.id}/user_agent_detail", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 92533f4dfea..9fc73c6e092 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe API::Todos do
- let(:project_1) { create(:empty_project, :test_repo) }
+ let(:project_1) { create(:project) }
let(:project_2) { create(:empty_project) }
let(:author_1) { create(:user) }
let(:author_2) { create(:user) }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 16ddade27d9..c2636b6614e 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -61,7 +61,8 @@ describe API::Triggers do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No pipeline created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 8640c16203e..66b165b438b 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -55,17 +55,22 @@ describe API::Users do
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
- allow_any_instance_of(API::Helpers).to receive(:authenticate!).and_return(true)
end
- it "renders 403" do
- get api("/users")
- expect(response).to have_http_status(403)
+ context 'when authenticate as a regular user' do
+ it "renders 403" do
+ get api("/users", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
end
- it "renders 404" do
- get api("/users/#{user.id}")
- expect(response).to have_http_status(404)
+ context 'when authenticate as an admin' do
+ it "renders 200" do
+ get api("/users", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
end
@@ -163,6 +168,35 @@ describe API::Users do
expect(response).to have_http_status(400)
end
+
+ it "returns a user created before a specific date" do
+ user = create(:user, created_at: Date.new(2000, 1, 1))
+
+ get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['username']).to eq(user.username)
+ end
+
+ it "returns no users created before a specific date" do
+ create(:user, created_at: Date.new(2001, 1, 1))
+
+ get api("/users?created_before=2000-01-02T00:00:00.060Z", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(0)
+ end
+
+ it "returns users created before and after a specific date" do
+ user = create(:user, created_at: Date.new(2001, 1, 1))
+
+ get api("/users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['username']).to eq(user.username)
+ end
end
end
@@ -390,6 +424,14 @@ describe API::Users do
expect(json_response['identities'].first['provider']).to eq('github')
end
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { '/users' }
+ let(:api_call) { method(:api) }
+
+ include_examples 'does not allow the "read_user" scope'
+ end
end
describe "GET /users/sign_up" do
@@ -887,6 +929,13 @@ describe API::Users do
expect(response).to match_response_schema('public_api/v4/user/public')
expect(json_response['id']).to eq(user.id)
end
+
+ context "scopes" do
+ let(:path) { "/user" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
context 'with admin' do
@@ -899,11 +948,11 @@ describe API::Users do
expect(response).to have_http_status(403)
end
- it 'returns initial current user without private token when sudo not defined' do
+ it 'returns initial current user without private token but with is_admin when sudo not defined' do
get api("/user?private_token=#{admin_personal_access_token}")
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/public')
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response['id']).to eq(admin.id)
end
end
@@ -917,11 +966,11 @@ describe API::Users do
expect(json_response['id']).to eq(user.id)
end
- it 'returns initial current user without private token when sudo not defined' do
+ it 'returns initial current user without private token but with is_admin when sudo not defined' do
get api("/user?private_token=#{admin.private_token}")
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/public')
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response['id']).to eq(admin.id)
end
end
@@ -956,6 +1005,13 @@ describe API::Users do
expect(json_response).to be_an Array
expect(json_response.first["title"]).to eq(key.title)
end
+
+ context "scopes" do
+ let(:path) { "/user/keys" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
end
@@ -989,6 +1045,13 @@ describe API::Users do
expect(response).to have_http_status(404)
end
+
+ context "scopes" do
+ let(:path) { "/user/keys/#{key.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe "POST /user/keys" do
@@ -1078,6 +1141,13 @@ describe API::Users do
expect(json_response).to be_an Array
expect(json_response.first["email"]).to eq(email.email)
end
+
+ context "scopes" do
+ let(:path) { "/user/emails" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
end
@@ -1110,6 +1180,13 @@ describe API::Users do
expect(response).to have_http_status(404)
end
+
+ context "scopes" do
+ let(:path) { "/user/emails/#{email.id}" }
+ let(:api_call) { method(:api) }
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe "POST /user/emails" do
@@ -1241,7 +1318,7 @@ describe API::Users do
end
end
- context "user activities", :redis do
+ context "user activities", :clean_gitlab_redis_shared_state do
let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) }
let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) }
diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb
index 63c5707b2e4..5cdc528e190 100644
--- a/spec/requests/api/v3/groups_spec.rb
+++ b/spec/requests/api/v3/groups_spec.rb
@@ -502,7 +502,7 @@ describe API::V3::Groups do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:empty_project) }
- let(:project_path) { "#{project.namespace.path}%2F#{project.path}" }
+ let(:project_path) { CGI.escape(project.full_path) }
before(:each) do
allow_any_instance_of(Projects::TransferService)
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index af44ffa2331..bbfcaab1ea1 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -720,7 +720,7 @@ describe API::V3::Projects do
dot_user = create(:user, username: 'dot.user')
project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
- get v3_api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
+ get v3_api("/projects/#{CGI.escape(project.full_path)}", dot_user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(project.name)
end
diff --git a/spec/requests/api/v3/settings_spec.rb b/spec/requests/api/v3/settings_spec.rb
index 41d039b7da0..291f6dcc2aa 100644
--- a/spec/requests/api/v3/settings_spec.rb
+++ b/spec/requests/api/v3/settings_spec.rb
@@ -10,7 +10,7 @@ describe API::V3::Settings, 'Settings' do
expect(response).to have_http_status(200)
expect(json_response).to be_an Hash
expect(json_response['default_projects_limit']).to eq(42)
- expect(json_response['signin_enabled']).to be_truthy
+ expect(json_response['password_authentication_enabled']).to be_truthy
expect(json_response['repository_storage']).to eq('default')
expect(json_response['koding_enabled']).to be_falsey
expect(json_response['koding_url']).to be_nil
@@ -28,11 +28,11 @@ describe API::V3::Settings, 'Settings' do
it "updates application settings" do
put v3_api("/application/settings", admin),
- default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com',
+ default_projects_limit: 3, password_authentication_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com',
plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com'
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
- expect(json_response['signin_enabled']).to be_falsey
+ expect(json_response['password_authentication_enabled']).to be_falsey
expect(json_response['repository_storage']).to eq('custom')
expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['koding_enabled']).to be_truthy
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index d3de6bf13bc..60212660fb6 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -52,7 +52,8 @@ describe API::V3::Triggers do
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No builds created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
index 6d7401f9764..de7499a4e43 100644
--- a/spec/requests/api/v3/users_spec.rb
+++ b/spec/requests/api/v3/users_spec.rb
@@ -67,6 +67,19 @@ describe API::V3::Users do
expect(json_response.first['title']).to eq(key.title)
end
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { "/users/#{user.id}/keys" }
+ let(:api_call) { method(:v3_api) }
+
+ before do
+ user.keys << key
+ user.save
+ end
+
+ include_examples 'allows the "read_user" scope'
+ end
end
describe 'GET /user/:id/emails' do
@@ -287,7 +300,7 @@ describe API::V3::Users do
end
it 'returns a 404 error if not found' do
- get v3_api('/users/42/events', user)
+ get v3_api('/users/420/events', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -312,5 +325,13 @@ describe API::V3::Users do
expect(json_response['is_admin']).to be_nil
end
+
+ context "scopes" do
+ let(:user) { admin }
+ let(:path) { '/users' }
+ let(:api_call) { method(:v3_api) }
+
+ include_examples 'does not allow the "read_user" scope'
+ end
end
end
diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb
index 8870d48bbc9..7bbf34422b8 100644
--- a/spec/requests/api/version_spec.rb
+++ b/spec/requests/api/version_spec.rb
@@ -6,7 +6,7 @@ describe API::Version do
it 'returns authentication error' do
get api('/version')
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -16,7 +16,7 @@ describe API::Version do
it 'returns the version information' do
get api('/version', user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['version']).to eq(Gitlab::VERSION)
expect(json_response['revision']).to eq(Gitlab::REVISION)
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index c969d08d0dd..49e815ee16c 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -69,6 +69,72 @@ describe Ci::API::Builds do
end
end
+ context 'when an old image syntax is used' do
+ before do
+ build.update!(options: { image: 'codeclimate' })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "image" => "codeclimate" })
+ end
+ end
+
+ context 'when a new image syntax is used' do
+ before do
+ build.update!(options: { image: { name: 'codeclimate' } })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "image" => "codeclimate" })
+ end
+ end
+
+ context 'when an old service syntax is used' do
+ before do
+ build.update!(options: { services: ['mysql'] })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "services" => ["mysql"] })
+ end
+ end
+
+ context 'when a new service syntax is used' do
+ before do
+ build.update!(options: { services: [name: 'mysql'] })
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["options"]).to eq({ "services" => ["mysql"] })
+ end
+ end
+
+ context 'when no image or service is defined' do
+ before do
+ build.update!(options: {})
+ end
+
+ it 'starts a build' do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+
+ expect(json_response["options"]).to be_empty
+ end
+ end
+
context 'when there is a pending build' do
it 'starts a build' do
register_builds info: { platform: :darwin }
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index 26b03c0f148..e481ca916ab 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -5,7 +5,14 @@ describe Ci::API::Triggers do
let!(:trigger_token) { 'secure token' }
let!(:project) { create(:project, :repository, ci_id: 10) }
let!(:project2) { create(:empty_project, ci_id: 11) }
- let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+
+ let!(:trigger) do
+ create(:ci_trigger,
+ project: project,
+ token: trigger_token,
+ owner: create(:user))
+ end
+
let(:options) do
{
token: trigger_token
@@ -14,6 +21,8 @@ describe Ci::API::Triggers do
before do
stub_ci_pipeline_to_return_yaml_file
+
+ project.add_developer(trigger.owner)
end
context 'Handles errors' do
@@ -47,7 +56,8 @@ describe Ci::API::Triggers do
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No builds created')
+ expect(json_response['message']['base'])
+ .to contain_exactly('Reference not found')
end
context 'Validates variables' do
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 185679e1a0f..d4a3e8b13e1 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-describe 'Git HTTP requests', lib: true do
+describe 'Git HTTP requests' do
include GitHttpHelpers
include WorkhorseHelpers
include UserActivitiesHelpers
@@ -406,7 +406,7 @@ describe 'Git HTTP requests', lib: true do
end
end
- it 'updates the user last activity', :redis do
+ it 'updates the user last activity', :clean_gitlab_redis_shared_state do
expect(user_activity(user)).to be_nil
download(path, env) do |response|
@@ -463,7 +463,7 @@ describe 'Git HTTP requests', lib: true do
context 'when internal auth is disabled' do
before do
- allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false }
+ allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false }
end
it 'rejects pulls with personal access token error message' do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index 5e4cf05748e..8d79ea3dd40 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -101,7 +101,7 @@ describe JwtController do
context 'when internal auth is disabled' do
it 'rejects the authorization attempt with personal access token message' do
- allow_any_instance_of(ApplicationSetting).to receive(:signin_enabled?) { false }
+ allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false }
get '/jwt/auth', parameters, headers
expect(response).to have_http_status(401)
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 6d1f0b24196..a927de952d0 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do
'email_verified' => true,
'website' => 'https://example.com',
'profile' => 'http://localhost/alice',
- 'picture' => "http://localhost/uploads/system/user/avatar/#{user.id}/dk.png"
+ 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png"
})
end
end
@@ -98,7 +98,7 @@ describe 'OpenID Connect requests' do
expect(@payload['sub']).to eq hashed_subject
end
- it 'includes the time of the last authentication', :redis do
+ it 'includes the time of the last authentication', :clean_gitlab_redis_shared_state do
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
end
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index d4d3c9478a0..e5d9d3df5a8 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'cycle analytics events', api: true do
+describe 'cycle analytics events' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
@@ -21,7 +21,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the issue events' do
- get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_issue_path(project, format: :json)
first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
@@ -30,7 +30,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the plan events' do
- get namespace_project_cycle_analytics_plan_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_plan_path(project, format: :json)
first_mr_short_sha = project.merge_requests.sort(:created_asc).first.commits.first.short_id
@@ -39,7 +39,7 @@ describe 'cycle analytics events', api: true do
end
it 'lists the code events' do
- get namespace_project_cycle_analytics_code_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_code_path(project, format: :json)
expect(json_response['events']).not_to be_empty
@@ -49,14 +49,14 @@ describe 'cycle analytics events', api: true do
end
it 'lists the test events' do
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_test_path(project, format: :json)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
end
it 'lists the review events' do
- get namespace_project_cycle_analytics_review_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_review_path(project, format: :json)
first_mr_iid = project.merge_requests.sort(:created_desc).pluck(:iid).first.to_s
@@ -65,14 +65,14 @@ describe 'cycle analytics events', api: true do
end
it 'lists the staging events' do
- get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_staging_path(project, format: :json)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
end
it 'lists the production events' do
- get namespace_project_cycle_analytics_production_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_production_path(project, format: :json)
first_issue_iid = project.issues.sort(:created_desc).pluck(:iid).first.to_s
@@ -84,7 +84,7 @@ describe 'cycle analytics events', api: true do
it 'lists the test events' do
branch = project.merge_requests.first.source_branch
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json, branch: branch)
+ get project_cycle_analytics_test_path(project, format: :json, branch: branch)
expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
@@ -97,19 +97,19 @@ describe 'cycle analytics events', api: true do
end
it 'does not list the test events' do
- get namespace_project_cycle_analytics_test_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_test_path(project, format: :json)
expect(response).to have_http_status(:not_found)
end
it 'does not list the staging events' do
- get namespace_project_cycle_analytics_staging_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_staging_path(project, format: :json)
expect(response).to have_http_status(:not_found)
end
it 'lists the issue events' do
- get namespace_project_cycle_analytics_issue_path(project.namespace, project, format: :json)
+ get project_cycle_analytics_issue_path(project, format: :json)
expect(response).to have_http_status(:ok)
end
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index 624f3c43f0a..9b70c2a24be 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'environments routing', :routing do
+describe 'environments routing' do
let(:project) { create(:empty_project) }
let(:environment) do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 2f1c3c95e59..c02409b2e0b 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -609,4 +609,26 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/pages/domains/my.domain.com')).to route_to('projects/pages_domains#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'my.domain.com')
end
end
+
+ describe Projects::Registry::TagsController, 'routing' do
+ describe '#destroy' do
+ it 'correctly routes to a destroy action' do
+ expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/rc1'))
+ .to route_to('projects/registry/tags#destroy',
+ namespace_id: 'gitlab',
+ project_id: 'gitlabhq',
+ repository_id: '1',
+ id: 'rc1')
+ end
+
+ it 'takes registry tag name constrains into account' do
+ expect(delete('/gitlab/gitlabhq/registry/repository/1/tags/-rc1'))
+ .not_to route_to('projects/registry/tags#destroy',
+ namespace_id: 'gitlab',
+ project_id: 'gitlabhq',
+ repository_id: '1',
+ id: '-rc1')
+ end
+ end
+ end
end
diff --git a/spec/rubocop/cop/active_record_dependent_spec.rb b/spec/rubocop/cop/active_record_dependent_spec.rb
new file mode 100644
index 00000000000..599a032bfc5
--- /dev/null
+++ b/spec/rubocop/cop/active_record_dependent_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/active_record_dependent'
+
+describe RuboCop::Cop::ActiveRecordDependent do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'inside the app/models directory' do
+ it 'registers an offense when dependent: is used' do
+ allow(cop).to receive(:in_model?).and_return(true)
+
+ inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside the app/models directory' do
+ it 'does nothing' do
+ allow(cop).to receive(:in_model?).and_return(false)
+
+ inspect_source(cop, 'belongs_to :foo, dependent: :destroy')
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/rubocop/cop/activerecord_serialize_spec.rb b/spec/rubocop/cop/active_record_serialize_spec.rb
index 5bd7e5fa926..b94b25cecd0 100644
--- a/spec/rubocop/cop/activerecord_serialize_spec.rb
+++ b/spec/rubocop/cop/active_record_serialize_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
-require_relative '../../../rubocop/cop/activerecord_serialize'
+require_relative '../../../rubocop/cop/active_record_serialize'
-describe RuboCop::Cop::ActiverecordSerialize do
+describe RuboCop::Cop::ActiveRecordSerialize do
include CopHelper
subject(:cop) { described_class.new }
diff --git a/spec/rubocop/cop/in_batches_spec.rb b/spec/rubocop/cop/in_batches_spec.rb
new file mode 100644
index 00000000000..072481984c6
--- /dev/null
+++ b/spec/rubocop/cop/in_batches_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/in_batches'
+
+describe RuboCop::Cop::InBatches do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'registers an offense when in_batches is used' do
+ inspect_source(cop, 'foo.in_batches do; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+end
diff --git a/spec/rubocop/cop/migration/hash_index_spec.rb b/spec/rubocop/cop/migration/hash_index_spec.rb
new file mode 100644
index 00000000000..9a8576a19e5
--- /dev/null
+++ b/spec/rubocop/cop/migration/hash_index_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/hash_index'
+
+describe RuboCop::Cop::Migration::HashIndex do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ it 'registers an offense when creating a hash index' do
+ inspect_source(cop, 'def change; add_index :table, :column, using: :hash; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers an offense when creating a concurrent hash index' do
+ inspect_source(cop, 'def change; add_concurrent_index :table, :column, using: :hash; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+
+ it 'registers an offense when creating a hash index using t.index' do
+ inspect_source(cop, 'def change; t.index :table, :column, using: :hash; end')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ end
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ inspect_source(cop, 'def change; index :table, :column, using: :hash; end')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/project_path_helper_spec.rb b/spec/rubocop/cop/project_path_helper_spec.rb
new file mode 100644
index 00000000000..bc47b45cad7
--- /dev/null
+++ b/spec/rubocop/cop/project_path_helper_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/project_path_helper'
+
+describe RuboCop::Cop::ProjectPathHelper do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context "when using namespace_project with the project's namespace" do
+ let(:source) { 'edit_namespace_project_issue_path(@issue.project.namespace, @issue.project, @issue)' }
+ let(:correct_source) { 'edit_project_issue_path(@issue.project, @issue)' }
+
+ it 'registers an offense' do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['edit_namespace_project_issue_path'])
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(cop, source)
+
+ expect(autocorrected).to eq(correct_source)
+ end
+ end
+
+ context 'when using namespace_project with a different namespace' do
+ it 'registers no offense' do
+ inspect_source(cop, 'edit_namespace_project_issue_path(namespace, project)')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb
index b92c1c28ba8..1332572fffc 100644
--- a/spec/serializers/build_details_entity_spec.rb
+++ b/spec/serializers/build_details_entity_spec.rb
@@ -9,47 +9,96 @@ describe BuildDetailsEntity do
describe '#as_json' do
let(:project) { create(:project, :repository) }
- let!(:build) { create(:ci_build, :failed, project: project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, :failed, pipeline: pipeline) }
let(:request) { double('request') }
- let(:entity) { described_class.new(build, request: request, current_user: user, project: project) }
+
+ let(:entity) do
+ described_class.new(build, request: request,
+ current_user: user,
+ project: project)
+ end
+
subject { entity.as_json }
before do
allow(request).to receive(:current_user).and_return(user)
end
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:coverage, :erased_at, :duration)
+ expect(subject).to include(:runner, :pipeline)
+ expect(subject).to include(:raw_path, :new_issue_path)
+ end
+
context 'when the user has access to issues and merge requests' do
- let!(:merge_request) do
- create(:merge_request, source_project: project, source_branch: build.ref)
- end
+ context 'when merge request orginates from the same project' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project, source_branch: build.ref)
+ end
- before do
- allow(build).to receive(:merge_request).and_return(merge_request)
- end
+ before do
+ allow(build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:merge_request)
+ expect(subject).to include(:new_issue_path)
+ end
- it 'contains the needed key value pairs' do
- expect(subject).to include(:coverage, :erased_at, :duration)
- expect(subject).to include(:runner, :pipeline)
- expect(subject).to include(:raw_path, :merge_request)
- expect(subject).to include(:new_issue_path)
+ it 'exposes correct details of the merge request' do
+ expect(subject[:merge_request][:iid]).to eq merge_request.iid
+ end
+
+ it 'has a correct merge request path' do
+ expect(subject[:merge_request][:path]).to include project.full_path
+ end
end
- it 'exposes details of the merge request' do
- expect(subject[:merge_request]).to include(:iid, :path)
+ context 'when merge request is from a fork' do
+ let(:fork_project) do
+ create(:empty_project, forked_from_project: project)
+ end
+
+ let(:pipeline) { create(:ci_pipeline, project: fork_project) }
+
+ before do
+ allow(build).to receive(:merge_request).and_return(merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request, source_project: fork_project,
+ target_project: project,
+ source_branch: build.ref)
+ end
+
+ it 'contains the needed key value pairs' do
+ expect(subject).to include(:merge_request)
+ expect(subject).to include(:new_issue_path)
+ end
+
+ it 'exposes details of the merge request' do
+ expect(subject[:merge_request][:iid]).to eq merge_request.iid
+ end
+
+ it 'has a merge request path to a target project' do
+ expect(subject[:merge_request][:path])
+ .to include project.full_path
+ end
end
- context 'when the build has been erased' do
- let!(:build) { create(:ci_build, :erasable, project: project) }
+ context 'when the build has not been erased' do
+ let(:build) { create(:ci_build, :erasable, project: project) }
- it 'exposes the user whom erased the build' do
+ it 'exposes a build erase path' do
expect(subject).to include(:erase_path)
end
end
context 'when the build has been erased' do
- let!(:build) { create(:ci_build, erased_at: Time.now, project: project, erased_by: user) }
+ let(:build) { create(:ci_build, :erased, project: project) }
- it 'exposes the user whom erased the build' do
+ it 'exposes the user who erased the build' do
expect(subject).to include(:erased_by)
end
end
diff --git a/spec/serializers/deploy_key_entity_spec.rb b/spec/serializers/deploy_key_entity_spec.rb
index ed89fccc3d0..8149de869f1 100644
--- a/spec/serializers/deploy_key_entity_spec.rb
+++ b/spec/serializers/deploy_key_entity_spec.rb
@@ -2,13 +2,15 @@ require 'spec_helper'
describe DeployKeyEntity do
include RequestAwareEntity
-
+
let(:user) { create(:user) }
let(:project) { create(:empty_project, :internal)}
let(:project_private) { create(:empty_project, :private)}
+ let!(:project_pending_delete) { create(:empty_project, :internal, pending_delete: true) }
let(:deploy_key) { create(:deploy_key) }
let!(:deploy_key_internal) { create(:deploy_keys_project, project: project, deploy_key: deploy_key) }
let!(:deploy_key_private) { create(:deploy_keys_project, project: project_private, deploy_key: deploy_key) }
+ let!(:deploy_key_pending_delete) { create(:deploy_keys_project, project: project_pending_delete, deploy_key: deploy_key) }
let(:entity) { described_class.new(deploy_key, user: user) }
@@ -29,7 +31,7 @@ describe DeployKeyEntity do
{
id: project.id,
name: project.name,
- full_path: namespace_project_path(project.namespace, project),
+ full_path: project_path(project),
full_name: project.full_name
}
]
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 5ca7bf2fcaf..026360e91a3 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -7,7 +7,9 @@ describe JobEntity do
let(:request) { double('request') }
before do
+ stub_not_protect_default_branch
allow(request).to receive(:current_user).and_return(user)
+
project.add_developer(user)
end
@@ -77,7 +79,7 @@ describe JobEntity do
project.add_developer(user)
create(:protected_branch, :developers_can_merge,
- name: 'master', project: project)
+ name: job.ref, project: job.project)
end
it 'contains path to play action' do
@@ -90,6 +92,13 @@ describe JobEntity do
end
context 'when user is not allowed to trigger action' do
+ before do
+ allow(job.project).to receive(:empty_repo?).and_return(false)
+
+ create(:protected_branch, :no_one_can_push,
+ name: job.ref, project: job.project)
+ end
+
it 'does not contain path to play action' do
expect(subject).not_to include(:play_path)
end
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index d38433c2365..b3d58b2636f 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -47,7 +47,7 @@ describe MergeRequestEntity do
:cancel_merge_when_pipeline_succeeds_path,
:create_issue_to_resolve_discussions_path,
:source_branch_path, :target_branch_commits_path,
- :commits_count)
+ :target_branch_tree_path, :commits_count)
end
it 'has email_patches_path' do
diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb
index d28dec9592a..b990370a271 100644
--- a/spec/serializers/pipeline_details_entity_spec.rb
+++ b/spec/serializers/pipeline_details_entity_spec.rb
@@ -9,6 +9,8 @@ describe PipelineDetailsEntity do
end
before do
+ stub_not_protect_default_branch
+
allow(request).to receive(:current_user).and_return(user)
end
@@ -52,7 +54,7 @@ describe PipelineDetailsEntity do
context 'user has ability to retry pipeline' do
before do
- project.team << [user, :developer]
+ project.add_developer(user)
end
it 'retryable flag is true' do
@@ -97,7 +99,7 @@ describe PipelineDetailsEntity do
context 'when pipeline has commit statuses' do
let(:pipeline) { create(:ci_empty_pipeline) }
-
+
before do
create(:generic_commit_status, pipeline: pipeline)
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 46650f3a80d..5b01cc4fc9e 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -5,6 +5,8 @@ describe PipelineEntity do
let(:request) { double('request') }
before do
+ stub_not_protect_default_branch
+
allow(request).to receive(:current_user).and_return(user)
end
@@ -52,7 +54,7 @@ describe PipelineEntity do
context 'user has ability to retry pipeline' do
before do
- project.team << [user, :developer]
+ project.add_developer(user)
end
it 'contains retry path' do
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 44813656aff..262bc4acb69 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -108,14 +108,35 @@ describe PipelineSerializer do
end
end
- it 'verifies number of queries', :request_store do
- recorded = ActiveRecord::QueryRecorder.new { subject }
- expect(recorded.count).to be_within(1).of(57)
- expect(recorded.cached_count).to eq(0)
+ shared_examples 'no N+1 queries' do
+ it 'verifies number of queries', :request_store do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+ expect(recorded.count).to be_within(1).of(59)
+ expect(recorded.cached_count).to eq(0)
+ end
+ end
+
+ context 'with the same ref' do
+ let(:ref) { 'feature' }
+
+ it_behaves_like 'no N+1 queries'
+ end
+
+ context 'with different refs' do
+ def ref
+ @sequence ||= 0
+ @sequence += 1
+ "feature-#{@sequence}"
+ end
+
+ it_behaves_like 'no N+1 queries'
end
def create_pipeline(status)
- create(:ci_empty_pipeline, project: project, status: status).tap do |pipeline|
+ create(:ci_empty_pipeline,
+ project: project,
+ status: status,
+ ref: ref).tap do |pipeline|
Ci::Build::AVAILABLE_STATUSES.each do |status|
create_build(pipeline, status, status)
end
@@ -125,7 +146,7 @@ describe PipelineSerializer do
def create_build(pipeline, stage, status)
create(:ci_build, :tags, :triggered, :artifacts,
pipeline: pipeline, stage: stage,
- name: stage, status: status)
+ name: stage, status: status, ref: pipeline.ref)
end
end
end
diff --git a/spec/services/access_token_validation_service_spec.rb b/spec/services/access_token_validation_service_spec.rb
index 87f093ee8ce..38a3f522504 100644
--- a/spec/services/access_token_validation_service_spec.rb
+++ b/spec/services/access_token_validation_service_spec.rb
@@ -1,41 +1,72 @@
require 'spec_helper'
-describe AccessTokenValidationService, services: true do
+describe AccessTokenValidationService do
describe ".include_any_scope?" do
+ let(:request) { double("request") }
+
it "returns true if the required scope is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:api]
- expect(described_class.new(token).include_any_scope?([:api])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if more than one of the required scopes is present in the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope])
+ scopes = [:api, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if the list of required scopes is an exact match for the token's scopes" do
token = double("token", scopes: [:api, :read_user, :other_scope])
+ scopes = [:api, :read_user, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns true if the list of required scopes contains all of the token's scopes, in addition to others" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:api, :read_user, :other_scope]
- expect(described_class.new(token).include_any_scope?([:api, :read_user, :other_scope])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it 'returns true if the list of required scopes is blank' do
token = double("token", scopes: [])
+ scopes = []
- expect(described_class.new(token).include_any_scope?([])).to be(true)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
end
it "returns false if there are no scopes in common between the required scopes and the token scopes" do
token = double("token", scopes: [:api, :read_user])
+ scopes = [:other_scope]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+ end
+
+ context "conditions" do
+ it "ignores any scopes whose `if` condition returns false" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { false })]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(false)
+ end
+
+ it "does not ignore scopes whose `if` condition is not set" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { false }), :read_user]
+
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+ end
+
+ it "does not ignore scopes whose `if` condition returns true" do
+ token = double("token", scopes: [:api, :read_user])
+ scopes = [API::Scope.new(:api, if: ->(_) { true }), API::Scope.new(:read_user, if: ->(_) { false })]
- expect(described_class.new(token).include_any_scope?([:other_scope])).to be(false)
+ expect(described_class.new(token, request: request).include_any_scope?(scopes)).to be(true)
+ end
end
end
end
diff --git a/spec/services/after_branch_delete_service_spec.rb b/spec/services/after_branch_delete_service_spec.rb
index 77ca17bc82c..bc9747d1413 100644
--- a/spec/services/after_branch_delete_service_spec.rb
+++ b/spec/services/after_branch_delete_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe AfterBranchDeleteService, services: true do
+describe AfterBranchDeleteService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 60cb7a9440f..66a8a93b168 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Auth::ContainerRegistryAuthenticationService, services: true do
+describe Auth::ContainerRegistryAuthenticationService do
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb
index effa4633d13..6e3227303fe 100644
--- a/spec/services/boards/create_service_spec.rb
+++ b/spec/services/boards/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::CreateService, services: true do
+describe Boards::CreateService do
describe '#execute' do
let(:project) { create(:empty_project) }
@@ -26,6 +26,8 @@ describe Boards::CreateService, services: true do
end
it 'does not create a new board' do
+ expect(service).to receive(:can_create_board?) { false }
+
expect { service.execute }.not_to change(project.boards, :count)
end
end
diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb
index 360ee398f77..23ad66e454b 100644
--- a/spec/services/boards/issues/create_service_spec.rb
+++ b/spec/services/boards/issues/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Issues::CreateService, services: true do
+describe Boards::Issues::CreateService do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index a66cc2cd6e9..2c293088097 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Issues::ListService, services: true do
+describe Boards::Issues::ListService do
describe '#execute' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 4ff7ac6bb2f..7dd1a601700 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Issues::MoveService, services: true do
+describe Boards::Issues::MoveService do
describe '#execute' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb
index dff33e4bcbb..a95b12eeaa3 100644
--- a/spec/services/boards/list_service_spec.rb
+++ b/spec/services/boards/list_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::ListService, services: true do
+describe Boards::ListService do
describe '#execute' do
let(:project) { create(:empty_project) }
diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb
index ebac38e68f1..86d3227a78d 100644
--- a/spec/services/boards/lists/create_service_spec.rb
+++ b/spec/services/boards/lists/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Lists::CreateService, services: true do
+describe Boards::Lists::CreateService do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb
index af2d7c784bb..ad6ae83286d 100644
--- a/spec/services/boards/lists/destroy_service_spec.rb
+++ b/spec/services/boards/lists/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Lists::DestroyService, services: true do
+describe Boards::Lists::DestroyService do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb
index ed0337662af..7dec9f7a4a5 100644
--- a/spec/services/boards/lists/generate_service_spec.rb
+++ b/spec/services/boards/lists/generate_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Lists::GenerateService, services: true do
+describe Boards::Lists::GenerateService do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 68140759600..c93788d4516 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Lists::ListService, services: true do
+describe Boards::Lists::ListService do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb
index 4b3bdd133f2..43e125a21df 100644
--- a/spec/services/boards/lists/move_service_spec.rb
+++ b/spec/services/boards/lists/move_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Boards::Lists::MoveService, services: true do
+describe Boards::Lists::MoveService do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:board) { create(:board, project: project) }
diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb
index d50bfb0492c..d88b2504133 100644
--- a/spec/services/chat_names/authorize_user_service_spec.rb
+++ b/spec/services/chat_names/authorize_user_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatNames::AuthorizeUserService, services: true do
+describe ChatNames::AuthorizeUserService do
describe '#execute' do
let(:service) { create(:service) }
diff --git a/spec/services/chat_names/find_user_service_spec.rb b/spec/services/chat_names/find_user_service_spec.rb
index 0dc96521fa8..79aaac3aeb6 100644
--- a/spec/services/chat_names/find_user_service_spec.rb
+++ b/spec/services/chat_names/find_user_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ChatNames::FindUserService, services: true do
+describe ChatNames::FindUserService do
describe '#execute' do
let(:service) { create(:service) }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 77c07b71c68..4ec495f612e 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1,21 +1,28 @@
require 'spec_helper'
-describe Ci::CreatePipelineService, :services do
+describe Ci::CreatePipelineService do
let(:project) { create(:project, :repository) }
let(:user) { create(:admin) }
+ let(:ref_name) { 'refs/heads/master' }
before do
stub_ci_pipeline_to_return_yaml_file
end
describe '#execute' do
- def execute_service(source: :push, after: project.commit.id, message: 'Message', ref: 'refs/heads/master')
+ def execute_service(
+ source: :push,
+ after: project.commit.id,
+ message: 'Message',
+ ref: ref_name,
+ trigger_request: nil)
params = { ref: ref,
before: '00000000',
after: after,
commits: [{ message: message }] }
- described_class.new(project, user, params).execute(source)
+ described_class.new(project, user, params).execute(
+ source, trigger_request: trigger_request)
end
context 'valid params' do
@@ -40,7 +47,7 @@ describe Ci::CreatePipelineService, :services do
it 'increments the prometheus counter' do
expect(Gitlab::Metrics).to receive(:counter)
- .with(:pipelines_created_count, "Pipelines created count")
+ .with(:pipelines_created_total, "Counter of pipelines created")
.and_call_original
pipeline
@@ -320,5 +327,223 @@ describe Ci::CreatePipelineService, :services do
end.not_to change { Environment.count }
end
end
+
+ context 'when builds with auto-retries are configured' do
+ before do
+ config = YAML.dump(rspec: { script: 'rspec', retry: 2 })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'correctly creates builds with auto-retry value configured' do
+ pipeline = execute_service
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.find_by(name: 'rspec').retries_max).to eq 2
+ end
+ end
+
+ shared_examples 'when ref is protected' do
+ let(:user) { create(:user) }
+
+ context 'when user is developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service).not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when user is master' do
+ before do
+ project.add_master(user)
+ end
+
+ it 'creates a pipeline' do
+ expect(execute_service).to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+
+ context 'when trigger belongs to no one' do
+ let(:user) {}
+ let(:trigger_request) { create(:ci_trigger_request) }
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when trigger belongs to a developer' do
+ let(:user) {}
+
+ let(:trigger_request) do
+ create(:ci_trigger_request).tap do |request|
+ user = create(:user)
+ project.add_developer(user)
+ request.trigger.update(owner: user)
+ end
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'when trigger belongs to a master' do
+ let(:user) {}
+
+ let(:trigger_request) do
+ create(:ci_trigger_request).tap do |request|
+ user = create(:user)
+ project.add_master(user)
+ request.trigger.update(owner: user)
+ end
+ end
+
+ it 'does not create a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+ end
+
+ context 'when ref is a protected branch' do
+ before do
+ create(:protected_branch, project: project, name: 'master')
+ end
+
+ it_behaves_like 'when ref is protected'
+ end
+
+ context 'when ref is a protected tag' do
+ let(:ref_name) { 'refs/tags/v1.0.0' }
+
+ before do
+ create(:protected_tag, project: project, name: '*')
+ end
+
+ it_behaves_like 'when ref is protected'
+ end
+
+ context 'when ref is not protected' do
+ context 'when trigger belongs to no one' do
+ let(:user) {}
+ let(:trigger_request) { create(:ci_trigger_request) }
+
+ it 'creates a pipeline' do
+ expect(execute_service(trigger_request: trigger_request))
+ .to be_persisted
+ expect(Ci::Pipeline.count).to eq(1)
+ end
+ end
+ end
+ end
+
+ describe '#allowed_to_create?' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:ref) { 'master' }
+
+ subject do
+ described_class.new(project, user, ref: ref)
+ .send(:allowed_to_create?, user)
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to merge' do
+ let!(:protected_branch) do
+ create(:protected_branch,
+ :developers_can_merge,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :developers_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when no one can create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :no_one_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ context 'when owner cannot create pipeline' do
+ it { is_expected.to be_falsey }
+ end
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index f2956262f4b..8295813a1ca 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -1,12 +1,15 @@
require 'spec_helper'
-describe Ci::CreateTriggerRequestService, services: true do
- let(:service) { described_class.new }
+describe Ci::CreateTriggerRequestService do
+ let(:service) { described_class }
let(:project) { create(:project, :repository) }
- let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+ let(:owner) { create(:user) }
before do
stub_ci_pipeline_to_return_yaml_file
+
+ project.add_developer(owner)
end
describe '#execute' do
@@ -14,29 +17,26 @@ describe Ci::CreateTriggerRequestService, services: true do
subject { service.execute(project, trigger, 'master') }
context 'without owner' do
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline).to be_trigger }
- it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'with owner' do
- let(:owner) { create(:user) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
-
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.trigger_request.builds.first).to be_kind_of(Ci::Build) }
+ it { expect(subject.trigger_request.builds.first.user).to eq(owner) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline).to be_trigger }
it { expect(subject.pipeline.user).to eq(owner) }
- it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
- it { expect(subject.builds.first.user).to eq(owner) }
end
end
context 'no commit for ref' do
subject { service.execute(project, trigger, 'other-branch') }
- it { expect(subject).to be_nil }
+ it { expect(subject.pipeline).not_to be_persisted }
end
context 'no builds created' do
@@ -46,7 +46,7 @@ describe Ci::CreateTriggerRequestService, services: true do
stub_ci_pipeline_yaml_file('script: { only: [develop], script: hello World }')
end
- it { expect(subject).to be_nil }
+ it { expect(subject.pipeline).not_to be_persisted }
end
end
end
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index ea211de1f82..1ced26ff98d 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::PlayBuildService, '#execute', :services do
+describe Ci::PlayBuildService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index efcaccc254e..3a702742681 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::ProcessPipelineService, '#execute', :services do
+describe Ci::ProcessPipelineService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
@@ -9,6 +9,8 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
@@ -463,6 +465,35 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
+ context 'when builds with auto-retries are configured' do
+ before do
+ create_build('build:1', stage_idx: 0, user: user, options: { retry: 2 })
+ create_build('test:1', stage_idx: 1, user: user, when: :on_failure)
+ create_build('test:2', stage_idx: 1, user: user, options: { retry: 1 })
+ end
+
+ it 'automatically retries builds in a valid order' do
+ expect(process_pipeline).to be_truthy
+
+ fail_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1]
+ expect(builds_statuses).to eq %w[failed pending]
+
+ succeed_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1 test:2]
+ expect(builds_statuses).to eq %w[failed success pending]
+
+ succeed_running_or_pending
+
+ expect(builds_names).to eq %w[build:1 build:1 test:2]
+ expect(builds_statuses).to eq %w[failed success success]
+
+ expect(pipeline.reload).to be_success
+ end
+ end
+
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb
index 62ba0b01339..23c0f715c3e 100644
--- a/spec/services/ci/register_job_service_spec.rb
+++ b/spec/services/ci/register_job_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
module Ci
- describe RegisterJobService, services: true do
+ describe RegisterJobService do
let!(:project) { FactoryGirl.create :empty_project, shared_runners_enabled: false }
let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
let!(:pending_build) { FactoryGirl.create :ci_build, pipeline: pipeline }
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index ef9927c5969..fd8a57d28f0 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::RetryBuildService, :services do
+describe Ci::RetryBuildService do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -85,6 +85,8 @@ describe Ci::RetryBuildService, :services do
context 'when user has ability to execute build' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
@@ -131,6 +133,8 @@ describe Ci::RetryBuildService, :services do
context 'when user has ability to execute build' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb
index 3e860203063..90b0c5a4dce 100644
--- a/spec/services/ci/retry_pipeline_service_spec.rb
+++ b/spec/services/ci/retry_pipeline_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::RetryPipelineService, '#execute', :services do
+describe Ci::RetryPipelineService, '#execute' do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -244,13 +244,9 @@ describe Ci::RetryPipelineService, '#execute', :services do
create_build('verify', :canceled, 1)
end
- it 'does not reprocess manual action' do
- service.execute(pipeline)
-
- expect(build('test')).to be_pending
- expect(build('deploy')).to be_failed
- expect(build('verify')).to be_created
- expect(pipeline.reload).to be_running
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
end
end
@@ -261,13 +257,9 @@ describe Ci::RetryPipelineService, '#execute', :services do
create_build('verify', :canceled, 2)
end
- it 'does not reprocess manual action' do
- service.execute(pipeline)
-
- expect(build('test')).to be_pending
- expect(build('deploy')).to be_failed
- expect(build('verify')).to be_created
- expect(pipeline.reload).to be_running
+ it 'raises an error' do
+ expect { service.execute(pipeline) }
+ .to raise_error Gitlab::Access::AccessDeniedError
end
end
end
diff --git a/spec/services/ci/stop_environments_service_spec.rb b/spec/services/ci/stop_environments_service_spec.rb
index 98044ad232e..e2a9ed27e87 100644
--- a/spec/services/ci/stop_environments_service_spec.rb
+++ b/spec/services/ci/stop_environments_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::StopEnvironmentsService, services: true do
+describe Ci::StopEnvironmentsService do
let(:project) { create(:project, :private, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index efefa8e8eca..0da0e57dbcd 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::UpdateBuildQueueService, :services do
+describe Ci::UpdateBuildQueueService do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
diff --git a/spec/services/ci/update_runner_service_spec.rb b/spec/services/ci/update_runner_service_spec.rb
index e429fcfc72f..7cc04c92d27 100644
--- a/spec/services/ci/update_runner_service_spec.rb
+++ b/spec/services/ci/update_runner_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::UpdateRunnerService, :services do
+describe Ci::UpdateRunnerService do
let(:runner) { create(:ci_runner) }
describe '#update' do
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index bea7c965233..9e15eae8c13 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CompareService, services: true do
+describe CompareService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:service) { described_class.new(project, 'feature') }
diff --git a/spec/services/create_branch_service_spec.rb b/spec/services/create_branch_service_spec.rb
index 3f548688c20..38096a080a7 100644
--- a/spec/services/create_branch_service_spec.rb
+++ b/spec/services/create_branch_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateBranchService, services: true do
+describe CreateBranchService do
let(:user) { create(:user) }
let(:service) { described_class.new(project, user) }
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index dfab6ebf372..049b082277a 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateDeploymentService, services: true do
+describe CreateDeploymentService do
let(:user) { create(:user) }
let(:options) { nil }
@@ -244,6 +244,8 @@ describe CreateDeploymentService, services: true do
context 'when job is retried' do
it_behaves_like 'creates deployment' do
before do
+ stub_not_protect_default_branch
+
project.add_developer(user)
end
diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb
index 271ccfe7968..ac0a0458f56 100644
--- a/spec/services/create_release_service_spec.rb
+++ b/spec/services/create_release_service_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe CreateReleaseService, services: true do
+describe CreateReleaseService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
- let(:service) { CreateReleaseService.new(project, user) }
+ let(:service) { described_class.new(project, user) }
it 'creates a new release' do
result = service.execute(tag_name, description)
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index d81d0fd76c9..b6ab6b8271c 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateSnippetService, services: true do
+describe CreateSnippetService do
before do
@user = create :user
@admin = create :user, admin: true
diff --git a/spec/services/delete_branch_service_spec.rb b/spec/services/delete_branch_service_spec.rb
index c4685c9aa31..19855c9bee2 100644
--- a/spec/services/delete_branch_service_spec.rb
+++ b/spec/services/delete_branch_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeleteBranchService, services: true do
+describe DeleteBranchService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/delete_merged_branches_service_spec.rb b/spec/services/delete_merged_branches_service_spec.rb
index fe21ca0b3cb..954c5d3ab73 100644
--- a/spec/services/delete_merged_branches_service_spec.rb
+++ b/spec/services/delete_merged_branches_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe DeleteMergedBranchesService, services: true do
+describe DeleteMergedBranchesService do
subject(:service) { described_class.new(project, project.owner) }
let(:project) { create(:project, :repository) }
diff --git a/spec/services/discussions/update_diff_position_service_spec.rb b/spec/services/discussions/update_diff_position_service_spec.rb
index 177e32e13bd..c239494298b 100644
--- a/spec/services/discussions/update_diff_position_service_spec.rb
+++ b/spec/services/discussions/update_diff_position_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Discussions::UpdateDiffPositionService, services: true do
+describe Discussions::UpdateDiffPositionService do
let(:project) { create(:project, :repository) }
let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index c1f477f551e..641d5538de8 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Emails::CreateService, services: true do
+describe Emails::CreateService do
let(:user) { create(:user) }
let(:opts) { { email: 'new@email.com' } }
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index 5e7ab4a40af..1f4294dd905 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Emails::DestroyService, services: true do
+describe Emails::DestroyService do
let!(:user) { create(:user) }
let!(:email) { create(:email, user: user) }
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index b06cefe071d..00104ae1fd9 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe EventCreateService, services: true do
+describe EventCreateService do
include UserActivitiesHelpers
- let(:service) { EventCreateService.new }
+ let(:service) { described_class.new }
describe 'Issues' do
describe '#open_issue' do
@@ -113,7 +113,7 @@ describe EventCreateService, services: true do
end
end
- describe '#push', :redis do
+ describe '#push', :clean_gitlab_redis_shared_state do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index 213678c27f5..3ce01a995b4 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe GitHooksService, services: true do
+describe GitHooksService do
include RepoHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
- let(:service) { GitHooksService.new }
+ let(:service) { described_class.new }
before do
@blankrev = Gitlab::Git::BLANK_SHA
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 8e8816870e1..75329e9dda2 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GitPushService, services: true do
+describe GitPushService do
include RepoHelpers
let(:user) { create(:user) }
@@ -108,7 +108,7 @@ describe GitPushService, services: true do
it { is_expected.to include(id: @commit.id) }
it { is_expected.to include(message: @commit.safe_message) }
- it { is_expected.to include(timestamp: @commit.date.xmlschema) }
+ it { expect(subject[:timestamp].in_time_zone).to eq(@commit.date.in_time_zone) }
it do
is_expected.to include(
url: [
@@ -163,7 +163,7 @@ describe GitPushService, services: true do
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
end
-
+
context "Sends System Push data" do
it "when pushing on a branch" do
expect(SystemHookPushWorker).to receive(:perform_async).with(@push_data, :push_hooks)
@@ -488,21 +488,57 @@ describe GitPushService, services: true do
end
end
- context "using wrong markdown" do
- let(:message) { "this is some work.\n\ncloses #1" }
+ context "using internal issue reference" do
+ context 'when internal issues are disabled' do
+ before do
+ project.issues_enabled = false
+ project.save!
+ end
+ let(:message) { "this is some work.\n\ncloses #1" }
- it "does not initiates one api call to jira server to close the issue" do
- execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ it "does not initiates one api call to jira server to close the issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
+ end
- expect(WebMock).not_to have_requested(:post, jira_api_transition_url('JIRA-1'))
+ it "does not initiates one api call to jira server to comment on the issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
end
- it "does not initiates one api call to jira server to comment on the issue" do
- execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ context 'when internal issues are enabled' do
+ let(:issue) { create(:issue, project: project) }
+ let(:message) { "this is some work.\n\ncloses JIRA-1 \n\n closes #{issue.to_reference}" }
- expect(WebMock).not_to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
- body: comment_body
- ).once
+ it "initiates one api call to jira server to close the jira issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).to have_requested(:post, jira_api_transition_url('JIRA-1')).once
+ end
+
+ it "initiates one api call to jira server to comment on the jira issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url('JIRA-1')).with(
+ body: comment_body
+ ).once
+ end
+
+ it "closes the internal issue" do
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ expect(issue.reload).to be_closed
+ end
+
+ it "adds a note indicating that the issue is now closed" do
+ expect(SystemNoteService).to receive(:change_status)
+ .with(issue, project, commit_author, "closed", closing_commit)
+ execute_service(project, commit_author, @oldrev, @newrev, @ref )
+ end
end
end
end
@@ -527,14 +563,18 @@ describe GitPushService, services: true do
let(:housekeeping) { Projects::HousekeepingService.new(project) }
before do
- # Flush any raw Redis data stored by the housekeeping code.
- Gitlab::Redis.with { |conn| conn.flushall }
+ # Flush any raw key-value data stored by the housekeeping code.
+ Gitlab::Redis::Cache.with { |conn| conn.flushall }
+ Gitlab::Redis::Queues.with { |conn| conn.flushall }
+ Gitlab::Redis::SharedState.with { |conn| conn.flushall }
allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping)
end
after do
- Gitlab::Redis.with { |conn| conn.flushall }
+ Gitlab::Redis::Cache.with { |conn| conn.flushall }
+ Gitlab::Redis::Queues.with { |conn| conn.flushall }
+ Gitlab::Redis::SharedState.with { |conn| conn.flushall }
end
it 'does not perform housekeeping when not needed' do
@@ -641,6 +681,24 @@ describe GitPushService, services: true do
end
end
+ describe '#update_signatures' do
+ let(:service) do
+ described_class.new(
+ project,
+ user,
+ oldrev: sample_commit.parent_id,
+ newrev: sample_commit.id,
+ ref: 'refs/heads/master'
+ )
+ end
+
+ it 'calls CreateGpgSignatureWorker.perform_async for each commit' do
+ expect(CreateGpgSignatureWorker).to receive(:perform_async).with(sample_commit.id, project.id)
+
+ execute_service(project, user, @oldrev, @newrev, @ref)
+ end
+ end
+
def execute_service(project, user, oldrev, newrev, ref)
service = described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref )
service.execute
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index 1fdcb420a8b..f877c145390 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
-describe GitTagPushService, services: true do
+describe GitTagPushService do
include RepoHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
- let(:service) { GitTagPushService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+ let(:service) { described_class.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
let(:oldrev) { Gitlab::Git::BLANK_SHA }
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
@@ -184,7 +184,7 @@ describe GitTagPushService, services: true do
describe "Webhooks" do
context "execute webhooks" do
- let(:service) { GitTagPushService.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
+ let(:service) { described_class.new(project, user, oldrev: 'oldrev', newrev: 'newrev', ref: 'refs/tags/v1.0.0') }
it "when pushing tags" do
expect(project).to receive(:execute_hooks)
diff --git a/spec/services/gravatar_service_spec.rb b/spec/services/gravatar_service_spec.rb
index 8c4ad8c7a3e..d2cc53fe0ee 100644
--- a/spec/services/gravatar_service_spec.rb
+++ b/spec/services/gravatar_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GravatarService, service: true do
+describe GravatarService do
describe '#execute' do
let(:url) { 'http://example.com/avatar?hash=%{hash}&size=%{size}&email=%{email}&username=%{username}' }
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index fbd9026640c..b2175717a70 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Groups::CreateService, '#execute', services: true do
+describe Groups::CreateService, '#execute' do
let!(:user) { create(:user) }
let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } }
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index d59b37bee36..3e7aa8c835c 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Groups::DestroyService, services: true do
+describe Groups::DestroyService do
include DatabaseConnectionHelpers
let!(:user) { create(:user) }
@@ -100,7 +100,7 @@ describe Groups::DestroyService, services: true do
# Kick off the initial group destroy in a new thread, so that
# it doesn't share this spec's database transaction.
- Thread.new { Groups::DestroyService.new(group, user).async_execute }.join(5)
+ Thread.new { described_class.new(group, user).async_execute }.join(5)
group_record = run_with_new_database_connection do |conn|
conn.execute("SELECT * FROM namespaces WHERE id = #{group.id}").first
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index f6ad5cebd2c..9902e1aff8b 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Groups::UpdateService, services: true do
+describe Groups::UpdateService do
let!(:user) { create(:user) }
let!(:private_group) { create(:group, :private) }
let!(:internal_group) { create(:group, :internal) }
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
index 81b1d327696..1875d0448cd 100644
--- a/spec/services/import_export_clean_up_service_spec.rb
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ImportExportCleanUpService, services: true do
+describe ImportExportCleanUpService do
describe '#execute' do
let(:service) { described_class.new }
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index eb9b1670c71..055aa6156cb 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issuable::BulkUpdateService, services: true do
+describe Issuable::BulkUpdateService do
let(:user) { create(:user) }
let(:project) { create(:empty_project, namespace: user.namespace) }
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb
index bed25fe7ccf..03f76bd428d 100644
--- a/spec/services/issues/build_service_spec.rb
+++ b/spec/services/issues/build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper.rb'
-describe Issues::BuildService, services: true do
+describe Issues::BuildService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index d6f4c694069..a03f68434de 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issues::CloseService, services: true do
+describe Issues::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
@@ -98,13 +98,13 @@ describe Issues::CloseService, services: true do
end
end
- context 'external issue tracker' do
+ context 'internal issues disabled' do
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
- described_class.new(project, user).close_issue(issue)
+ project.issues_enabled = false
+ project.save!
end
- it 'closes the issue' do
+ it 'does not close the issue' do
expect(issue).to be_valid
expect(issue).to be_opened
expect(todo.reload).to be_pending
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index ae9d2b2855d..a48748e274d 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issues::CreateService, services: true do
+describe Issues::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/services/issues/duplicate_service_spec.rb b/spec/services/issues/duplicate_service_spec.rb
new file mode 100644
index 00000000000..ed55664e382
--- /dev/null
+++ b/spec/services/issues/duplicate_service_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Issues::DuplicateService do
+ let(:user) { create(:user) }
+ let(:canonical_project) { create(:empty_project) }
+ let(:duplicate_project) { create(:empty_project) }
+
+ let(:canonical_issue) { create(:issue, project: canonical_project) }
+ let(:duplicate_issue) { create(:issue, project: duplicate_project) }
+
+ subject { described_class.new(duplicate_project, user, {}) }
+
+ describe '#execute' do
+ context 'when the issues passed are the same' do
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, duplicate_issue)
+ end
+ end
+
+ context 'when the user cannot update the duplicate issue' do
+ before do
+ canonical_project.add_reporter(user)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+
+ context 'when the user cannot comment on the canonical issue' do
+ before do
+ duplicate_project.add_reporter(user)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:close_service)
+ expect(SystemNoteService).not_to receive(:mark_duplicate_issue)
+ expect(SystemNoteService).not_to receive(:mark_canonical_issue_of_duplicate)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+
+ context 'when the user can mark the issue as a duplicate' do
+ before do
+ canonical_project.add_reporter(user)
+ duplicate_project.add_reporter(user)
+ end
+
+ it 'closes the duplicate issue' do
+ subject.execute(duplicate_issue, canonical_issue)
+
+ expect(duplicate_issue.reload).to be_closed
+ expect(canonical_issue.reload).to be_open
+ end
+
+ it 'adds a system note to the duplicate issue' do
+ expect(SystemNoteService)
+ .to receive(:mark_duplicate_issue).with(duplicate_issue, duplicate_project, user, canonical_issue)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+
+ it 'adds a system note to the canonical issue' do
+ expect(SystemNoteService)
+ .to receive(:mark_canonical_issue_of_duplicate).with(canonical_issue, canonical_project, user, duplicate_issue)
+
+ subject.execute(duplicate_issue, canonical_issue)
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index d1dd1466d95..171fc7334c4 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issues::MoveService, services: true do
+describe Issues::MoveService do
let(:user) { create(:user) }
let(:author) { create(:user) }
let(:title) { 'Some issue' }
@@ -37,9 +37,6 @@ describe Issues::MoveService, services: true do
describe '#execute' do
shared_context 'issue move executed' do
- let!(:milestone2) do
- create(:milestone, project_id: new_project.id, title: 'v9.0')
- end
let!(:award_emoji) { create(:award_emoji, awardable: old_issue) }
let!(:new_issue) { move_service.execute(old_issue, new_project) }
@@ -48,6 +45,63 @@ describe Issues::MoveService, services: true do
context 'issue movable' do
include_context 'user can move issue'
+ context 'move to new milestone' do
+ let(:new_issue) { move_service.execute(old_issue, new_project) }
+
+ context 'project milestone' do
+ let!(:milestone2) do
+ create(:milestone, project_id: new_project.id, title: 'v9.0')
+ end
+
+ it 'assigns milestone to new issue' do
+ expect(new_issue.reload.milestone.title).to eq 'v9.0'
+ expect(new_issue.reload.milestone).to eq(milestone2)
+ end
+ end
+
+ context 'group milestones' do
+ let!(:group) { create(:group, :private) }
+ let!(:group_milestone_1) do
+ create(:milestone, group_id: group.id, title: 'v9.0_group')
+ end
+
+ before do
+ old_issue.update(milestone: group_milestone_1)
+ old_project.update(namespace: group)
+ new_project.update(namespace: group)
+
+ group.add_users([user], GroupMember::DEVELOPER)
+ end
+
+ context 'when moving to a project of the same group' do
+ it 'keeps the same group milestone' do
+ expect(new_issue.reload.project).to eq(new_project)
+ expect(new_issue.reload.milestone).to eq(group_milestone_1)
+ end
+ end
+
+ context 'when moving to a project of a different group' do
+ let!(:group_2) { create(:group, :private) }
+
+ let!(:group_milestone_2) do
+ create(:milestone, group_id: group_2.id, title: 'v9.0_group')
+ end
+
+ before do
+ old_issue.update(milestone: group_milestone_1)
+ new_project.update(namespace: group_2)
+
+ group_2.add_users([user], GroupMember::DEVELOPER)
+ end
+
+ it 'assigns to new group milestone of same title' do
+ expect(new_issue.reload.project).to eq(new_project)
+ expect(new_issue.reload.milestone).to eq(group_milestone_2)
+ end
+ end
+ end
+ end
+
context 'generic issue' do
include_context 'issue move executed'
@@ -55,11 +109,6 @@ describe Issues::MoveService, services: true do
expect(new_issue.project).to eq new_project
end
- it 'assigns milestone to new issue' do
- expect(new_issue.reload.milestone.title).to eq 'v9.0'
- expect(new_issue.reload.milestone).to eq(milestone2)
- end
-
it 'assign labels to new issue' do
expected_label_titles = new_issue.reload.labels.map(&:title)
expect(expected_label_titles).to include 'label1'
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 391ecad303a..1ff988e9b47 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Issues::ReopenService, services: true do
+describe Issues::ReopenService do
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, :closed, project: project) }
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index 86f218dec12..fac66791ffb 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper.rb'
-describe Issues::ResolveDiscussions, services: true do
+describe Issues::ResolveDiscussions do
class DummyService < Issues::BaseService
include ::Issues::ResolveDiscussions
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index c26642f5015..eec2096fa34 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -1,7 +1,7 @@
# coding: utf-8
require 'spec_helper'
-describe Issues::UpdateService, services: true do
+describe Issues::UpdateService do
include EmailHelpers
let(:user) { create(:user) }
@@ -253,13 +253,13 @@ describe Issues::UpdateService, services: true do
end
context 'when the milestone change' do
- before do
+ it 'marks todos as done' do
update_issue(milestone: create(:milestone))
- end
- it 'marks todos as done' do
expect(todo.reload.done?).to eq true
end
+
+ it_behaves_like 'system notes for milestones'
end
context 'when the labels change' do
@@ -488,7 +488,28 @@ describe Issues::UpdateService, services: true do
context 'updating mentions' do
let(:mentionable) { issue }
- include_examples 'updating mentions', Issues::UpdateService
+ include_examples 'updating mentions', described_class
+ end
+
+ context 'duplicate issue' do
+ let(:canonical_issue) { create(:issue, project: project) }
+
+ context 'invalid canonical_issue_id' do
+ it 'does not call the duplicate service' do
+ expect(Issues::DuplicateService).not_to receive(:new)
+
+ update_issue(canonical_issue_id: 123456789)
+ end
+ end
+
+ context 'valid canonical_issue_id' do
+ it 'calls the duplicate service with both issues' do
+ expect_any_instance_of(Issues::DuplicateService)
+ .to receive(:execute).with(issue, canonical_issue)
+
+ update_issue(canonical_issue_id: canonical_issue.id)
+ end
+ end
end
include_examples 'issuable update service' do
diff --git a/spec/services/labels/create_service_spec.rb b/spec/services/labels/create_service_spec.rb
index 0ecab0c3475..ecb88653001 100644
--- a/spec/services/labels/create_service_spec.rb
+++ b/spec/services/labels/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Labels::CreateService, services: true do
+describe Labels::CreateService do
describe '#execute' do
let(:project) { create(:project) }
let(:group) { create(:group) }
@@ -17,7 +17,7 @@ describe Labels::CreateService, services: true do
context 'in a project' do
context 'with color in hex-code' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(hex_color)).execute(project: project)
+ label = described_class.new(params_with(hex_color)).execute(project: project)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -26,7 +26,7 @@ describe Labels::CreateService, services: true do
context 'with color in allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(named_color)).execute(project: project)
+ label = described_class.new(params_with(named_color)).execute(project: project)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -35,7 +35,7 @@ describe Labels::CreateService, services: true do
context 'with color in up-case allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(upcase_color)).execute(project: project)
+ label = described_class.new(params_with(upcase_color)).execute(project: project)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -44,7 +44,7 @@ describe Labels::CreateService, services: true do
context 'with color surrounded by spaces' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(spaced_color)).execute(project: project)
+ label = described_class.new(params_with(spaced_color)).execute(project: project)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -53,7 +53,7 @@ describe Labels::CreateService, services: true do
context 'with unknown color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(unknown_color)).execute(project: project)
+ label = described_class.new(params_with(unknown_color)).execute(project: project)
expect(label).not_to be_persisted
end
@@ -61,7 +61,7 @@ describe Labels::CreateService, services: true do
context 'with no color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(no_color)).execute(project: project)
+ label = described_class.new(params_with(no_color)).execute(project: project)
expect(label).not_to be_persisted
end
@@ -71,7 +71,7 @@ describe Labels::CreateService, services: true do
context 'in a group' do
context 'with color in hex-code' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(hex_color)).execute(group: group)
+ label = described_class.new(params_with(hex_color)).execute(group: group)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -80,7 +80,7 @@ describe Labels::CreateService, services: true do
context 'with color in allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(named_color)).execute(group: group)
+ label = described_class.new(params_with(named_color)).execute(group: group)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -89,7 +89,7 @@ describe Labels::CreateService, services: true do
context 'with color in up-case allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(upcase_color)).execute(group: group)
+ label = described_class.new(params_with(upcase_color)).execute(group: group)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -98,7 +98,7 @@ describe Labels::CreateService, services: true do
context 'with color surrounded by spaces' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(spaced_color)).execute(group: group)
+ label = described_class.new(params_with(spaced_color)).execute(group: group)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -107,7 +107,7 @@ describe Labels::CreateService, services: true do
context 'with unknown color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(unknown_color)).execute(group: group)
+ label = described_class.new(params_with(unknown_color)).execute(group: group)
expect(label).not_to be_persisted
end
@@ -115,7 +115,7 @@ describe Labels::CreateService, services: true do
context 'with no color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(no_color)).execute(group: group)
+ label = described_class.new(params_with(no_color)).execute(group: group)
expect(label).not_to be_persisted
end
@@ -125,7 +125,7 @@ describe Labels::CreateService, services: true do
context 'in admin area' do
context 'with color in hex-code' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(hex_color)).execute(template: true)
+ label = described_class.new(params_with(hex_color)).execute(template: true)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -134,7 +134,7 @@ describe Labels::CreateService, services: true do
context 'with color in allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(named_color)).execute(template: true)
+ label = described_class.new(params_with(named_color)).execute(template: true)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -143,7 +143,7 @@ describe Labels::CreateService, services: true do
context 'with color in up-case allowed name' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(upcase_color)).execute(template: true)
+ label = described_class.new(params_with(upcase_color)).execute(template: true)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -152,7 +152,7 @@ describe Labels::CreateService, services: true do
context 'with color surrounded by spaces' do
it 'creates a label' do
- label = Labels::CreateService.new(params_with(spaced_color)).execute(template: true)
+ label = described_class.new(params_with(spaced_color)).execute(template: true)
expect(label).to be_persisted
expect(label.color).to eq expected_saved_color
@@ -161,7 +161,7 @@ describe Labels::CreateService, services: true do
context 'with unknown color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(unknown_color)).execute(template: true)
+ label = described_class.new(params_with(unknown_color)).execute(template: true)
expect(label).not_to be_persisted
end
@@ -169,7 +169,7 @@ describe Labels::CreateService, services: true do
context 'with no color' do
it 'doesn\'t create a label' do
- label = Labels::CreateService.new(params_with(no_color)).execute(template: true)
+ label = described_class.new(params_with(no_color)).execute(template: true)
expect(label).not_to be_persisted
end
diff --git a/spec/services/labels/find_or_create_service_spec.rb b/spec/services/labels/find_or_create_service_spec.rb
index de8f1827cce..2d3a15a1c11 100644
--- a/spec/services/labels/find_or_create_service_spec.rb
+++ b/spec/services/labels/find_or_create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Labels::FindOrCreateService, services: true do
+describe Labels::FindOrCreateService do
describe '#execute' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, namespace: group) }
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
index 500afdfb916..7cea877ad88 100644
--- a/spec/services/labels/promote_service_spec.rb
+++ b/spec/services/labels/promote_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Labels::PromoteService, services: true do
+describe Labels::PromoteService do
describe '#execute' do
let!(:user) { create(:user) }
diff --git a/spec/services/labels/transfer_service_spec.rb b/spec/services/labels/transfer_service_spec.rb
index 11d5f1cad5e..f70edd3d16e 100644
--- a/spec/services/labels/transfer_service_spec.rb
+++ b/spec/services/labels/transfer_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Labels::TransferService, services: true do
+describe Labels::TransferService do
describe '#execute' do
let(:user) { create(:admin) }
let(:group_1) { create(:group) }
diff --git a/spec/services/labels/update_service_spec.rb b/spec/services/labels/update_service_spec.rb
index f2498ea6990..bb95fe20fbf 100644
--- a/spec/services/labels/update_service_spec.rb
+++ b/spec/services/labels/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Labels::UpdateService, services: true do
+describe Labels::UpdateService do
describe '#execute' do
let(:project) { create(:project) }
@@ -20,7 +20,7 @@ describe Labels::UpdateService, services: true do
context 'with color in hex-code' do
it 'updates the label' do
- label = Labels::UpdateService.new(params_with(hex_color)).execute(@label)
+ label = described_class.new(params_with(hex_color)).execute(@label)
expect(label).to be_valid
expect(label.reload.color).to eq expected_saved_color
@@ -29,7 +29,7 @@ describe Labels::UpdateService, services: true do
context 'with color in allowed name' do
it 'updates the label' do
- label = Labels::UpdateService.new(params_with(named_color)).execute(@label)
+ label = described_class.new(params_with(named_color)).execute(@label)
expect(label).to be_valid
expect(label.reload.color).to eq expected_saved_color
@@ -38,7 +38,7 @@ describe Labels::UpdateService, services: true do
context 'with color in up-case allowed name' do
it 'updates the label' do
- label = Labels::UpdateService.new(params_with(upcase_color)).execute(@label)
+ label = described_class.new(params_with(upcase_color)).execute(@label)
expect(label).to be_valid
expect(label.reload.color).to eq expected_saved_color
@@ -47,7 +47,7 @@ describe Labels::UpdateService, services: true do
context 'with color surrounded by spaces' do
it 'updates the label' do
- label = Labels::UpdateService.new(params_with(spaced_color)).execute(@label)
+ label = described_class.new(params_with(spaced_color)).execute(@label)
expect(label).to be_valid
expect(label.reload.color).to eq expected_saved_color
@@ -56,7 +56,7 @@ describe Labels::UpdateService, services: true do
context 'with unknown color' do
it 'doesn\'t update the label' do
- label = Labels::UpdateService.new(params_with(unknown_color)).execute(@label)
+ label = described_class.new(params_with(unknown_color)).execute(@label)
expect(label).not_to be_valid
end
@@ -64,7 +64,7 @@ describe Labels::UpdateService, services: true do
context 'with no color' do
it 'doesn\'t update the label' do
- label = Labels::UpdateService.new(params_with(no_color)).execute(@label)
+ label = described_class.new(params_with(no_color)).execute(@label)
expect(label).not_to be_valid
end
diff --git a/spec/services/members/approve_access_request_service_spec.rb b/spec/services/members/approve_access_request_service_spec.rb
index 7d5a66801db..ddba96b8d03 100644
--- a/spec/services/members/approve_access_request_service_spec.rb
+++ b/spec/services/members/approve_access_request_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Members::ApproveAccessRequestService, services: true do
+describe Members::ApproveAccessRequestService do
let(:user) { create(:user) }
let(:access_requester) { create(:user) }
let(:project) { create(:empty_project, :public, :access_requestable) }
diff --git a/spec/services/members/authorized_destroy_service_spec.rb b/spec/services/members/authorized_destroy_service_spec.rb
index f99b11f208c..f0abbc46ca3 100644
--- a/spec/services/members/authorized_destroy_service_spec.rb
+++ b/spec/services/members/authorized_destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Members::AuthorizedDestroyService, services: true do
+describe Members::AuthorizedDestroyService do
let(:member_user) { create(:user) }
let(:project) { create(:empty_project, :public) }
let(:group) { create(:group, :public) }
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 5a05ab3ea50..c73a229823d 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Members::CreateService, services: true do
+describe Members::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:project_user) { create(:user) }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 9ab7839430c..0e30b343eaf 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Members::DestroyService, services: true do
+describe Members::DestroyService do
let(:user) { create(:user) }
let(:member_user) { create(:user) }
let(:project) { create(:empty_project, :public) }
diff --git a/spec/services/members/request_access_service_spec.rb b/spec/services/members/request_access_service_spec.rb
index 814c17b9ad0..f39d4f47904 100644
--- a/spec/services/members/request_access_service_spec.rb
+++ b/spec/services/members/request_access_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Members::RequestAccessService, services: true do
+describe Members::RequestAccessService do
let(:user) { create(:user) }
shared_examples 'a service raising Gitlab::Access::AccessDeniedError' do
diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb
index d3556020d4d..fcbe0e5985f 100644
--- a/spec/services/merge_requests/assign_issues_service_spec.rb
+++ b/spec/services/merge_requests/assign_issues_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::AssignIssuesService, services: true do
+describe MergeRequests::AssignIssuesService do
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 01ef52396d7..ea192e51f89 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::BuildService, services: true do
+describe MergeRequests::BuildService do
include RepoHelpers
let(:project) { create(:project, :repository) }
@@ -19,7 +19,7 @@ describe MergeRequests::BuildService, services: true do
let(:commits) { nil }
let(:service) do
- MergeRequests::BuildService.new(project, user,
+ described_class.new(project, user,
description: description,
source_branch: source_branch,
target_branch: target_branch,
@@ -207,7 +207,7 @@ describe MergeRequests::BuildService, services: true do
let(:source_branch) { '12345-fix-issue' }
before do
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ allow(project).to receive(:external_issue_tracker).and_return(true)
end
it 'sets the title to: Resolves External Issue $issue-iid' do
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 074d4672b06..04bf267d167 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::CloseService, services: true do
+describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 1588d30c394..492b55cdece 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::CreateFromIssueService, services: true do
+describe MergeRequests::CreateFromIssueService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 36a2b672473..8fef480274d 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::CreateService, services: true do
+describe MergeRequests::CreateService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:assignee) { create(:user) }
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 711059208c1..e593bfeeaf7 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe MergeRequests::MergeService, services: true do
+describe MergeRequests::MergeService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
- let(:merge_request) { create(:merge_request, assignee: user2) }
+ let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
let(:project) { merge_request.project }
before do
@@ -13,7 +13,7 @@ describe MergeRequests::MergeService, services: true do
describe '#execute' do
context 'valid params' do
- let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
+ let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
before do
allow(service).to receive(:execute_hooks)
@@ -112,7 +112,7 @@ describe MergeRequests::MergeService, services: true do
context 'closes related todos' do
let(:merge_request) { create(:merge_request, assignee: user, author: user) }
let(:project) { merge_request.project }
- let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
+ let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
let!(:todo) do
create(:todo, :assigned,
project: project,
@@ -133,23 +133,70 @@ describe MergeRequests::MergeService, services: true do
it { expect(todo).to be_done }
end
- context 'remove source branch by author' do
- let(:service) do
- merge_request.merge_params['force_remove_source_branch'] = '1'
- merge_request.save!
- MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
+ context 'source branch removal' do
+ context 'when the source branch is protected' do
+ let(:service) do
+ described_class.new(project, user, should_remove_source_branch: '1')
+ end
+
+ before do
+ create(:protected_branch, project: project, name: merge_request.source_branch)
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+ service.execute(merge_request)
+ end
end
- it 'removes the source branch' do
- expect(DeleteBranchService).to receive(:new)
- .with(merge_request.source_project, merge_request.author)
- .and_call_original
- service.execute(merge_request)
+ context 'when the source branch is the default branch' do
+ let(:service) do
+ described_class.new(project, user, should_remove_source_branch: '1')
+ end
+
+ before do
+ allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
+ end
+
+ it 'does not delete the source branch' do
+ expect(DeleteBranchService).not_to receive(:new)
+ service.execute(merge_request)
+ end
+ end
+
+ context 'when the source branch can be removed' do
+ context 'when MR author set the source branch to be removed' do
+ let(:service) do
+ merge_request.merge_params['force_remove_source_branch'] = '1'
+ merge_request.save!
+ described_class.new(project, user, commit_message: 'Awesome message')
+ end
+
+ it 'removes the source branch using the author user' do
+ expect(DeleteBranchService).to receive(:new)
+ .with(merge_request.source_project, merge_request.author)
+ .and_call_original
+ service.execute(merge_request)
+ end
+ end
+
+ context 'when MR merger set the source branch to be removed' do
+ let(:service) do
+ described_class.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1')
+ end
+
+ it 'removes the source branch using the current user' do
+ expect(DeleteBranchService).to receive(:new)
+ .with(merge_request.source_project, user)
+ .and_call_original
+ service.execute(merge_request)
+ end
+ end
end
end
context "error handling" do
- let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
+ let(:service) { described_class.new(project, user, commit_message: 'Awesome message') }
before do
allow(Rails.logger).to receive(:error)
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index a20b32eda5f..a37cdab8928 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::PostMergeService, services: true do
+describe MergeRequests::PostMergeService do
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user) }
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 671a932441e..2af2485eeed 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe MergeRequests::RefreshService, services: true do
+describe MergeRequests::RefreshService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:service) { MergeRequests::RefreshService }
+ let(:service) { described_class }
describe '#execute' do
before do
@@ -98,18 +98,52 @@ describe MergeRequests::RefreshService, services: true do
end
context 'push to origin repo target branch' do
- before do
- service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
- reload_mrs
+ context 'when all MRs to the target branch had diffs' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it 'updates the merge state' do
+ expect(@merge_request.notes.last.note).to include('merged')
+ expect(@merge_request).to be_merged
+ expect(@fork_merge_request).to be_merged
+ expect(@fork_merge_request.notes.last.note).to include('merged')
+ expect(@build_failed_todo).to be_done
+ expect(@fork_build_failed_todo).to be_done
+ end
end
- it 'updates the merge state' do
- expect(@merge_request.notes.last.note).to include('merged')
- expect(@merge_request).to be_merged
- expect(@fork_merge_request).to be_merged
- expect(@fork_merge_request.notes.last.note).to include('merged')
- expect(@build_failed_todo).to be_done
- expect(@fork_build_failed_todo).to be_done
+ context 'when an MR to be closed was empty already' do
+ let!(:empty_fork_merge_request) do
+ create(:merge_request,
+ source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'master',
+ target_project: @project)
+ end
+
+ before do
+ # This spec already has a fake push, so pretend that we were targeting
+ # feature all along.
+ empty_fork_merge_request.update_columns(target_branch: 'feature')
+
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ empty_fork_merge_request.reload
+ end
+
+ it 'only updates the non-empty MRs' do
+ expect(@merge_request).to be_merged
+ expect(@merge_request.notes.last.note).to include('merged')
+
+ expect(@fork_merge_request).to be_merged
+ expect(@fork_merge_request.notes.last.note).to include('merged')
+
+ expect(empty_fork_merge_request).to be_open
+ expect(empty_fork_merge_request.merge_request_diff.state).to eq('empty')
+ expect(empty_fork_merge_request.notes).to be_empty
+ end
end
end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 6cc403bdb7f..ce1e9f2ff45 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::ReopenService, services: true do
+describe MergeRequests::ReopenService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:guest) { create(:user) }
diff --git a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
index 7ddd812e513..e3fd906fe7b 100644
--- a/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
+++ b/spec/services/merge_requests/resolved_discussion_notification_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::ResolvedDiscussionNotificationService, services: true do
+describe MergeRequests::ResolvedDiscussionNotificationService do
let(:merge_request) { create(:merge_request) }
let(:user) { create(:user) }
let(:project) { merge_request.project }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index ec15b5cac14..dd3ac9c4ac6 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe MergeRequests::UpdateService, services: true do
+describe MergeRequests::UpdateService do
include EmailHelpers
let(:project) { create(:project, :repository) }
@@ -55,7 +55,7 @@ describe MergeRequests::UpdateService, services: true do
}
end
- let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
+ let(:service) { described_class.new(project, user, opts) }
before do
allow(service).to receive(:execute_hooks)
@@ -145,7 +145,7 @@ describe MergeRequests::UpdateService, services: true do
}
end
- let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
+ let(:service) { described_class.new(project, user, opts) }
context 'without pipeline' do
before do
@@ -205,7 +205,7 @@ describe MergeRequests::UpdateService, services: true do
context 'with a non-authorised user' do
let(:visitor) { create(:user) }
- let(:service) { MergeRequests::UpdateService.new(project, visitor, opts) }
+ let(:service) { described_class.new(project, visitor, opts) }
before do
merge_request.update_attribute(:merge_error, 'Error')
@@ -296,13 +296,13 @@ describe MergeRequests::UpdateService, services: true do
end
context 'when the milestone change' do
- before do
+ it 'marks pending todos as done' do
update_merge_request({ milestone: create(:milestone) })
- end
- it 'marks pending todos as done' do
expect(pending_todo.reload).to be_done
end
+
+ it_behaves_like 'system notes for milestones'
end
context 'when the labels change' do
@@ -348,7 +348,7 @@ describe MergeRequests::UpdateService, services: true do
opts = { label_ids: [label.id] }
perform_enqueued_jobs do
- @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project, user, opts).execute(merge_request)
end
should_email(subscriber)
@@ -364,7 +364,7 @@ describe MergeRequests::UpdateService, services: true do
opts = { label_ids: [label.id, label2.id] }
perform_enqueued_jobs do
- @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project, user, opts).execute(merge_request)
end
should_not_email(subscriber)
@@ -375,7 +375,7 @@ describe MergeRequests::UpdateService, services: true do
opts = { label_ids: [label2.id] }
perform_enqueued_jobs do
- @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
+ @merge_request = described_class.new(project, user, opts).execute(merge_request)
end
should_not_email(subscriber)
@@ -386,7 +386,7 @@ describe MergeRequests::UpdateService, services: true do
context 'updating mentions' do
let(:mentionable) { merge_request }
- include_examples 'updating mentions', MergeRequests::UpdateService
+ include_examples 'updating mentions', described_class
end
context 'when MergeRequest has tasks' do
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index d581b94ff43..fa0686d8061 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Milestones::CloseService, services: true do
+describe Milestones::CloseService do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) }
@@ -11,7 +11,7 @@ describe Milestones::CloseService, services: true do
describe '#execute' do
before do
- Milestones::CloseService.new(project, user, {}).execute(milestone)
+ described_class.new(project, user, {}).execute(milestone)
end
it { expect(milestone).to be_valid }
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index 6d29edb449a..c6fe8e65912 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Milestones::CreateService, services: true do
+describe Milestones::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -14,7 +14,7 @@ describe Milestones::CreateService, services: true do
description: 'Patch release to fix security issue'
}
- @milestone = Milestones::CreateService.new(project, user, opts).execute
+ @milestone = described_class.new(project, user, opts).execute
end
it { expect(@milestone).to be_valid }
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
new file mode 100644
index 00000000000..5739386dd0d
--- /dev/null
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Milestones::DestroyService do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:milestone) { create(:milestone, title: 'Milestone v1.0', project: project) }
+ let(:issue) { create(:issue, project: project, milestone: milestone) }
+ let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ def service
+ described_class.new(project, user, {})
+ end
+
+ describe '#execute' do
+ it 'deletes milestone' do
+ service.execute(milestone)
+
+ expect { milestone.reload }.to raise_error ActiveRecord::RecordNotFound
+ end
+
+ it 'deletes milestone id from issuables' do
+ service.execute(milestone)
+
+ expect(issue.reload.milestone).to be_nil
+ expect(merge_request.reload.milestone).to be_nil
+ end
+
+ context 'group milestones' do
+ let(:group) { create(:group) }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(namespace: group)
+ group.add_developer(user)
+ end
+
+ it { expect(service.execute(group_milestone)).to be_nil }
+
+ it 'does not update milestone issuables' do
+ expect(MergeRequests::UpdateService).not_to receive(:new)
+ expect(Issues::UpdateService).not_to receive(:new)
+
+ service.execute(group_milestone)
+ end
+ end
+ end
+end
diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb
index 39f06f8f8e7..dda579c7080 100644
--- a/spec/services/note_summary_spec.rb
+++ b/spec/services/note_summary_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NoteSummary, services: true do
+describe NoteSummary do
let(:project) { build(:empty_project) }
let(:noteable) { build(:issue) }
let(:user) { build(:user) }
diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb
index 133175769ca..6e1c1fe6c02 100644
--- a/spec/services/notes/build_service_spec.rb
+++ b/spec/services/notes/build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::BuildService, services: true do
+describe Notes::BuildService do
let(:note) { create(:discussion_note_on_issue) }
let(:project) { note.project }
let(:author) { note.author }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 152c6d20daa..c08a5a940bb 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::CreateService, services: true do
+describe Notes::CreateService do
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb
index f53f96e0c2b..4330190caaa 100644
--- a/spec/services/notes/destroy_service_spec.rb
+++ b/spec/services/notes/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::DestroyService, services: true do
+describe Notes::DestroyService do
describe '#execute' do
it 'deletes a note' do
project = create(:empty_project)
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index e33a611929b..bf9320e5fce 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::PostProcessService, services: true do
+describe Notes::PostProcessService do
let(:project) { create(:empty_project) }
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
@@ -21,7 +21,7 @@ describe Notes::PostProcessService, services: true do
expect(project).to receive(:execute_hooks)
expect(project).to receive(:execute_services)
- Notes::PostProcessService.new(@note).execute
+ described_class.new(@note).execute
end
end
end
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 9a98499826f..fc4cd3dc2b7 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::QuickActionsService, services: true do
+describe Notes::QuickActionsService do
shared_context 'note on noteable' do
let(:project) { create(:empty_project) }
let(:master) { create(:user).tap { |u| project.team << [u, :master] } }
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
index 905e2f46bde..dea2dc02d00 100644
--- a/spec/services/notes/update_service_spec.rb
+++ b/spec/services/notes/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Notes::UpdateService, services: true do
+describe Notes::UpdateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb
index dfe1ee7c41e..77233dc1b2f 100644
--- a/spec/services/notification_recipient_service_spec.rb
+++ b/spec/services/notification_recipient_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NotificationRecipientService, services: true do
+describe NotificationRecipientService do
set(:user) { create(:user) }
set(:project) { create(:empty_project, :public) }
set(:issue) { create(:issue, project: project) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f1e00c1163b..49d6fc7853f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe NotificationService, services: true do
+describe NotificationService do
include EmailHelpers
- let(:notification) { NotificationService.new }
+ let(:notification) { described_class.new }
let(:assignee) { create(:user) }
around(:each) do |example|
@@ -93,6 +93,18 @@ describe NotificationService, services: true do
end
end
+ describe 'GpgKeys' do
+ describe '#new_gpg_key' do
+ let!(:key) { create(:gpg_key) }
+
+ it { expect(notification.new_gpg_key(key)).to be_truthy }
+
+ it 'sends email to key owner' do
+ expect{ notification.new_gpg_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
+ end
+ end
+ end
+
describe 'Email' do
describe '#new_email' do
let!(:email) { create(:email) }
@@ -383,7 +395,7 @@ describe NotificationService, services: true do
before do
build_team(note.project)
reset_delivered_emails!
- allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer)
+ allow(note.noteable).to receive(:author).and_return(@u_committer)
update_custom_notification(:new_note, @u_guest_custom, resource: project)
update_custom_notification(:new_note, @u_custom_global)
end
diff --git a/spec/services/pages_service_spec.rb b/spec/services/pages_service_spec.rb
index cf38c7c75e5..f8db6900a0a 100644
--- a/spec/services/pages_service_spec.rb
+++ b/spec/services/pages_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe PagesService, services: true do
+describe PagesService do
let(:build) { create(:ci_build) }
let(:data) { Gitlab::DataBuilder::Build.build(build) }
- let(:service) { PagesService.new(data) }
+ let(:service) { described_class.new(data) }
before do
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index c198c3eedfc..fc7238862ab 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::AutocompleteService, services: true do
+describe Projects::AutocompleteService do
describe '#issues' do
describe 'confidential issues' do
let(:author) { create(:user) }
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 40298dcb723..b0dc7488b5f 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::CreateService, '#execute', services: true do
+describe Projects::CreateService, '#execute' do
let(:user) { create :user }
let(:opts) do
{
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 697dc18feb0..85b05ef6d05 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::DestroyService, services: true do
+describe Projects::DestroyService do
let!(:user) { create(:user) }
let!(:project) { create(:project, :repository, namespace: user.namespace) }
let!(:path) { project.repository.path_to_repo }
@@ -36,6 +36,27 @@ describe Projects::DestroyService, services: true do
end
end
+ shared_examples 'handles errors thrown during async destroy' do |error_message|
+ it 'does not allow the error to bubble up' do
+ expect do
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+ end.not_to raise_error
+ end
+
+ it 'unmarks the project as "pending deletion"' do
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+
+ expect(project.reload.pending_delete).to be(false)
+ end
+
+ it 'stores an error message in `projects.delete_error`' do
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+
+ expect(project.reload.delete_error).to be_present
+ expect(project.delete_error).to include(error_message)
+ end
+ end
+
context 'Sidekiq inline' do
before do
# Run sidekiq immediatly to check that renamed repository will be removed
@@ -60,14 +81,14 @@ describe Projects::DestroyService, services: true do
before do
new_user = create(:user)
project.team.add_user(new_user, Gitlab::Access::DEVELOPER)
- allow_any_instance_of(Projects::DestroyService).to receive(:flush_caches).and_raise(Redis::CannotConnectError)
+ allow_any_instance_of(described_class).to receive(:flush_caches).and_raise(::Redis::CannotConnectError)
end
it 'keeps project team intact upon an error' do
Sidekiq::Testing.inline! do
begin
destroy_project(project, user, {})
- rescue Redis::CannotConnectError
+ rescue ::Redis::CannotConnectError
end
end
@@ -89,10 +110,51 @@ describe Projects::DestroyService, services: true do
end
it_behaves_like 'deleting the project with pipeline and build'
- end
- context 'with execute' do
- it_behaves_like 'deleting the project with pipeline and build'
+ context 'errors' do
+ context 'when `remove_legacy_registry_tags` fails' do
+ before do
+ expect_any_instance_of(described_class)
+ .to receive(:remove_legacy_registry_tags).and_return(false)
+ end
+
+ it_behaves_like 'handles errors thrown during async destroy', "Failed to remove some tags"
+ end
+
+ context 'when `remove_repository` fails' do
+ before do
+ expect_any_instance_of(described_class)
+ .to receive(:remove_repository).and_return(false)
+ end
+
+ it_behaves_like 'handles errors thrown during async destroy', "Failed to remove project repository"
+ end
+
+ context 'when `execute` raises expected error' do
+ before do
+ expect_any_instance_of(Project)
+ .to receive(:destroy!).and_raise(StandardError.new("Other error message"))
+ end
+
+ it_behaves_like 'handles errors thrown during async destroy', "Other error message"
+ end
+
+ context 'when `execute` raises unexpected error' do
+ before do
+ expect_any_instance_of(Project)
+ .to receive(:destroy!).and_raise(Exception.new("Other error message"))
+ end
+
+ it 'allows error to bubble up and rolls back project deletion' do
+ expect do
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+ end.to raise_error
+
+ expect(project.reload.pending_delete).to be(false)
+ expect(project.delete_error).to include("Other error message")
+ end
+ end
+ end
end
describe 'container registry' do
@@ -119,8 +181,7 @@ describe Projects::DestroyService, services: true do
expect_any_instance_of(ContainerRepository)
.to receive(:delete_tags!).and_return(false)
- expect{ destroy_project(project, user) }
- .to raise_error(ActiveRecord::RecordNotDestroyed)
+ expect(destroy_project(project, user)).to be false
end
end
end
@@ -145,8 +206,7 @@ describe Projects::DestroyService, services: true do
expect_any_instance_of(ContainerRepository)
.to receive(:delete_tags!).and_return(false)
- expect { destroy_project(project, user) }
- .to raise_error(Projects::DestroyService::DestroyError)
+ expect(destroy_project(project, user)).to be false
end
end
end
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index 33b267c069c..701f6cc8c6a 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::DownloadService, services: true do
+describe Projects::DownloadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb
index 78626fbad4b..9b8c24ba112 100644
--- a/spec/services/projects/enable_deploy_key_service_spec.rb
+++ b/spec/services/projects/enable_deploy_key_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::EnableDeployKeyService, services: true do
+describe Projects::EnableDeployKeyService do
let(:deploy_key) { create(:deploy_key, public: true) }
let(:project) { create(:empty_project) }
let(:user) { project.creator}
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 0df81f3abcb..c90536ba346 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::ForkService, services: true do
+describe Projects::ForkService do
describe 'fork by user' do
before do
@from_user = create(:user)
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index e855de38037..67fe610f92a 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::ImportService, services: true do
+describe Projects::ImportService do
let!(:project) { create(:empty_project) }
let(:user) { project.creator }
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index d75851134ee..8777e63a101 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::ParticipantsService, services: true do
+describe Projects::ParticipantsService do
describe '#groups' do
describe 'avatar_url' do
let(:project) { create(:empty_project, :public) }
@@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/uploads/system/group/avatar/#{group.id}/dk.png")
+ expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
it 'should return an url for the avatar with relative url' do
@@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/system/group/avatar/#{group.id}/dk.png")
+ expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
end
end
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index a6d43c4f0f1..2763437f184 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::PropagateServiceTemplate, services: true do
+describe Projects::PropagateServiceTemplate do
describe '.propagate' do
let!(:service_template) do
PushoverService.create(
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 441a5276c56..ae32e85b2a7 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::TransferService, services: true do
+describe Projects::TransferService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
@@ -37,7 +37,7 @@ describe Projects::TransferService, services: true do
end
it 'executes system hooks' do
- expect_any_instance_of(Projects::TransferService).to receive(:execute_system_hooks)
+ expect_any_instance_of(described_class).to receive(:execute_system_hooks)
transfer_project(project, user, group)
end
@@ -80,7 +80,7 @@ describe Projects::TransferService, services: true do
end
it "doesn't run system hooks" do
- expect_any_instance_of(Projects::TransferService).not_to receive(:execute_system_hooks)
+ expect_any_instance_of(described_class).not_to receive(:execute_system_hooks)
attempt_project_transfer
end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index d34652bd7ac..2ae8d5f7c54 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Projects::UnlinkForkService, services: true do
- subject { Projects::UnlinkForkService.new(fork_project, user) }
+describe Projects::UnlinkForkService do
+ subject { described_class.new(fork_project, user) }
let(:fork_link) { create(:forked_project_link) }
let(:fork_project) { fork_link.forked_to_project }
diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb
index 8b329bc21c3..42925e73978 100644
--- a/spec/services/projects/update_pages_configuration_service_spec.rb
+++ b/spec/services/projects/update_pages_configuration_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::UpdatePagesConfigurationService, services: true do
+describe Projects::UpdatePagesConfigurationService do
let(:project) { create(:empty_project) }
subject { described_class.new(project) }
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index fc0a17296f3..aa6ad6340f5 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -96,6 +96,78 @@ describe Projects::UpdatePagesService do
expect(execute).not_to eq(:success)
end
+ describe 'maximum pages artifacts size' do
+ let(:metadata) { spy('metadata') }
+
+ before do
+ file = fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip')
+ metafile = fixture_file_upload(Rails.root + 'spec/fixtures/pages.zip.meta')
+
+ build.update_attributes(artifacts_file: file)
+ build.update_attributes(artifacts_metadata: metafile)
+
+ allow(build).to receive(:artifacts_metadata_entry)
+ .and_return(metadata)
+ end
+
+ shared_examples 'pages size limit exceeded' do
+ it 'limits the maximum size of gitlab pages' do
+ subject.execute
+
+ expect(deploy_status.description)
+ .to match(/artifacts for pages are too large/)
+ end
+ end
+
+ context 'when maximum pages size is set to zero' do
+ before do
+ stub_application_setting(max_pages_size: 0)
+ end
+
+ context 'when page size does not exceed internal maximum' do
+ before do
+ allow(metadata).to receive(:total_size).and_return(200.megabytes)
+ end
+
+ it 'updates pages correctly' do
+ subject.execute
+
+ expect(deploy_status.description).not_to be_present
+ end
+ end
+
+ context 'when pages size does exceed internal maximum' do
+ before do
+ allow(metadata).to receive(:total_size).and_return(2.terabytes)
+ end
+
+ it_behaves_like 'pages size limit exceeded'
+ end
+ end
+
+ context 'when pages size is greater than max size setting' do
+ before do
+ stub_application_setting(max_pages_size: 200)
+ allow(metadata).to receive(:total_size).and_return(201.megabytes)
+ end
+
+ it_behaves_like 'pages size limit exceeded'
+ end
+
+ context 'when max size setting is greater than internal max size' do
+ before do
+ stub_application_setting(max_pages_size: 3.terabytes / 1.megabyte)
+ allow(metadata).to receive(:total_size).and_return(2.terabytes)
+ end
+
+ it_behaves_like 'pages size limit exceeded'
+ end
+ end
+
+ def deploy_status
+ GenericCommitStatus.find_by(name: 'pages:deploy')
+ end
+
def execute
subject.execute[:status]
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 05b18fef061..d7b0df9a671 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -1,11 +1,14 @@
require 'spec_helper'
-describe Projects::UpdateService, services: true do
+describe Projects::UpdateService, '#execute' do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
- let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
- describe 'update_by_user' do
+ let(:project) do
+ create(:empty_project, creator: user, namespace: user.namespace)
+ end
+
+ context 'when changing visibility level' do
context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
@@ -40,7 +43,7 @@ describe Projects::UpdateService, services: true do
it 'does not update the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- expect(result).to eq({ status: :error, message: 'Visibility level unallowed' })
+ expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_private
end
@@ -55,12 +58,13 @@ describe Projects::UpdateService, services: true do
end
end
- describe 'visibility_level' do
+ describe 'when updating project that has forks' do
let(:project) { create(:empty_project, :internal) }
let(:forked_project) { create(:forked_project_with_submodules, :internal) }
before do
- forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
+ forked_project.build_forked_project_link(forked_to_project_id: forked_project.id,
+ forked_from_project_id: project.id)
forked_project.save
end
@@ -89,10 +93,45 @@ describe Projects::UpdateService, services: true do
end
end
- it 'returns an error result when record cannot be updated' do
- result = update_project(project, admin, { name: 'foo&bar' })
+ context 'when updating a default branch' do
+ let(:project) { create(:project, :repository) }
+
+ it 'changes a default branch' do
+ update_project(project, admin, default_branch: 'feature')
+
+ expect(Project.find(project.id).default_branch).to eq 'feature'
+ end
+ end
+
+ context 'when updating a project that contains container images' do
+ before do
+ stub_container_registry_config(enabled: true)
+ stub_container_registry_tags(repository: /image/, tags: %w[rc1])
+ create(:container_repository, project: project, name: :image)
+ end
+
+ it 'does not allow to rename the project' do
+ result = update_project(project, admin, path: 'renamed')
+
+ expect(result).to include(status: :error)
+ expect(result[:message]).to match(/contains container registry tags/)
+ end
+
+ it 'allows to update other settings' do
+ result = update_project(project, admin, public_builds: true)
+
+ expect(result[:status]).to eq :success
+ expect(project.reload.public_builds).to be true
+ end
+ end
+
+ context 'when passing invalid parameters' do
+ it 'returns an error result when record cannot be updated' do
+ result = update_project(project, admin, { name: 'foo&bar' })
- expect(result).to eq({ status: :error, message: 'Project could not be updated' })
+ expect(result).to eq({ status: :error,
+ message: 'Project could not be updated!' })
+ end
end
def update_project(project, user, opts)
diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb
index 6ea8f309981..592f9b5929e 100644
--- a/spec/services/protected_branches/create_service_spec.rb
+++ b/spec/services/protected_branches/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedBranches::CreateService, services: true do
+describe ProtectedBranches::CreateService do
let(:project) { create(:empty_project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb
index 62bdd49a4d7..5698101af54 100644
--- a/spec/services/protected_branches/update_service_spec.rb
+++ b/spec/services/protected_branches/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedBranches::UpdateService, services: true do
+describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project }
let(:user) { project.owner }
diff --git a/spec/services/protected_tags/create_service_spec.rb b/spec/services/protected_tags/create_service_spec.rb
index d91a58e8de5..3a3cc9c1573 100644
--- a/spec/services/protected_tags/create_service_spec.rb
+++ b/spec/services/protected_tags/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedTags::CreateService, services: true do
+describe ProtectedTags::CreateService do
let(:project) { create(:empty_project) }
let(:user) { project.owner }
let(:params) do
diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb
index e78fde4c48d..d333430088d 100644
--- a/spec/services/protected_tags/update_service_spec.rb
+++ b/spec/services/protected_tags/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ProtectedTags::UpdateService, services: true do
+describe ProtectedTags::UpdateService do
let(:protected_tag) { create(:protected_tag) }
let(:project) { protected_tag.project }
let(:user) { project.owner }
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 35373675894..42a4a2591ea 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe QuickActions::InterpretService, services: true do
+describe QuickActions::InterpretService do
let(:project) { create(:empty_project, :public) }
let(:developer) { create(:user) }
let(:developer2) { create(:user) }
@@ -261,6 +261,15 @@ describe QuickActions::InterpretService, services: true do
end
end
+ shared_examples 'duplicate command' do
+ it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do
+ issue_duplicate # populate the issue
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(canonical_issue_id: issue_duplicate.id)
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -431,22 +440,6 @@ describe QuickActions::InterpretService, services: true do
end
end
- context 'reassign command' do
- let(:content) { '/reassign' }
-
- context 'Issue' do
- it 'reassigns user if content contains /reassign @user' do
- user = create(:user)
-
- issue.update(assignee_ids: [developer.id])
-
- _, updates = service.execute("/reassign @#{user.username}", issue)
-
- expect(updates).to eq(assignee_ids: [user.id])
- end
- end
- end
-
it_behaves_like 'milestone command' do
let(:content) { "/milestone %#{milestone.title}" }
let(:issuable) { issue }
@@ -660,6 +653,41 @@ describe QuickActions::InterpretService, services: true do
let(:issuable) { issue }
end
+ context '/duplicate command' do
+ it_behaves_like 'duplicate command' do
+ let(:issue_duplicate) { create(:issue, project: project) }
+ let(:content) { "/duplicate #{issue_duplicate.to_reference}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/duplicate' }
+ let(:issuable) { issue }
+ end
+
+ context 'cross project references' do
+ it_behaves_like 'duplicate command' do
+ let(:other_project) { create(:empty_project, :public) }
+ let(:issue_duplicate) { create(:issue, project: other_project) }
+ let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/duplicate imaginary#1234" }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'empty command' do
+ let(:other_project) { create(:empty_project, :private) }
+ let(:issue_duplicate) { create(:issue, project: other_project) }
+
+ let(:content) { "/duplicate #{issue_duplicate.to_reference(project)}" }
+ let(:issuable) { issue }
+ end
+ end
+ end
+
context 'when current_user cannot :admin_issue' do
let(:visitor) { create(:user) }
let(:issue) { create(:issue, project: project, author: visitor) }
@@ -709,6 +737,11 @@ describe QuickActions::InterpretService, services: true do
let(:content) { '/remove_due_date' }
let(:issuable) { issue }
end
+
+ it_behaves_like 'empty command' do
+ let(:content) { '/duplicate #{issue.to_reference}' }
+ let(:issuable) { issue }
+ end
end
context '/award command' do
diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb
index 87192457298..bf79cfe74b7 100644
--- a/spec/services/repair_ldap_blocked_user_service_spec.rb
+++ b/spec/services/repair_ldap_blocked_user_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe RepairLdapBlockedUserService, services: true do
+describe RepairLdapBlockedUserService do
let(:user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
let(:identity) { user.ldap_identity }
- subject(:service) { RepairLdapBlockedUserService.new(user) }
+ subject(:service) { described_class.new(user) }
describe '#execute' do
it 'changes to normal block after destroying last ldap identity' do
diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb
index 842585f9e54..2d7fa3f80f7 100644
--- a/spec/services/repository_archive_clean_up_service_spec.rb
+++ b/spec/services/repository_archive_clean_up_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe RepositoryArchiveCleanUpService, services: true do
+describe RepositoryArchiveCleanUpService do
describe '#execute' do
subject(:service) { described_class.new }
diff --git a/spec/services/search/global_service_spec.rb b/spec/services/search/global_service_spec.rb
index cbf4f56213d..de921573b1a 100644
--- a/spec/services/search/global_service_spec.rb
+++ b/spec/services/search/global_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Search::GlobalService, services: true do
+describe Search::GlobalService do
let(:user) { create(:user) }
let(:internal_user) { create(:user) }
@@ -16,7 +16,7 @@ describe Search::GlobalService, services: true do
describe '#execute' do
context 'unauthenticated' do
it 'returns public projects only' do
- results = Search::GlobalService.new(nil, search: "searchable").execute
+ results = described_class.new(nil, search: "searchable").execute
expect(results.objects('projects')).to match_array [public_project]
end
@@ -24,19 +24,19 @@ describe Search::GlobalService, services: true do
context 'authenticated' do
it 'returns public, internal and private projects' do
- results = Search::GlobalService.new(user, search: "searchable").execute
+ results = described_class.new(user, search: "searchable").execute
expect(results.objects('projects')).to match_array [public_project, found_project, internal_project]
end
it 'returns only public & internal projects' do
- results = Search::GlobalService.new(internal_user, search: "searchable").execute
+ results = described_class.new(internal_user, search: "searchable").execute
expect(results.objects('projects')).to match_array [internal_project, public_project]
end
it 'namespace name is searchable' do
- results = Search::GlobalService.new(user, search: found_project.namespace.path).execute
+ results = described_class.new(user, search: found_project.namespace.path).execute
expect(results.objects('projects')).to match_array [found_project]
end
diff --git a/spec/services/search/group_service_spec.rb b/spec/services/search/group_service_spec.rb
index 38f264f6e7b..cb3f02d2883 100644
--- a/spec/services/search/group_service_spec.rb
+++ b/spec/services/search/group_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Search::GroupService, services: true do
+describe Search::GroupService do
shared_examples_for 'group search' do
context 'finding projects by name' do
let(:user) { create(:user) }
@@ -17,7 +17,7 @@ describe Search::GroupService, services: true do
let!(:project2) { create(:empty_project, :internal, namespace: nested_group, name: "Inner #{term} 2") }
let!(:project3) { create(:empty_project, :internal, namespace: nested_group.parent, name: "Outer #{term}") }
- let(:results) { Search::GroupService.new(user, search_group, search: term).execute }
+ let(:results) { described_class.new(user, search_group, search: term).execute }
subject { results.objects('projects') }
context 'in parent group' do
diff --git a/spec/services/search/snippet_service_spec.rb b/spec/services/search/snippet_service_spec.rb
index 14f3301d9f4..69438a3fa36 100644
--- a/spec/services/search/snippet_service_spec.rb
+++ b/spec/services/search/snippet_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Search::SnippetService, services: true do
+describe Search::SnippetService do
let(:author) { create(:author) }
let(:project) { create(:empty_project) }
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 5cf989105d0..a6ef7561bc8 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SearchService, services: true do
+describe SearchService do
let(:user) { create(:user) }
let(:accessible_group) { create(:group, :private) }
@@ -22,7 +22,7 @@ describe SearchService, services: true do
describe '#project' do
context 'when the project is accessible' do
it 'returns the project' do
- project = SearchService.new(user, project_id: accessible_project.id).project
+ project = described_class.new(user, project_id: accessible_project.id).project
expect(project).to eq accessible_project
end
@@ -31,7 +31,7 @@ describe SearchService, services: true do
search_project = create :empty_project
search_project.add_guest(user)
- project = SearchService.new(user, project_id: search_project.id).project
+ project = described_class.new(user, project_id: search_project.id).project
expect(project).to eq search_project
end
@@ -39,7 +39,7 @@ describe SearchService, services: true do
context 'when the project is not accessible' do
it 'returns nil' do
- project = SearchService.new(user, project_id: inaccessible_project.id).project
+ project = described_class.new(user, project_id: inaccessible_project.id).project
expect(project).to be_nil
end
@@ -47,7 +47,7 @@ describe SearchService, services: true do
context 'when there is no project_id' do
it 'returns nil' do
- project = SearchService.new(user).project
+ project = described_class.new(user).project
expect(project).to be_nil
end
@@ -57,7 +57,7 @@ describe SearchService, services: true do
describe '#group' do
context 'when the group is accessible' do
it 'returns the group' do
- group = SearchService.new(user, group_id: accessible_group.id).group
+ group = described_class.new(user, group_id: accessible_group.id).group
expect(group).to eq accessible_group
end
@@ -65,7 +65,7 @@ describe SearchService, services: true do
context 'when the group is not accessible' do
it 'returns nil' do
- group = SearchService.new(user, group_id: inaccessible_group.id).group
+ group = described_class.new(user, group_id: inaccessible_group.id).group
expect(group).to be_nil
end
@@ -73,7 +73,7 @@ describe SearchService, services: true do
context 'when there is no group_id' do
it 'returns nil' do
- group = SearchService.new(user).group
+ group = described_class.new(user).group
expect(group).to be_nil
end
@@ -83,7 +83,7 @@ describe SearchService, services: true do
describe '#show_snippets?' do
context 'when :snippets is \'true\'' do
it 'returns true' do
- show_snippets = SearchService.new(user, snippets: 'true').show_snippets?
+ show_snippets = described_class.new(user, snippets: 'true').show_snippets?
expect(show_snippets).to be_truthy
end
@@ -91,7 +91,7 @@ describe SearchService, services: true do
context 'when :snippets is not \'true\'' do
it 'returns false' do
- show_snippets = SearchService.new(user, snippets: 'tru').show_snippets?
+ show_snippets = described_class.new(user, snippets: 'tru').show_snippets?
expect(show_snippets).to be_falsey
end
@@ -99,7 +99,7 @@ describe SearchService, services: true do
context 'when :snippets is missing' do
it 'returns false' do
- show_snippets = SearchService.new(user).show_snippets?
+ show_snippets = described_class.new(user).show_snippets?
expect(show_snippets).to be_falsey
end
@@ -110,7 +110,7 @@ describe SearchService, services: true do
context 'with accessible project_id' do
context 'and allowed scope' do
it 'returns the specified scope' do
- scope = SearchService.new(user, project_id: accessible_project.id, scope: 'notes').scope
+ scope = described_class.new(user, project_id: accessible_project.id, scope: 'notes').scope
expect(scope).to eq 'notes'
end
@@ -118,7 +118,7 @@ describe SearchService, services: true do
context 'and disallowed scope' do
it 'returns the default scope' do
- scope = SearchService.new(user, project_id: accessible_project.id, scope: 'projects').scope
+ scope = described_class.new(user, project_id: accessible_project.id, scope: 'projects').scope
expect(scope).to eq 'blobs'
end
@@ -126,7 +126,7 @@ describe SearchService, services: true do
context 'and no scope' do
it 'returns the default scope' do
- scope = SearchService.new(user, project_id: accessible_project.id).scope
+ scope = described_class.new(user, project_id: accessible_project.id).scope
expect(scope).to eq 'blobs'
end
@@ -136,7 +136,7 @@ describe SearchService, services: true do
context 'with \'true\' snippets' do
context 'and allowed scope' do
it 'returns the specified scope' do
- scope = SearchService.new(user, snippets: 'true', scope: 'snippet_titles').scope
+ scope = described_class.new(user, snippets: 'true', scope: 'snippet_titles').scope
expect(scope).to eq 'snippet_titles'
end
@@ -144,7 +144,7 @@ describe SearchService, services: true do
context 'and disallowed scope' do
it 'returns the default scope' do
- scope = SearchService.new(user, snippets: 'true', scope: 'projects').scope
+ scope = described_class.new(user, snippets: 'true', scope: 'projects').scope
expect(scope).to eq 'snippet_blobs'
end
@@ -152,7 +152,7 @@ describe SearchService, services: true do
context 'and no scope' do
it 'returns the default scope' do
- scope = SearchService.new(user, snippets: 'true').scope
+ scope = described_class.new(user, snippets: 'true').scope
expect(scope).to eq 'snippet_blobs'
end
@@ -162,7 +162,7 @@ describe SearchService, services: true do
context 'with no project_id, no snippets' do
context 'and allowed scope' do
it 'returns the specified scope' do
- scope = SearchService.new(user, scope: 'issues').scope
+ scope = described_class.new(user, scope: 'issues').scope
expect(scope).to eq 'issues'
end
@@ -170,7 +170,7 @@ describe SearchService, services: true do
context 'and disallowed scope' do
it 'returns the default scope' do
- scope = SearchService.new(user, scope: 'blobs').scope
+ scope = described_class.new(user, scope: 'blobs').scope
expect(scope).to eq 'projects'
end
@@ -178,7 +178,7 @@ describe SearchService, services: true do
context 'and no scope' do
it 'returns the default scope' do
- scope = SearchService.new(user).scope
+ scope = described_class.new(user).scope
expect(scope).to eq 'projects'
end
@@ -189,7 +189,7 @@ describe SearchService, services: true do
describe '#search_results' do
context 'with accessible project_id' do
it 'returns an instance of Gitlab::ProjectSearchResults' do
- search_results = SearchService.new(
+ search_results = described_class.new(
user,
project_id: accessible_project.id,
scope: 'notes',
@@ -201,7 +201,7 @@ describe SearchService, services: true do
context 'with accessible project_id and \'true\' snippets' do
it 'returns an instance of Gitlab::ProjectSearchResults' do
- search_results = SearchService.new(
+ search_results = described_class.new(
user,
project_id: accessible_project.id,
snippets: 'true',
@@ -214,7 +214,7 @@ describe SearchService, services: true do
context 'with \'true\' snippets' do
it 'returns an instance of Gitlab::SnippetSearchResults' do
- search_results = SearchService.new(
+ search_results = described_class.new(
user,
snippets: 'true',
search: snippet.content).search_results
@@ -225,7 +225,7 @@ describe SearchService, services: true do
context 'with no project_id and no snippets' do
it 'returns an instance of Gitlab::SearchResults' do
- search_results = SearchService.new(
+ search_results = described_class.new(
user,
search: public_project.name).search_results
@@ -237,7 +237,7 @@ describe SearchService, services: true do
describe '#search_objects' do
context 'with accessible project_id' do
it 'returns objects in the project' do
- search_objects = SearchService.new(
+ search_objects = described_class.new(
user,
project_id: accessible_project.id,
scope: 'notes',
@@ -249,7 +249,7 @@ describe SearchService, services: true do
context 'with accessible project_id and \'true\' snippets' do
it 'returns objects in the project' do
- search_objects = SearchService.new(
+ search_objects = described_class.new(
user,
project_id: accessible_project.id,
snippets: 'true',
@@ -262,7 +262,7 @@ describe SearchService, services: true do
context 'with \'true\' snippets' do
it 'returns objects in snippets' do
- search_objects = SearchService.new(
+ search_objects = described_class.new(
user,
snippets: 'true',
search: snippet.content).search_objects
@@ -273,7 +273,7 @@ describe SearchService, services: true do
context 'with accessible group_id' do
it 'returns objects in the group' do
- search_objects = SearchService.new(
+ search_objects = described_class.new(
user,
group_id: accessible_group.id,
search: group_project.name).search_objects
@@ -284,7 +284,7 @@ describe SearchService, services: true do
context 'with no project_id, group_id or snippets' do
it 'returns objects in global' do
- search_objects = SearchService.new(
+ search_objects = described_class.new(
user,
search: public_project.name).search_objects
diff --git a/spec/services/spam_service_spec.rb b/spec/services/spam_service_spec.rb
index 5e6e43b7a90..46349c3e951 100644
--- a/spec/services/spam_service_spec.rb
+++ b/spec/services/spam_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SpamService, services: true do
+describe SpamService do
describe '#when_recaptcha_verified' do
def check_spam(issue, request, recaptcha_verified)
described_class.new(issue, request).when_recaptcha_verified(recaptcha_verified) do
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 667059f230c..b2d9862e71e 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe SystemHooksService, services: true do
+describe SystemHooksService do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:project_member) { create(:project_member) }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 8d3dafafab2..cb59493c343 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe SystemNoteService, services: true do
- include Gitlab::Routing.url_helpers
+describe SystemNoteService do
+ include Gitlab::Routing
let(:project) { create(:empty_project) }
let(:author) { create(:user) }
@@ -807,7 +807,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_commit_url(project.namespace, project, commit),
+ url: project_commit_url(project, commit),
title: "GitLab: Mentioned on commit - #{commit.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -833,7 +833,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_issue_url(project.namespace, project, issue),
+ url: project_issue_url(project, issue),
title: "GitLab: Mentioned on issue - #{issue.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -859,7 +859,7 @@ describe SystemNoteService, services: true do
body: hash_including(
GlobalID: "GitLab",
object: {
- url: namespace_project_snippet_url(project.namespace, project, snippet),
+ url: project_snippet_url(project, snippet),
title: "GitLab: Mentioned on snippet - #{snippet.title}",
icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
status: { resolved: false }
@@ -1098,7 +1098,57 @@ describe SystemNoteService, services: true do
diff_id = merge_request.merge_request_diff.id
line_code = change_position.line_code(project.repository)
- expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code))
+ expect(subject.note).to include(diffs_project_merge_request_url(project, merge_request, diff_id: diff_id, anchor: line_code))
+ end
+ end
+
+ describe '.mark_duplicate_issue' do
+ subject { described_class.mark_duplicate_issue(noteable, project, author, canonical_issue) }
+
+ context 'within the same project' do
+ let(:canonical_issue) { create(:issue, project: project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference}" }
+ end
+
+ context 'across different projects' do
+ let(:other_project) { create(:empty_project) }
+ let(:canonical_issue) { create(:issue, project: other_project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked this issue as a duplicate of #{canonical_issue.to_reference(project)}" }
+ end
+ end
+
+ describe '.mark_canonical_issue_of_duplicate' do
+ subject { described_class.mark_canonical_issue_of_duplicate(noteable, project, author, duplicate_issue) }
+
+ context 'within the same project' do
+ let(:duplicate_issue) { create(:issue, project: project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference} as a duplicate of this issue" }
+ end
+
+ context 'across different projects' do
+ let(:other_project) { create(:empty_project) }
+ let(:duplicate_issue) { create(:issue, project: other_project) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'duplicate' }
+ end
+
+ it { expect(subject.note).to eq "marked #{duplicate_issue.to_reference(project)} as a duplicate of this issue" }
end
end
end
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index 9f143cc5667..1b31ce29f7a 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Tags::CreateService, services: true do
+describe Tags::CreateService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index 28396fc3658..7c8c1dd0d3a 100644
--- a/spec/services/tags/destroy_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Tags::DestroyService, services: true do
+describe Tags::DestroyService do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:user) { create(:user) }
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
deleted file mode 100644
index f99fd8434c2..00000000000
--- a/spec/services/test_hook_service_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require 'spec_helper'
-
-describe TestHookService, services: true do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:hook) { create(:project_hook, project: project) }
-
- describe '#execute' do
- it "executes successfully" do
- stub_request(:post, hook.url).to_return(status: 200)
- expect(TestHookService.new.execute(hook, user)).to be_truthy
- end
- end
-end
diff --git a/spec/services/test_hooks/project_service_spec.rb b/spec/services/test_hooks/project_service_spec.rb
new file mode 100644
index 00000000000..4218c15a3ce
--- /dev/null
+++ b/spec/services/test_hooks/project_service_spec.rb
@@ -0,0 +1,188 @@
+require 'spec_helper'
+
+describe TestHooks::ProjectService do
+ let(:current_user) { create(:user) }
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:hook) { create(:project_hook, project: project) }
+ let(:service) { described_class.new(hook, current_user, trigger) }
+ let(:sample_data) { { data: 'sample' } }
+ let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
+
+ context 'hook with not implemented test' do
+ let(:trigger) { 'not_implemented_events' }
+
+ it 'returns error message' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
+ end
+ end
+
+ context 'push_events' do
+ let(:trigger) { 'push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'tag_push_events' do
+ let(:trigger) { 'tag_push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has at least one commit.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'note_events' do
+ let(:trigger) { 'note_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has notes.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:notes).and_return([Note.new])
+ allow(Gitlab::DataBuilder::Note).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'issues_events' do
+ let(:trigger) { 'issues_events' }
+ let(:issue) { build(:issue) }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:issues).and_return([issue])
+ allow(issue).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'confidential_issues_events' do
+ let(:trigger) { 'confidential_issues_events' }
+ let(:issue) { build(:issue) }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has issues.' })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:issues).and_return([issue])
+ allow(issue).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'merge_requests_events' do
+ let(:trigger) { 'merge_requests_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has merge requests.' })
+ end
+
+ it 'executes hook' do
+ create(:merge_request, source_project: project)
+ allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'job_events' do
+ let(:trigger) { 'job_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI jobs.' })
+ end
+
+ it 'executes hook' do
+ create(:ci_build, project: project)
+ allow(Gitlab::DataBuilder::Build).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'pipeline_events' do
+ let(:trigger) { 'pipeline_events' }
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the project has CI pipelines.' })
+ end
+
+ it 'executes hook' do
+ create(:ci_empty_pipeline, project: project)
+ allow(Gitlab::DataBuilder::Pipeline).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'wiki_page_events' do
+ let(:trigger) { 'wiki_page_events' }
+
+ it 'returns error message if wiki disabled' do
+ allow(project).to receive(:wiki_enabled?).and_return(false)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
+ end
+
+ it 'returns error message if not enough data' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure the wiki is enabled and has pages.' })
+ end
+
+ it 'executes hook' do
+ create(:wiki_page, wiki: project.wiki)
+ allow(Gitlab::DataBuilder::WikiPage).to receive(:build).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
new file mode 100644
index 00000000000..00d89924766
--- /dev/null
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe TestHooks::SystemService do
+ let(:current_user) { create(:user) }
+
+ describe '#execute' do
+ let(:project) { create(:project, :repository) }
+ let(:hook) { create(:system_hook) }
+ let(:service) { described_class.new(hook, current_user, trigger) }
+ let(:sample_data) { { data: 'sample' }}
+ let(:success_result) { { status: :success, http_status: 200, message: 'ok' } }
+
+ before do
+ allow(Project).to receive(:first).and_return(project)
+ end
+
+ context 'hook with not implemented test' do
+ let(:trigger) { 'not_implemented_events' }
+
+ it 'returns error message' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Testing not available for this hook' })
+ end
+ end
+
+ context 'push_events' do
+ let(:trigger) { 'push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:empty_repo?).and_return(true)
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'tag_push_events' do
+ let(:trigger) { 'tag_push_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project.repository).to receive(:tags).and_return([])
+
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has tags." })
+ end
+
+ it 'executes hook' do
+ allow(project.repository).to receive(:tags).and_return(['tag'])
+ allow(Gitlab::DataBuilder::Push).to receive(:build_sample).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+
+ context 'repository_update_events' do
+ let(:trigger) { 'repository_update_events' }
+
+ it 'returns error message if not enough data' do
+ allow(project).to receive(:commit).and_return(nil)
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: "Ensure project \"#{project.human_name}\" has commits." })
+ end
+
+ it 'executes hook' do
+ allow(project).to receive(:empty_repo?).and_return(false)
+ allow(Gitlab::DataBuilder::Repository).to receive(:update).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 175a42a32d9..230e40de9e0 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe TodoService, services: true do
+describe TodoService do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
let(:non_member) { create(:user) }
@@ -873,21 +873,21 @@ describe TodoService, services: true do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todos = TodosFinder.new(john_doe, {}).execute
- expect { TodoService.new.mark_todos_as_done(todos, john_doe) }
+ expect { described_class.new.mark_todos_as_done(todos, john_doe) }
.to change { john_doe.todos.done.count }.from(0).to(1)
end
it 'marks an array of todos as done' do
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect { TodoService.new.mark_todos_as_done([todo], john_doe) }
+ expect { described_class.new.mark_todos_as_done([todo], john_doe) }
.to change { todo.reload.state }.from('pending').to('done')
end
it 'returns the ids of updated todos' do # Needed on API
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id])
+ expect(described_class.new.mark_todos_as_done([todo], john_doe)).to eq([todo.id])
end
context 'when some of the todos are done already' do
@@ -895,23 +895,23 @@ describe TodoService, services: true do
let!(:second_todo) { create(:todo, :mentioned, user: john_doe, target: another_issue, project: project) }
it 'returns the ids of those still pending' do
- TodoService.new.mark_pending_todos_as_done(issue, john_doe)
+ described_class.new.mark_pending_todos_as_done(issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
+ expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([second_todo.id])
end
it 'returns an empty array if all are done' do
- TodoService.new.mark_pending_todos_as_done(issue, john_doe)
- TodoService.new.mark_pending_todos_as_done(another_issue, john_doe)
+ described_class.new.mark_pending_todos_as_done(issue, john_doe)
+ described_class.new.mark_pending_todos_as_done(another_issue, john_doe)
- expect(TodoService.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
+ expect(described_class.new.mark_todos_as_done(Todo.all, john_doe)).to eq([])
end
end
- it 'caches the number of todos of a user', :caching do
+ it 'caches the number of todos of a user', :use_clean_rails_memory_store_caching do
create(:todo, :mentioned, user: john_doe, target: issue, project: project)
todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
- TodoService.new.mark_todos_as_done([todo], john_doe)
+ described_class.new.mark_todos_as_done([todo], john_doe)
expect_any_instance_of(TodosFinder).not_to receive(:execute)
diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb
index 69ed8de9c31..dc2d0e2d47a 100644
--- a/spec/services/update_release_service_spec.rb
+++ b/spec/services/update_release_service_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
-describe UpdateReleaseService, services: true do
+describe UpdateReleaseService do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
let(:new_description) { 'The best release!' }
- let(:service) { UpdateReleaseService.new(project, user) }
+ let(:service) { described_class.new(project, user) }
context 'with an existing release' do
let(:create_service) { CreateReleaseService.new(project, user) }
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 37c2e861362..ef535c5cf1f 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe UpdateSnippetService, services: true do
+describe UpdateSnippetService do
before do
@user = create :user
@admin = create :user, admin: true
diff --git a/spec/services/upload_service_spec.rb b/spec/services/upload_service_spec.rb
index 95ba28dbecd..cf76a18f171 100644
--- a/spec/services/upload_service_spec.rb
+++ b/spec/services/upload_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe UploadService, services: true do
+describe UploadService do
describe 'File service' do
before do
@user = create(:user)
diff --git a/spec/services/users/activity_service_spec.rb b/spec/services/users/activity_service_spec.rb
index 2e009d4ce1c..fef4da0c76e 100644
--- a/spec/services/users/activity_service_spec.rb
+++ b/spec/services/users/activity_service_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
-describe Users::ActivityService, services: true do
+describe Users::ActivityService do
include UserActivitiesHelpers
let(:user) { create(:user) }
subject(:service) { described_class.new(user, 'type') }
- describe '#execute', :redis do
+ describe '#execute', :clean_gitlab_redis_shared_state do
context 'when last activity is nil' do
before do
service.execute
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index 2a6bfc1b3a0..677d4a622e1 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Users::BuildService, services: true do
+describe Users::BuildService do
describe '#execute' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
diff --git a/spec/services/users/create_service_spec.rb b/spec/services/users/create_service_spec.rb
index 75746278573..24dac569678 100644
--- a/spec/services/users/create_service_spec.rb
+++ b/spec/services/users/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Users::CreateService, services: true do
+describe Users::CreateService do
describe '#execute' do
let(:admin_user) { create(:admin) }
diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb
index 5409f67c091..786335120fd 100644
--- a/spec/services/users/destroy_service_spec.rb
+++ b/spec/services/users/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Users::DestroyService, services: true do
+describe Users::DestroyService do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
diff --git a/spec/services/users/migrate_to_ghost_user_service_spec.rb b/spec/services/users/migrate_to_ghost_user_service_spec.rb
index 9e1edf1ac30..a0030ce8809 100644
--- a/spec/services/users/migrate_to_ghost_user_service_spec.rb
+++ b/spec/services/users/migrate_to_ghost_user_service_spec.rb
@@ -1,22 +1,38 @@
require 'spec_helper'
-describe Users::MigrateToGhostUserService, services: true do
+describe Users::MigrateToGhostUserService do
let!(:user) { create(:user) }
let!(:project) { create(:project) }
let(:service) { described_class.new(user) }
context "migrating a user's associated records to the ghost user" do
context 'issues' do
- include_examples "migrating a deleted user's associated records to the ghost user", Issue do
- let(:created_record) { create(:issue, project: project, author: user) }
- let(:assigned_record) { create(:issue, project: project, assignee: user) }
+ context 'deleted user is present as both author and edited_user' do
+ include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:author, :last_edited_by] do
+ let(:created_record) do
+ create(:issue, project: project, author: user, last_edited_by: user)
+ end
+ end
+ end
+
+ context 'deleted user is present only as edited_user' do
+ include_examples "migrating a deleted user's associated records to the ghost user", Issue, [:last_edited_by] do
+ let(:created_record) { create(:issue, project: project, author: create(:user), last_edited_by: user) }
+ end
end
end
context 'merge requests' do
- include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest do
- let(:created_record) { create(:merge_request, source_project: project, author: user, target_branch: "first") }
- let(:assigned_record) { create(:merge_request, source_project: project, assignee: user, target_branch: 'second') }
+ context 'deleted user is present as both author and merge_user' do
+ include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:author, :merge_user] do
+ let(:created_record) { create(:merge_request, source_project: project, author: user, merge_user: user, target_branch: "first") }
+ end
+ end
+
+ context 'deleted user is present only as both merge_user' do
+ include_examples "migrating a deleted user's associated records to the ghost user", MergeRequest, [:merge_user] do
+ let(:created_record) { create(:merge_request, source_project: project, merge_user: user, target_branch: "first") }
+ end
end
end
@@ -33,9 +49,8 @@ describe Users::MigrateToGhostUserService, services: true do
end
context 'award emoji' do
- include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji do
+ include_examples "migrating a deleted user's associated records to the ghost user", AwardEmoji, [:user] do
let(:created_record) { create(:award_emoji, user: user) }
- let(:author_alias) { :user }
context "when the awardable already has an award emoji of the same name assigned to the ghost user" do
let(:awardable) { create(:issue) }
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index b65cadbb2f5..1c0f55d2965 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -8,7 +8,7 @@ describe Users::RefreshAuthorizedProjectsService do
let(:user) { project.namespace.owner }
let(:service) { described_class.new(user) }
- describe '#execute', :redis do
+ describe '#execute', :clean_gitlab_redis_shared_state do
it 'refreshes the authorizations using a lease' do
expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain)
.and_return('foo')
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index 0b2f840c462..343804e3de0 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Users::UpdateService, services: true do
+describe Users::UpdateService do
let(:user) { create(:user) }
describe '#execute' do
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index b5abc46e80c..a5de86a0835 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WebHookService, services: true do
+describe WebHookService do
let(:project) { create(:empty_project) }
let(:project_hook) { create(:project_hook) }
let(:headers) do
@@ -12,7 +12,7 @@ describe WebHookService, services: true do
let(:data) do
{ before: 'oldrev', after: 'newrev', ref: 'ref' }
end
- let(:service_instance) { WebHookService.new(project_hook, data, 'push_hooks') }
+ let(:service_instance) { described_class.new(project_hook, data, 'push_hooks') }
describe '#execute' do
before(:each) do
@@ -58,7 +58,7 @@ describe WebHookService, services: true do
exception = exception_class.new('Exception message')
WebMock.stub_request(:post, project_hook.url).to_raise(exception)
- expect(service_instance.execute).to eq([nil, exception.message])
+ expect(service_instance.execute).to eq({ status: :error, message: exception.message })
expect { service_instance.execute }.not_to raise_error
end
end
@@ -66,13 +66,13 @@ describe WebHookService, services: true do
it 'handles 200 status code' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
- expect(service_instance.execute).to eq([200, 'Success'])
+ expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
end
it 'handles 2xx status codes' do
WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
- expect(service_instance.execute).to eq([201, 'Success'])
+ expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
end
context 'execution logging' do
@@ -114,7 +114,7 @@ describe WebHookService, services: true do
context 'should not log ServiceHooks' do
let(:service_hook) { create(:service_hook) }
- let(:service_instance) { WebHookService.new(service_hook, data, 'service_hook') }
+ let(:service_instance) { described_class.new(service_hook, data, 'service_hook') }
before do
WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
@@ -131,7 +131,7 @@ describe WebHookService, services: true do
it 'enqueue WebHookWorker' do
expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')
- WebHookService.new(project_hook, data, 'push_hooks').async_execute
+ described_class.new(project_hook, data, 'push_hooks').async_execute
end
end
end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 054e28ae7b0..fa3863e9b30 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WikiPages::CreateService, services: true do
+describe WikiPages::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index 920be4d4c8a..1668cd00ca8 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WikiPages::DestroyService, services: true do
+describe WikiPages::DestroyService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:page) { create(:wiki_page) }
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 5e36ea4cf94..a672c84034b 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe WikiPages::UpdateService, services: true do
+describe WikiPages::UpdateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:page) { create(:wiki_page) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index fdef6fd5221..e7329210896 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,7 +3,6 @@ SimpleCovEnv.start!
ENV["RAILS_ENV"] ||= 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
-# ENV['prometheus_multiproc_dir'] = 'tmp/prometheus_multiproc_dir_test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
@@ -57,8 +56,9 @@ RSpec.configure do |config|
config.include StubGitlabCalls
config.include StubGitlabData
config.include ApiHelpers, :api
- config.include Rails.application.routes.url_helpers, type: :routing
+ config.include Gitlab::Routing, type: :routing
config.include MigrationsHelpers, :migration
+ config.include StubFeatureFlags
config.infer_spec_type_from_file_location!
@@ -76,6 +76,13 @@ RSpec.configure do |config|
TestEnv.cleanup
end
+ config.before(:example) do
+ # Skip pre-receive hook check so we can use the web editor and merge.
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+ # Enable all features by default for testing
+ allow(Feature).to receive(:enabled?) { true }
+ end
+
config.before(:example, :request_store) do
RequestStore.begin!
end
@@ -91,20 +98,30 @@ RSpec.configure do |config|
end
end
- config.around(:each, :caching) do |example|
+ config.around(:each, :use_clean_rails_memory_store_caching) do |example|
caching_store = Rails.cache
- Rails.cache = ActiveSupport::Cache::MemoryStore.new if example.metadata[:caching]
+ Rails.cache = ActiveSupport::Cache::MemoryStore.new
+
example.run
+
Rails.cache = caching_store
end
- config.around(:each, :redis) do |example|
- Gitlab::Redis.with(&:flushall)
+ config.around(:each, :clean_gitlab_redis_cache) do |example|
+ Gitlab::Redis::Cache.with(&:flushall)
+
+ example.run
+
+ Gitlab::Redis::Cache.with(&:flushall)
+ end
+
+ config.around(:each, :clean_gitlab_redis_shared_state) do |example|
+ Gitlab::Redis::SharedState.with(&:flushall)
Sidekiq.redis(&:flushall)
example.run
- Gitlab::Redis.with(&:flushall)
+ Gitlab::Redis::SharedState.with(&:flushall)
Sidekiq.redis(&:flushall)
end
@@ -131,3 +148,10 @@ FactoryGirl::SyntaxRunner.class_eval do
end
ActiveRecord::Migration.maintain_test_schema!
+
+Shoulda::Matchers.configure do |config|
+ config.integrate do |with|
+ with.test_framework :rspec
+ with.library :rails
+ end
+end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/support/api/milestones_shared_examples.rb
index ab5ea3e8f2c..480e7d5151f 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -1,21 +1,14 @@
-require 'spec_helper'
-
-describe API::Milestones do
- let(:user) { create(:user) }
- let!(:project) { create(:empty_project, namespace: user.namespace ) }
- let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
- let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
+shared_examples_for 'group and project milestones' do |route_definition|
+ let(:resource_route) { "#{route}/#{milestone.id}" }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
let(:label_2) { create(:label, title: 'label_2', project: project, priority: 2) }
let(:label_3) { create(:label, title: 'label_3', project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:another_merge_request) { create(:merge_request, :simple, source_project: project) }
- before do
- project.team << [user, :developer]
- end
-
- describe 'GET /projects/:id/milestones' do
- it 'returns project milestones' do
- get api("/projects/#{project.id}/milestones", user)
+ describe "GET #{route_definition}" do
+ it 'returns milestones list' do
+ get api(route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -24,13 +17,13 @@ describe API::Milestones do
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones")
+ get api(route)
expect(response).to have_http_status(401)
end
it 'returns an array of active milestones' do
- get api("/projects/#{project.id}/milestones?state=active", user)
+ get api("#{route}/?state=active", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -40,7 +33,7 @@ describe API::Milestones do
end
it 'returns an array of closed milestones' do
- get api("/projects/#{project.id}/milestones?state=closed", user)
+ get api("#{route}/?state=closed", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -50,9 +43,9 @@ describe API::Milestones do
end
it 'returns an array of milestones specified by iids' do
- other_milestone = create(:milestone, project: project)
+ other_milestone = create(:milestone, project: try(:project), group: try(:group))
- get api("/projects/#{project.id}/milestones", user), iids: [closed_milestone.iid, other_milestone.iid]
+ get api(route, user), iids: [closed_milestone.iid, other_milestone.iid]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -61,25 +54,15 @@ describe API::Milestones do
end
it 'does not return any milestone if none found' do
- get api("/projects/#{project.id}/milestones", user), iids: [Milestone.maximum(:iid).succ]
+ get api(route, user), iids: [Milestone.maximum(:iid).succ]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- end
-
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
-
- expect(response).to have_http_status(200)
- expect(json_response['title']).to eq(milestone.title)
- expect(json_response['iid']).to eq(milestone.iid)
- end
- it 'returns a project milestone by iids array' do
- get api("/projects/#{project.id}/milestones?iids=#{closed_milestone.iid}", user)
+ it 'returns a milestone by iids array' do
+ get api("#{route}?iids=#{closed_milestone.iid}", user)
expect(response.status).to eq 200
expect(response).to include_pagination_headers
@@ -89,8 +72,8 @@ describe API::Milestones do
expect(json_response.first['id']).to eq closed_milestone.id
end
- it 'returns a project milestone by searching for title' do
- get api("/projects/#{project.id}/milestones", user), search: 'version2'
+ it 'returns a milestone by searching for title' do
+ get api(route, user), search: 'version2'
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -99,8 +82,8 @@ describe API::Milestones do
expect(json_response.first['id']).to eq milestone.id
end
- it 'returns a project milestones by searching for description' do
- get api("/projects/#{project.id}/milestones", user), search: 'open'
+ it 'returns a milestones by searching for description' do
+ get api(route, user), search: 'open'
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -110,9 +93,17 @@ describe API::Milestones do
end
end
- describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'returns a project milestone by id' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
+ describe "GET #{route_definition}/:milestone_id" do
+ it 'returns a milestone by id' do
+ get api(resource_route, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['title']).to eq(milestone.title)
+ expect(json_response['iid']).to eq(milestone.iid)
+ end
+
+ it 'returns a milestone by id' do
+ get api(resource_route, user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(milestone.title)
@@ -120,29 +111,29 @@ describe API::Milestones do
end
it 'returns 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}")
+ get api(resource_route)
expect(response).to have_http_status(401)
end
it 'returns a 404 error if milestone id not found' do
- get api("/projects/#{project.id}/milestones/1234", user)
+ get api("#{route}/1234", user)
expect(response).to have_http_status(404)
end
end
- describe 'POST /projects/:id/milestones' do
- it 'creates a new project milestone' do
- post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
+ describe "POST #{route_definition}" do
+ it 'creates a new milestone' do
+ post api(route, user), title: 'new milestone'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new milestone')
expect(json_response['description']).to be_nil
end
- it 'creates a new project milestone with description and dates' do
- post api("/projects/#{project.id}/milestones", user),
+ it 'creates a new milestone with description and dates' do
+ post api(route, user),
title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02'
expect(response).to have_http_status(201)
@@ -152,20 +143,20 @@ describe API::Milestones do
end
it 'returns a 400 error if title is missing' do
- post api("/projects/#{project.id}/milestones", user)
+ post api(route, user)
expect(response).to have_http_status(400)
end
it 'returns a 400 error if params are invalid (duplicate title)' do
- post api("/projects/#{project.id}/milestones", user),
+ post api(route, user),
title: milestone.title, description: 'release', due_date: '2013-03-02'
expect(response).to have_http_status(400)
end
- it 'creates a new project with reserved html characters' do
- post api("/projects/#{project.id}/milestones", user), title: 'foo & bar 1.1 -> 2.2'
+ it 'creates a new milestone with reserved html characters' do
+ post api(route, user), title: 'foo & bar 1.1 -> 2.2'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2')
@@ -173,9 +164,9 @@ describe API::Milestones do
end
end
- describe 'PUT /projects/:id/milestones/:milestone_id' do
- it 'updates a project milestone' do
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ describe "PUT #{route_definition}/:milestone_id" do
+ it 'updates a milestone' do
+ put api(resource_route, user),
title: 'updated title'
expect(response).to have_http_status(200)
@@ -185,23 +176,21 @@ describe API::Milestones do
it 'removes a due date if nil is passed' do
milestone.update!(due_date: "2016-08-05")
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user), due_date: nil
+ put api(resource_route, user), due_date: nil
expect(response).to have_http_status(200)
expect(json_response['due_date']).to be_nil
end
it 'returns a 404 error if milestone id not found' do
- put api("/projects/#{project.id}/milestones/1234", user),
+ put api("#{route}/1234", user),
title: 'updated title'
expect(response).to have_http_status(404)
end
- end
- describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do
- it 'updates a project milestone' do
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
+ it 'closes milestone' do
+ put api(resource_route, user),
state_event: 'close'
expect(response).to have_http_status(200)
@@ -209,21 +198,14 @@ describe API::Milestones do
end
end
- describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
- it 'creates an activity event when an milestone is closed' do
- expect(Event).to receive(:create)
-
- put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
- state_event: 'close'
- end
- end
+ describe "GET #{route_definition}/:milestone_id/issues" do
+ let(:issues_route) { "#{route}/#{milestone.id}/issues" }
- describe 'GET /projects/:id/milestones/:milestone_id/issues' do
before do
milestone.issues << create(:issue, project: project)
end
- it 'returns project issues for a particular milestone' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ it 'returns issues for a particular milestone' do
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -231,12 +213,12 @@ describe API::Milestones do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
- it 'returns project issues sorted by label priority' do
+ it 'returns issues sorted by label priority' do
issue_1 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_3])
issue_2 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_1])
issue_3 = create(:labeled_issue, project: project, milestone: milestone, labels: [label_2])
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(json_response.first['id']).to eq(issue_2.id)
expect(json_response.second['id']).to eq(issue_3.id)
@@ -244,44 +226,58 @@ describe API::Milestones do
end
it 'matches V4 response schema for a list of issues' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to match_response_schema('public_api/v4/issues')
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
+ get api(issues_route)
expect(response).to have_http_status(401)
end
describe 'confidential issues' do
- let(:public_project) { create(:empty_project, :public) }
- let(:milestone) { create(:milestone, project: public_project) }
- let(:issue) { create(:issue, project: public_project) }
- let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ let!(:public_project) { create(:empty_project, :public) }
+ let!(:context_group) { try(:group) }
+ let!(:milestone) do
+ context_group ? create(:milestone, group: context_group) : create(:milestone, project: public_project)
+ end
+ let!(:issue) { create(:issue, project: public_project) }
+ let!(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+ let!(:issues_route) do
+ if context_group
+ "#{route}/#{milestone.id}/issues"
+ else
+ "/projects/#{public_project.id}/milestones/#{milestone.id}/issues"
+ end
+ end
before do
+ # Add public project to the group in context
+ setup_for_group if context_group
+
public_project.team << [user, :developer]
milestone.issues << issue << confidential_issue
end
it 'returns confidential issues to team members' do
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(2)
+ # 2 for projects, 3 for group(which has another project with an issue)
+ expect(json_response.size).to be_between(2, 3)
expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
end
it 'does not return confidential issues to team members with guest role' do
member = create(:user)
- project.team << [member, :guest]
+ public_project.team << [member, :guest]
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", member)
+ get api(issues_route, member)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -291,7 +287,7 @@ describe API::Milestones do
end
it 'does not return confidential issues to regular users' do
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+ get api(issues_route, create(:user))
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
@@ -304,30 +300,30 @@ describe API::Milestones do
issue.labels << label_2
confidential_issue.labels << label_1
- get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+ get api(issues_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(json_response.size).to eq(2)
+ # 2 for projects, 3 for group(which has another project with an issue)
+ expect(json_response.size).to be_between(2, 3)
expect(json_response.first['id']).to eq(confidential_issue.id)
expect(json_response.second['id']).to eq(issue.id)
end
end
end
- describe 'GET /projects/:id/milestones/:milestone_id/merge_requests' do
- let(:merge_request) { create(:merge_request, source_project: project) }
- let(:another_merge_request) { create(:merge_request, :simple, source_project: project) }
+ describe "GET #{route_definition}/:milestone_id/merge_requests" do
+ let(:merge_requests_route) { "#{route}/#{milestone.id}/merge_requests" }
before do
milestone.merge_requests << merge_request
end
- it 'returns project merge_requests for a particular milestone' do
+ it 'returns merge_requests for a particular milestone' do
# eager-load another_merge_request
another_merge_request
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -336,12 +332,12 @@ describe API::Milestones do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
- it 'returns project merge_requests sorted by label priority' do
+ it 'returns merge_requests sorted by label priority' do
merge_request_1 = create(:labeled_merge_request, source_branch: 'branch_1', source_project: project, milestone: milestone, labels: [label_2])
merge_request_2 = create(:labeled_merge_request, source_branch: 'branch_2', source_project: project, milestone: milestone, labels: [label_1])
merge_request_3 = create(:labeled_merge_request, source_branch: 'branch_3', source_project: project, milestone: milestone, labels: [label_3])
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(json_response.first['id']).to eq(merge_request_2.id)
expect(json_response.second['id']).to eq(merge_request_1.id)
@@ -349,20 +345,22 @@ describe API::Milestones do
end
it 'returns a 404 error if milestone id not found' do
- get api("/projects/#{project.id}/milestones/1234/merge_requests", user)
+ not_found_route = "#{route}/1234/merge_requests"
+
+ get api(not_found_route, user)
expect(response).to have_http_status(404)
end
it 'returns a 404 if the user has no access to the milestone' do
new_user = create :user
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", new_user)
+ get api(merge_requests_route, new_user)
expect(response).to have_http_status(404)
end
it 'returns a 401 error if user not authenticated' do
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests")
+ get api(merge_requests_route)
expect(response).to have_http_status(401)
end
@@ -372,7 +370,7 @@ describe API::Milestones do
another_merge_request.labels << label_1
merge_request.labels << label_2
- get api("/projects/#{project.id}/milestones/#{milestone.id}/merge_requests", user)
+ get api(merge_requests_route, user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index dff0dfba675..67599f77adb 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -5,7 +5,14 @@ end
RSpec::Matchers.define :match_response_schema do |schema, **options|
match do |response|
- JSON::Validator.validate!(schema_path(schema), response.body, options)
+ @errors = JSON::Validator.fully_validate(schema_path(schema), response.body, options)
+
+ @errors.empty?
+ end
+
+ failure_message do |response|
+ "didn't match the schema defined by #{schema_path(schema)}" \
+ " The validation errors were:\n#{@errors.join("\n")}"
end
end
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
new file mode 100644
index 00000000000..3bd589d64b9
--- /dev/null
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -0,0 +1,79 @@
+shared_examples_for 'allows the "read_user" scope' do
+ context 'for personal access tokens' do
+ context 'when the requesting token has the "api" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['api'], user: user) }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token has the "read_user" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token does not have any required scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
+
+ it 'returns a "401" response' do
+ get api_call.call(path, user, personal_access_token: token)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'for doorkeeper (OAuth) tokens' do
+ let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
+
+ context 'when the requesting token has the "api" scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token has the "read_user" scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "read_user" }
+
+ it 'returns a "200" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'when the requesting token does not have any required scope' do
+ let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "invalid" }
+
+ it 'returns a "403" response' do
+ get api_call.call(path, user, oauth_access_token: token)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+end
+
+shared_examples_for 'does not allow the "read_user" scope' do
+ context 'when the requesting token has the "read_user" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "401" response' do
+ post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3)
+
+ expect(response).to have_http_status(401)
+ end
+ end
+end
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index 35d1e1cfc7d..ac0aaa524b7 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -17,14 +17,18 @@ module ApiHelpers
# => "/api/v2/issues?foo=bar&private_token=..."
#
# Returns the relative path to the requested API resource
- def api(path, user = nil, version: API::API.version)
+ def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
"/api/#{version}#{path}" +
# Normalize query string
(path.index('?') ? '' : '?') +
+ if personal_access_token.present?
+ "&private_token=#{personal_access_token.token}"
+ elsif oauth_access_token.present?
+ "&access_token=#{oauth_access_token.token}"
# Append private_token if given a User object
- if user.respond_to?(:private_token)
+ elsif user.respond_to?(:private_token)
"&private_token=#{user.private_token}"
else
''
@@ -32,8 +36,14 @@ module ApiHelpers
end
# Temporary helper method for simplifying V3 exclusive API specs
- def v3_api(path, user = nil)
- api(path, user, version: 'v3')
+ def v3_api(path, user = nil, personal_access_token: nil, oauth_access_token: nil)
+ api(
+ path,
+ user,
+ version: 'v3',
+ personal_access_token: personal_access_token,
+ oauth_access_token: oauth_access_token
+ )
end
def ci_api(path, user = nil)
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
index b57a3493aff..3eb7bea3227 100644
--- a/spec/support/capybara_helpers.rb
+++ b/spec/support/capybara_helpers.rb
@@ -35,6 +35,11 @@ module CapybaraHelpers
visit 'about:blank'
visit url
end
+
+ # Simulate a browser restart by clearing the session cookie.
+ def clear_browser_session
+ page.driver.remove_cookie('_gitlab_session')
+ end
end
RSpec.configure do |config|
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 6e1eb5c678d..c0a5491a430 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -74,7 +74,9 @@ module CycleAnalyticsHelpers
def dummy_pipeline
@dummy_pipeline ||=
- Ci::Pipeline.new(sha: project.repository.commit('master').sha)
+ Ci::Pipeline.new(
+ sha: project.repository.commit('master').sha,
+ project: project)
end
def new_dummy_job(environment)
diff --git a/spec/support/devise_helpers.rb b/spec/support/devise_helpers.rb
new file mode 100644
index 00000000000..890a2d9d287
--- /dev/null
+++ b/spec/support/devise_helpers.rb
@@ -0,0 +1,14 @@
+module DeviseHelpers
+ # explicitly tells Devise which mapping to use
+ # this is needed when we are testing a Devise controller bypassing the router
+ def set_devise_mapping(context:)
+ env =
+ if context.respond_to?(:env_config)
+ context.env_config
+ elsif context.respond_to?(:env)
+ context.env
+ end
+
+ env['devise.mapping'] = Devise.mappings[:user] if env
+ end
+end
diff --git a/spec/support/dropzone_helper.rb b/spec/support/dropzone_helper.rb
index 02fdeb08afe..fe72d320fcf 100644
--- a/spec/support/dropzone_helper.rb
+++ b/spec/support/dropzone_helper.rb
@@ -54,4 +54,23 @@ module DropzoneHelper
loop until page.evaluate_script('window._dropzoneComplete === true')
end
end
+
+ def drop_in_dropzone(file_path)
+ # Generate a fake input selector
+ page.execute_script <<-JS
+ var fakeFileInput = window.$('<input/>').attr(
+ {id: 'fakeFileInput', type: 'file'}
+ ).appendTo('body');
+ JS
+
+ # Attach the file to the fake input selector with Capybara
+ attach_file('fakeFileInput', file_path)
+
+ # Add the file to a fileList array and trigger the fake drop event
+ page.execute_script <<-JS
+ var fileList = [$('#fakeFileInput')[0].files[0]];
+ var e = jQuery.Event('drop', { dataTransfer : { files : fileList } });
+ $('.dropzone')[0].dropzone.listeners[0].events.drop(e);
+ JS
+ end
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 98b014df6cd..035428a7d9b 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -5,8 +5,6 @@ shared_examples 'issuable record that supports quick actions in its description
include QuickActionsHelpers
let(:master) { create(:user) }
- let(:assignee) { create(:user, username: 'bob') }
- let(:guest) { create(:user) }
let(:project) { create(:project, :public) }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let!(:label_bug) { create(:label, project: project, title: 'bug') }
@@ -15,9 +13,8 @@ shared_examples 'issuable record that supports quick actions in its description
before do
project.team << [master, :master]
- project.team << [assignee, :developer]
- project.team << [guest, :guest]
- gitlab_sign_in(master)
+
+ sign_in(master)
end
after do
@@ -56,6 +53,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing commands' do
it 'creates a note without the commands and interpret the commands accordingly' do
+ assignee = create(:user, username: 'bob')
write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
expect(page).to have_content 'Awesome!'
@@ -76,6 +74,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing only commands' do
it 'does not create a note but interpret the commands accordingly' do
+ assignee = create(:user, username: 'bob')
write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
expect(page).not_to have_content '/assign @bob'
@@ -110,8 +109,12 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot close #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ guest = create(:user)
+ project.add_guest(guest)
+
+ sign_out(:user)
+ sign_in(guest)
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -145,8 +148,12 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot reopen #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ guest = create(:user)
+ project.add_guest(guest)
+
+ sign_out(:user)
+ sign_in(guest)
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -175,8 +182,11 @@ shared_examples 'issuable record that supports quick actions in its description
context "when current user cannot change title of #{issuable_type}" do
before do
- gitlab_sign_out
- gitlab_sign_in(guest)
+ guest = create(:user)
+ project.add_guest(guest)
+
+ sign_out(:user)
+ sign_in(guest)
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
@@ -266,6 +276,8 @@ shared_examples 'issuable record that supports quick actions in its description
describe "preview of note on #{issuable_type}" do
it 'removes quick actions from note and explains them' do
+ create(:user, username: 'bob')
+
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
page.within('.js-main-target-form') do
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 1cbb4134995..50fbbc7f55b 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,12 +1,12 @@
shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
it "has an RSS autodiscovery link tag with current_user's RSS token" do
- expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false)
+ expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false)
end
end
shared_examples "it has an RSS button with current_user's RSS token" do
it "shows the RSS button with current_user's RSS token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']")
+ expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']")
end
end
diff --git a/spec/support/forgery_protection.rb b/spec/support/forgery_protection.rb
new file mode 100644
index 00000000000..a5e7b761651
--- /dev/null
+++ b/spec/support/forgery_protection.rb
@@ -0,0 +1,11 @@
+RSpec.configure do |config|
+ config.around(:each, :allow_forgery_protection) do |example|
+ begin
+ ActionController::Base.allow_forgery_protection = true
+
+ example.call
+ ensure
+ ActionController::Base.allow_forgery_protection = false
+ end
+ end
+end
diff --git a/spec/support/gitaly.rb b/spec/support/gitaly.rb
index 2bf159002a0..89fb362cf14 100644
--- a/spec/support/gitaly.rb
+++ b/spec/support/gitaly.rb
@@ -1,8 +1,6 @@
-if Gitlab::GitalyClient.enabled?
- RSpec.configure do |config|
- config.before(:each) do |example|
- next if example.metadata[:skip_gitaly_mock]
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
- end
+RSpec.configure do |config|
+ config.before(:each) do |example|
+ next if example.metadata[:skip_gitaly_mock]
+ allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
end
end
diff --git a/spec/support/gpg_helpers.rb b/spec/support/gpg_helpers.rb
new file mode 100644
index 00000000000..96ea6f28b30
--- /dev/null
+++ b/spec/support/gpg_helpers.rb
@@ -0,0 +1,202 @@
+module GpgHelpers
+ module User1
+ extend self
+
+ def signed_commit_signature
+ <<~SIGNATURE
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG v1
+
+ iJwEAAECAAYFAliu264ACgkQzPvhnwCsix1VXgP9F6zwAMb3OXKZzqGxJ4MQIBoL
+ OdiUSJpL/4sIA9uhFeIv3GIA+uhsG1BHHsG627+sDy7b8W9VWEd7tbcoz4Mvhf3P
+ 8g0AIt9/KJuStQZDrXwP1uP6Rrl759nDcNpoOKdSQ5EZ1zlRzeDROlZeDp7Ckfvw
+ GLmN/74Gl3pk0wfgHFY=
+ =wSgS
+ -----END PGP SIGNATURE-----
+ SIGNATURE
+ end
+
+ def signed_commit_base_data
+ <<~SIGNEDDATA
+ tree ed60cfd202644fda1abaf684e7d965052db18c13
+ parent caf6a0334a855e12f30205fff3d7333df1f65127
+ author Nannie Bernhard <nannie.bernhard@example.com> 1487854510 +0100
+ committer Nannie Bernhard <nannie.bernhard@example.com> 1487854510 +0100
+
+ signed commit, verified key/email
+ SIGNEDDATA
+ end
+
+ def secret_key
+ <<~KEY.strip
+ -----BEGIN PGP PRIVATE KEY BLOCK-----
+ Version: GnuPG v1
+
+ lQHYBFiu1ScBBADUhWsrlWHp5e7ASlI5iMcA0XN43fivhVlGYJJy4Ii3Hr2i4f5s
+ VffHS8QyhgxxzSnPwe2OKnZWWL9cHzUFbiG3fHalEBTjpB+7pG4HBgU8R/tiDOu8
+ vkAR+tfJbkuRs9XeG3dGKBX/8WRhIfRucYnM+04l2Myyo5zIx7thJmxXjwARAQAB
+ AAP/XUtcqrtfSnDYCK4Xvo4e3msUSAEZxOPDNzP51lhfbBQgp7qSGDj9Fw5ZyNwz
+ 5llse3nksT5OyMUY7HX+rq2UOs12a/piLqvhtX1okp/oTAETmKXNYkZLenv6t94P
+ NqLi0o2AnXAvL9ueXa7WUY3l4DkvuLcjT4+9Ut2Y71zIjeECAN7q9ohNL7E8tNkf
+ Elsbx+8KfyHRQXiSUYaQLlvDRq2lYCKIS7sogTqjZMEgbZx2mRX1fakRlvcmqOwB
+ QoX34zcCAPQPd+yTteNUV12uvDaj8V9DICktPPhbHdYYaUoHjF8RrIHCTRUPzk9E
+ KzCL9dUP8eXPPBV/ty+zjUwl69IgCmkB/3pnNZ0D4EJsNgu24UgI0N+c8H/PE1D6
+ K+bGQ/jK83uYPMXJUsiojssCHLGNp7eBGHFn1PpEqZphgVI50ZMrZQWhJbQtTmFu
+ bmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhhcmRAZXhhbXBsZS5jb20+iLgEEwEC
+ ACIFAliu1ScCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEMz74Z8ArIsd
+ p5ID/32hRalvTY+V+QAtzHlGdxugweSBzNgRT3A4UiC9chF6zBOEIw689lqmK6L4
+ i3Il9XeKMl87wi9tsVy9TuOMYDTvcFvu1vMAQ5AsDXqZaAEtCUZpFZscNbi7AXG+
+ QkoDQbMSxp0Rd6eIRJpk9zis5co87f78xJBZLZua+8awFMS6nQHYBFiu1ScBBADI
+ XkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUhYl6Q
+ XQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJitLY4w
+ /nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABAAP+IoZfU1XUdVbr
+ +RPWp3ny5SekviDPu8co9BZ4ANTh5+8wyfA3oNbGUxTlYthoU07MZYqq+/k63R28
+ 6HgVGC3gdvCiRMGmryIQ6roLLRXkfzjXrI7Lgnhx4OtVjo62pAKDqdl45wEa1Q+M
+ v08CQF6XNpb5R9Xszz4aBC4eV0KjtjkCANlGSQHZ1B81g+iltj1FAhRHkyUFlrc1
+ cqLVhNgxtHZ96+R57Uk2A7dIJBsE00eIYaHOfk5X5GD/95s1QvPcQskCAOwUk5xj
+ NeQ6VV/1+cI91TrWU6VnT2Yj8632fM/JlKKfaS15pp8t5Ha6pNFr3xD4KgQutchq
+ fPsEOjaU7nwQ/i8B/1rDPTYfNXFpRNt33WAB1XtpgOIHlpmOfaYYqf6lneTlZWBc
+ TgyO+j+ZsHAvP18ugIRkU8D192NflzgAGwXLryijyYifBBgBAgAJBQJYrtUnAhsM
+ AAoJEMz74Z8ArIsdlkUEALTl6QUutJsqwVF4ZXKmmw0IEk8PkqW4G+tYRDHJMs6Z
+ O0nzDS89BG2DL4/UlOs5wRvERnlJYz01TMTxq/ciKaBTEjygFIv9CgIEZh97VacZ
+ TIqcF40k9SbpJNnh3JLf94xsNxNRJTEhbVC3uruaeILue/IR7pBMEyCs49Gcguwy
+ =b6UD
+ -----END PGP PRIVATE KEY BLOCK-----
+ KEY
+ end
+
+ def public_key
+ <<~KEY.strip
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1
+
+ mI0EWK7VJwEEANSFayuVYenl7sBKUjmIxwDRc3jd+K+FWUZgknLgiLcevaLh/mxV
+ 98dLxDKGDHHNKc/B7Y4qdlZYv1wfNQVuIbd8dqUQFOOkH7ukbgcGBTxH+2IM67y+
+ QBH618luS5Gz1d4bd0YoFf/xZGEh9G5xicz7TiXYzLKjnMjHu2EmbFePABEBAAG0
+ LU5hbm5pZSBCZXJuaGFyZCA8bmFubmllLmJlcm5oYXJkQGV4YW1wbGUuY29tPoi4
+ BBMBAgAiBQJYrtUnAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDM++Gf
+ AKyLHaeSA/99oUWpb02PlfkALcx5RncboMHkgczYEU9wOFIgvXIReswThCMOvPZa
+ piui+ItyJfV3ijJfO8IvbbFcvU7jjGA073Bb7tbzAEOQLA16mWgBLQlGaRWbHDW4
+ uwFxvkJKA0GzEsadEXeniESaZPc4rOXKPO3+/MSQWS2bmvvGsBTEuriNBFiu1ScB
+ BADIXkITf+kKCkD+n8tMsdTLInefu8KrJ8p7YRYCCabEXnWRsDb5zxUAG2VXCVUh
+ Yl6QXQybkNiBaduS+uxilz7gtYZUMFJvQ09+fV7D2N9B7u/1bGdIYz+cDFJnEJit
+ LY4w/nju2Sno5CL5Ead8sZuslKetSXPYHR/kbW462EOw5wARAQABiJ8EGAECAAkF
+ Aliu1ScCGwwACgkQzPvhnwCsix2WRQQAtOXpBS60myrBUXhlcqabDQgSTw+Spbgb
+ 61hEMckyzpk7SfMNLz0EbYMvj9SU6znBG8RGeUljPTVMxPGr9yIpoFMSPKAUi/0K
+ AgRmH3tVpxlMipwXjST1Jukk2eHckt/3jGw3E1ElMSFtULe6u5p4gu578hHukEwT
+ IKzj0ZyC7DI=
+ =Ug0r
+ -----END PGP PUBLIC KEY BLOCK-----
+ KEY
+ end
+
+ def primary_keyid
+ fingerprint[-16..-1]
+ end
+
+ def fingerprint
+ '5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D'
+ end
+
+ def names
+ ['Nannie Bernhard']
+ end
+
+ def emails
+ ['nannie.bernhard@example.com']
+ end
+ end
+
+ module User2
+ extend self
+
+ def private_key
+ <<~KEY.strip
+ -----BEGIN PGP PRIVATE KEY BLOCK-----
+ Version: GnuPG v1
+
+ lQHYBFiuqioBBADg46jkiATWMy9t1npxFWJ77xibPXdUo36LAZgZ6uGungSzcFL4
+ 50bdEyMMGm5RJp6DCYkZlwQDlM//YEqwf0Cmq/AibC5m9bHr7hf5sMxl40ssJ4fj
+ dzT6odihO0vxD2ARSrtiwkESzFxjJ51mjOfdPvAGf0ucxzgeRfUlCrM3kwARAQAB
+ AAP8CJlDFnbywR9dWfqBxi19sFMOk/smCObNQanuTcx6CDcu4zHi0Yxx6BoNCQES
+ cDRCLX5HevnpZngzQB3qa7dga+yqxKzwO8v0P0hliL81B1ZVXUk9TWhBj3NS3m3v
+ +kf2XeTxuZFb9fj44/4HpfbQ2yazTs/Xa+/ZeMqFPCYSNEECAOtjIbwHdfjkpVWR
+ uiwphRkNimv5hdObufs63m9uqhpKPdPKmr2IXgahPZg5PooxqE0k9IXaX2pBsJUF
+ DyuL1dsCAPSVL+YAOviP8ecM1jvdKpkFDd67kR5C+7jEvOGl+c2aX3qLvKt62HPR
+ +DxvYE0Oy0xfoHT14zNSfqthmlhIPqkB/i4WyJaafQVvkkoA9+A5aXbyihOR+RTx
+ p+CMNYvaAplFAyey7nv8l5+la/N+Sv86utjaenLZmCf34nDQEZy7rFWny7QvQmV0
+ dGUgQ2FydHdyaWdodCA8YmV0dGUuY2FydHdyaWdodEBleGFtcGxlLmNvbT6IuAQT
+ AQIAIgUCWK6qKgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQv52SX5Ee
+ /WVCGwP/QsOLTTyEJ6hl0Yy7DLY3kUxS6xiD9fW1FDoTQlxhiO+8TmghmhdtU3TI
+ ssP30/Su3pNKW3TkILtE9U8I2krEpsX5NkyMwmI6LXdeZjli2Lvtkx0Fm0Psd4HO
+ ORYJW5HqTx4jDLzeeIcYjqnobztDpfG8ONDvB0EI0GnCTOZNggG0L0JldHRlIENh
+ cnR3cmlnaHQgPGJldHRlLmNhcnR3cmlnaHRAZXhhbXBsZS5uZXQ+iLgEEwECACIF
+ AlivAsUCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEL+dkl+RHv1lXOwE
+ ANh7ce/vUjv6VMkO8o5OZXszhKE5+MSmYO8v/kkHcXNccC5wI6VF4K//r41p8Cyk
+ 9NzW7Kzjt2+14/BBqWugCx3xjWCuf88KH5PHbuSmfVYbzJmNSy6rfPmusZG5ePqD
+ xp5l2qQxMdRUX0Z36D/koM4N0ls6PAf6Xrdv9s6IBMMVnQHYBFiuqioBBADe5nUd
+ VOcbZlnxOjl0KBAT+A5bmyBLUT0BmLPsmA4PuXDSth7WvibPC8wcCdCYVk0IRMYn
+ eZUiWq/o5c4rthfLR4jg8kruvomQ4E4d4hyI6R0MLxXYZ3XMu67VuScFgbLURw1e
+ RZ16ANd3Nc1VuFW7ms0vCG0idB8iSZBoULaK8QARAQABAAP5AdCfUT/y2kmi75iF
+ ZX1ahSkax9LraEWW8TOCuolR6v2b7jFKrr2xX/P1A2DulID2Y1v4/5MJPHR/1G4D
+ l95Fkw+iGsTvKB5rPG5xye0vOYbbujRa6B9LL6s4Taf486shEegOrdjN9FIweM6f
+ vuVaDYzIk8Qwv5/sStEBxx8rxIkCAOBftFi56AY0gLniyEMAvVRjyVeOZPPJbS8i
+ v6L9asJB5wdsGJxJVyUZ/ylar5aCS7sroOcYTN2b1tOPoWuGqIkCAP5RlDRgm3Zg
+ xL6hXejqZp3G1/DXhKBSI/yUTR/D89H5/qNQe3W7dZqns9mSAJNtqOu+UMZ5UreY
+ Ond0/dmL5SkCAOO5r6gXM8ZDcNjydlQexCLnH70yVkCL6hG9Va1gOuFyUztRnCd+
+ E35YRCEwZREZDr87BRr2Aak5t+lb1EFVqV+nvYifBBgBAgAJBQJYrqoqAhsMAAoJ
+ EL+dkl+RHv1lQggEANWwQwrlT2BFLWV8Fx+wlg31+mcjkTq0LaWu3oueAluoSl93
+ 2B6ToruMh66JoxpSDU44x3JbCaZ/6poiYs5Aff8ZeyEVlfkVaQ7IWd5spjpXaS4i
+ oCOfkZepmbTuE7TPQWM4iBAtuIfiJGiwcpWWM+KIH281yhfCcbRzzFLsCVQx
+ =yEqv
+ -----END PGP PRIVATE KEY BLOCK-----
+ KEY
+ end
+
+ def public_key
+ <<~KEY.strip
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1
+
+ mI0EWK6qKgEEAODjqOSIBNYzL23WenEVYnvvGJs9d1SjfosBmBnq4a6eBLNwUvjn
+ Rt0TIwwablEmnoMJiRmXBAOUz/9gSrB/QKar8CJsLmb1sevuF/mwzGXjSywnh+N3
+ NPqh2KE7S/EPYBFKu2LCQRLMXGMnnWaM590+8AZ/S5zHOB5F9SUKszeTABEBAAG0
+ L0JldHRlIENhcnR3cmlnaHQgPGJldHRlLmNhcnR3cmlnaHRAZXhhbXBsZS5jb20+
+ iLgEEwECACIFAliuqioCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEL+d
+ kl+RHv1lQhsD/0LDi008hCeoZdGMuwy2N5FMUusYg/X1tRQ6E0JcYYjvvE5oIZoX
+ bVN0yLLD99P0rt6TSlt05CC7RPVPCNpKxKbF+TZMjMJiOi13XmY5Yti77ZMdBZtD
+ 7HeBzjkWCVuR6k8eIwy83niHGI6p6G87Q6XxvDjQ7wdBCNBpwkzmTYIBtC9CZXR0
+ ZSBDYXJ0d3JpZ2h0IDxiZXR0ZS5jYXJ0d3JpZ2h0QGV4YW1wbGUubmV0Poi4BBMB
+ AgAiBQJYrwLFAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRC/nZJfkR79
+ ZVzsBADYe3Hv71I7+lTJDvKOTmV7M4ShOfjEpmDvL/5JB3FzXHAucCOlReCv/6+N
+ afAspPTc1uys47dvtePwQalroAsd8Y1grn/PCh+Tx27kpn1WG8yZjUsuq3z5rrGR
+ uXj6g8aeZdqkMTHUVF9Gd+g/5KDODdJbOjwH+l63b/bOiATDFbiNBFiuqioBBADe
+ 5nUdVOcbZlnxOjl0KBAT+A5bmyBLUT0BmLPsmA4PuXDSth7WvibPC8wcCdCYVk0I
+ RMYneZUiWq/o5c4rthfLR4jg8kruvomQ4E4d4hyI6R0MLxXYZ3XMu67VuScFgbLU
+ Rw1eRZ16ANd3Nc1VuFW7ms0vCG0idB8iSZBoULaK8QARAQABiJ8EGAECAAkFAliu
+ qioCGwwACgkQv52SX5Ee/WVCCAQA1bBDCuVPYEUtZXwXH7CWDfX6ZyOROrQtpa7e
+ i54CW6hKX3fYHpOiu4yHromjGlINTjjHclsJpn/qmiJizkB9/xl7IRWV+RVpDshZ
+ 3mymOldpLiKgI5+Rl6mZtO4TtM9BYziIEC24h+IkaLBylZYz4ogfbzXKF8JxtHPM
+ UuwJVDE=
+ =0vYo
+ -----END PGP PUBLIC KEY BLOCK-----
+ KEY
+ end
+
+ def primary_keyid
+ fingerprint[-16..-1]
+ end
+
+ def fingerprint
+ '6D494CA6FC90C0CAE0910E42BF9D925F911EFD65'
+ end
+
+ def names
+ ['Bette Cartwright', 'Bette Cartwright']
+ end
+
+ def emails
+ ['bette.cartwright@example.com', 'bette.cartwright@example.net']
+ end
+ end
+end
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
index 03011535351..970fe10db2b 100644
--- a/spec/support/issuable_shared_examples.rb
+++ b/spec/support/issuable_shared_examples.rb
@@ -5,3 +5,34 @@ shared_examples 'cache counters invalidator' do
described_class.new(project, user, {}).execute(merge_request)
end
end
+
+shared_examples 'system notes for milestones' do
+ def update_issuable(opts)
+ issuable = try(:issue) || try(:merge_request)
+ described_class.new(project, user, opts).execute(issuable)
+ end
+
+ context 'group milestones' do
+ let(:group) { create(:group) }
+ let(:group_milestone) { create(:milestone, group: group) }
+
+ before do
+ project.update(namespace: group)
+ create(:group_member, group: group, user: user)
+ end
+
+ it 'does not create system note' do
+ expect do
+ update_issuable(milestone: group_milestone)
+ end.not_to change { Note.system.count }
+ end
+ end
+
+ context 'project milestones' do
+ it 'creates system note' do
+ expect do
+ update_issuable(milestone: create(:milestone))
+ end.to change { Note.system.count }.by(1)
+ end
+ end
+end
diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb
index 85241793743..ffd72515f37 100644
--- a/spec/support/issue_helpers.rb
+++ b/spec/support/issue_helpers.rb
@@ -1,6 +1,6 @@
module IssueHelpers
def visit_issues(project, opts = {})
- visit namespace_project_issues_path project.namespace, project, opts
+ visit project_issues_path project, opts
end
def first_issue
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
index 97ae0b6afc5..0b5f66597fd 100644
--- a/spec/support/jira_service_helper.rb
+++ b/spec/support/jira_service_helper.rb
@@ -51,7 +51,7 @@ module JiraServiceHelper
end
def jira_project_url
- JIRA_API + "/project/#{jira_tracker.project_key}"
+ JIRA_API + "/project"
end
def jira_api_comment_url(issue_id)
diff --git a/spec/support/json_response_helpers.rb b/spec/support/json_response_helpers.rb
index e8d2ef2d7f0..aa235529c56 100644
--- a/spec/support/json_response_helpers.rb
+++ b/spec/support/json_response_helpers.rb
@@ -3,7 +3,7 @@ shared_context 'JSON response' do
end
RSpec.configure do |config|
- config.include_context 'JSON response', type: :controller
+ config.include_context 'JSON response'
config.include_context 'JSON response', type: :request
config.include_context 'JSON response', :api
end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 4c88958264b..c714d1b08a6 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -1,4 +1,6 @@
module LoginHelpers
+ include DeviseHelpers
+
# Internal: Log in as a specific user or a new user of a specific role
#
# user_or_role - User object, or a role to create (e.g., :admin, :user)
@@ -15,14 +17,16 @@ module LoginHelpers
# user = create(:user)
# gitlab_sign_in(user)
def gitlab_sign_in(user_or_role, **kwargs)
- @user =
+ user =
if user_or_role.is_a?(User)
user_or_role
else
create(user_or_role)
end
- gitlab_sign_in_with(@user, **kwargs)
+ gitlab_sign_in_with(user, **kwargs)
+
+ user
end
def gitlab_sign_in_via(provider, user, uid)
@@ -35,13 +39,8 @@ module LoginHelpers
def gitlab_sign_out
find(".header-user-dropdown-toggle").click
click_link "Sign out"
- # check the sign_in button
- expect(page).to have_button('Sign in')
- end
- # Logout without JavaScript driver
- def gitlab_sign_out_direct
- page.driver.submit :delete, '/users/sign_out', {}
+ expect(page).to have_button('Sign in')
end
private
@@ -58,8 +57,16 @@ module LoginHelpers
check 'user_remember_me' if remember
click_button "Sign in"
+ end
+
+ def login_via(provider, user, uid, remember_me: false)
+ mock_auth_hash(provider, uid, user.email)
+ visit new_user_session_path
+ expect(page).to have_content('Sign in with')
+
+ check 'remember_me' if remember_me
- Thread.current[:current_user] = user
+ click_link "oauth-login-#{provider}"
end
def mock_auth_hash(provider, uid, email)
@@ -101,13 +108,14 @@ module LoginHelpers
end
def stub_omniauth_saml_config(messages)
- Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
stub_omniauth_setting(messages)
- expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
+ allow_any_instance_of(Object).to receive(:user_saml_omniauth_authorize_path).and_return('/users/auth/saml')
+ allow_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
end
diff --git a/spec/support/malicious_regexp_shared_examples.rb b/spec/support/malicious_regexp_shared_examples.rb
new file mode 100644
index 00000000000..ac5d22298bb
--- /dev/null
+++ b/spec/support/malicious_regexp_shared_examples.rb
@@ -0,0 +1,8 @@
+shared_examples 'malicious regexp' do
+ let(:malicious_text) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!' }
+ let(:malicious_regexp) { '(?i)^(([a-z])+.)+[A-Z]([a-z])+$' }
+
+ it 'takes under a second' do
+ expect { Timeout.timeout(1) { subject } }.not_to raise_error
+ end
+end
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index fb43f51c70c..ff60bd0c0ae 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -50,9 +50,24 @@ module AccessMatchersForController
"be #{type} for #{role}. Expected: #{expected.join(',')} Got: #{result}"
end
+ def update_owner(objects, user)
+ return unless objects
+
+ objects.each do |object|
+ if object.respond_to?(:owner)
+ object.update_attribute(:owner, user)
+ elsif object.respond_to?(:user)
+ object.update_attribute(:user, user)
+ else
+ raise ArgumentError, "cannot own this object #{object}"
+ end
+ end
+ end
+
matcher :be_allowed_for do |role|
match do |action|
- emulate_user(role, @membership)
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
action.call
EXPECTED_STATUS_CODE_ALLOWED.include?(response.status)
@@ -62,13 +77,18 @@ module AccessMatchersForController
@membership = membership
end
+ chain :own do |*objects|
+ @objects = objects
+ end
+
description { description_for(role, 'allowed', EXPECTED_STATUS_CODE_ALLOWED, response.status) }
supports_block_expectations
end
matcher :be_denied_for do |role|
match do |action|
- emulate_user(role, @membership)
+ user = emulate_user(role, @membership)
+ update_owner(@objects, user)
action.call
EXPECTED_STATUS_CODE_DENIED.include?(response.status)
@@ -78,6 +98,10 @@ module AccessMatchersForController
@membership = membership
end
+ chain :own do |*objects|
+ @objects = objects
+ end
+
description { description_for(role, 'denied', EXPECTED_STATUS_CODE_DENIED, response.status) }
supports_block_expectations
end
diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb
new file mode 100644
index 00000000000..3198f1b9edd
--- /dev/null
+++ b/spec/support/matchers/have_gitlab_http_status.rb
@@ -0,0 +1,14 @@
+RSpec::Matchers.define :have_gitlab_http_status do |expected|
+ match do |actual|
+ expect(actual).to have_http_status(expected)
+ end
+
+ description do
+ "respond with numeric status code #{expected}"
+ end
+
+ failure_message do |actual|
+ "expected the response to have status code #{expected.inspect}" \
+ " but it was #{actual.response_code}. The response was: #{actual.body}"
+ end
+end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index bbbbaf4c5e8..7afa57fb76b 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -17,7 +17,7 @@ module MarkdownMatchers
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
- expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
+ expect(image['data-src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
end
end
@@ -70,7 +70,7 @@ module MarkdownMatchers
# GollumTagsFilter
matcher :parse_gollum_tags do
def have_image(src)
- have_css("img[src$='#{src}']")
+ have_css("img[data-src$='#{src}']")
end
prefix = '/namespace1/gitlabhq/wikis'
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
index 326b85eabd0..772adff4626 100644
--- a/spec/support/merge_request_helpers.rb
+++ b/spec/support/merge_request_helpers.rb
@@ -1,6 +1,6 @@
module MergeRequestHelpers
def visit_merge_requests(project, opts = {})
- visit namespace_project_merge_requests_path project.namespace, project, opts
+ visit project_merge_requests_path project, opts
end
def first_merge_request
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 1d11512ef82..421a51fc336 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,7 +1,7 @@
RSpec.shared_examples "protected tags > access control > CE" do
ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected tags that #{access_type_name} can create" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('master')
@@ -22,7 +22,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
end
it "allows updating protected tags so that #{access_type_name} can create them" do
- visit namespace_project_protected_tags_path(project.namespace, project)
+ visit project_protected_tags_path(project)
set_protected_tag_name('master')
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/support/redis/redis_shared_examples.rb
index 593aa5038ad..f9552e41894 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -1,12 +1,10 @@
-require 'spec_helper'
-
-describe Gitlab::Redis do
+RSpec.shared_examples "redis_shared_examples" do
include StubENV
- let(:config) { 'config/resque.yml' }
+ let(:test_redis_url) { "redis://redishost:#{redis_port}"}
before(:each) do
- stub_env('GITLAB_REDIS_CONFIG_FILE', Rails.root.join(config).to_s)
+ stub_env(environment_config_file_name, Rails.root.join(config_file_name))
clear_raw_config
end
@@ -26,46 +24,40 @@ describe Gitlab::Redis do
end
context 'when url contains unix socket reference' do
- let(:config_old) { 'spec/fixtures/config/redis_old_format_socket.yml' }
- let(:config_new) { 'spec/fixtures/config/redis_new_format_socket.yml' }
-
context 'with old format' do
- let(:config) { config_old }
+ let(:config_file_name) { config_old_format_socket }
it 'returns path key instead' do
- is_expected.to include(path: '/path/to/old/redis.sock')
+ is_expected.to include(path: old_socket_path)
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- let(:config) { config_new }
+ let(:config_file_name) { config_new_format_socket }
it 'returns path key instead' do
- is_expected.to include(path: '/path/to/redis.sock')
+ is_expected.to include(path: new_socket_path)
is_expected.not_to have_key(:url)
end
end
end
context 'when url is host based' do
- let(:config_old) { 'spec/fixtures/config/redis_old_format_host.yml' }
- let(:config_new) { 'spec/fixtures/config/redis_new_format_host.yml' }
-
context 'with old format' do
- let(:config) { config_old }
+ let(:config_file_name) { config_old_format_host }
it 'returns hash with host, port, db, and password' do
- is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
+ is_expected.to include(host: 'localhost', password: 'mypassword', port: redis_port, db: redis_database)
is_expected.not_to have_key(:url)
end
end
context 'with new format' do
- let(:config) { config_new }
+ let(:config_file_name) { config_new_format_host }
it 'returns hash with host, port, db, and password' do
- is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
+ is_expected.to include(host: 'localhost', password: 'mynewpassword', port: redis_port, db: redis_database)
is_expected.not_to have_key(:url)
end
end
@@ -73,30 +65,22 @@ describe Gitlab::Redis do
end
describe '.url' do
- it 'withstands mutation' do
- url1 = described_class.url
- url2 = described_class.url
- url1 << 'foobar'
-
- expect(url2).not_to end_with('foobar')
- end
-
context 'when yml file with env variable' do
- let(:config) { 'spec/fixtures/config/redis_config_with_env.yml' }
+ let(:config_file_name) { config_with_environment_variable_inside }
before do
- stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
+ stub_env(config_env_variable_url, test_redis_url)
end
it 'reads redis url from env variable' do
- expect(described_class.url).to eq 'redis://redishost:6379'
+ expect(described_class.url).to eq test_redis_url
end
end
end
describe '._raw_config' do
subject { described_class._raw_config }
- let(:config) { '/var/empty/doesnotexist' }
+ let(:config_file_name) { '/var/empty/doesnotexist' }
it 'should be frozen' do
expect(subject).to be_frozen
@@ -105,6 +89,12 @@ describe Gitlab::Redis do
it 'returns false when the file does not exist' do
expect(subject).to eq(false)
end
+
+ it "returns false when the filename can't be determined" do
+ expect(described_class).to receive(:config_file_name).and_return(nil)
+
+ expect(subject).to eq(false)
+ end
end
describe '.with' do
@@ -124,7 +114,7 @@ describe Gitlab::Redis do
it 'instantiates a connection pool with size 5' do
expect(ConnectionPool).to receive(:new).with(size: 5).and_call_original
- described_class.with { |_redis| true }
+ described_class.with { |_redis_shared_example| true }
end
end
@@ -137,7 +127,7 @@ describe Gitlab::Redis do
it 'instantiates a connection pool with a size based on the concurrency of the worker' do
expect(ConnectionPool).to receive(:new).with(size: 18 + 5).and_call_original
- described_class.with { |_redis| true }
+ described_class.with { |_redis_shared_example| true }
end
end
end
@@ -146,16 +136,16 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels }
context 'when sentinels are defined' do
- let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
+ let(:config_file_name) { config_new_format_host }
it 'returns an array of hashes with host and port keys' do
- is_expected.to include(host: 'localhost', port: 26380)
- is_expected.to include(host: 'slave2', port: 26381)
+ is_expected.to include(host: 'localhost', port: sentinel_port)
+ is_expected.to include(host: 'slave2', port: sentinel_port)
end
end
context 'when sentinels are not defined' do
- let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
+ let(:config_file_name) { config_old_format_host }
it 'returns nil' do
is_expected.to be_nil
@@ -167,7 +157,7 @@ describe Gitlab::Redis do
subject { described_class.new(Rails.env).sentinels? }
context 'when sentinels are defined' do
- let(:config) { 'spec/fixtures/config/redis_new_format_host.yml' }
+ let(:config_file_name) { config_new_format_host }
it 'returns true' do
is_expected.to be_truthy
@@ -175,7 +165,7 @@ describe Gitlab::Redis do
end
context 'when sentinels are not defined' do
- let(:config) { 'spec/fixtures/config/redis_old_format_host.yml' }
+ let(:config_file_name) { config_old_format_host }
it 'returns false' do
is_expected.to be_falsey
@@ -187,12 +177,12 @@ describe Gitlab::Redis do
it 'returns default redis url when no config file is present' do
expect(subject).to receive(:fetch_config) { false }
- expect(subject.send(:raw_config_hash)).to eq(url: Gitlab::Redis::DEFAULT_REDIS_URL)
+ expect(subject.send(:raw_config_hash)).to eq(url: class_redis_url )
end
it 'returns old-style single url config in a hash' do
- expect(subject).to receive(:fetch_config) { 'redis://myredis:6379' }
- expect(subject.send(:raw_config_hash)).to eq(url: 'redis://myredis:6379')
+ expect(subject).to receive(:fetch_config) { test_redis_url }
+ expect(subject.send(:raw_config_hash)).to eq(url: test_redis_url)
end
end
@@ -200,7 +190,13 @@ describe Gitlab::Redis do
it 'returns false when no config file is present' do
allow(described_class).to receive(:_raw_config) { false }
- expect(subject.send(:fetch_config)).to be_falsey
+ expect(subject.send(:fetch_config)).to eq false
+ end
+
+ it 'returns false when config file is present but has invalid YAML' do
+ allow(described_class).to receive(:_raw_config) { "# development: true" }
+
+ expect(subject.send(:fetch_config)).to eq false
end
end
diff --git a/spec/support/routing_helpers.rb b/spec/support/routing_helpers.rb
new file mode 100644
index 00000000000..af1f4760804
--- /dev/null
+++ b/spec/support/routing_helpers.rb
@@ -0,0 +1,3 @@
+RSpec.configure do |config|
+ config.include GitlabRoutingHelper
+end
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index dcc562c684b..855051921f0 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -1,6 +1,6 @@
require "spec_helper"
-shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class|
+shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
record_class_name = record_class.to_s.titleize.downcase
let(:project) { create(:project) }
@@ -11,6 +11,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user
context "for a #{record_class_name} the user has created" do
let!(:record) { created_record }
+ let(:migrated_fields) { fields || [:author] }
it "does not delete the #{record_class_name}" do
service.execute
@@ -18,22 +19,20 @@ shared_examples "migrating a deleted user's associated records to the ghost user
expect(record_class.find_by_id(record.id)).to be_present
end
- it "migrates the #{record_class_name} so that the 'Ghost User' is the #{record_class_name} owner" do
+ it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do
service.execute
- migrated_record = record_class.find_by_id(record.id)
-
- if migrated_record.respond_to?(:author)
- expect(migrated_record.author).to eq(User.ghost)
- else
- expect(migrated_record.send(author_alias)).to eq(User.ghost)
- end
+ expect(user).to be_blocked
end
- it "blocks the user before migrating #{record_class_name}s to the 'Ghost User'" do
+ it 'migrates all associated fields to te "Ghost user"' do
service.execute
- expect(user).to be_blocked
+ migrated_record = record_class.find_by_id(record.id)
+
+ migrated_fields.each do |field|
+ expect(migrated_record.public_send(field)).to eq(User.ghost)
+ end
end
context "race conditions" do
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index b6341127a76..d5bc12f3bc5 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,11 +1,11 @@
shared_examples "protected branches > access control > CE" do
ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
- within('.new_protected_branch') do
+ within('.js-new-protected-branch') do
allowed_to_push_button = find(".js-allowed-to-push")
unless allowed_to_push_button.text == access_type_name
@@ -21,7 +21,7 @@ shared_examples "protected branches > access control > CE" do
end
it "allows updating protected branches so that #{access_type_name} can push to them" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
@@ -46,11 +46,11 @@ shared_examples "protected branches > access control > CE" do
ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can merge to" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
- within('.new_protected_branch') do
+ within('.js-new-protected-branch') do
allowed_to_merge_button = find(".js-allowed-to-merge")
unless allowed_to_merge_button.text == access_type_name
@@ -66,7 +66,7 @@ shared_examples "protected branches > access control > CE" do
end
it "allows updating protected branches so that #{access_type_name} can merge to them" do
- visit namespace_project_protected_branches_path(project.namespace, project)
+ visit project_protected_branches_path(project)
set_protected_branch_name('master')
diff --git a/spec/support/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index 3481749a7f0..226277411d6 100644
--- a/spec/support/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -9,7 +9,7 @@ shared_examples_for '400 response' do
end
it 'returns 400' do
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
end
@@ -20,7 +20,7 @@ shared_examples_for '403 response' do
end
it 'returns 403' do
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -32,7 +32,7 @@ shared_examples_for '404 response' do
end
it 'returns 404' do
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
expect(json_response).to be_an Object
if message.present?
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index 575d3451150..d143014692d 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -3,3 +3,13 @@ require 'sidekiq/testing/inline'
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ServerMiddleware
end
+
+RSpec.configure do |config|
+ config.after(:each, :sidekiq) do
+ Sidekiq::Worker.clear_all
+ end
+
+ config.after(:each, :sidekiq, :redis) do
+ Sidekiq.redis { |redis| redis.flushdb }
+ end
+end
diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 044c09d5fde..6accf16bea4 100644
--- a/spec/support/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -78,7 +78,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
wiki_page_service = WikiPages::CreateService.new(project, user, opts)
@wiki_page = wiki_page_service.execute
- @wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
+ @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create')
end
it "calls Slack/Mattermost API for push events" do
diff --git a/spec/support/sorting_helper.rb b/spec/support/sorting_helper.rb
new file mode 100644
index 00000000000..577518d726c
--- /dev/null
+++ b/spec/support/sorting_helper.rb
@@ -0,0 +1,18 @@
+# Helper allows you to sort items
+#
+# Params
+# value - value for sorting
+#
+# Usage:
+# include SortingHelper
+#
+# sorting_by('Oldest updated')
+#
+module SortingHelper
+ def sorting_by(value)
+ find('button.dropdown-toggle').click
+ page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
+ click_link value
+ end
+ end
+end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 48f454c7187..516f8878679 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -4,29 +4,38 @@ module StubConfiguration
# Stubbing both of these because we're not yet consistent with how we access
# current application settings
- allow_any_instance_of(ApplicationSetting).to receive_messages(messages)
+ allow_any_instance_of(ApplicationSetting).to receive_messages(to_settings(messages))
allow(Gitlab::CurrentSettings.current_application_settings)
- .to receive_messages(messages)
+ .to receive_messages(to_settings(messages))
+ end
+
+ def stub_not_protect_default_branch
+ stub_application_setting(
+ default_branch_protection: Gitlab::Access::PROTECTION_NONE)
end
def stub_config_setting(messages)
- allow(Gitlab.config.gitlab).to receive_messages(messages)
+ allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages))
end
def stub_gravatar_setting(messages)
- allow(Gitlab.config.gravatar).to receive_messages(messages)
+ allow(Gitlab.config.gravatar).to receive_messages(to_settings(messages))
end
def stub_incoming_email_setting(messages)
- allow(Gitlab.config.incoming_email).to receive_messages(messages)
+ allow(Gitlab.config.incoming_email).to receive_messages(to_settings(messages))
end
def stub_mattermost_setting(messages)
- allow(Gitlab.config.mattermost).to receive_messages(messages)
+ allow(Gitlab.config.mattermost).to receive_messages(to_settings(messages))
end
def stub_omniauth_setting(messages)
- allow(Gitlab.config.omniauth).to receive_messages(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(to_settings(messages))
+ end
+
+ def stub_backup_setting(messages)
+ allow(Gitlab.config.backup).to receive_messages(to_settings(messages))
end
private
@@ -49,4 +58,15 @@ module StubConfiguration
messages[predicate.to_sym] = messages[key.to_sym]
end
end
+
+ # Support nested hashes by converting all values into Settingslogic objects
+ def to_settings(hash)
+ hash.transform_values do |value|
+ if value.is_a? Hash
+ Settingslogic.new(value.deep_stringify_keys)
+ else
+ value
+ end
+ end
+ end
end
diff --git a/spec/support/stub_feature_flags.rb b/spec/support/stub_feature_flags.rb
new file mode 100644
index 00000000000..b96338bf548
--- /dev/null
+++ b/spec/support/stub_feature_flags.rb
@@ -0,0 +1,8 @@
+module StubFeatureFlags
+ def stub_feature_flags(features)
+ features.each do |feature_name, enabled|
+ allow(Feature).to receive(:enabled?).with(feature_name) { enabled }
+ allow(Feature).to receive(:enabled?).with(feature_name.to_s) { enabled }
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 32546abcad4..7682bdf8cd0 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,6 +5,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
+ 'signed-commits' => '5d4a1cb',
'not-merged-branch' => 'b83d6e3',
'branch-merged' => '498214d',
'empty-branch' => '7efb185',
@@ -41,7 +42,8 @@ module TestEnv
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
'add-ipython-files' => '93ee732',
- 'add-pdf-file' => 'e774ebd'
+ 'add-pdf-file' => 'e774ebd',
+ 'add-pdf-text-binary' => '79faa7b'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -69,7 +71,7 @@ module TestEnv
# Setup GitLab shell for test instance
setup_gitlab_shell
- setup_gitaly if Gitlab::GitalyClient.enabled?
+ setup_gitaly
# Create repository for FactoryGirl.create(:project)
setup_factory_repo
@@ -206,6 +208,7 @@ module TestEnv
# Otherwise they'd be created by the first test, often timing out and
# causing a transient test failure
def eager_load_driver_server
+ return unless ENV['CI']
return unless defined?(Capybara)
puts "Starting the Capybara driver server..."
diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb
index 1986d202c4a..ff0b47899f5 100644
--- a/spec/support/unique_ip_check_shared_examples.rb
+++ b/spec/support/unique_ip_check_shared_examples.rb
@@ -1,7 +1,9 @@
shared_context 'unique ips sign in limit' do
include StubENV
before(:each) do
- Gitlab::Redis.with(&:flushall)
+ Gitlab::Redis::Cache.with(&:flushall)
+ Gitlab::Redis::Queues.with(&:flushall)
+ Gitlab::Redis::SharedState.with(&:flushall)
end
before do
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index 91cc684d032..d34617be474 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it' do
expect(subject).to receive(:checkout_version).with(tag, clone_path)
- expect(subject).to receive(:reset_to_version).with(tag, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
@@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it with a branch name' do
expect(subject).to receive(:checkout_version).with(branch, clone_path)
- expect(subject).to receive(:reset_to_version).with(branch, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
@@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do
describe '#checkout_version' do
it 'clones the repo in the target dir' do
expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
+ .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet origin #{tag}])
expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
+ .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout -f --quiet FETCH_HEAD --])
subject.checkout_version(tag, clone_path)
end
end
-
- describe '#reset_to_version' do
- it 'resets --hard to the given version' do
- expect(subject)
- .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
-
- subject.reset_to_version(tag, clone_path)
- end
- end
end
diff --git a/spec/uploaders/attachment_uploader_spec.rb b/spec/uploaders/attachment_uploader_spec.rb
index d82dbe871d5..04ee6e9bfad 100644
--- a/spec/uploaders/attachment_uploader_spec.rb
+++ b/spec/uploaders/attachment_uploader_spec.rb
@@ -5,7 +5,7 @@ describe AttachmentUploader do
describe "#store_dir" do
it "stores in the system dir" do
- expect(uploader.store_dir).to start_with("uploads/system/user")
+ expect(uploader.store_dir).to start_with("uploads/-/system/user")
end
it "uses the old path when using object storage" do
diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb
index 201fe6949aa..1dc574699d8 100644
--- a/spec/uploaders/avatar_uploader_spec.rb
+++ b/spec/uploaders/avatar_uploader_spec.rb
@@ -5,7 +5,7 @@ describe AvatarUploader do
describe "#store_dir" do
it "stores in the system dir" do
- expect(uploader.store_dir).to start_with("uploads/system/user")
+ expect(uploader.store_dir).to start_with("uploads/-/system/user")
end
it "uses the old path when using object storage" do
diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb
index 896cb410ed5..d7c1b390f9a 100644
--- a/spec/uploaders/file_mover_spec.rb
+++ b/spec/uploaders/file_mover_spec.rb
@@ -4,11 +4,11 @@ describe FileMover do
let(:filename) { 'banana_sample.gif' }
let(:file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', filename)) }
let(:temp_description) do
- 'test ![banana_sample](/uploads/temp/secret55/banana_sample.gif) same ![banana_sample]'\
- '(/uploads/temp/secret55/banana_sample.gif)'
+ 'test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif) same ![banana_sample]'\
+ '(/uploads/system/temp/secret55/banana_sample.gif)'
end
let(:temp_file_path) { File.join('secret55', filename).to_s }
- let(:file_path) { File.join('uploads', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
+ let(:file_path) { File.join('uploads', 'system', 'personal_snippet', snippet.id.to_s, 'secret55', filename).to_s }
let(:snippet) { create(:personal_snippet, description: temp_description) }
@@ -28,8 +28,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/system/personal_snippet/#{snippet.id}/secret55/banana_sample.gif)"
)
end
@@ -50,8 +50,8 @@ describe FileMover do
expect(snippet.reload.description)
.to eq(
- "test ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"\
- " same ![banana_sample](/uploads/temp/secret55/banana_sample.gif)"
+ "test ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"\
+ " same ![banana_sample](/uploads/system/temp/secret55/banana_sample.gif)"
)
end
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index fb92f2ae3ab..eb55e8ebd24 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -10,7 +10,7 @@ describe PersonalFileUploader do
dynamic_segment = "personal_snippet/#{snippet.id}"
- expect(described_class.absolute_path(upload)).to end_with("#{dynamic_segment}/secret/foo.jpg")
+ expect(described_class.absolute_path(upload)).to end_with("/system/#{dynamic_segment}/secret/foo.jpg")
end
end
@@ -19,7 +19,7 @@ describe PersonalFileUploader do
uploader = described_class.new(snippet, 'secret')
allow(uploader).to receive(:file).and_return(double(extension: 'txt', filename: 'file_name'))
- expected_url = "/uploads/personal_snippet/#{snippet.id}/secret/file_name"
+ expected_url = "/uploads/system/personal_snippet/#{snippet.id}/secret/file_name"
expect(uploader.to_h).to eq(
alt: 'file_name',
diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb
index 72323da2838..de0b59f83f8 100644
--- a/spec/views/ci/status/_badge.html.haml_spec.rb
+++ b/spec/views/ci/status/_badge.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'ci/status/_badge', :view do
+describe 'ci/status/_badge' do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :private) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -16,8 +16,7 @@ describe 'ci/status/_badge', :view do
end
it 'has link to build details page' do
- details_path = namespace_project_job_path(
- project.namespace, project, build)
+ details_path = project_job_path(project, build)
render_status(build)
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index f5381a48207..f8c6cb6b5c6 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/_home_panel', :view do
+describe 'projects/_home_panel' do
let(:project) { create(:empty_project, :public) }
let(:notification_settings) do
diff --git a/spec/views/projects/blob/_viewer.html.haml_spec.rb b/spec/views/projects/blob/_viewer.html.haml_spec.rb
index bbd7f98fa8d..af833168bd9 100644
--- a/spec/views/projects/blob/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/blob/_viewer.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/blob/_viewer.html.haml', :view do
+describe 'projects/blob/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { build(:empty_project) }
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index ab120929c6c..448b925cf34 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/commit/_commit_box.html.haml', :view do
+describe 'projects/commit/_commit_box.html.haml' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index 92b4aa12d49..32c95c6bb0d 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/commit/show.html.haml', :view do
+describe 'projects/commit/show.html.haml' do
let(:project) { create(:project, :repository) }
before do
diff --git a/spec/views/projects/diffs/_viewer.html.haml_spec.rb b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
index 32469202508..8ac32db5585 100644
--- a/spec/views/projects/diffs/_viewer.html.haml_spec.rb
+++ b/spec/views/projects/diffs/_viewer.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/diffs/_viewer.html.haml', :view do
+describe 'projects/diffs/_viewer.html.haml' do
include FakeBlobHelpers
let(:project) { create(:project, :repository) }
diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb
index d9a7ba265f8..117f48450e2 100644
--- a/spec/views/projects/jobs/show.html.haml_spec.rb
+++ b/spec/views/projects/jobs/show.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/jobs/show', :view do
+describe 'projects/jobs/show' do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
index 3e17fe2104b..98c7de9b709 100644
--- a/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/_commits.html.haml_spec.rb
@@ -25,10 +25,7 @@ describe 'projects/merge_requests/_commits.html.haml' do
render
commit = source_project.commit(merge_request.source_branch)
- href = namespace_project_commit_path(
- source_project.namespace,
- source_project,
- commit)
+ href = project_commit_path(source_project, commit)
expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
end
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 1e9bdf9108f..5770cf92b4e 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/merge_requests/creations/_new_submit.html.haml', :view do
+describe 'projects/merge_requests/creations/_new_submit.html.haml' do
let(:merge_request) { create(:merge_request) }
let!(:pipeline) { create(:ci_empty_pipeline) }
diff --git a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
index e56c0f6be03..37ce7121ccb 100644
--- a/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/notes/_more_actions_dropdown', :view do
+describe 'projects/notes/_more_actions_dropdown' do
let(:author_user) { create(:user) }
let(:not_author_user) { create(:user) }
diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
index 9c91c4e0fbd..e40e16e742b 100644
--- a/spec/views/projects/pipelines/_stage.html.haml_spec.rb
+++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/pipelines/_stage', :view do
+describe 'projects/pipelines/_stage' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:stage) { build(:ci_stage, pipeline: pipeline) }
diff --git a/spec/views/projects/registry/repositories/index.html.haml_spec.rb b/spec/views/projects/registry/repositories/index.html.haml_spec.rb
index ceeace3dc8d..f13b657d474 100644
--- a/spec/views/projects/registry/repositories/index.html.haml_spec.rb
+++ b/spec/views/projects/registry/repositories/index.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/registry/repositories/index', :view do
+describe 'projects/registry/repositories/index' do
let(:group) { create(:group, path: 'group') }
let(:project) { create(:empty_project, group: group, path: 'test') }
diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb
index 33122365e9a..f65cd9f398f 100644
--- a/spec/views/projects/tags/index.html.haml_spec.rb
+++ b/spec/views/projects/tags/index.html.haml_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'projects/tags/index', :view do
+describe 'projects/tags/index' do
let(:project) { create(:project) }
before do
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 85939429feb..4f6e3474634 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe BackgroundMigrationWorker do
+describe BackgroundMigrationWorker, :sidekiq do
describe '.perform' do
it 'performs a background migration' do
expect(Gitlab::BackgroundMigration)
@@ -10,4 +10,35 @@ describe BackgroundMigrationWorker do
described_class.new.perform('Foo', [10, 20])
end
end
+
+ describe '.perform_bulk' do
+ it 'enqueues background migrations in bulk' do
+ Sidekiq::Testing.fake! do
+ described_class.perform_bulk([['Foo', [1]], ['Foo', [2]]])
+
+ expect(described_class.jobs.count).to eq 2
+ expect(described_class.jobs).to all(include('enqueued_at'))
+ end
+ end
+ end
+
+ describe '.perform_bulk_in' do
+ context 'when delay is valid' do
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ described_class.perform_bulk_in(1.minute, [['Foo', [1]], ['Foo', [2]]])
+
+ expect(described_class.jobs.count).to eq 2
+ expect(described_class.jobs).to all(include('at'))
+ end
+ end
+ end
+
+ context 'when delay is invalid' do
+ it 'raises an ArgumentError exception' do
+ expect { described_class.perform_bulk_in(-60, [['Foo']]) }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
end
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
new file mode 100644
index 00000000000..c6a17d77d73
--- /dev/null
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe CreateGpgSignatureWorker do
+ context 'when GpgKey is found' do
+ it 'calls Commit#signature' do
+ commit_sha = '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
+ project = create :project
+ commit = instance_double(Commit)
+
+ allow(Project).to receive(:find_by).with(id: project.id).and_return(project)
+ allow(project).to receive(:commit).with(commit_sha).and_return(commit)
+
+ expect(commit).to receive(:signature)
+
+ described_class.new.perform(commit_sha, project.id)
+ end
+ end
+
+ context 'when Commit is not found' do
+ let(:nonexisting_commit_sha) { 'bogus' }
+ let(:project) { create :project }
+
+ it 'does not raise errors' do
+ expect { described_class.new.perform(nonexisting_commit_sha, project.id) }.not_to raise_error
+ end
+
+ it 'does not call Commit#signature' do
+ expect_any_instance_of(Commit).not_to receive(:signature)
+
+ described_class.new.perform(nonexisting_commit_sha, project.id)
+ end
+ end
+
+ context 'when Project is not found' do
+ let(:nonexisting_project_id) { -1 }
+
+ it 'does not raise errors' do
+ expect { described_class.new.perform(anything, nonexisting_project_id) }.not_to raise_error
+ end
+
+ it 'does not call Commit#signature' do
+ expect_any_instance_of(Commit).not_to receive(:signature)
+
+ described_class.new.perform(anything, nonexisting_project_id)
+ end
+ end
+end
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index 1d8da68883b..bed5c5e2ecb 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -30,20 +30,6 @@ describe ExpireBuildInstanceArtifactsWorker do
expect(build.reload.artifacts_file_identifier).to be_nil
end
end
-
- context 'when associated project was removed' do
- let(:build) do
- create(:ci_build, :artifacts, artifacts_expiry) do |build|
- build.project.pending_delete = true
- end
- end
-
- it 'does not remove artifacts' do
- expect do
- build.reload.artifacts_file
- end.not_to raise_error
- end
- end
end
context 'with not yet expired artifacts' do
diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
new file mode 100644
index 00000000000..5972696515b
--- /dev/null
+++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe InvalidGpgSignatureUpdateWorker do
+ context 'when GpgKey is found' do
+ it 'calls NotificationService.new.run' do
+ gpg_key = create(:gpg_key)
+ invalid_signature_updater = double(:invalid_signature_updater)
+
+ expect(Gitlab::Gpg::InvalidGpgSignatureUpdater).to receive(:new).with(gpg_key).and_return(invalid_signature_updater)
+ expect(invalid_signature_updater).to receive(:run)
+
+ described_class.new.perform(gpg_key.id)
+ end
+ end
+
+ context 'when GpgKey is not found' do
+ let(:nonexisting_gpg_key_id) { -1 }
+
+ it 'does not raise errors' do
+ expect { described_class.new.perform(nonexisting_gpg_key_id) }.not_to raise_error
+ end
+
+ it 'does not call NotificationService.new.run' do
+ expect(Gitlab::Gpg::InvalidGpgSignatureUpdater).not_to receive(:new)
+
+ described_class.new.perform(nonexisting_gpg_key_id)
+ end
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index a8f4bb72acf..74a9f90195c 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -74,6 +74,7 @@ describe PostReceive do
OpenStruct.new(id: '123456')
end
allow_any_instance_of(Ci::CreatePipelineService).to receive(:branch?).and_return(true)
+ allow_any_instance_of(Repository).to receive(:ref_exists?).and_return(true)
stub_ci_pipeline_to_return_yaml_file
end
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 3d135f40c1f..f19c9dff941 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -1,24 +1,36 @@
require 'spec_helper'
describe ProjectDestroyWorker do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, pending_delete: true) }
let(:path) { project.repository.path_to_repo }
subject { described_class.new }
- describe "#perform" do
- it "deletes the project" do
+ describe '#perform' do
+ it 'deletes the project' do
subject.perform(project.id, project.owner.id, {})
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey
end
- it "deletes the project but skips repo deletion" do
+ it 'deletes the project but skips repo deletion' do
subject.perform(project.id, project.owner.id, { "skip_repo" => true })
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_truthy
end
+
+ it 'does not raise error when project could not be found' do
+ expect do
+ subject.perform(-1, project.owner.id, {})
+ end.not_to raise_error
+ end
+
+ it 'does not raise error when user could not be found' do
+ expect do
+ subject.perform(project.id, -1, {})
+ end.not_to raise_error
+ end
end
end
diff --git a/spec/workers/schedule_update_user_activity_worker_spec.rb b/spec/workers/schedule_update_user_activity_worker_spec.rb
index e583c3203aa..32c59381b01 100644
--- a/spec/workers/schedule_update_user_activity_worker_spec.rb
+++ b/spec/workers/schedule_update_user_activity_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe ScheduleUpdateUserActivityWorker, :redis do
+describe ScheduleUpdateUserActivityWorker, :clean_gitlab_redis_shared_state do
let(:now) { Time.now }
before do
diff --git a/spec/workers/update_user_activity_worker_spec.rb b/spec/workers/update_user_activity_worker_spec.rb
index 43e9511f116..268ca1d81f2 100644
--- a/spec/workers/update_user_activity_worker_spec.rb
+++ b/spec/workers/update_user_activity_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe UpdateUserActivityWorker, :redis do
+describe UpdateUserActivityWorker, :clean_gitlab_redis_shared_state do
let(:user_active_2_days_ago) { create(:user, current_sign_in_at: 10.months.ago) }
let(:user_active_yesterday_1) { create(:user) }
let(:user_active_yesterday_2) { create(:user) }
@@ -25,7 +25,7 @@ describe UpdateUserActivityWorker, :redis do
end
end
- it 'deletes the pairs from Redis' do
+ it 'deletes the pairs from SharedState' do
data.each { |id, time| Gitlab::UserActivities.record(id, time) }
subject.perform(data)
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
index 5b6af7be8c4..eeefadaa019 100644
--- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -4,10 +4,21 @@ image: docker:latest
services:
- docker:dind
+before_script:
+ - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
+
+build-master:
+ stage: build
+ script:
+ - docker build --pull -t "$CI_REGISTRY_IMAGE" .
+ - docker push "$CI_REGISTRY_IMAGE"
+ only:
+ - master
+
build:
stage: build
- before_script:
- - docker login -u "$CI_REGISTRY_USER" -p "CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- - docker build --pull -t "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG" .
- - docker push "$CI_REGISTRY_IMAGE:CI_COMMIT_REF_SLUG"
+ - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" .
+ - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG"
+ except:
+ - master
diff --git a/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml
index 555a51d35b9..06b0c84e516 100644
--- a/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/autodeploy/Kubernetes-with-canary.gitlab-ci.yml
@@ -28,7 +28,7 @@ canary:
- command canary
environment:
name: production
- url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN
when: manual
only:
- master
@@ -39,7 +39,7 @@ production:
- command deploy
environment:
name: production
- url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN
when: manual
only:
- master
@@ -50,7 +50,7 @@ staging:
- command deploy
environment:
name: staging
- url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN
only:
- master
@@ -60,7 +60,7 @@ review:
- command deploy
environment:
name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
on_stop: stop_review
only:
- branches
diff --git a/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml
index ee830ec2eb0..722934b7981 100644
--- a/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/autodeploy/Kubernetes.gitlab-ci.yml
@@ -27,7 +27,7 @@ production:
- command deploy
environment:
name: production
- url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN
when: manual
only:
- master
@@ -38,7 +38,7 @@ staging:
- command deploy
environment:
name: staging
- url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN
only:
- master
@@ -48,7 +48,7 @@ review:
- command deploy
environment:
name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
on_stop: stop_review
only:
- branches
diff --git a/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml b/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml
index 27c9107e0d7..acba718ebe4 100644
--- a/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/autodeploy/OpenShift.gitlab-ci.yml
@@ -23,38 +23,32 @@ build:
production:
stage: production
- variables:
- CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME.$KUBE_DOMAIN
script:
- command deploy
environment:
name: production
- url: http://$CI_PROJECT_NAME.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_DOMAIN
when: manual
only:
- master
staging:
stage: staging
- variables:
- CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN
script:
- command deploy
environment:
name: staging
- url: http://$CI_PROJECT_NAME-staging.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_DOMAIN
only:
- master
review:
stage: review
- variables:
- CI_ENVIRONMENT_URL: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
script:
- command deploy
environment:
name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_DOMAIN
on_stop: stop_review
only:
- branches
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 5dcac5947a1..5beb3e5e9bf 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -1,9 +1,9 @@
RedCloth,4.3.2,MIT
-abbrev,1.0.9,ISC
+abbrev,1.1.0,ISC
accepts,1.3.3,MIT
ace-rails-ap,4.1.2,MIT
-acorn,5.0.3,MIT
-acorn-dynamic-import,2.0.1,MIT
+acorn,5.1.1,MIT
+acorn-dynamic-import,2.0.2,MIT
acorn-jsx,3.0.1,MIT
actionmailer,4.2.8,MIT
actionpack,4.2.8,MIT
@@ -16,7 +16,7 @@ acts-as-taggable-on,4.0.0,MIT
addressable,2.3.8,Apache 2.0
after,0.8.2,MIT
after_commit_queue,1.3.0,MIT
-ajv,4.11.2,MIT
+ajv,4.11.8,MIT
ajv-keywords,1.5.1,MIT
akismet,2.0.0,MIT
align-text,0.1.4,MIT
@@ -26,16 +26,17 @@ amdefine,1.0.1,BSD-3-Clause OR MIT
ansi-escapes,1.4.0,MIT
ansi-html,0.0.5,"Apache, Version 2.0"
ansi-regex,2.1.1,MIT
-ansi-styles,2.2.1,MIT
+ansi-styles,3.1.0,MIT
anymatch,1.3.0,ISC
append-transform,0.4.0,MIT
-aproba,1.1.0,ISC
-are-we-there-yet,1.1.2,ISC
+aproba,1.1.2,ISC
+are-we-there-yet,1.1.4,ISC
arel,6.0.4,MIT
argparse,1.0.9,MIT
arr-diff,2.0.0,MIT
-arr-flatten,1.0.1,MIT
+arr-flatten,1.1.0,MIT
array-find,1.0.0,MIT
+array-find-index,1.0.2,MIT
array-flatten,1.1.1,MIT
array-slice,0.2.3,MIT
array-union,1.0.2,MIT
@@ -63,26 +64,27 @@ aws-sign2,0.6.0,Apache 2.0
aws4,1.6.0,MIT
axiom-types,0.1.1,MIT
babel-code-frame,6.22.0,MIT
-babel-core,6.23.1,MIT
-babel-generator,6.23.0,MIT
-babel-helper-bindify-decorators,6.22.0,MIT
-babel-helper-builder-binary-assignment-operator-visitor,6.22.0,MIT
-babel-helper-call-delegate,6.22.0,MIT
-babel-helper-define-map,6.23.0,MIT
-babel-helper-explode-assignable-expression,6.22.0,MIT
-babel-helper-explode-class,6.22.0,MIT
-babel-helper-function-name,6.23.0,MIT
-babel-helper-get-function-arity,6.22.0,MIT
-babel-helper-hoist-variables,6.22.0,MIT
-babel-helper-optimise-call-expression,6.23.0,MIT
-babel-helper-regex,6.22.0,MIT
-babel-helper-remap-async-to-generator,6.22.0,MIT
-babel-helper-replace-supers,6.23.0,MIT
-babel-helpers,6.23.0,MIT
-babel-loader,6.2.10,MIT
+babel-core,6.25.0,MIT
+babel-eslint,7.2.3,MIT
+babel-generator,6.25.0,MIT
+babel-helper-bindify-decorators,6.24.1,MIT
+babel-helper-builder-binary-assignment-operator-visitor,6.24.1,MIT
+babel-helper-call-delegate,6.24.1,MIT
+babel-helper-define-map,6.24.1,MIT
+babel-helper-explode-assignable-expression,6.24.1,MIT
+babel-helper-explode-class,6.24.1,MIT
+babel-helper-function-name,6.24.1,MIT
+babel-helper-get-function-arity,6.24.1,MIT
+babel-helper-hoist-variables,6.24.1,MIT
+babel-helper-optimise-call-expression,6.24.1,MIT
+babel-helper-regex,6.24.1,MIT
+babel-helper-remap-async-to-generator,6.24.1,MIT
+babel-helper-replace-supers,6.24.1,MIT
+babel-helpers,6.24.1,MIT
+babel-loader,6.4.1,MIT
babel-messages,6.23.0,MIT
babel-plugin-check-es2015-constants,6.22.0,MIT
-babel-plugin-istanbul,4.0.0,New BSD
+babel-plugin-istanbul,4.1.4,New BSD
babel-plugin-syntax-async-functions,6.13.0,MIT
babel-plugin-syntax-async-generators,6.13.0,MIT
babel-plugin-syntax-class-properties,6.13.0,MIT
@@ -91,57 +93,57 @@ babel-plugin-syntax-dynamic-import,6.18.0,MIT
babel-plugin-syntax-exponentiation-operator,6.13.0,MIT
babel-plugin-syntax-object-rest-spread,6.13.0,MIT
babel-plugin-syntax-trailing-function-commas,6.22.0,MIT
-babel-plugin-transform-async-generator-functions,6.22.0,MIT
-babel-plugin-transform-async-to-generator,6.22.0,MIT
-babel-plugin-transform-class-properties,6.23.0,MIT
-babel-plugin-transform-decorators,6.22.0,MIT
-babel-plugin-transform-define,1.2.0,MIT
+babel-plugin-transform-async-generator-functions,6.24.1,MIT
+babel-plugin-transform-async-to-generator,6.24.1,MIT
+babel-plugin-transform-class-properties,6.24.1,MIT
+babel-plugin-transform-decorators,6.24.1,MIT
+babel-plugin-transform-define,1.3.0,MIT
babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT
babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT
-babel-plugin-transform-es2015-block-scoping,6.23.0,MIT
-babel-plugin-transform-es2015-classes,6.23.0,MIT
-babel-plugin-transform-es2015-computed-properties,6.22.0,MIT
+babel-plugin-transform-es2015-block-scoping,6.24.1,MIT
+babel-plugin-transform-es2015-classes,6.24.1,MIT
+babel-plugin-transform-es2015-computed-properties,6.24.1,MIT
babel-plugin-transform-es2015-destructuring,6.23.0,MIT
-babel-plugin-transform-es2015-duplicate-keys,6.22.0,MIT
+babel-plugin-transform-es2015-duplicate-keys,6.24.1,MIT
babel-plugin-transform-es2015-for-of,6.23.0,MIT
-babel-plugin-transform-es2015-function-name,6.22.0,MIT
+babel-plugin-transform-es2015-function-name,6.24.1,MIT
babel-plugin-transform-es2015-literals,6.22.0,MIT
-babel-plugin-transform-es2015-modules-amd,6.24.0,MIT
-babel-plugin-transform-es2015-modules-commonjs,6.24.0,MIT
-babel-plugin-transform-es2015-modules-systemjs,6.23.0,MIT
-babel-plugin-transform-es2015-modules-umd,6.24.0,MIT
-babel-plugin-transform-es2015-object-super,6.22.0,MIT
-babel-plugin-transform-es2015-parameters,6.23.0,MIT
-babel-plugin-transform-es2015-shorthand-properties,6.22.0,MIT
+babel-plugin-transform-es2015-modules-amd,6.24.1,MIT
+babel-plugin-transform-es2015-modules-commonjs,6.24.1,MIT
+babel-plugin-transform-es2015-modules-systemjs,6.24.1,MIT
+babel-plugin-transform-es2015-modules-umd,6.24.1,MIT
+babel-plugin-transform-es2015-object-super,6.24.1,MIT
+babel-plugin-transform-es2015-parameters,6.24.1,MIT
+babel-plugin-transform-es2015-shorthand-properties,6.24.1,MIT
babel-plugin-transform-es2015-spread,6.22.0,MIT
-babel-plugin-transform-es2015-sticky-regex,6.22.0,MIT
+babel-plugin-transform-es2015-sticky-regex,6.24.1,MIT
babel-plugin-transform-es2015-template-literals,6.22.0,MIT
babel-plugin-transform-es2015-typeof-symbol,6.23.0,MIT
-babel-plugin-transform-es2015-unicode-regex,6.22.0,MIT
-babel-plugin-transform-exponentiation-operator,6.22.0,MIT
+babel-plugin-transform-es2015-unicode-regex,6.24.1,MIT
+babel-plugin-transform-exponentiation-operator,6.24.1,MIT
babel-plugin-transform-object-rest-spread,6.23.0,MIT
-babel-plugin-transform-regenerator,6.22.0,MIT
-babel-plugin-transform-strict-mode,6.22.0,MIT
-babel-preset-es2015,6.24.0,MIT
-babel-preset-es2016,6.22.0,MIT
-babel-preset-es2017,6.22.0,MIT
-babel-preset-latest,6.24.0,MIT
-babel-preset-stage-2,6.22.0,MIT
-babel-preset-stage-3,6.22.0,MIT
-babel-register,6.23.0,MIT
-babel-runtime,6.22.0,MIT
-babel-template,6.23.0,MIT
-babel-traverse,6.23.1,MIT
-babel-types,6.23.0,MIT
+babel-plugin-transform-regenerator,6.24.1,MIT
+babel-plugin-transform-strict-mode,6.24.1,MIT
+babel-preset-es2015,6.24.1,MIT
+babel-preset-es2016,6.24.1,MIT
+babel-preset-es2017,6.24.1,MIT
+babel-preset-latest,6.24.1,MIT
+babel-preset-stage-2,6.24.1,MIT
+babel-preset-stage-3,6.24.1,MIT
+babel-register,6.24.1,MIT
+babel-runtime,6.23.0,MIT
+babel-template,6.25.0,MIT
+babel-traverse,6.25.0,MIT
+babel-types,6.25.0,MIT
babosa,1.0.2,MIT
-babylon,6.15.0,MIT
+babylon,6.17.4,MIT
backo2,1.0.2,MIT
-balanced-match,0.4.2,MIT
+balanced-match,1.0.0,MIT
base32,0.3.2,MIT
base64-arraybuffer,0.1.5,MIT
-base64-js,1.2.0,MIT
+base64-js,1.2.1,MIT
base64id,1.0.0,MIT
-batch,0.5.3,MIT
+batch,0.6.1,MIT
bcrypt,3.1.11,MIT
bcrypt-pbkdf,1.0.1,New BSD
better-assert,1.0.2,MIT
@@ -150,24 +152,28 @@ binary-extensions,1.8.0,MIT
bindata,2.3.5,ruby
blob,0.0.4,unknown
block-stream,0.0.9,ISC
-bluebird,3.4.7,MIT
-bn.js,4.11.6,MIT
+bluebird,3.5.0,MIT
+bn.js,4.11.7,MIT
body-parser,1.17.2,MIT
+bonjour,3.5.0,MIT
boom,2.10.1,New BSD
-bootsnap,1.0.0,MIT
+bootsnap,1.1.1,MIT
bootstrap-sass,3.3.6,MIT
-brace-expansion,1.1.6,MIT
+bootstrap-sass,3.3.7,MIT
+bootstrap_form,2.7.0,MIT
+brace-expansion,1.1.8,MIT
braces,1.8.5,MIT
-brorand,1.0.7,MIT
+brorand,1.1.0,MIT
browser,2.2.0,MIT
browserify-aes,1.0.6,MIT
browserify-cipher,1.0.0,MIT
browserify-des,1.0.0,MIT
browserify-rsa,4.0.1,MIT
-browserify-sign,4.0.0,ISC
+browserify-sign,4.0.4,ISC
browserify-zlib,0.1.4,MIT
browserslist,1.7.7,MIT
buffer,4.9.1,MIT
+buffer-indexof,1.1.0,MIT
buffer-shims,1.0.0,MIT
buffer-xor,1.0.3,MIT
builder,3.2.3,MIT
@@ -178,29 +184,30 @@ caller-path,0.1.0,MIT
callsite,1.0.0,unknown
callsites,0.2.0,MIT
camelcase,1.2.1,MIT
+camelcase-keys,2.1.0,MIT
caniuse-api,1.6.1,MIT
-caniuse-db,1.0.30000649,CC-BY-4.0
-carrierwave,1.0.0,MIT
-caseless,0.11.0,Apache 2.0
+caniuse-db,1.0.30000699,CC-BY-4.0
+carrierwave,1.1.0,MIT
+caseless,0.12.0,Apache 2.0
cause,0.1,MIT
center-align,0.1.3,MIT
chalk,1.1.3,MIT
charlock_holmes,0.7.3,MIT
-chokidar,1.6.1,MIT
+chokidar,1.7.0,MIT
chronic,0.10.2,MIT
chronic_duration,0.10.6,MIT
chunky_png,1.3.5,MIT
-cipher-base,1.0.3,MIT
+cipher-base,1.0.4,MIT
circular-json,0.3.1,MIT
citrus,3.0.2,MIT
-clap,1.1.3,MIT
+clap,1.2.0,MIT
cli-cursor,1.0.2,MIT
cli-width,2.1.0,ISC
-clipboard,1.6.1,MIT
+clipboard,1.7.1,MIT
cliui,2.1.0,ISC
clone,1.0.2,MIT
co,4.6.0,MIT
-coa,1.0.1,MIT
+coa,1.0.4,MIT
code-point-at,1.1.0,MIT
coercible,1.0.0,MIT
coffee-rails,4.1.1,MIT
@@ -214,13 +221,13 @@ colormin,1.1.2,MIT
colors,1.1.2,MIT
combine-lists,1.0.1,MIT
combined-stream,1.0.5,MIT
-commander,2.9.0,MIT
+commander,2.11.0,MIT
commondir,1.0.1,MIT
component-bind,1.0.0,unknown
component-emitter,1.2.1,MIT
component-inherit,0.0.3,unknown
-compressible,2.0.9,MIT
-compression,1.6.2,MIT
+compressible,2.0.10,MIT
+compression,1.7.0,MIT
compression-webpack-plugin,0.3.2,MIT
concat-map,0.0.1,MIT
concat-stream,1.6.0,MIT
@@ -237,38 +244,40 @@ constants-browserify,1.0.0,MIT
contains-path,0.1.0,MIT
content-disposition,0.5.2,MIT
content-type,1.0.2,MIT
-convert-source-map,1.3.0,MIT
+convert-source-map,1.5.0,MIT
cookie,0.3.1,MIT
cookie-signature,1.0.6,MIT
core-js,2.4.1,MIT
core-util-is,1.0.2,MIT
-cosmiconfig,2.1.1,MIT
+cosmiconfig,2.1.3,MIT
crack,0.4.3,MIT
create-ecdh,4.0.0,MIT
-create-hash,1.1.2,MIT
-create-hmac,1.1.4,MIT
+create-hash,1.1.3,MIT
+create-hmac,1.1.6,MIT
creole,0.5.0,ruby
cryptiles,2.0.5,New BSD
crypto-browserify,3.11.0,MIT
css-color-names,0.0.4,MIT
-css-loader,0.28.0,MIT
-css-selector-tokenizer,0.7.0,MIT
-css_parser,1.4.1,MIT
+css-loader,0.28.4,MIT
+css-selector-tokenizer,"",unknown
+css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
cssnano,3.10.0,MIT
csso,2.3.2,MIT
+currently-unhandled,0.4.1,MIT
custom-event,1.0.1,MIT
-d,0.1.1,MIT
-d3,3.5.11,New BSD
+d,1.0.0,MIT
+d3,3.5.17,New BSD
d3_rails,3.5.11,MIT
dashdash,1.14.1,MIT
date-now,0.1.4,MIT
de-indent,1.0.2,MIT
-debug,2.6.0,MIT
+debug,2.6.8,MIT
debugger-ruby_core_source,1.3.8,MIT
decamelize,1.2.0,MIT
deckar01-task_list,2.0.0,MIT
-deep-extend,0.4.1,MIT
+deep-equal,1.0.1,MIT
+deep-extend,0.4.2,MIT
deep-is,0.1.3,MIT
default-require-extensions,1.0.0,MIT
default_value_for,3.0.2,MIT
@@ -276,31 +285,35 @@ defaults,1.0.3,MIT
defined,1.0.0,MIT
del,2.2.2,MIT
delayed-stream,1.0.0,MIT
-delegate,3.1.2,MIT
+delegate,3.1.3,MIT
delegates,1.0.0,MIT
depd,1.1.0,MIT
des.js,1.0.0,MIT
descendants_tracker,0.0.4,MIT
destroy,1.0.4,MIT
detect-indent,4.0.0,MIT
+detect-node,2.0.3,ISC
devise,4.2.0,MIT
devise-two-factor,3.0.0,MIT
di,0.0.1,MIT
diff-lcs,1.2.5,"MIT,Perl Artistic v2,GNU GPL v2"
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
-doctrine,1.5.0,BSD
-document-register-element,1.3.0,MIT
+dns-equal,1.0.0,MIT
+dns-packet,1.1.1,MIT
+dns-txt,2.0.2,MIT
+doctrine,2.0.0,Apache 2.0
+document-register-element,1.5.0,MIT
dom-serialize,2.2.1,MIT
dom-serializer,0.1.0,MIT
domain-browser,1.1.7,MIT
domain_name,0.5.20161021,"Simplified BSD,New BSD,Mozilla Public License 2.0"
domelementtype,1.3.0,unknown
-domhandler,2.3.0,unknown
-domutils,1.5.1,unknown
+domhandler,2.4.1,Simplified BSD
+domutils,1.6.2,Simplified BSD
doorkeeper,4.2.0,MIT
doorkeeper-openid_connect,1.1.2,MIT
-dropzone,4.2.0,MIT
+dropzone,4.3.0,MIT
dropzonejs-rails,0.7.2,MIT
duplexer,0.1.1,MIT
duplexify,3.5.0,MIT
@@ -308,8 +321,8 @@ ecc-jsbn,0.1.1,MIT
editorconfig,0.13.2,MIT
ee-first,1.1.1,MIT
ejs,2.5.6,Apache 2.0
-electron-to-chromium,1.3.3,ISC
-elliptic,6.3.3,MIT
+electron-to-chromium,1.3.15,ISC
+elliptic,6.4.0,MIT
email_reply_trimmer,0.1.6,MIT
emoji-unicode-version,0.2.1,MIT
emojis-list,2.1.0,MIT
@@ -319,44 +332,45 @@ end-of-stream,1.0.0,MIT
engine.io,1.8.3,MIT
engine.io-client,1.8.3,MIT
engine.io-parser,1.3.2,MIT
-enhanced-resolve,3.1.0,MIT
+enhanced-resolve,3.3.0,MIT
ent,2.2.0,MIT
entities,1.1.1,BSD-like
equalizer,0.0.11,MIT
errno,0.1.4,MIT
-error-ex,1.3.0,MIT
+error-ex,1.3.1,MIT
erubis,2.7.0,MIT
-es5-ext,0.10.12,MIT
-es6-iterator,2.0.0,MIT
-es6-map,0.1.4,MIT
+es5-ext,0.10.24,MIT
+es6-iterator,2.0.1,MIT
+es6-map,0.1.5,MIT
es6-promise,3.0.2,MIT
-es6-set,0.1.4,MIT
-es6-symbol,3.1.0,MIT
-es6-weak-map,2.0.1,MIT
+es6-set,0.1.5,MIT
+es6-symbol,3.1.1,MIT
+es6-weak-map,2.0.2,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
escape_utils,1.1.1,MIT
escodegen,1.8.1,Simplified BSD
escope,3.6.0,Simplified BSD
-eslint,3.15.0,MIT
+eslint,3.19.0,MIT
eslint-config-airbnb-base,10.0.1,MIT
-eslint-import-resolver-node,0.2.3,MIT
-eslint-import-resolver-webpack,0.8.1,MIT
-eslint-module-utils,2.0.0,MIT
-eslint-plugin-filenames,1.1.0,MIT
-eslint-plugin-html,2.0.1,ISC
-eslint-plugin-import,2.2.0,MIT
-eslint-plugin-jasmine,2.2.0,MIT
+eslint-import-resolver-node,0.3.1,MIT
+eslint-import-resolver-webpack,0.8.3,MIT
+eslint-module-utils,2.1.1,MIT
+eslint-plugin-filenames,1.2.0,MIT
+eslint-plugin-html,2.0.3,ISC
+eslint-plugin-import,2.7.0,MIT
+eslint-plugin-jasmine,2.7.1,MIT
eslint-plugin-promise,3.5.0,ISC
-espree,3.4.0,Simplified BSD
-esprima,3.1.3,Simplified BSD
-esrecurse,4.1.0,Simplified BSD
-estraverse,4.1.1,Simplified BSD
+espree,3.4.3,Simplified BSD
+esprima,2.7.3,Simplified BSD
+esquery,1.0.0,BSD
+esrecurse,4.2.0,Simplified BSD
+estraverse,4.2.0,Simplified BSD
esutils,2.0.2,BSD
et-orbi,1.0.3,MIT
etag,1.8.0,MIT
eve-raphael,0.5.0,Apache 2.0
-event-emitter,0.3.4,MIT
+event-emitter,0.3.5,MIT
event-stream,3.3.4,MIT
eventemitter3,1.2.0,MIT
events,1.1.1,MIT
@@ -371,13 +385,14 @@ expand-range,1.8.2,MIT
exports-loader,0.6.4,MIT
express,4.15.3,MIT
expression_parser,0.9.0,MIT
-extend,3.0.0,MIT
+extend,3.0.1,MIT
extglob,0.3.2,MIT
extlib,0.9.16,MIT
extsprintf,1.0.2,MIT
-faraday,0.11.0,MIT
+faraday,0.12.1,MIT
faraday_middleware,0.11.0.1,MIT
faraday_middleware-multi_json,0.0.6,MIT
+fast-deep-equal,1.0.0,MIT
fast-levenshtein,2.0.6,MIT
fast_gettext,1.4.0,"MIT,ruby"
fastparse,1.1.1,MIT
@@ -385,15 +400,15 @@ faye-websocket,0.7.3,MIT
ffi,1.9.10,BSD
figures,1.7.0,MIT
file-entry-cache,2.0.0,MIT
-file-loader,0.11.1,MIT
-filename-regex,2.0.0,MIT
+file-loader,0.11.2,MIT
+filename-regex,2.0.1,MIT
fileset,2.0.3,MIT
filesize,3.3.0,New BSD
fill-range,2.2.3,MIT
finalhandler,1.0.3,MIT
find-cache-dir,0.1.1,MIT
find-root,0.1.2,MIT
-find-up,2.1.0,MIT
+find-up,1.1.2,MIT
flat-cache,1.2.2,MIT
flatten,1.0.2,MIT
flipper,0.10.2,MIT
@@ -409,41 +424,43 @@ fog-openstack,0.1.6,MIT
fog-rackspace,0.1.1,MIT
fog-xml,0.1.3,MIT
font-awesome-rails,4.7.0.1,"MIT,SIL Open Font License"
-for-in,0.1.6,MIT
-for-own,0.1.4,MIT
+for-in,1.0.2,MIT
+for-own,0.1.5,MIT
forever-agent,0.6.1,Apache 2.0
-form-data,2.1.2,MIT
+form-data,2.1.4,MIT
formatador,0.2.5,MIT
forwarded,0.1.0,MIT
fresh,0.5.0,MIT
from,0.1.7,MIT
+fs-access,1.0.1,MIT
fs.realpath,1.0.0,ISC
-fsevents,,unknown
-fstream,1.0.10,ISC
+fsevents,1.1.2,MIT
+fstream,1.0.11,ISC
fstream-ignore,1.0.5,ISC
function-bind,1.1.0,MIT
-gauge,2.7.2,ISC
+gauge,2.7.4,ISC
gemnasium-gitlab-service,0.2.6,MIT
gemojione,3.0.1,MIT
generate-function,2.0.0,MIT
generate-object-property,1.2.0,MIT
get-caller-file,1.0.2,ISC
+get-stdin,4.0.1,MIT
get_process_mem,0.2.0,MIT
-getpass,0.1.6,MIT
+getpass,0.1.7,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.2.0,MIT
-gitaly,0.8.0,MIT
+gitaly,0.14.0,MIT
github-linguist,4.7.6,MIT
github-markup,1.4.0,MIT
gitlab-flowdock-git-hook,1.0.1,MIT
gitlab-grit,2.8.1,MIT
gitlab-markup,1.5.1,MIT
gitlab_omniauth-ldap,1.2.1,MIT
-glob,7.1.1,ISC
+glob,7.1.2,ISC
glob-base,0.3.0,MIT
glob-parent,2.0.0,ISC
globalid,0.3.7,MIT
-globals,9.14.0,MIT
+globals,9.18.0,MIT
globby,5.0.0,MIT
gollum-grit_adapter,1.0.1,MIT
gollum-lib,4.2.1,MIT
@@ -455,32 +472,34 @@ google-protobuf,3.2.0.2,New BSD
googleauth,0.5.1,Apache 2.0
got,3.3.1,MIT
graceful-fs,4.1.11,ISC
-graceful-readlink,1.0.1,MIT
grape,0.19.1,MIT
grape-entity,0.6.0,MIT
-grpc,1.2.5,New BSD
+grpc,1.4.0,New BSD
gzip-size,3.0.0,MIT
hamlit,2.6.1,MIT
handle-thing,1.2.5,MIT
-handlebars,4.0.6,MIT
-har-validator,2.0.6,ISC
+handlebars,4.0.10,MIT
+har-schema,1.0.5,ISC
+har-validator,4.2.1,ISC
has,1.0.1,MIT
has-ansi,2.0.0,MIT
has-binary,0.1.7,MIT
has-cors,1.1.0,MIT
-has-flag,1.0.0,MIT
+has-flag,2.0.0,MIT
has-unicode,2.0.1,ISC
+hash-base,2.0.2,MIT
hash-sum,1.0.2,MIT
-hash.js,1.0.3,MIT
+hash.js,1.1.3,MIT
hashie,3.5.5,MIT
hashie-forbidden_attributes,0.1.1,MIT
hawk,3.1.3,New BSD
he,1.1.1,MIT
health_check,2.6.0,MIT
hipchat,1.5.2,MIT
+hmac-drbg,1.0.1,MIT
hoek,2.16.3,New BSD
home-or-tmp,2.0.0,MIT
-hosted-git-info,2.2.0,ISC
+hosted-git-info,2.5.0,ISC
hpack.js,2.1.6,MIT
html-comment-regex,1.1.1,MIT
html-entities,1.2.0,MIT
@@ -503,12 +522,14 @@ https-browserify,0.0.1,MIT
i18n,0.8.1,MIT
ice_nine,0.11.2,MIT
iconv-lite,0.4.15,MIT
-icss-replace-symbols,1.0.2,ISC
+icss-replace-symbols,1.1.0,ISC
+icss-utils,2.1.0,ISC
ieee754,1.1.8,New BSD
-ignore,3.2.2,MIT
+ignore,3.3.3,MIT
ignore-by-default,1.0.1,ISC
immediate,3.0.6,MIT
imurmurhash,0.1.4,MIT
+indent-string,2.1.0,MIT
indexes-of,1.0.1,MIT
indexof,0.0.1,unknown
infinity-agent,2.0.3,MIT
@@ -517,25 +538,28 @@ influxdb,0.2.3,MIT
inherits,2.0.3,ISC
ini,1.3.4,ISC
inquirer,0.12.0,MIT
-interpret,1.0.1,MIT
+internal-ip,1.2.0,MIT
+interpret,1.0.3,MIT
invariant,2.2.2,New BSD
invert-kv,1.0.0,MIT
+ip,1.1.5,MIT
ipaddr.js,1.3.0,MIT
ipaddress,0.8.3,MIT
is-absolute,0.2.6,MIT
is-absolute-url,2.1.0,MIT
is-arrayish,0.2.1,MIT
is-binary-path,1.0.1,MIT
-is-buffer,1.1.4,MIT
+is-buffer,1.1.5,MIT
is-builtin-module,1.0.0,MIT
-is-dotfile,1.0.2,MIT
+is-directory,0.3.1,MIT
+is-dotfile,1.0.3,MIT
is-equal-shallow,0.1.3,MIT
is-extendable,0.1.1,MIT
is-extglob,1.0.0,MIT
is-finite,1.0.2,MIT
is-fullwidth-code-point,1.0.0,MIT
is-glob,2.0.1,MIT
-is-my-json-valid,2.15.0,MIT
+is-my-json-valid,2.16.0,MIT
is-npm,1.0.0,MIT
is-number,2.1.0,MIT
is-path-cwd,1.0.0,MIT
@@ -556,56 +580,58 @@ is-utf8,0.2.1,MIT
is-windows,0.2.0,MIT
isarray,1.0.0,MIT
isbinaryfile,3.0.2,MIT
-isexe,1.1.2,ISC
+isexe,2.0.0,ISC
isobject,2.1.0,MIT
isstream,0.1.2,MIT
istanbul,0.4.5,New BSD
-istanbul-api,1.1.1,New BSD
-istanbul-lib-coverage,1.0.1,New BSD
-istanbul-lib-hook,1.0.0,New BSD
-istanbul-lib-instrument,1.4.2,New BSD
-istanbul-lib-report,1.0.0-alpha.3,New BSD
-istanbul-lib-source-maps,1.1.0,New BSD
-istanbul-reports,1.0.1,New BSD
-jasmine-core,2.6.3,MIT
+istanbul-api,1.1.10,New BSD
+istanbul-lib-coverage,1.1.1,New BSD
+istanbul-lib-hook,1.0.7,New BSD
+istanbul-lib-instrument,1.7.3,New BSD
+istanbul-lib-report,1.1.1,New BSD
+istanbul-lib-source-maps,1.2.1,New BSD
+istanbul-reports,1.1.1,New BSD
+jasmine-core,2.6.4,MIT
jasmine-jquery,2.1.1,MIT
jed,1.1.1,MIT
jira-ruby,1.1.2,MIT
jodid25519,1.0.2,MIT
-jquery,2.2.1,MIT
+jquery,2.2.4,MIT
jquery-atwho-rails,1.3.2,MIT
jquery-rails,4.1.1,MIT
-jquery-ujs,1.2.1,MIT
+jquery-ujs,1.2.2,MIT
js-base64,2.1.9,BSD
-js-beautify,1.6.12,MIT
-js-cookie,2.1.3,MIT
-js-tokens,3.0.1,MIT
-js-yaml,3.7.0,MIT
-jsbn,0.1.0,BSD
+js-beautify,1.6.14,MIT
+js-cookie,2.1.4,MIT
+js-tokens,3.0.2,MIT
+js-yaml,"",unknown
+jsbn,0.1.1,MIT
jsesc,1.3.0,MIT
json,1.8.6,ruby
json-jwt,1.7.1,MIT
json-loader,0.5.4,MIT
json-schema,0.2.3,"AFLv2.1,BSD"
+json-schema-traverse,0.3.1,MIT
json-stable-stringify,1.0.1,MIT
json-stringify-safe,5.0.1,ISC
json3,3.3.2,MIT
json5,0.5.1,MIT
jsonify,0.0.0,Public Domain
jsonpointer,4.0.1,MIT
-jsprim,1.3.1,MIT
+jsprim,1.4.0,MIT
jszip,3.1.3,(MIT OR GPL-3.0)
jszip-utils,0.0.2,MIT or GPLv3
jwt,1.5.6,MIT
kaminari,0.17.0,MIT
karma,1.7.0,MIT
-karma-coverage-istanbul-reporter,0.2.0,MIT
+karma-chrome-launcher,2.2.0,MIT
+karma-coverage-istanbul-reporter,0.2.3,MIT
karma-jasmine,1.1.0,MIT
-karma-mocha-reporter,2.2.2,MIT
+karma-mocha-reporter,2.2.3,MIT
karma-sourcemap-loader,0.3.7,MIT
-karma-webpack,2.0.2,MIT
+karma-webpack,2.0.4,MIT
kgio,2.10.0,LGPL-2.1+
-kind-of,3.1.0,MIT
+kind-of,3.2.2,MIT
kubeclient,2.2.0,MIT
latest-version,1.0.1,MIT
launchy,2.4.3,ISC
@@ -617,7 +643,7 @@ lie,3.1.1,MIT
little-plugger,1.1.4,MIT
load-json-file,1.1.0,MIT
loader-runner,2.3.0,MIT
-loader-utils,0.2.16,MIT
+loader-utils,"",unknown
locale,2.1.2,"ruby,LGPLv3+"
locate-path,2.0.0,MIT
lodash,4.17.4,MIT
@@ -631,65 +657,69 @@ lodash._isiterateecall,3.0.9,MIT
lodash._topath,3.8.1,MIT
lodash.assign,3.2.0,MIT
lodash.camelcase,4.3.0,MIT
-lodash.capitalize,4.2.1,MIT
lodash.cond,4.5.2,MIT
-lodash.deburr,4.1.0,MIT
lodash.defaults,3.1.2,MIT
-lodash.get,4.4.2,MIT
+lodash.get,3.7.0,MIT
lodash.isarguments,3.1.0,MIT
lodash.isarray,3.0.4,MIT
-lodash.kebabcase,4.0.1,MIT
+lodash.kebabcase,4.1.1,MIT
lodash.keys,3.1.2,MIT
lodash.memoize,4.1.2,MIT
lodash.restparam,3.6.1,MIT
-lodash.snakecase,4.0.1,MIT
+lodash.snakecase,4.1.1,MIT
lodash.uniq,4.5.0,MIT
-lodash.words,4.2.0,MIT
+lodash.upperfirst,4.3.1,MIT
log4js,0.6.38,Apache 2.0
logging,2.2.2,MIT
longest,1.0.1,MIT
loofah,2.0.3,MIT
loose-envify,1.3.1,MIT
+loud-rejection,1.6.0,MIT
lowercase-keys,1.0.0,MIT
lru-cache,3.2.0,ISC
macaddress,0.2.8,MIT
mail,2.6.5,MIT
mail_room,0.9.1,MIT
+map-obj,1.0.1,MIT
map-stream,0.1.0,unknown
marked,0.3.6,MIT
-math-expression-evaluator,1.2.16,MIT
+math-expression-evaluator,1.2.17,MIT
media-typer,0.3.0,MIT
memoist,0.15.0,MIT
memory-fs,0.4.1,MIT
+meow,3.7.0,MIT
merge-descriptors,1.0.1,MIT
method_source,0.8.2,MIT
methods,1.1.2,MIT
micromatch,2.3.11,MIT
miller-rabin,4.0.0,MIT
-mime,1.3.4,MIT
+mime,1.3.6,MIT
mime-db,1.27.0,MIT
+mime-types,2.1.15,MIT
mime-types,2.99.3,"MIT,Artistic-2.0,GPL-2.0"
mimemagic,0.3.0,MIT
mini_portile2,2.1.0,MIT
minimalistic-assert,1.0.0,ISC
-minimatch,3.0.3,ISC
+minimalistic-crypto-utils,1.0.1,MIT
+minimatch,3.0.4,ISC
minimist,0.0.8,MIT
mkdirp,0.5.1,MIT
-mmap2,2.2.6,ruby
-moment,2.17.1,MIT
-mousetrap,1.4.6,Apache 2.0
+mmap2,2.2.7,ruby
+moment,2.18.1,MIT
+mousetrap,1.6.1,Apache 2.0
mousetrap-rails,1.4.6,"MIT,Apache"
-ms,0.7.2,MIT
+ms,2.0.0,MIT
msgpack,1.1.0,Apache 2.0
multi_json,1.12.1,MIT
multi_xml,0.6.0,MIT
+multicast-dns,6.1.1,MIT
+multicast-dns-service-types,1.1.0,MIT
multipart-post,2.0.0,MIT
mustermann,0.4.0,MIT
mustermann-grape,0.4.0,MIT
mute-stream,0.0.5,ISC
-mysql2,0.3.20,MIT
name-all-modules-plugin,1.0.1,MIT
-nan,2.5.1,MIT
+nan,2.6.2,MIT
natural-compare,1.4.0,MIT
negotiator,0.6.1,MIT
nested-error-stacks,1.0.2,MIT
@@ -697,23 +727,25 @@ net-ldap,0.12.1,MIT
net-ssh,3.0.1,MIT
netrc,0.11.0,MIT
node-ensure,0.0.0,MIT
+node-forge,0.6.33,BSD
node-libs-browser,2.0.0,MIT
-node-pre-gyp,0.6.33,New BSD
+node-pre-gyp,0.6.36,New BSD
node-zopfli,2.0.2,MIT
nodemon,1.11.0,MIT
nokogiri,1.6.8.1,MIT
-nopt,3.0.6,ISC
-normalize-package-data,2.3.5,Simplified BSD
-normalize-path,2.0.1,MIT
+nopt,4.0.1,ISC
+normalize-package-data,2.4.0,Simplified BSD
+normalize-path,2.1.1,MIT
normalize-range,0.1.2,MIT
normalize-url,1.9.1,MIT
-npmlog,4.0.2,ISC
+npmlog,4.1.2,ISC
+null-check,1.0.0,MIT
num2fraction,1.2.2,MIT
number-is-nan,1.0.1,MIT
numerizer,0.1.1,MIT
oauth,0.5.1,MIT
oauth-sign,0.8.2,Apache 2.0
-oauth2,1.3.1,MIT
+oauth2,1.4.0,MIT
object-assign,4.1.1,MIT
object-component,0.0.3,unknown
object.omit,2.0.1,MIT
@@ -740,7 +772,7 @@ omniauth-twitter,1.2.1,MIT
omniauth_crowd,2.2.3,MIT
on-finished,2.3.0,MIT
on-headers,1.0.1,MIT
-once,1.3.3,ISC
+once,1.4.0,ISC
onetime,1.1.0,MIT
opener,1.4.3,(WTFPL OR MIT)
opn,4.0.2,MIT
@@ -758,10 +790,11 @@ os-tmpdir,1.0.2,MIT
osenv,0.1.4,ISC
p-limit,1.1.0,MIT
p-locate,2.0.0,MIT
+p-map,1.1.1,MIT
package-json,1.2.0,MIT
pako,1.0.5,(MIT AND Zlib)
-paranoia,2.2.0,MIT
-parse-asn1,5.0.0,ISC
+paranoia,2.3.1,MIT
+parse-asn1,5.1.0,ISC
parse-glob,3.0.4,MIT
parse-json,2.2.0,MIT
parsejson,0.0.3,MIT
@@ -769,36 +802,35 @@ parseqs,0.0.5,MIT
parseuri,0.0.5,MIT
parseurl,1.3.1,MIT
path-browserify,0.0.0,MIT
-path-exists,3.0.0,MIT
+path-exists,2.1.0,MIT
path-is-absolute,1.0.1,MIT
path-is-inside,1.0.2,(WTFPL OR MIT)
path-parse,1.0.5,MIT
path-to-regexp,0.1.7,MIT
path-type,1.1.0,MIT
pause-stream,0.0.11,"MIT,Apache2"
-pbkdf2,3.0.9,MIT
-pdfjs-dist,1.8.252,Apache 2.0
+pbkdf2,3.0.12,MIT
+pdfjs-dist,1.8.527,Apache 2.0
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
peek-host,1.0.0,MIT
-peek-mysql2,1.1.0,MIT
peek-performance_bar,1.2.1,MIT
peek-pg,1.3.0,MIT
peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
peek-sidekiq,1.0.3,MIT
+performance-now,0.2.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
pify,2.3.0,MIT
-pikaday,1.5.1,"BSD,MIT"
+pikaday,1.6.1,(0BSD OR MIT)
pinkie,2.0.4,MIT
pinkie-promise,2.0.1,MIT
pkg-dir,1.0.0,MIT
-pkg-up,1.0.0,MIT
pluralize,1.2.1,MIT
po_to_json,1.0.1,MIT
portfinder,1.0.13,MIT
posix-spawn,0.3.11,"MIT,LGPL"
-postcss,5.2.16,MIT
+postcss,5.2.17,MIT
postcss-calc,5.3.1,MIT
postcss-colormin,2.2.2,MIT
postcss-convert-values,2.6.1,MIT
@@ -819,10 +851,10 @@ postcss-minify-font-values,1.0.5,MIT
postcss-minify-gradients,1.0.5,MIT
postcss-minify-params,1.2.2,MIT
postcss-minify-selectors,2.1.1,MIT
-postcss-modules-extract-imports,1.0.1,ISC
-postcss-modules-local-by-default,1.1.1,MIT
-postcss-modules-scope,1.0.2,ISC
-postcss-modules-values,1.2.2,ISC
+postcss-modules-extract-imports,1.1.0,ISC
+postcss-modules-local-by-default,1.2.0,MIT
+postcss-modules-scope,1.1.0,ISC
+postcss-modules-values,1.3.0,ISC
postcss-normalize-charset,1.1.1,MIT
postcss-normalize-url,3.0.8,MIT
postcss-ordered-values,2.2.3,MIT
@@ -835,16 +867,16 @@ postcss-unique-selectors,2.0.2,MIT
postcss-value-parser,3.3.0,MIT
postcss-zindex,2.2.0,MIT
prelude-ls,1.1.2,MIT
-premailer,1.8.6,New BSD
-premailer-rails,1.9.2,MIT
+premailer,1.10.4,New BSD
+premailer-rails,1.9.7,MIT
prepend-http,1.0.4,MIT
preserve,0.2.0,MIT
prismjs,1.6.0,MIT
private,0.1.7,MIT
-process,0.11.9,MIT
+process,0.11.10,MIT
process-nextick-args,1.0.7,MIT
progress,1.1.8,MIT
-prometheus-client-mmap,0.7.0.beta5,Apache 2.0
+prometheus-client-mmap,0.7.0.beta8,Apache 2.0
proto-list,1.2.4,ISC
proxy-addr,1.1.4,MIT
prr,0.0.0,MIT
@@ -855,8 +887,8 @@ punycode,1.4.1,MIT
pyu-ruby-sasl,0.0.3.3,MIT
q,1.5.0,MIT
qjobs,1.1.5,MIT
-qs,6.3.0,New BSD
-query-string,4.3.2,MIT
+qs,6.4.0,New BSD
+query-string,4.3.4,MIT
querystring,0.2.0,MIT
querystring-es3,0.2.1,MIT
querystringify,0.0.4,MIT
@@ -872,24 +904,25 @@ rails,4.2.8,MIT
rails-deprecated_sanitizer,1.0.3,MIT
rails-dom-testing,1.0.8,MIT
rails-html-sanitizer,1.0.3,MIT
+rails-i18n,4.0.9,MIT
railties,4.2.8,MIT
-rainbow,2.1.0,MIT
-raindrops,0.17.0,LGPL-2.1+
+rainbow,2.2.2,MIT
+raindrops,0.18.0,LGPL-2.1+
rake,10.5.0,MIT
-randomatic,1.1.6,MIT
-randombytes,2.0.3,MIT
+randomatic,1.1.7,MIT
+randombytes,2.0.5,MIT
range-parser,1.2.0,MIT
raphael,2.2.7,MIT
-raven-js,3.14.0,Simplified BSD
+raven-js,3.16.1,Simplified BSD
raw-body,2.2.0,MIT
raw-loader,0.5.1,MIT
-rc,1.1.6,(BSD-2-Clause OR MIT OR Apache-2.0)
+rc,1.2.1,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,4.2.2,ruby
react-dev-utils,0.5.2,New BSD
read-all-stream,3.1.0,MIT
read-pkg,1.1.0,MIT
read-pkg-up,1.0.1,MIT
-readable-stream,2.2.2,MIT
+readable-stream,2.3.3,MIT
readdirp,2.1.0,MIT
readline2,1.0.1,MIT
recaptcha,3.0.0,MIT
@@ -897,6 +930,7 @@ rechoir,0.6.2,MIT
recursive-open-struct,1.0.0,MIT
recursive-readdir,2.1.1,MIT
redcarpet,3.4.0,MIT
+redent,1.0.0,MIT
redis,3.3.3,MIT
redis-actionpack,5.0.1,MIT
redis-activesupport,5.0.1,MIT
@@ -907,33 +941,34 @@ redis-store,1.2.0,MIT
reduce-css-calc,1.3.0,MIT
reduce-function-call,1.0.2,MIT
regenerate,1.3.2,MIT
-regenerator-runtime,0.10.1,MIT
-regenerator-transform,0.9.8,BSD
+regenerator-runtime,0.10.5,MIT
+regenerator-transform,0.9.11,BSD
regex-cache,0.4.3,MIT
-regexpu-core,2.0.0,MIT
+regexpu-core,"",unknown
registry-url,3.1.0,MIT
regjsgen,0.2.0,MIT
regjsparser,0.1.5,BSD
+remove-trailing-separator,1.0.2,ISC
repeat-element,1.1.2,MIT
repeat-string,1.6.1,MIT
repeating,2.0.1,MIT
-request,2.79.0,Apache 2.0
+request,2.81.0,Apache 2.0
request_store,1.3.1,MIT
require-directory,2.1.1,MIT
require-from-string,1.2.1,MIT
require-main-filename,1.0.1,ISC
require-uncached,1.0.3,MIT
requires-port,1.0.0,MIT
-resolve,1.2.0,MIT
+resolve,1.3.3,MIT
resolve-from,1.0.1,MIT
responders,2.3.0,MIT
rest-client,2.0.0,MIT
restore-cursor,1.0.1,MIT
retriable,1.4.1,MIT
right-align,0.1.3,MIT
-rimraf,2.5.4,ISC
+rimraf,2.6.1,ISC
rinku,2.0.0,ISC
-ripemd160,1.0.1,New BSD
+ripemd160,2.0.1,MIT
rotp,2.1.2,MIT
rouge,2.1.0,MIT
rqrcode,0.7.0,MIT
@@ -941,40 +976,42 @@ rqrcode-rails3,0.1.7,MIT
ruby-fogbugz,0.2.1,MIT
ruby-prof,0.16.2,Simplified BSD
ruby-saml,1.4.1,MIT
-ruby_parser,3.8.4,MIT
+ruby_parser,3.9.0,MIT
rubyntlm,0.5.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
rugged,0.25.1.1,MIT
run-async,0.1.0,MIT
rx-lite,3.1.2,Apache 2.0
-safe-buffer,5.0.1,MIT
+safe-buffer,5.1.1,MIT
safe_yaml,1.0.4,MIT
sanitize,2.1.0,MIT
sass,3.4.22,MIT
sass-rails,5.0.6,MIT
sawyer,0.8.1,MIT
-sax,1.2.2,ISC
+sax,1.2.4,ISC
+schema-utils,0.3.0,MIT
securecompare,1.0.0,MIT
seed-fu,2.3.6,MIT
select,1.1.2,MIT
select-hose,2.0.0,MIT
select2,3.5.2-browserify,unknown
select2-rails,3.5.9.3,MIT
+selfsigned,1.9.1,MIT
semver,5.3.0,ISC
semver-diff,2.1.0,MIT
send,0.15.3,MIT
-sentry-raven,2.4.0,Apache 2.0
-serve-index,1.8.0,MIT
+sentry-raven,2.5.3,Apache 2.0
+serve-index,1.9.0,MIT
serve-static,1.12.3,MIT
set-blocking,2.0.0,ISC
set-immediate-shim,1.0.1,MIT
setimmediate,1.0.5,MIT
setprototypeof,1.0.3,ISC
settingslogic,2.0.9,MIT
-sexp_processor,4.8.0,MIT
+sexp_processor,4.9.0,MIT
sha.js,2.4.8,MIT
-shelljs,0.7.6,New BSD
+shelljs,0.7.8,New BSD
sidekiq,5.0.0,LGPL
sidekiq-cron,0.6.0,MIT
sidekiq-limit_fetch,3.4.0,MIT
@@ -995,18 +1032,18 @@ sockjs-client,1.0.1,MIT
sort-keys,1.1.2,MIT
source-list-map,0.1.8,MIT
source-map,0.5.6,New BSD
-source-map-support,0.4.11,MIT
+source-map-support,0.4.15,MIT
spdx-correct,1.0.2,Apache 2.0
spdx-expression-parse,1.0.4,(MIT AND CC-BY-3.0)
spdx-license-ids,1.2.2,Unlicense
-spdy,3.4.4,MIT
-spdy-transport,2.0.18,MIT
+spdy,3.4.7,MIT
+spdy-transport,2.0.20,MIT
split,0.3.3,MIT
sprintf-js,1.0.3,New BSD
sprockets,3.7.1,MIT
sprockets-rails,3.2.0,MIT
sql.js,0.4.0,MIT
-sshpk,1.10.2,MIT
+sshpk,1.13.1,MIT
state_machines,0.4.0,MIT
state_machines-activemodel,0.4.0,MIT
state_machines-activerecord,0.4.0,MIT
@@ -1014,7 +1051,7 @@ stats-webpack-plugin,0.4.3,MIT
statuses,1.3.1,MIT
stream-browserify,2.0.1,MIT
stream-combiner,0.0.4,MIT
-stream-http,2.6.3,MIT
+stream-http,2.7.2,MIT
stream-shift,1.0.0,MIT
strict-uri-encode,1.1.0,MIT
string-length,1.0.1,MIT
@@ -1024,44 +1061,47 @@ stringex,2.5.2,MIT
stringstream,0.0.5,MIT
strip-ansi,3.0.1,MIT
strip-bom,2.0.0,MIT
-strip-json-comments,1.0.4,MIT
-supports-color,0.2.0,MIT
+strip-indent,1.0.1,MIT
+strip-json-comments,2.0.1,MIT
+supports-color,4.2.0,MIT
svgo,0.7.2,MIT
sys-filesystem,1.1.6,Artistic 2.0
table,3.8.3,New BSD
tapable,0.2.6,MIT
tar,2.2.1,ISC
-tar-pack,3.3.0,Simplified BSD
+tar-pack,3.4.0,Simplified BSD
temple,0.7.7,MIT
-test-exclude,4.0.0,ISC
+test-exclude,4.1.1,ISC
text,1.3.1,MIT
text-table,0.2.0,MIT
thor,0.19.4,MIT
thread_safe,0.3.6,Apache 2.0
three,0.84.0,MIT
three-orbit-controls,82.1.0,MIT
-three-stl-loader,1.0.4,MIT
+three-stl-loader,1.0.5,MIT
through,2.3.8,MIT
+thunky,0.1.0,unknown
tilt,2.0.6,MIT
timeago.js,2.0.5,MIT
timed-out,2.0.0,MIT
timers-browserify,2.0.2,MIT
timfel-krb5-auth,0.8.3,LGPL
-tiny-emitter,1.1.0,MIT
+tiny-emitter,2.0.1,MIT
tmp,0.0.31,MIT
to-array,0.1.4,MIT
to-arraybuffer,1.0.1,MIT
-to-fast-properties,1.0.2,MIT
+to-fast-properties,1.0.3,MIT
toml-rb,0.3.15,MIT
tool,0.2.3,MIT
touch,1.0.0,ISC
tough-cookie,2.3.2,New BSD
traverse,0.6.6,MIT
+trim-newlines,1.0.0,MIT
trim-right,1.0.1,MIT
truncato,0.7.8,MIT
tryit,1.0.3,MIT
tty-browserify,0.0.0,MIT
-tunnel-agent,0.4.3,Apache 2.0
+tunnel-agent,0.6.0,Apache 2.0
tweetnacl,0.14.5,Unlicense
type-check,0.3.2,MIT
type-is,1.6.15,MIT
@@ -1069,7 +1109,7 @@ typedarray,0.0.6,MIT
tzinfo,1.2.2,MIT
u2f,0.2.1,MIT
uglifier,2.7.2,MIT
-uglify-js,2.8.27,Simplified BSD
+uglify-js,2.8.29,Simplified BSD
uglify-to-browserify,1.0.2,MIT
uid-number,0.0.6,ISC
ultron,1.1.0,MIT
@@ -1087,15 +1127,15 @@ uniqs,2.0.0,MIT
unpipe,1.0.0,MIT
update-notifier,0.5.0,Simplified BSD
url,0.11.0,MIT
-url-loader,0.5.8,MIT
+url-loader,0.5.9,MIT
url-parse,1.0.5,MIT
url_safe_base64,0.2.2,MIT
user-home,2.0.0,MIT
-useragent,2.1.13,MIT
+useragent,2.2.0,MIT
util,0.10.3,MIT
util-deprecate,1.0.2,MIT
utils-merge,1.0.0,MIT
-uuid,3.0.1,MIT
+uuid,3.1.0,MIT
validate-npm-package-license,3.0.1,Apache 2.0
validates_hostname,1.0.6,MIT
vary,1.1.1,MIT
@@ -1107,36 +1147,36 @@ visibilityjs,1.2.4,MIT
vm-browserify,0.0.4,MIT
vmstat,2.3.0,MIT
void-elements,2.0.1,MIT
-vue,2.2.6,MIT
-vue-hot-reload-api,2.0.11,MIT
+vue,2.3.4,MIT
+vue-hot-reload-api,2.1.0,MIT
vue-loader,11.3.4,MIT
vue-resource,0.9.3,MIT
vue-style-loader,2.0.5,MIT
-vue-template-compiler,2.2.6,MIT
-vue-template-es2015-compiler,1.5.1,MIT
+vue-template-compiler,2.3.4,MIT
+vue-template-es2015-compiler,1.5.3,MIT
warden,1.2.6,MIT
watchpack,1.3.1,MIT
wbuf,1.7.2,MIT
webpack,2.6.1,MIT
webpack-bundle-analyzer,2.8.2,MIT
-webpack-dev-middleware,1.10.0,MIT
-webpack-dev-server,2.4.2,MIT
+webpack-dev-middleware,1.11.0,MIT
+webpack-dev-server,2.5.1,MIT
webpack-rails,0.9.10,MIT
-webpack-sources,0.1.4,MIT
+webpack-sources,0.1.5,MIT
websocket-driver,0.6.5,MIT
websocket-extensions,0.1.1,MIT
whet.extend,0.9.9,MIT
-which,1.2.12,ISC
+which,1.2.14,ISC
which-module,1.0.0,ISC
-wide-align,1.1.0,ISC
+wide-align,1.1.2,ISC
wikicloth,0.8.1,MIT
window-size,0.1.0,MIT
wordwrap,0.0.2,MIT/X11
-worker-loader,0.8.0,MIT
+worker-loader,0.8.1,MIT
wrap-ansi,2.1.0,MIT
wrappy,1.0.2,ISC
write,0.2.1,MIT
-write-file-atomic,1.3.1,ISC
+write-file-atomic,1.3.4,ISC
ws,2.3.1,MIT
wtf-8,1.0.0,MIT
xdg-basedir,2.0.0,MIT
diff --git a/yarn.lock b/yarn.lock
index b04eebe60af..98da6a984d1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1575,6 +1575,12 @@ deckar01-task_list@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.0.tgz#7f7a595430d21b3036ed5dfbf97d6b65de18e2c9"
+decompress-response@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
+ dependencies:
+ mimic-response "^1.0.0"
+
deep-extend@~0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253"
@@ -1712,6 +1718,10 @@ dropzone@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
+duplexer3@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
+
duplexer@^0.1.1, duplexer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -2445,6 +2455,10 @@ get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+
getpass@^0.1.1:
version "0.1.6"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
@@ -2521,6 +2535,25 @@ got@^3.2.0:
read-all-stream "^3.0.0"
timed-out "^2.0.0"
+got@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a"
+ dependencies:
+ decompress-response "^3.2.0"
+ duplexer3 "^0.1.4"
+ get-stream "^3.0.0"
+ is-plain-obj "^1.1.0"
+ is-retry-allowed "^1.0.0"
+ is-stream "^1.0.0"
+ isurl "^1.0.0-alpha5"
+ lowercase-keys "^1.0.0"
+ p-cancelable "^0.3.0"
+ p-timeout "^1.1.1"
+ safe-buffer "^5.0.1"
+ timed-out "^4.0.0"
+ url-parse-lax "^1.0.0"
+ url-to-options "^1.0.1"
+
graceful-fs@^4.1.11, graceful-fs@^4.1.2:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -2578,6 +2611,16 @@ has-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+has-symbol-support-x@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8"
+
+has-to-string-tag-x@^1.2.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f"
+ dependencies:
+ has-symbol-support-x "^1.3.0"
+
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2902,6 +2945,10 @@ is-number@^2.0.2, is-number@^2.1.0:
dependencies:
kind-of "^3.0.2"
+is-object@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
+
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -2918,7 +2965,7 @@ is-path-inside@^1.0.0:
dependencies:
path-is-inside "^1.0.1"
-is-plain-obj@^1.0.0:
+is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
@@ -2950,6 +2997,10 @@ is-resolvable@^1.0.0:
dependencies:
tryit "^1.0.1"
+is-retry-allowed@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
+
is-stream@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -3087,6 +3138,13 @@ istanbul@^0.4.5:
which "^1.1.1"
wordwrap "^1.0.0"
+isurl@^1.0.0-alpha5:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
+ dependencies:
+ has-to-string-tag-x "^1.2.0"
+ is-object "^1.0.1"
+
jasmine-core@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815"
@@ -3633,6 +3691,10 @@ mime@1.3.4, mime@1.3.x, mime@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+mimic-response@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
+
minimalistic-assert@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
@@ -3981,6 +4043,14 @@ osenv@^0.1.0:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
+p-cancelable@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+
p-limit@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@@ -3991,6 +4061,12 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"
+p-timeout@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c"
+ dependencies:
+ p-finally "^1.0.0"
+
package-json@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
@@ -4419,7 +4495,7 @@ prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
-prepend-http@^1.0.0:
+prepend-http@^1.0.0, prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -5384,6 +5460,10 @@ timed-out@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
+timed-out@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
+
timers-browserify@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d"
@@ -5545,6 +5625,12 @@ url-loader@^0.5.8:
loader-utils "^1.0.2"
mime "1.3.x"
+url-parse-lax@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
+ dependencies:
+ prepend-http "^1.0.1"
+
url-parse@1.0.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
@@ -5559,6 +5645,10 @@ url-parse@^1.0.1, url-parse@^1.1.1:
querystringify "0.0.x"
requires-port "1.0.x"
+url-to-options@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
+
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -5657,9 +5747,11 @@ vue-loader@^11.3.4:
vue-style-loader "^2.0.0"
vue-template-es2015-compiler "^1.2.2"
-vue-resource@^0.9.3:
- version "0.9.3"
- resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-0.9.3.tgz#ab46e1c44ea219142dcc28ae4043b3b04c80959d"
+vue-resource@^1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/vue-resource/-/vue-resource-1.3.4.tgz#9fc0bdf6a2f5cab430129fc99d347b3deae7b099"
+ dependencies:
+ got "^7.0.0"
vue-style-loader@^2.0.0:
version "2.0.5"