summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.rubocop.yml4
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock33
-rw-r--r--app/assets/javascripts/api.js1
-rw-r--r--app/assets/javascripts/fly_out_nav.js8
-rw-r--r--app/assets/javascripts/gpg_badges.js4
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js50
-rw-r--r--app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue59
-rw-r--r--app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue21
-rw-r--r--app/assets/javascripts/project.js4
-rw-r--r--app/assets/javascripts/repo/components/repo.vue52
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue90
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue39
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue104
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue67
-rw-r--r--app/assets/javascripts/repo/components/repo_file_buttons.vue47
-rw-r--r--app/assets/javascripts/repo/components/repo_file_options.vue2
-rw-r--r--app/assets/javascripts/repo/components/repo_loading_file.vue61
-rw-r--r--app/assets/javascripts/repo/components/repo_prev_directory.vue16
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue30
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue50
-rw-r--r--app/assets/javascripts/repo/components/repo_tab.vue38
-rw-r--r--app/assets/javascripts/repo/components/repo_tabs.vue25
-rw-r--r--app/assets/javascripts/repo/helpers/monaco_loader_helper.js8
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js92
-rw-r--r--app/assets/javascripts/repo/index.js8
-rw-r--r--app/assets/javascripts/repo/services/repo_service.js18
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js68
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/popup_dialog.vue64
-rw-r--r--app/assets/stylesheets/framework/animations.scss78
-rw-r--r--app/assets/stylesheets/framework/layout.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss10
-rw-r--r--app/assets/stylesheets/new_nav.scss1
-rw-r--r--app/assets/stylesheets/new_sidebar.scss8
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/notes.scss56
-rw-r--r--app/assets/stylesheets/pages/repo.scss124
-rw-r--r--app/assets/stylesheets/pages/tree.scss4
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb9
-rw-r--r--app/controllers/groups/application_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/projects/blob_controller.rb5
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb24
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/pipeline_schedules_helper.rb10
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/mailers/emails/members.rb4
-rw-r--r--app/models/blob_viewer/notebook.rb2
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/deploy_keys_project.rb2
-rw-r--r--app/models/group.rb30
-rw-r--r--app/models/member.rb15
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/namespace.rb14
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/redirect_route.rb2
-rw-r--r--app/models/repository.rb19
-rw-r--r--app/models/user.rb6
-rw-r--r--app/serializers/project_entity.rb2
-rw-r--r--app/serializers/tree_root_entity.rb15
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/fork_service.rb6
-rw-r--r--app/services/projects/forks_count_service.rb30
-rw-r--r--app/services/projects/unlink_fork_service.rb6
-rw-r--r--app/views/layouts/nav/_new_admin_sidebar.html.haml271
-rw-r--r--app/views/layouts/nav/_new_group_sidebar.html.haml153
-rw-r--r--app/views/layouts/nav/_new_profile_sidebar.html.haml153
-rw-r--r--app/views/layouts/nav/_new_project_sidebar.html.haml479
-rw-r--r--app/views/projects/commit/_ajax_signature.html.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml3
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml2
-rw-r--r--app/views/projects/notes/_actions.html.haml38
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml9
-rw-r--r--app/views/projects/tree/_tree_header.html.haml5
-rw-r--r--app/views/shared/_ref_switcher.html.haml2
-rw-r--r--app/views/shared/_target_switcher.html.haml2
-rw-r--r--app/views/shared/icons/_ellipsis_v.svg1
-rw-r--r--app/views/shared/icons/_express.svg (renamed from app/views/shared/icons/_node_express.svg)0
-rw-r--r--app/views/shared/icons/_spring.svg (renamed from app/views/shared/icons/_java_spring.svg)0
-rw-r--r--app/views/shared/repo/_repo.html.haml9
-rw-r--r--app/views/snippets/notes/_actions.html.haml17
-rw-r--r--changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml4
-rw-r--r--changelogs/unreleased/34371-pipeline-schedule-vue-files.yml6
-rw-r--r--changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml4
-rw-r--r--changelogs/unreleased/34533-speed-up-group-project-authorizations.yml5
-rw-r--r--changelogs/unreleased/34643-fix-project-path-slugify.yml4
-rw-r--r--changelogs/unreleased/36385-pipeline-graph-dropdown.yml5
-rw-r--r--changelogs/unreleased/fix-btn-alignment.yml5
-rw-r--r--changelogs/unreleased/fix-edit-merge-request-button-case.yml5
-rw-r--r--changelogs/unreleased/forks-count-cache.yml5
-rw-r--r--changelogs/unreleased/issue_31790.yml4
-rw-r--r--changelogs/unreleased/seven-days-cycle-analytics.yml5
-rw-r--r--config/initializers/0_acts_as_taggable.rb2
-rw-r--r--config/initializers/active_record_mysql_timestamp.rb30
-rw-r--r--config/initializers/static_files.rb10
-rw-r--r--config/initializers/trusted_proxies.rb2
-rw-r--r--config/prometheus/additional_metrics.yml38
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/webpack.config.js3
-rw-r--r--db/fixtures/development/10_merge_requests.rb2
-rw-r--r--db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb1
-rw-r--r--db/migrate/20160817133006_add_koding_to_application_settings.rb1
-rw-r--r--db/migrate/20161020075830_default_request_access_projects.rb2
-rw-r--r--db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb1
-rw-r--r--db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb1
-rw-r--r--db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb1
-rw-r--r--db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb14
-rw-r--r--db/post_migrate/20170503004427_update_retried_for_ci_build.rb2
-rw-r--r--db/post_migrate/20170523083112_migrate_old_artifacts.rb6
-rw-r--r--db/post_migrate/20170815060945_remove_duplicate_mr_events.rb26
-rw-r--r--db/schema.rb2
-rw-r--r--doc/README.md1
-rw-r--r--doc/articles/artifactory_and_gitlab/index.md278
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md2
-rw-r--r--doc/articles/how_to_install_git/index.md2
-rw-r--r--doc/articles/index.md1
-rw-r--r--doc/articles/openshift_and_gitlab/index.md2
-rw-r--r--doc/ci/README.md3
-rw-r--r--doc/ci/autodeploy/img/auto_monitoring.pngbin0 -> 89206 bytes
-rw-r--r--doc/ci/autodeploy/index.md25
-rw-r--r--doc/ci/environments.md5
-rw-r--r--doc/development/fe_guide/style_guide_js.md17
-rw-r--r--doc/development/testing.md37
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md2
-rw-r--r--doc/user/group/index.md10
-rw-r--r--doc/user/permissions.md113
-rw-r--r--doc/user/project/index.md6
-rw-r--r--doc/user/project/integrations/img/jira_service_page.pngbin83466 -> 193364 bytes
-rw-r--r--doc/user/project/integrations/jira.md8
-rw-r--r--doc/user/project/integrations/prometheus.md6
-rw-r--r--doc/user/project/integrations/prometheus_library/metrics.md6
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md25
-rw-r--r--doc/user/project/members/img/access_requests_management.png (renamed from doc/workflow/add-user/img/access_requests_management.png)bin11018 -> 11018 bytes
-rw-r--r--doc/user/project/members/img/add_new_user_to_project_settings.png (renamed from doc/workflow/add-user/img/add_new_user_to_project_settings.png)bin11046 -> 11046 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_accept.png (renamed from doc/workflow/add-user/img/add_user_email_accept.png)bin16890 -> 16890 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_ready.png (renamed from doc/workflow/add-user/img/add_user_email_ready.png)bin28171 -> 28171 bytes
-rw-r--r--doc/user/project/members/img/add_user_email_search.png (renamed from doc/workflow/add-user/img/add_user_email_search.png)bin29628 -> 29628 bytes
-rw-r--r--doc/user/project/members/img/add_user_give_permissions.png (renamed from doc/workflow/add-user/img/add_user_give_permissions.png)bin36619 -> 36619 bytes
-rw-r--r--doc/user/project/members/img/add_user_import_members_from_another_project.png (renamed from doc/workflow/add-user/img/add_user_import_members_from_another_project.png)bin25343 -> 25343 bytes
-rw-r--r--doc/user/project/members/img/add_user_imported_members.png (renamed from doc/workflow/add-user/img/add_user_imported_members.png)bin25398 -> 25398 bytes
-rw-r--r--doc/user/project/members/img/add_user_list_members.png (renamed from doc/workflow/add-user/img/add_user_list_members.png)bin16916 -> 16916 bytes
-rw-r--r--doc/user/project/members/img/add_user_members_menu.png (renamed from doc/workflow/add-user/img/add_user_members_menu.png)bin28994 -> 28994 bytes
-rw-r--r--doc/user/project/members/img/add_user_search_people.png (renamed from doc/workflow/add-user/img/add_user_search_people.png)bin25368 -> 25368 bytes
-rw-r--r--doc/user/project/members/img/max_access_level.png (renamed from doc/workflow/groups/max_access_level.png)bin34718 -> 34718 bytes
-rw-r--r--doc/user/project/members/img/other_group_sees_shared_project.png (renamed from doc/workflow/groups/other_group_sees_shared_project.png)bin30182 -> 30182 bytes
-rw-r--r--doc/user/project/members/img/request_access_button.png (renamed from doc/workflow/add-user/img/request_access_button.png)bin25281 -> 25281 bytes
-rw-r--r--doc/user/project/members/img/share_project_with_groups.png (renamed from doc/workflow/groups/share_project_with_groups.png)bin30307 -> 30307 bytes
-rw-r--r--doc/user/project/members/img/withdraw_access_request_button.png (renamed from doc/workflow/add-user/img/withdraw_access_request_button.png)bin26135 -> 26135 bytes
-rw-r--r--doc/user/project/members/index.md116
-rw-r--r--doc/user/project/members/share_project_with_groups.md41
-rw-r--r--doc/user/project/repository/index.md6
-rw-r--r--doc/user/search/img/group_issues_filter.pngbin0 -> 45288 bytes
-rw-r--r--doc/user/search/index.md8
-rw-r--r--doc/user/snippets.md2
-rw-r--r--doc/workflow/README.md7
-rw-r--r--doc/workflow/add-user/add-user.md115
-rw-r--r--doc/workflow/project_features.md46
-rw-r--r--doc/workflow/share_projects_with_other_groups.md33
-rw-r--r--doc/workflow/share_with_group.md14
-rw-r--r--features/steps/profile/emails.rb2
-rw-r--r--features/steps/project/merge_requests.rb3
-rw-r--r--features/steps/shared/note.rb7
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/files.rb3
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/v3/projects.rb2
-rw-r--r--lib/banzai/filter/image_lazy_load_filter.rb4
-rw-r--r--lib/constraints/project_url_constrainer.rb2
-rw-r--r--lib/declarative_policy/base.rb2
-rw-r--r--lib/gitlab/auth/ip_rate_limiter.rb12
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/git/blob.rb2
-rw-r--r--lib/gitlab/git/repository.rb24
-rw-r--r--lib/gitlab/import_export/attributes_finder.rb1
-rw-r--r--lib/gitlab/ldap/auth_hash.rb2
-rw-r--r--lib/gitlab/middleware/rails_queue_duration.rb2
-rw-r--r--lib/gitlab/project_template.rb4
-rw-r--r--lib/gitlab/redis/cache.rb5
-rw-r--r--lib/gitlab/redis/queues.rb5
-rw-r--r--lib/gitlab/redis/shared_state.rb5
-rw-r--r--lib/gitlab/redis/wrapper.rb13
-rw-r--r--lib/gitlab/shell.rb12
-rw-r--r--lib/gitlab/slash_commands/presenters/help.rb2
-rw-r--r--lib/gitlab/utils.rb13
-rw-r--r--lib/gitlab/workhorse.rb1
-rw-r--r--lib/tasks/gitlab/gitaly.rake8
-rw-r--r--lib/tasks/gitlab/update_templates.rake9
-rw-r--r--locale/bg/gitlab.po19
-rw-r--r--locale/en/gitlab.po123
-rw-r--r--locale/eo/gitlab.po28
-rw-r--r--locale/es/gitlab.po20
-rw-r--r--locale/fr/gitlab.po19
-rw-r--r--locale/gitlab.pot10
-rw-r--r--locale/it/gitlab.po20
-rw-r--r--locale/ja/gitlab.po19
-rw-r--r--locale/ko/gitlab.po18
-rw-r--r--locale/pt_BR/gitlab.po26
-rw-r--r--locale/ru/gitlab.po20
-rw-r--r--locale/uk/gitlab.po20
-rw-r--r--locale/zh_CN/gitlab.po14
-rw-r--r--locale/zh_HK/gitlab.po14
-rw-r--r--locale/zh_TW/gitlab.po14
-rw-r--r--package.json2
-rw-r--r--rubocop/cop/migration/safer_boolean_column.rb94
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb6
-rw-r--r--spec/factories/deployments.rb4
-rw-r--r--spec/factories/merge_requests.rb11
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/cycle_analytics_spec.rb38
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb4
-rw-r--r--spec/features/issues/note_polling_spec.rb2
-rw-r--r--spec/features/issues/update_issues_spec.rb12
-rw-r--r--spec/features/issues_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb10
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb12
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb6
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb6
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb16
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb3
-rw-r--r--spec/features/milestones/show_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb6
-rw-r--r--spec/features/task_lists_spec.rb4
-rw-r--r--spec/finders/environments_finder_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb12
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb2
-rw-r--r--spec/javascripts/fixtures/services.rb1
-rw-r--r--spec/javascripts/fly_out_nav_spec.js6
-rw-r--r--spec/javascripts/gpg_badges_spec.js48
-rw-r--r--spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js2
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js58
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js18
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js51
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js19
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js6
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js2
-rw-r--r--spec/javascripts/repo/components/repo_sidebar_spec.js50
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js34
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js23
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb4
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb10
-rw-r--r--spec/lib/gitlab/key_fingerprint_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb12
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/project_template_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb4
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb7
-rw-r--r--spec/lib/gitlab/shell_spec.rb35
-rw-r--r--spec/lib/gitlab/utils_spec.rb16
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb4
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb26
-rw-r--r--spec/models/ci/build_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb30
-rw-r--r--spec/models/project_spec.rb10
-rw-r--r--spec/models/repository_spec.rb21
-rw-r--r--spec/requests/api/commits_spec.rb213
-rw-r--r--spec/requests/api/files_spec.rb10
-rw-r--r--spec/requests/api/merge_requests_spec.rb200
-rw-r--r--spec/requests/api/projects_spec.rb8
-rw-r--r--spec/requests/api/protected_branches_spec.rb4
-rw-r--r--spec/requests/api/settings_spec.rb2
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb24
-rw-r--r--spec/requests/api/v3/projects_spec.rb8
-rw-r--r--spec/requests/ci/api/builds_spec.rb2
-rw-r--r--spec/rubocop/cop/migration/safer_boolean_column_spec.rb85
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb2
-rw-r--r--spec/services/create_deployment_service_spec.rb4
-rw-r--r--spec/services/issues/resolve_discussions_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb10
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb8
-rw-r--r--spec/services/projects/destroy_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb8
-rw-r--r--spec/services/projects/forks_count_service_spec.rb40
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb10
-rw-r--r--spec/services/web_hook_service_spec.rb2
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb7
-rwxr-xr-xspec/support/generate-seed-repo-rb12
-rw-r--r--spec/support/gitlab-git-test.git/objects/3e/20715310a699808282e772720b9c04a0696bccbin0 -> 566 bytes
-rw-r--r--spec/support/gitlab-git-test.git/objects/95/96bc54a6f0c0c98248fe97077eb5ccf48a98d02
-rw-r--r--spec/support/gitlab-git-test.git/packed-refs1
-rw-r--r--spec/support/gpg_helpers.rb2
-rw-r--r--spec/support/issuables_list_metadata_shared_examples.rb4
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb2
-rw-r--r--spec/support/seed_repo.rb1
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb21
-rw-r--r--spec/views/projects/commits/_commit.html.haml_spec.rb22
-rw-r--r--spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb6
-rw-r--r--vendor/project_templates/express.tar.gzbin0 -> 4572 bytes
-rw-r--r--vendor/project_templates/spring.tar.gzbin0 -> 49882 bytes
-rw-r--r--yarn.lock6
314 files changed, 3759 insertions, 2229 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 024f2929252..df7244d5a2e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-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"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-phantomjs-2.1-node-7.1-postgresql-9.6"
.default-cache: &default-cache
key: "ruby-233-with-yarn"
@@ -226,6 +226,7 @@ update-tests-metadata:
flaky-examples-check:
<<: *dedicated-runner
+ <<: *except-docs
image: ruby:2.3-alpine
services: []
before_script: []
@@ -473,7 +474,6 @@ db:rollback-mysql:
variables:
SIZE: "1"
SETUP_DB: "false"
- RAILS_ENV: "development"
script:
- git clone https://gitlab.com/gitlab-org/gitlab-test.git
/home/git/repositories/gitlab-org/gitlab-test.git
@@ -522,7 +522,7 @@ karma:
<<: *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"
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.13-chrome-59.0-node-7.1-postgresql-9.6"
stage: test
variables:
BABEL_ENV: "coverage"
diff --git a/.rubocop.yml b/.rubocop.yml
index 583648bb877..23bb0fa8be8 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -251,6 +251,10 @@ Layout/Tab:
Layout/TrailingBlankLines:
Enabled: true
+# Avoid trailing whitespace.
+Layout/TrailingWhitespace:
+ Enabled: true
+
# Style #######################################################################
# Check the naming of accessor methods for get_/set_.
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index cf14285ec2a..4b4f14efea4 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -57,11 +57,6 @@ Layout/SpaceInsideParens:
Layout/SpaceInsidePercentLiteralDelimiters:
Enabled: false
-# Offense count: 89
-# Cop supports --auto-correct.
-Layout/TrailingWhitespace:
- Enabled: false
-
# Offense count: 272
RSpec/EmptyLineAfterFinalLet:
Enabled: false
diff --git a/Gemfile b/Gemfile
index 93363edf66e..a484cefb9a4 100644
--- a/Gemfile
+++ b/Gemfile
@@ -324,6 +324,7 @@ group :development, :test do
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
+ gem 'rspec-parameterized'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3e661d4e3ad..a93caba2393 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
RedCloth (4.3.2)
+ abstract_type (0.0.7)
ace-rails-ap (4.1.2)
actionmailer (4.2.8)
actionpack (= 4.2.8)
@@ -41,6 +42,9 @@ GEM
tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0)
activerecord (>= 4.0)
+ adamantium (0.2.0)
+ ice_nine (~> 0.11.0)
+ memoizable (~> 0.4.0)
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
@@ -124,6 +128,9 @@ GEM
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
colorize (0.7.7)
+ concord (0.1.5)
+ adamantium (~> 0.2.0)
+ equalizer (~> 0.0.9)
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
@@ -470,6 +477,8 @@ GEM
mime-types (>= 1.16, < 4)
mail_room (0.9.1)
memoist (0.15.0)
+ memoizable (0.4.2)
+ thread_safe (~> 0.3, >= 0.3.1)
method_source (0.8.2)
mime-types (2.99.3)
mimemagic (0.3.0)
@@ -610,6 +619,11 @@ GEM
premailer-rails (1.9.7)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
+ proc_to_ast (0.1.0)
+ coderay
+ parser
+ unparser
+ procto (0.0.3)
prometheus-client-mmap (0.7.0.beta11)
mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4)
@@ -718,6 +732,10 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
+ rspec (3.6.0)
+ rspec-core (~> 3.6.0)
+ rspec-expectations (~> 3.6.0)
+ rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
@@ -726,6 +744,12 @@ GEM
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
+ rspec-parameterized (0.4.0)
+ binding_of_caller
+ parser
+ proc_to_ast
+ rspec (>= 2.13, < 4)
+ unparser
rspec-rails (3.6.0)
actionpack (>= 3.0)
activesupport (>= 3.0)
@@ -892,6 +916,14 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
+ unparser (0.2.6)
+ abstract_type (~> 0.0.7)
+ adamantium (~> 0.2.0)
+ concord (~> 0.1.5)
+ diff-lcs (~> 1.3)
+ equalizer (~> 0.0.9)
+ parser (>= 2.3.1.2, < 2.5)
+ procto (~> 0.0.2)
url_safe_base64 (0.2.2)
validates_hostname (1.0.6)
activerecord (>= 3.0)
@@ -1094,6 +1126,7 @@ DEPENDENCIES
responders (~> 2.0)
rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
+ rspec-parameterized
rspec-rails (~> 3.6.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 76b724e1bcb..56f91e95bb9 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -97,7 +97,6 @@ const Api = {
},
commitMultiple(id, data, callback) {
- // see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitPath)
.replace(':id', id);
return $.ajax({
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index cbc3ad23990..32cb42c8b10 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -15,6 +15,10 @@ export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
+let headerHeight = 50;
+
+export const getHeaderHeight = () => headerHeight;
+
export const canShowActiveSubItems = (el) => {
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
@@ -74,7 +78,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
const isAbove = top < boundingRect.top;
subItems.classList.add('fly-out-list');
- subItems.style.transform = `translate3d(0, ${Math.floor(top)}px, 0)`; // eslint-disable-line no-param-reassign
+ subItems.style.transform = `translate3d(0, ${Math.floor(top) - headerHeight}px, 0)`; // eslint-disable-line no-param-reassign
const subItemsRect = subItems.getBoundingClientRect();
@@ -153,6 +157,8 @@ export default () => {
}, getHideSubItemsInterval());
});
+ headerHeight = document.querySelector('.nav-sidebar').offsetTop;
+
items.forEach((el) => {
const subItems = el.querySelector('.sidebar-sub-level-items');
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
index 1c379e9bb67..7ac9dcd1112 100644
--- a/app/assets/javascripts/gpg_badges.js
+++ b/app/assets/javascripts/gpg_badges.js
@@ -1,12 +1,14 @@
export default class GpgBadges {
static fetch() {
+ const badges = $('.js-loading-gpg-badge');
const form = $('.commits-search-form');
+ badges.html('<i class="fa fa-spinner fa-spin"></i>');
+
$.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/pipeline_schedules/components/pipeline_schedules_callout.js b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js
deleted file mode 100644
index c827b7402dc..00000000000
--- a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import Vue from 'vue';
-import Cookies from 'js-cookie';
-import Translate from '../../vue_shared/translate';
-import illustrationSvg from '../icons/intro_illustration.svg';
-
-Vue.use(Translate);
-
-const cookieKey = 'pipeline_schedules_callout_dismissed';
-
-export default {
- name: 'PipelineSchedulesCallout',
- data() {
- return {
- docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
- illustrationSvg,
- calloutDismissed: Cookies.get(cookieKey) === 'true',
- };
- },
- methods: {
- dismissCallout() {
- this.calloutDismissed = true;
- Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
- },
- },
- template: `
- <div v-if="!calloutDismissed" class="pipeline-schedules-user-callout user-callout">
- <div class="bordered-box landing content-block">
- <button
- id="dismiss-callout-btn"
- class="btn btn-default close"
- @click="dismissCallout">
- <i class="fa fa-times"></i>
- </button>
- <div class="svg-container" v-html="illustrationSvg"></div>
- <div class="user-callout-copy">
- <h4>{{ __('Scheduling Pipelines') }}</h4>
- <p>
- {{ __('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.') }}
- </p>
- <p> {{ __('Learn more in the') }}
- <a
- :href="docsUrl"
- target="_blank"
- rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
- </p>
- </div>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
new file mode 100644
index 00000000000..6e0bc2d697a
--- /dev/null
+++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.vue
@@ -0,0 +1,59 @@
+<script>
+ import Vue from 'vue';
+ import Cookies from 'js-cookie';
+ import Translate from '../../vue_shared/translate';
+ import illustrationSvg from '../icons/intro_illustration.svg';
+
+ Vue.use(Translate);
+
+ const cookieKey = 'pipeline_schedules_callout_dismissed';
+
+ export default {
+ name: 'PipelineSchedulesCallout',
+ data() {
+ return {
+ docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
+ calloutDismissed: Cookies.get(cookieKey) === 'true',
+ };
+ },
+ methods: {
+ dismissCallout() {
+ this.calloutDismissed = true;
+ Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
+ },
+ },
+ created() {
+ this.illustrationSvg = illustrationSvg;
+ },
+ };
+</script>
+<template>
+ <div
+ v-if="!calloutDismissed"
+ class="pipeline-schedules-user-callout user-callout">
+ <div class="bordered-box landing content-block">
+ <button
+ id="dismiss-callout-btn"
+ class="btn btn-default close"
+ @click="dismissCallout">
+ <i
+ aria-hidden="true"
+ class="fa fa-times">
+ </i>
+ </button>
+ <div class="svg-container" v-html="illustrationSvg"></div>
+ <div class="user-callout-copy">
+ <h4>{{ __('Scheduling Pipelines') }}</h4>
+ <p>
+ {{ __('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.') }}
+ </p>
+ <p> {{ __('Learn more in the') }}
+ <a
+ :href="docsUrl"
+ target="_blank"
+ rel="nofollow">{{ s__('Learn more in the|pipeline schedules documentation') }}</a>. <!-- oneline to prevent extra space before period -->
+ </p>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js b/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js
index 6584549ad06..a6c945e22b0 100644
--- a/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js
+++ b/app/assets/javascripts/pipeline_schedules/pipeline_schedules_index_bundle.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import PipelineSchedulesCallout from './components/pipeline_schedules_callout';
+import PipelineSchedulesCallout from './components/pipeline_schedules_callout.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#pipeline-schedules-callout',
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index 2944689a5a7..7695b04db74 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -48,6 +48,27 @@
return `${this.job.name} - ${this.job.status.label}`;
},
},
+
+ methods: {
+ /**
+ * When the user right clicks or cmd/ctrl + click in the job name
+ * the dropdown should not be closed and the link should open in another tab,
+ * so we stop propagation of the click event inside the dropdown.
+ *
+ * Since this component is rendered multiple times per page we need to guarantee we only
+ * target the click event of this component.
+ */
+ stopDropdownClickPropagation() {
+ $(this.$el.querySelectorAll('.js-grouped-pipeline-dropdown a.mini-pipeline-graph-dropdown-item'))
+ .on('click', (e) => {
+ e.stopPropagation();
+ });
+ },
+ },
+
+ mounted() {
+ this.stopDropdownClickPropagation();
+ },
};
</script>
<template>
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 1c2100a1c25..d7e3ab42f00 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -126,11 +126,11 @@ import Cookies from 'js-cookie';
var $form = $dropdown.closest('form');
var $visit = $dropdown.data('visit');
- var shouldVisit = typeof $visit === 'undefined' ? true : $visit;
+ var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action');
var divider = action.indexOf('?') === -1 ? '?' : '&';
if (shouldVisit) {
- gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
+ gl.utils.visitUrl(`${action}${divider}${$form.serialize()}`);
}
}
}
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
index 703da749ad3..3d5e01c8ec0 100644
--- a/app/assets/javascripts/repo/components/repo.vue
+++ b/app/assets/javascripts/repo/components/repo.vue
@@ -14,13 +14,13 @@ export default {
data: () => Store,
mixins: [RepoMixin],
components: {
- 'repo-sidebar': RepoSidebar,
- 'repo-tabs': RepoTabs,
- 'repo-file-buttons': RepoFileButtons,
+ RepoSidebar,
+ RepoTabs,
+ RepoFileButtons,
'repo-editor': MonacoLoaderHelper.repoEditorLoader,
- 'repo-commit-section': RepoCommitSection,
- 'popup-dialog': PopupDialog,
- 'repo-preview': RepoPreview,
+ RepoCommitSection,
+ PopupDialog,
+ RepoPreview,
},
mounted() {
@@ -28,12 +28,12 @@ export default {
},
methods: {
- dialogToggled(toggle) {
+ toggleDialogOpen(toggle) {
this.dialog.open = toggle;
},
dialogSubmitted(status) {
- this.dialog.open = false;
+ this.toggleDialogOpen(false);
this.dialog.status = status;
},
@@ -43,21 +43,25 @@ export default {
</script>
<template>
-<div class="repository-view tree-content-holder">
- <repo-sidebar/><div class="panel-right" :class="{'edit-mode': editMode}">
- <repo-tabs/>
- <component :is="currentBlobView" class="blob-viewer-container"></component>
- <repo-file-buttons/>
+ <div class="repository-view tree-content-holder">
+ <repo-sidebar/><div v-if="isMini"
+ class="panel-right"
+ :class="{'edit-mode': editMode}">
+ <repo-tabs/>
+ <component
+ :is="currentBlobView"
+ class="blob-viewer-container"/>
+ <repo-file-buttons/>
+ </div>
+ <repo-commit-section/>
+ <popup-dialog
+ v-show="dialog.open"
+ :primary-button-label="__('Discard changes')"
+ kind="warning"
+ :title="__('Are you sure?')"
+ :body="__('Are you sure you want to discard your changes?')"
+ @toggle="toggleDialogOpen"
+ @submit="dialogSubmitted"
+ />
</div>
- <repo-commit-section/>
- <popup-dialog
- :primary-button-label="__('Discard changes')"
- :open="dialog.open"
- kind="warning"
- :title="__('Are you sure?')"
- :body="__('Are you sure you want to discard your changes?')"
- @toggle="dialogToggled"
- @submit="dialogSubmitted"
- />
-</div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index bd83f80c928..5ec4a9b6593 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -2,18 +2,20 @@
/* global Flash */
import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin';
-import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
-const RepoCommitSection = {
+export default {
data: () => Store,
mixins: [RepoMixin],
computed: {
+ showCommitable() {
+ return this.isCommitable && this.changedFiles.length;
+ },
+
branchPaths() {
- const branch = Helper.getBranch();
- return this.changedFiles.map(f => Helper.getFilePathFromFullPath(f.url, branch));
+ return this.changedFiles.map(f => f.path);
},
cantCommitYet() {
@@ -28,11 +30,10 @@ const RepoCommitSection = {
methods: {
makeCommit() {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
- const branch = Helper.getBranch();
const commitMessage = this.commitMessage;
const actions = this.changedFiles.map(f => ({
action: 'update',
- file_path: Helper.getFilePathFromFullPath(f.url, branch),
+ file_path: f.path,
content: f.newContent,
}));
const payload = {
@@ -47,51 +48,80 @@ const RepoCommitSection = {
resetCommitState() {
this.submitCommitsLoading = false;
this.changedFiles = [];
- this.openedFiles = [];
this.commitMessage = '';
this.editMode = false;
- $('html, body').animate({ scrollTop: 0 }, 'fast');
+ window.scrollTo(0, 0);
},
},
};
-
-export default RepoCommitSection;
</script>
<template>
-<div id="commit-area" v-if="isCommitable && changedFiles.length" >
- <form class="form-horizontal">
+<div
+ v-if="showCommitable"
+ id="commit-area">
+ <form
+ class="form-horizontal"
+ @submit.prevent="makeCommit">
<fieldset>
<div class="form-group">
- <label class="col-md-4 control-label staged-files">Staged files ({{changedFiles.length}})</label>
- <div class="col-md-4">
+ <label class="col-md-4 control-label staged-files">
+ Staged files ({{changedFiles.length}})
+ </label>
+ <div class="col-md-6">
<ul class="list-unstyled changed-files">
- <li v-for="file in branchPaths" :key="file.id">
- <span class="help-block">{{file}}</span>
+ <li
+ v-for="branchPath in branchPaths"
+ :key="branchPath">
+ <span class="help-block">
+ {{branchPath}}
+ </span>
</li>
</ul>
</div>
</div>
- <!-- Textarea
- -->
<div class="form-group">
- <label class="col-md-4 control-label" for="commit-message">Commit message</label>
- <div class="col-md-4">
- <textarea class="form-control" id="commit-message" name="commit-message" v-model="commitMessage"></textarea>
+ <label
+ class="col-md-4 control-label"
+ for="commit-message">
+ Commit message
+ </label>
+ <div class="col-md-6">
+ <textarea
+ id="commit-message"
+ class="form-control"
+ name="commit-message"
+ v-model="commitMessage">
+ </textarea>
</div>
</div>
- <!-- Button Drop Down
- -->
<div class="form-group target-branch">
- <label class="col-md-4 control-label" for="target-branch">Target branch</label>
- <div class="col-md-4">
- <span class="help-block">{{targetBranch}}</span>
+ <label
+ class="col-md-4 control-label"
+ for="target-branch">
+ Target branch
+ </label>
+ <div class="col-md-6">
+ <span class="help-block">
+ {{targetBranch}}
+ </span>
</div>
</div>
- <div class="col-md-offset-4 col-md-4">
- <button type="submit" :disabled="cantCommitYet" class="btn btn-success submit-commit" @click.prevent="makeCommit">
- <i class="fa fa-spinner fa-spin" v-if="submitCommitsLoading"></i>
- <span class="commit-summary">Commit {{changedFiles.length}} {{filePluralize}}</span>
+ <div class="col-md-offset-4 col-md-6">
+ <button
+ ref="submitCommit"
+ type="submit"
+ :disabled="cantCommitYet"
+ class="btn btn-success">
+ <i
+ v-if="submitCommitsLoading"
+ class="fa fa-spinner fa-spin"
+ aria-hidden="true"
+ aria-label="loading">
+ </i>
+ <span class="commit-summary">
+ Commit {{changedFiles.length}} {{filePluralize}}
+ </span>
</button>
</div>
</fieldset>
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index e954fd38fc9..29b76975561 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -10,12 +10,15 @@ export default {
return this.editMode ? this.__('Cancel edit') : this.__('Edit');
},
- buttonIcon() {
- return this.editMode ? [] : ['fa', 'fa-pencil'];
+ showButton() {
+ return this.isCommitable &&
+ !this.activeFile.render_error &&
+ !this.binary &&
+ this.openedFiles.length;
},
},
methods: {
- editClicked() {
+ editCancelClicked() {
if (this.changedFiles.length) {
this.dialog.open = true;
return;
@@ -23,27 +26,33 @@ export default {
this.editMode = !this.editMode;
Store.toggleBlobView();
},
+ toggleProjectRefsForm() {
+ $('.project-refs-form').toggleClass('disabled', this.editMode);
+ $('.js-tree-ref-target-holder').toggle(this.editMode);
+ },
},
watch: {
editMode() {
- if (this.editMode) {
- $('.project-refs-form').addClass('disabled');
- $('.fa-long-arrow-right').show();
- $('.project-refs-target-form').show();
- } else {
- $('.project-refs-form').removeClass('disabled');
- $('.fa-long-arrow-right').hide();
- $('.project-refs-target-form').hide();
- }
+ this.toggleProjectRefsForm();
},
},
};
</script>
<template>
-<button class="btn btn-default" @click.prevent="editClicked" v-cloak v-if="isCommitable && !activeFile.render_error" :disabled="binary">
- <i :class="buttonIcon"></i>
- <span>{{buttonLabel}}</span>
+<button
+ v-if="showButton"
+ class="btn btn-default"
+ type="button"
+ @click.prevent="editCancelClicked">
+ <i
+ v-if="!editMode"
+ class="fa fa-pencil"
+ aria-hidden="true">
+ </i>
+ <span>
+ {{buttonLabel}}
+ </span>
</button>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
index fd1a21e15b4..96d6a75bb61 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -8,38 +8,39 @@ const RepoEditor = {
data: () => Store,
destroyed() {
- // this.monacoInstance.getModels().forEach((m) => {
- // m.dispose();
- // });
- this.monacoInstance.destroy();
+ if (Helper.monacoInstance) {
+ Helper.monacoInstance.destroy();
+ }
},
mounted() {
Service.getRaw(this.activeFile.raw_path)
- .then((rawResponse) => {
- Store.blobRaw = rawResponse.data;
- Helper.findOpenedFileFromActive().plain = rawResponse.data;
+ .then((rawResponse) => {
+ Store.blobRaw = rawResponse.data;
+ Store.activeFile.plain = rawResponse.data;
- const monacoInstance = this.monaco.editor.create(this.$el, {
- model: null,
- readOnly: false,
- contextmenu: false,
- });
+ const monacoInstance = Helper.monaco.editor.create(this.$el, {
+ model: null,
+ readOnly: false,
+ contextmenu: false,
+ });
- Store.monacoInstance = monacoInstance;
+ Helper.monacoInstance = monacoInstance;
- this.addMonacoEvents();
+ this.addMonacoEvents();
- const languages = this.monaco.languages.getLanguages();
- const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
- this.showHide();
- const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
-
- this.monacoInstance.setModel(newModel);
- }).catch(Helper.loadingError);
+ this.setupEditor();
+ })
+ .catch(Helper.loadingError);
},
methods: {
+ setupEditor() {
+ this.showHide();
+
+ Helper.setMonacoModelFromLanguage();
+ },
+
showHide() {
if (!this.openedFiles.length || (this.binary && !this.activeFile.raw)) {
this.$el.style.display = 'none';
@@ -49,41 +50,36 @@ const RepoEditor = {
},
addMonacoEvents() {
- this.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp);
- this.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
+ Helper.monacoInstance.onMouseUp(this.onMonacoEditorMouseUp);
+ Helper.monacoInstance.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
},
onMonacoEditorKeysPressed() {
- Store.setActiveFileContents(this.monacoInstance.getValue());
+ Store.setActiveFileContents(Helper.monacoInstance.getValue());
},
onMonacoEditorMouseUp(e) {
+ if (!e.target.position) return;
const lineNumber = e.target.position.lineNumber;
- if (e.target.element.className === 'line-numbers') {
+ if (e.target.element.classList.contains('line-numbers')) {
location.hash = `L${lineNumber}`;
Store.activeLine = lineNumber;
+
+ Helper.monacoInstance.setPosition({
+ lineNumber: this.activeLine,
+ column: 1,
+ });
}
},
},
watch: {
- activeLine() {
- this.monacoInstance.setPosition({
- lineNumber: this.activeLine,
- column: 1,
- });
- },
-
- activeFileLabel() {
- this.showHide();
- },
-
dialog: {
handler(obj) {
const newObj = obj;
if (newObj.status) {
newObj.status = false;
- this.openedFiles.map((file) => {
+ this.openedFiles = this.openedFiles.map((file) => {
const f = file;
if (f.active) {
this.blobRaw = f.plain;
@@ -94,35 +90,21 @@ const RepoEditor = {
return f;
});
this.editMode = false;
+ Store.toggleBlobView();
}
},
deep: true,
},
- isTree() {
- this.showHide();
- },
-
- openedFiles() {
- this.showHide();
- },
-
- binary() {
- this.showHide();
- },
-
blobRaw() {
- this.showHide();
-
- if (this.isTree) return;
-
- this.monacoInstance.setModel(null);
-
- const languages = this.monaco.languages.getLanguages();
- const languageID = Helper.getLanguageIDForFile(this.activeFile, languages);
- const newModel = this.monaco.editor.createModel(this.blobRaw, languageID);
-
- this.monacoInstance.setModel(newModel);
+ if (Helper.monacoInstance && !this.isTree) {
+ this.setupEditor();
+ }
+ },
+ },
+ computed: {
+ shouldHideEditor() {
+ return !this.openedFiles.length || (this.binary && !this.activeFile.raw);
},
},
};
@@ -131,5 +113,5 @@ export default RepoEditor;
</script>
<template>
-<div id="ide"></div>
+<div id="ide" v-if='!shouldHideEditor'></div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue
index f604bc22a26..20ebf840774 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/repo/components/repo_file.vue
@@ -33,6 +33,26 @@ const RepoFile = {
canShowFile() {
return !this.loading.tree || this.hasFiles;
},
+
+ fileIcon() {
+ const classObj = {
+ 'fa-spinner fa-spin': this.file.loading,
+ [this.file.icon]: !this.file.loading,
+ };
+ return classObj;
+ },
+
+ fileIndentation() {
+ return {
+ 'margin-left': `${this.file.level * 10}px`,
+ };
+ },
+
+ activeFileClass() {
+ return {
+ active: this.activeFile.url === this.file.url,
+ };
+ },
},
methods: {
@@ -46,21 +66,42 @@ export default RepoFile;
</script>
<template>
-<tr class="file" v-if="canShowFile" :class="{'active': activeFile.url === file.url}">
- <td @click.prevent="linkClicked(file)">
- <i class="fa file-icon" v-if="!file.loading" :class="file.icon" :style="{'margin-left': file.level * 10 + 'px'}"></i>
- <i class="fa fa-spinner fa-spin" v-if="file.loading" :style="{'margin-left': file.level * 10 + 'px'}"></i>
- <a :href="file.url" class="repo-file-name" :title="file.url">{{file.name}}</a>
+<tr
+ v-if="canShowFile"
+ class="file"
+ :class="activeFileClass"
+ @click.prevent="linkClicked(file)">
+ <td>
+ <i
+ class="fa fa-fw file-icon"
+ :class="fileIcon"
+ :style="fileIndentation"
+ aria-label="file icon">
+ </i>
+ <a
+ :href="file.url"
+ class="repo-file-name"
+ :title="file.url">
+ {{file.name}}
+ </a>
</td>
- <td v-if="!isMini" class="hidden-sm hidden-xs">
- <div class="commit-message">
- <a :href="file.lastCommitUrl">{{file.lastCommitMessage}}</a>
- </div>
- </td>
+ <template v-if="!isMini">
+ <td class="hidden-sm hidden-xs">
+ <div class="commit-message">
+ <a @click.stop :href="file.lastCommitUrl">
+ {{file.lastCommitMessage}}
+ </a>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-xs">
- <span class="commit-update" :title="tooltipTitle(file.lastCommitUpdate)">{{timeFormated(file.lastCommitUpdate)}}</span>
- </td>
+ <td class="hidden-xs">
+ <span
+ class="commit-update"
+ :title="tooltipTitle(file.lastCommitUpdate)">
+ {{timeFormated(file.lastCommitUpdate)}}
+ </span>
+ </td>
+ </template>
</tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue
index 628d02ca704..e43ef366f47 100644
--- a/app/assets/javascripts/repo/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/repo/components/repo_file_buttons.vue
@@ -15,7 +15,7 @@ const RepoFileButtons = {
},
canPreview() {
- return Helper.isKindaBinary();
+ return Helper.isRenderable();
},
},
@@ -28,15 +28,42 @@ export default RepoFileButtons;
</script>
<template>
-<div id="repo-file-buttons" v-if="isMini">
- <a :href="activeFile.raw_path" target="_blank" class="btn btn-default raw" rel="noopener noreferrer">{{rawDownloadButtonLabel}}</a>
+ <div id="repo-file-buttons">
+ <a
+ :href="activeFile.raw_path"
+ target="_blank"
+ class="btn btn-default raw"
+ rel="noopener noreferrer">
+ {{rawDownloadButtonLabel}}
+ </a>
- <div class="btn-group" role="group" aria-label="File actions">
- <a :href="activeFile.blame_path" class="btn btn-default blame">Blame</a>
- <a :href="activeFile.commits_path" class="btn btn-default history">History</a>
- <a :href="activeFile.permalink" class="btn btn-default permalink">Permalink</a>
- </div>
+ <div
+ class="btn-group"
+ role="group"
+ aria-label="File actions">
+ <a
+ :href="activeFile.blame_path"
+ class="btn btn-default blame">
+ Blame
+ </a>
+ <a
+ :href="activeFile.commits_path"
+ class="btn btn-default history">
+ History
+ </a>
+ <a
+ :href="activeFile.permalink"
+ class="btn btn-default permalink">
+ Permalink
+ </a>
+ </div>
- <a href="#" v-if="canPreview" @click.prevent="rawPreviewToggle" class="btn btn-default preview">{{activeFileLabel}}</a>
-</div>
+ <a
+ v-if="canPreview"
+ href="#"
+ @click.prevent="rawPreviewToggle"
+ class="btn btn-default preview">
+ {{activeFileLabel}}
+ </a>
+ </div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue
index ba53ce0eecc..6a15755f029 100644
--- a/app/assets/javascripts/repo/components/repo_file_options.vue
+++ b/app/assets/javascripts/repo/components/repo_file_options.vue
@@ -17,7 +17,7 @@ export default RepoFileOptions;
</script>
<template>
-<tr v-if="isMini" class="repo-file-options">
+ <tr v-if="isMini" class="repo-file-options">
<td>
<span class="title">{{projectName}}</span>
</td>
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue
index 38e9f16d041..bc8c64c8362 100644
--- a/app/assets/javascripts/repo/components/repo_loading_file.vue
+++ b/app/assets/javascripts/repo/components/repo_loading_file.vue
@@ -18,9 +18,15 @@ const RepoLoadingFile = {
},
},
+ computed: {
+ showGhostLines() {
+ return this.loading.tree && !this.hasFiles;
+ },
+ },
+
methods: {
lineOfCode(n) {
- return `line-of-code-${n}`;
+ return `skeleton-line-${n}`;
},
},
};
@@ -29,23 +35,42 @@ export default RepoLoadingFile;
</script>
<template>
-<tr v-if="loading.tree && !hasFiles" class="loading-file">
- <td>
- <div class="animation-container animation-container-small">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
+ <tr
+ v-if="showGhostLines"
+ class="loading-file">
+ <td>
+ <div
+ class="animation-container animation-container-small">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-sm hidden-xs">
- <div class="animation-container">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
+ <td
+ v-if="!isMini"
+ class="hidden-sm hidden-xs">
+ <div class="animation-container">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
- <td v-if="!isMini" class="hidden-xs">
- <div class="animation-container animation-container-small">
- <div v-for="n in 6" :class="lineOfCode(n)" :key="n"></div>
- </div>
- </td>
-</tr>
+ <td
+ v-if="!isMini"
+ class="hidden-xs">
+ <div class="animation-container animation-container-small">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
+ </div>
+ </td>
+ </tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue
index 6a0d684052f..bbdbdc61e38 100644
--- a/app/assets/javascripts/repo/components/repo_prev_directory.vue
+++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue
@@ -1,4 +1,6 @@
<script>
+import RepoMixin from '../mixins/repo_mixin';
+
const RepoPreviousDirectory = {
props: {
prevUrl: {
@@ -7,6 +9,14 @@ const RepoPreviousDirectory = {
},
},
+ mixins: [RepoMixin],
+
+ computed: {
+ colSpanCondition() {
+ return this.isMini ? undefined : 3;
+ },
+ },
+
methods: {
linkClicked(file) {
this.$emit('linkclicked', file);
@@ -19,8 +29,10 @@ export default RepoPreviousDirectory;
<template>
<tr class="prev-directory">
- <td colspan="3">
- <a :href="prevUrl" @click.prevent="linkClicked(prevUrl)">..</a>
+ <td
+ :colspan="colSpanCondition"
+ @click.prevent="linkClicked(prevUrl)">
+ <a :href="prevUrl">..</a>
</td>
</tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index d8de022335b..2200754cbef 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -4,7 +4,7 @@ import Store from '../stores/repo_store';
export default {
data: () => Store,
mounted() {
- $(this.$el).find('.file-content').syntaxHighlight();
+ this.highlightFile();
},
computed: {
html() {
@@ -12,10 +12,16 @@ export default {
},
},
+ methods: {
+ highlightFile() {
+ $(this.$el).find('.file-content').syntaxHighlight();
+ },
+ },
+
watch: {
html() {
this.$nextTick(() => {
- $(this.$el).find('.file-content').syntaxHighlight();
+ this.highlightFile();
});
},
},
@@ -24,9 +30,23 @@ export default {
<template>
<div>
- <div v-if="!activeFile.render_error" v-html="activeFile.html"></div>
- <div v-if="activeFile.render_error" class="vertical-center render-error">
- <p class="text-center">The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.</p>
+ <div
+ v-if="!activeFile.render_error"
+ v-html="activeFile.html">
+ </div>
+ <div
+ v-else-if="activeFile.tooLarge"
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.
+ </p>
+ </div>
+ <div
+ v-else
+ class="vertical-center render-error">
+ <p class="text-center">
+ The source could not be displayed because a rendering error occured. You can <a :href="activeFile.raw_path">download</a> it instead.
+ </p>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
index d6d832efc49..72b40288566 100644
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ b/app/assets/javascripts/repo/components/repo_sidebar.vue
@@ -8,7 +8,7 @@ import RepoFile from './repo_file.vue';
import RepoLoadingFile from './repo_loading_file.vue';
import RepoMixin from '../mixins/repo_mixin';
-const RepoSidebar = {
+export default {
mixins: [RepoMixin],
components: {
'repo-file-options': RepoFileOptions,
@@ -33,40 +33,36 @@ const RepoSidebar = {
});
},
- linkClicked(clickedFile) {
- let url = '';
+ fileClicked(clickedFile) {
let file = clickedFile;
- if (typeof file === 'object') {
- file.loading = true;
- if (file.type === 'tree' && file.opened) {
- file = Store.removeChildFilesOfTree(file);
- file.loading = false;
- } else {
- url = file.url;
- Service.url = url;
- // I need to refactor this to do the `then` here.
- // Not a callback. For now this is good enough.
- // it works.
- Helper.getContent(file, () => {
+ if (file.loading) return;
+ file.loading = true;
+ if (file.type === 'tree' && file.opened) {
+ file = Store.removeChildFilesOfTree(file);
+ file.loading = false;
+ } else {
+ Service.url = file.url;
+ Helper.getContent(file)
+ .then(() => {
file.loading = false;
Helper.scrollTabsRight();
- });
- }
- } else if (typeof file === 'string') {
- // go back
- url = file;
- Service.url = url;
- Helper.getContent(null, () => Helper.scrollTabsRight());
+ })
+ .catch(Helper.loadingError);
}
},
+
+ goToPreviousDirectoryClicked(prevURL) {
+ Service.url = prevURL;
+ Helper.getContent(null)
+ .then(() => Helper.scrollTabsRight())
+ .catch(Helper.loadingError);
+ },
},
};
-
-export default RepoSidebar;
</script>
<template>
-<div id="sidebar" :class="{'sidebar-mini' : isMini}" v-cloak>
+<div id="sidebar" :class="{'sidebar-mini' : isMini}">
<table class="table">
<thead v-if="!isMini">
<tr>
@@ -82,7 +78,7 @@ export default RepoSidebar;
<repo-previous-directory
v-if="isRoot"
:prev-url="prevURL"
- @linkclicked="linkClicked(prevURL)"/>
+ @linkclicked="goToPreviousDirectoryClicked(prevURL)"/>
<repo-loading-file
v-for="n in 5"
:key="n"
@@ -94,7 +90,7 @@ export default RepoSidebar;
:key="file.id"
:file="file"
:is-mini="isMini"
- @linkclicked="linkClicked(file)"
+ @linkclicked="fileClicked(file)"
:is-tree="isTree"
:has-files="!!files.length"
:active-file="activeFile"/>
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue
index 712d64c236f..0d0c34ec741 100644
--- a/app/assets/javascripts/repo/components/repo_tab.vue
+++ b/app/assets/javascripts/repo/components/repo_tab.vue
@@ -10,10 +10,16 @@ const RepoTab = {
},
computed: {
+ closeLabel() {
+ if (this.tab.changed) {
+ return `${this.tab.name} changed`;
+ }
+ return `Close ${this.tab.name}`;
+ },
changedClass() {
const tabChangedObj = {
- 'fa-times': !this.tab.changed,
- 'fa-circle': this.tab.changed,
+ 'fa-times close-icon': !this.tab.changed,
+ 'fa-circle unsaved-icon': this.tab.changed,
};
return tabChangedObj;
},
@@ -22,9 +28,9 @@ const RepoTab = {
methods: {
tabClicked: Store.setActiveFiles,
- xClicked(file) {
+ closeTab(file) {
if (file.changed) return;
- this.$emit('xclicked', file);
+ this.$emit('tabclosed', file);
},
},
};
@@ -33,13 +39,25 @@ export default RepoTab;
</script>
<template>
-<li>
- <a href="#" class="close" @click.prevent="xClicked(tab)" v-if="!tab.loading">
- <i class="fa" :class="changedClass"></i>
+<li @click="tabClicked(tab)">
+ <a
+ href="#0"
+ class="close"
+ @click.stop.prevent="closeTab(tab)"
+ :aria-label="closeLabel">
+ <i
+ class="fa"
+ :class="changedClass"
+ aria-hidden="true">
+ </i>
</a>
- <a href="#" class="repo-tab" v-if="!tab.loading" :title="tab.url" @click.prevent="tabClicked(tab)">{{tab.name}}</a>
-
- <i v-if="tab.loading" class="fa fa-spinner fa-spin"></i>
+ <a
+ href="#"
+ class="repo-tab"
+ :title="tab.url"
+ @click.prevent="tabClicked(tab)">
+ {{tab.name}}
+ </a>
</li>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue
index 907a03e1601..9c5bfc5d0cf 100644
--- a/app/assets/javascripts/repo/components/repo_tabs.vue
+++ b/app/assets/javascripts/repo/components/repo_tabs.vue
@@ -1,5 +1,4 @@
<script>
-import Vue from 'vue';
import Store from '../stores/repo_store';
import RepoTab from './repo_tab.vue';
import RepoMixin from '../mixins/repo_mixin';
@@ -14,30 +13,24 @@ const RepoTabs = {
data: () => Store,
methods: {
- isOverflow() {
- return this.$el.scrollWidth > this.$el.offsetWidth;
- },
-
- xClicked(file) {
+ tabClosed(file) {
Store.removeFromOpenedFiles(file);
},
},
-
- watch: {
- openedFiles() {
- Vue.nextTick(() => {
- this.tabsOverflow = this.isOverflow();
- });
- },
- },
};
export default RepoTabs;
</script>
<template>
-<ul id="tabs" v-if="isMini" v-cloak :class="{'overflown': tabsOverflow}">
- <repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
+<ul id="tabs">
+ <repo-tab
+ v-for="tab in openedFiles"
+ :key="tab.id"
+ :tab="tab"
+ :class="{'active' : tab.active}"
+ @tabclosed="tabClosed"
+ />
<li class="tabs-divider" />
</ul>
</template>
diff --git a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
index 8ee2df5c879..f8729bbf585 100644
--- a/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
+++ b/app/assets/javascripts/repo/helpers/monaco_loader_helper.js
@@ -1,16 +1,20 @@
/* global monaco */
import RepoEditor from '../components/repo_editor.vue';
import Store from '../stores/repo_store';
+import Helper from '../helpers/repo_helper';
import monacoLoader from '../monaco_loader';
function repoEditorLoader() {
Store.monacoLoading = true;
return new Promise((resolve, reject) => {
monacoLoader(['vs/editor/editor.main'], () => {
- Store.monaco = monaco;
+ Helper.monaco = monaco;
Store.monacoLoading = false;
resolve(RepoEditor);
- }, reject);
+ }, () => {
+ Store.monacoLoading = false;
+ reject();
+ });
});
}
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
index fee98c12592..2bd8d7eea65 100644
--- a/app/assets/javascripts/repo/helpers/repo_helper.js
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -4,6 +4,8 @@ import Store from '../stores/repo_store';
import '../../flash';
const RepoHelper = {
+ monacoInstance: null,
+
getDefaultActiveFile() {
return {
active: true,
@@ -33,19 +35,23 @@ const RepoHelper = {
? window.performance
: Date,
- getBranch() {
- return $('button.dropdown-menu-toggle').attr('data-ref');
+ getFileExtension(fileName) {
+ return fileName.split('.').pop();
},
getLanguageIDForFile(file, langs) {
- const ext = file.name.split('.').pop();
+ const ext = RepoHelper.getFileExtension(file.name);
const foundLang = RepoHelper.findLanguage(ext, langs);
return foundLang ? foundLang.id : 'plaintext';
},
- getFilePathFromFullPath(fullPath, branch) {
- return fullPath.split(`${Store.projectUrl}/blob/${branch}`)[1];
+ setMonacoModelFromLanguage() {
+ RepoHelper.monacoInstance.setModel(null);
+ const languages = RepoHelper.monaco.languages.getLanguages();
+ const languageID = RepoHelper.getLanguageIDForFile(Store.activeFile, languages);
+ const newModel = RepoHelper.monaco.editor.createModel(Store.blobRaw, languageID);
+ RepoHelper.monacoInstance.setModel(newModel);
},
findLanguage(ext, langs) {
@@ -58,11 +64,11 @@ const RepoHelper = {
file.opened = true;
file.icon = 'fa-folder-open';
- RepoHelper.toURL(file.url, file.name);
+ RepoHelper.updateHistoryEntry(file.url, file.name);
return file;
},
- isKindaBinary() {
+ isRenderable() {
const okExts = ['md', 'svg'];
return okExts.indexOf(Store.activeFile.extension) > -1;
},
@@ -76,22 +82,8 @@ const RepoHelper = {
.catch(RepoHelper.loadingError);
},
- toggleFakeTab(loading, file) {
- if (loading) return Store.addPlaceholderFile();
- return Store.removeFromOpenedFiles(file);
- },
-
- setLoading(loading, file) {
- if (Service.url.indexOf('blob') > -1) {
- Store.loading.blob = loading;
- return RepoHelper.toggleFakeTab(loading, file);
- }
-
- if (Service.url.indexOf('tree') > -1) Store.loading.tree = loading;
-
- return undefined;
- },
-
+ // when you open a directory you need to put the directory files under
+ // the directory... This will merge the list of the current directory and the new list.
getNewMergedList(inDirectory, currentList, newList) {
const newListSorted = newList.sort(this.compareFilesCaseInsensitive);
if (!inDirectory) return newListSorted;
@@ -100,6 +92,9 @@ const RepoHelper = {
return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile);
},
+ // within the get new merged list this does the merging of the current list of files
+ // and the new list of files. The files are never "in" another directory they just
+ // appear like they are because of the margin.
mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) {
newList.reverse().forEach((newFile) => {
const fileIndex = indexOfFile + 1;
@@ -135,21 +130,17 @@ const RepoHelper = {
return isRoot;
},
- getContent(treeOrFile, cb) {
+ getContent(treeOrFile) {
let file = treeOrFile;
- // const loadingData = RepoHelper.setLoading(true);
return Service.getContent()
.then((response) => {
const data = response.data;
- // RepoHelper.setLoading(false, loadingData);
- if (cb) cb();
Store.isTree = RepoHelper.isTree(data);
if (!Store.isTree) {
if (!file) file = data;
Store.binary = data.binary;
if (data.binary) {
- Store.binaryMimeType = data.mime_type;
// file might be undefined
RepoHelper.setBinaryDataAsBase64(data);
Store.setViewToPreview();
@@ -188,9 +179,8 @@ const RepoHelper = {
setFile(data, file) {
const newFile = data;
- newFile.url = file.url || location.pathname;
newFile.url = file.url;
- if (newFile.render_error === 'too_large') {
+ if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') {
newFile.tooLarge = true;
}
newFile.newContent = '';
@@ -199,10 +189,6 @@ const RepoHelper = {
Store.setActiveFiles(newFile);
},
- toFA(icon) {
- return `fa-${icon}`;
- },
-
serializeBlob(blob) {
const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
simpleBlob.lastCommitMessage = blob.last_commit.message;
@@ -226,7 +212,7 @@ const RepoHelper = {
type,
name,
url,
- icon: RepoHelper.toFA(icon),
+ icon: `fa-${icon}`,
level: 0,
loading: false,
};
@@ -244,42 +230,24 @@ const RepoHelper = {
setTimeout(() => {
const tabs = document.getElementById('tabs');
if (!tabs) return;
- tabs.scrollLeft = 12000;
+ tabs.scrollLeft = tabs.scrollWidth;
}, 200);
},
dataToListOfFiles(data) {
- const a = [];
-
- // push in blobs
- data.blobs.forEach((blob) => {
- a.push(RepoHelper.serializeBlob(blob));
- });
-
- data.trees.forEach((tree) => {
- a.push(RepoHelper.serializeTree(tree));
- });
-
- data.submodules.forEach((submodule) => {
- a.push(RepoHelper.serializeSubmodule(submodule));
- });
-
- return a;
+ const { blobs, trees, submodules } = data;
+ return [
+ ...blobs.map(blob => RepoHelper.serializeBlob(blob)),
+ ...trees.map(tree => RepoHelper.serializeTree(tree)),
+ ...submodules.map(submodule => RepoHelper.serializeSubmodule(submodule)),
+ ];
},
genKey() {
return RepoHelper.Time.now().toFixed(3);
},
- getStateKey() {
- return RepoHelper.key;
- },
-
- setStateKey(key) {
- RepoHelper.key = key;
- },
-
- toURL(url, title) {
+ updateHistoryEntry(url, title) {
const history = window.history;
RepoHelper.key = RepoHelper.genKey();
@@ -296,7 +264,7 @@ const RepoHelper = {
},
loadingError() {
- Flash('Unable to load the file at this time.');
+ Flash('Unable to load this content at this time.');
},
};
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
index 67c03680fca..6c1d468e937 100644
--- a/app/assets/javascripts/repo/index.js
+++ b/app/assets/javascripts/repo/index.js
@@ -7,8 +7,7 @@ import RepoEditButton from './components/repo_edit_button.vue';
import Translate from '../vue_shared/translate';
function initDropdowns() {
- $('.project-refs-target-form').hide();
- $('.fa-long-arrow-right').hide();
+ $('.js-tree-ref-target-holder').hide();
}
function addEventsForNonVueEls() {
@@ -34,6 +33,8 @@ function setInitialStore(data) {
Store.projectId = data.projectId;
Store.projectName = data.projectName;
Store.projectUrl = data.projectUrl;
+ Store.canCommit = data.canCommit;
+ Store.onTopOfBranch = data.onTopOfBranch;
Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref');
Store.checkIsCommitable();
}
@@ -44,6 +45,9 @@ function initRepo(el) {
components: {
repo: Repo,
},
+ render(createElement) {
+ return createElement('repo');
+ },
});
}
diff --git a/app/assets/javascripts/repo/services/repo_service.js b/app/assets/javascripts/repo/services/repo_service.js
index 8fba928e456..3cf204e6ec8 100644
--- a/app/assets/javascripts/repo/services/repo_service.js
+++ b/app/assets/javascripts/repo/services/repo_service.js
@@ -2,6 +2,7 @@
import axios from 'axios';
import Store from '../stores/repo_store';
import Api from '../../api';
+import Helper from '../helpers/repo_helper';
const RepoService = {
url: '',
@@ -12,16 +13,9 @@ const RepoService = {
},
richExtensionRegExp: /md/,
- checkCurrentBranchIsCommitable() {
- const url = Store.service.refsUrl;
- return axios.get(url, { params: {
- ref: Store.currentBranch,
- search: Store.currentBranch,
- } });
- },
-
getRaw(url) {
return axios.get(url, {
+ // Stop Axios from parsing a JSON file into a JS object
transformResponse: [res => res],
});
},
@@ -36,7 +30,7 @@ const RepoService = {
},
urlIsRichBlob(url = this.url) {
- const extension = url.split('.').pop();
+ const extension = Helper.getFileExtension(url);
return this.richExtensionRegExp.test(extension);
},
@@ -73,7 +67,11 @@ const RepoService = {
commitFiles(payload, cb) {
Api.commitMultiple(Store.projectId, payload, (data) => {
- Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
+ if (data.short_id && data.stats) {
+ Flash(`Your changes have been committed. Commit ${data.short_id} with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`, 'notice');
+ } else {
+ Flash(data.message);
+ }
cb();
});
},
diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js
index 06ca391ed0c..1c0df528aea 100644
--- a/app/assets/javascripts/repo/stores/repo_store.js
+++ b/app/assets/javascripts/repo/stores/repo_store.js
@@ -3,13 +3,11 @@ import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
const RepoStore = {
- ideEl: {},
monaco: {},
monacoLoading: false,
- monacoInstance: {},
service: '',
- editor: '',
- sidebar: '',
+ canCommit: false,
+ onTopOfBranch: false,
editMode: false,
isTree: false,
isRoot: false,
@@ -17,19 +15,10 @@ const RepoStore = {
projectId: '',
projectName: '',
projectUrl: '',
- trees: [],
- blobs: [],
- submodules: [],
blobRaw: '',
- blobRendered: '',
currentBlobView: 'repo-preview',
openedFiles: [],
- tabSize: 100,
- defaultTabSize: 100,
- minTabSize: 30,
- tabsOverflow: 41,
submitCommitsLoading: false,
- binaryLoaded: false,
dialog: {
open: false,
title: '',
@@ -45,9 +34,6 @@ const RepoStore = {
currentBranch: '',
targetBranch: 'new-branch',
commitMessage: '',
- binaryMimeType: '',
- // scroll bar space for windows
- scrollWidth: 0,
binaryTypes: {
png: false,
md: false,
@@ -58,7 +44,6 @@ const RepoStore = {
tree: false,
blob: false,
},
- readOnly: true,
resetBinaryTypes() {
Object.keys(RepoStore.binaryTypes).forEach((key) => {
@@ -68,14 +53,7 @@ const RepoStore = {
// mutations
checkIsCommitable() {
- RepoStore.service.checkCurrentBranchIsCommitable()
- .then((data) => {
- // you shouldn't be able to make commits on commits or tags.
- const { Branches, Commits, Tags } = data.data;
- if (Branches && Branches.length) RepoStore.isCommitable = true;
- if (Commits && Commits.length) RepoStore.isCommitable = false;
- if (Tags && Tags.length) RepoStore.isCommitable = false;
- }).catch(() => Flash('Failed to check if branch can be committed to.'));
+ RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit;
},
addFilesToDirectory(inDirectory, currentList, newList) {
@@ -96,7 +74,6 @@ const RepoStore = {
if (file.binary) {
RepoStore.blobRaw = file.base64;
- RepoStore.binaryMimeType = file.mime_type;
} else if (file.newContent || file.plain) {
RepoStore.blobRaw = file.newContent || file.plain;
} else {
@@ -107,7 +84,7 @@ const RepoStore = {
}).catch(Helper.loadingError);
}
- if (!file.loading) Helper.toURL(file.url, file.name);
+ if (!file.loading) Helper.updateHistoryEntry(file.url, file.name);
RepoStore.binary = file.binary;
},
@@ -134,15 +111,15 @@ const RepoStore = {
removeChildFilesOfTree(tree) {
let foundTree = false;
const treeToClose = tree;
- let wereDone = false;
+ let canStopSearching = false;
RepoStore.files = RepoStore.files.filter((file) => {
const isItTheTreeWeWant = file.url === treeToClose.url;
// if it's the next tree
if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) {
- wereDone = true;
+ canStopSearching = true;
return true;
}
- if (wereDone) return true;
+ if (canStopSearching) return true;
if (isItTheTreeWeWant) foundTree = true;
@@ -159,8 +136,8 @@ const RepoStore = {
if (file.type === 'tree') return;
let foundIndex;
RepoStore.openedFiles = RepoStore.openedFiles.filter((openedFile, i) => {
- if (openedFile.url === file.url) foundIndex = i;
- return openedFile.url !== file.url;
+ if (openedFile.path === file.path) foundIndex = i;
+ return openedFile.path !== file.path;
});
// now activate the right tab based on what you closed.
@@ -174,36 +151,16 @@ const RepoStore = {
return;
}
- if (foundIndex) {
- if (foundIndex > 0) {
- RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]);
- }
+ if (foundIndex && foundIndex > 0) {
+ RepoStore.setActiveFiles(RepoStore.openedFiles[foundIndex - 1]);
}
},
- addPlaceholderFile() {
- const randomURL = Helper.Time.now();
- const newFakeFile = {
- active: false,
- binary: true,
- type: 'blob',
- loading: true,
- mime_type: 'loading',
- name: 'loading',
- url: randomURL,
- fake: true,
- };
-
- RepoStore.openedFiles.push(newFakeFile);
-
- return newFakeFile;
- },
-
addToOpenedFiles(file) {
const openFile = file;
const openedFilesAlreadyExists = RepoStore.openedFiles
- .some(openedFile => openedFile.url === openFile.url);
+ .some(openedFile => openedFile.path === openFile.path);
if (openedFilesAlreadyExists) return;
@@ -238,4 +195,5 @@ const RepoStore = {
return RepoStore.currentBlobView === 'repo-preview';
},
};
+
export default RepoStore;
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 422c02c7b7e..cfacba09fad 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -71,7 +71,7 @@ export default {
/>
<div v-if="!isConfidential" class="no-value confidential-value">
<i class="fa fa-eye is-not-confidential"></i>
- None
+ This issue is not confidential
</div>
<div v-else class="value confidential-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
diff --git a/app/assets/javascripts/vue_shared/components/popup_dialog.vue b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
index 7d339c0e753..994b33bc1c9 100644
--- a/app/assets/javascripts/vue_shared/components/popup_dialog.vue
+++ b/app/assets/javascripts/vue_shared/components/popup_dialog.vue
@@ -1,31 +1,37 @@
<script>
-const PopupDialog = {
+export default {
name: 'popup-dialog',
props: {
- open: Boolean,
- title: String,
- body: String,
+ title: {
+ type: String,
+ required: true,
+ },
+ body: {
+ type: String,
+ required: true,
+ },
kind: {
type: String,
+ required: false,
default: 'primary',
},
closeButtonLabel: {
type: String,
+ required: false,
default: 'Cancel',
},
primaryButtonLabel: {
type: String,
- default: 'Save changes',
+ required: true,
},
},
computed: {
- typeOfClass() {
- const className = `btn-${this.kind}`;
- const returnObj = {};
- returnObj[className] = true;
- return returnObj;
+ btnKindClass() {
+ return {
+ [`btn-${this.kind}`]: true,
+ };
},
},
@@ -33,33 +39,45 @@ const PopupDialog = {
close() {
this.$emit('toggle', false);
},
-
- yesClick() {
- this.$emit('submit', true);
- },
-
- noClick() {
- this.$emit('submit', false);
+ emitSubmit(status) {
+ this.$emit('submit', status);
},
},
};
-
-export default PopupDialog;
</script>
+
<template>
-<div class="modal popup-dialog" tabindex="-1" v-show="open" role="dialog">
+<div
+ class="modal popup-dialog"
+ role="dialog"
+ tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
- <button type="button" class="close" @click="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+ <button type="button"
+ class="close"
+ @click="close"
+ aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
<h4 class="modal-title">{{this.title}}</h4>
</div>
<div class="modal-body">
<p>{{this.body}}</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal" @click="noClick">{{closeButtonLabel}}</button>
- <button type="button" class="btn" :class="typeOfClass" @click="yesClick">{{primaryButtonLabel}}</button>
+ <button
+ type="button"
+ class="btn btn-default"
+ @click="emitSubmit(false)">
+ {{closeButtonLabel}}
+ </button>
+ <button type="button"
+ class="btn"
+ :class="btnKindClass"
+ @click="emitSubmit(true)">
+ {{primaryButtonLabel}}
+ </button>
</div>
</div>
</div>
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 3cd7f81da47..667b73e150d 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -187,3 +187,81 @@ a {
.fade-in-full {
animation: fadeInFull $fade-in-duration 1;
}
+
+
+.animation-container {
+ background: $repo-editor-grey;
+ height: 40px;
+ overflow: hidden;
+ position: relative;
+
+ &.animation-container-small {
+ height: 12px;
+ }
+
+ &::before {
+ animation-duration: 1s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-name: blockTextShine;
+ animation-timing-function: linear;
+ background-image: $repo-editor-linear-gradient;
+ background-repeat: no-repeat;
+ background-size: 800px 45px;
+ content: ' ';
+ display: block;
+ height: 100%;
+ position: relative;
+ }
+
+ div {
+ background: $white-light;
+ height: 6px;
+ left: 0;
+ position: absolute;
+ right: 0;
+ }
+
+ .skeleton-line-1 {
+ left: 0;
+ top: 8px;
+ }
+
+ .skeleton-line-2 {
+ left: 150px;
+ top: 0;
+ height: 10px;
+ }
+
+ .skeleton-line-3 {
+ left: 0;
+ top: 23px;
+ }
+
+ .skeleton-line-4 {
+ left: 0;
+ top: 38px;
+ }
+
+ .skeleton-line-5 {
+ left: 200px;
+ top: 28px;
+ height: 10px;
+ }
+
+ .skeleton-line-6 {
+ top: 14px;
+ left: 230px;
+ height: 10px;
+ }
+}
+
+@keyframes blockTextShine {
+ 0% {
+ transform: translateX(-468px);
+ }
+
+ 100% {
+ transform: translateX(468px);
+ }
+}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index bd0367f86dd..bd521028c44 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -117,10 +117,6 @@ body {
margin-top: $header-height + $performance-bar-height;
}
-[v-cloak] {
- display: none;
-}
-
.vertical-center {
min-height: 100vh;
display: flex;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index fcd4c72b430..e3920b5d3d9 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -204,6 +204,16 @@
}
}
+ div.avatar {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ .center {
+ line-height: 14px;
+ }
+ }
+
strong {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss
index 795ee91af8b..3e2f23e6b2a 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/new_nav.scss
@@ -403,6 +403,7 @@ header.navbar-gitlab-new {
}
.breadcrumbs-extra {
+ display: flex;
flex: 0 0 auto;
margin-left: auto;
}
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss
index faedd207e01..d078c8b956b 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/new_sidebar.scss
@@ -97,9 +97,9 @@ $new-sidebar-collapsed-width: 50px;
top: $header-height;
bottom: 0;
left: 0;
- overflow: auto;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
+ transform: translate3d(0, 0, 0);
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
@@ -176,6 +176,12 @@ $new-sidebar-collapsed-width: 50px;
}
}
+.nav-sidebar-inner-scroll {
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+}
+
.with-performance-bar .nav-sidebar {
top: $header-height + $performance-bar-height;
}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index cd9f2d787c5..46fbfe5f91e 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -286,6 +286,10 @@
.gpg-status-box {
+ &:empty {
+ display: none;
+ }
+
&.valid {
@include green-status-color;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 215bedc04fd..913a1a95dca 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -560,9 +560,13 @@
}
.diff-files-changed {
+ .inline-parallel-buttons {
+ position: relative;
+ z-index: 1;
+ }
+
.commit-stat-summary {
@include new-style-dropdown;
- z-index: -1;
@media (min-width: $screen-sm-min) {
margin-left: -$gl-padding;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index d14b976374c..87eaf27663f 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -8,13 +8,13 @@
.is-confidential {
color: $orange-600;
background-color: $orange-50;
- border-radius: 3px;
+ border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
}
.is-not-confidential {
- border-radius: 3px;
+ border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 2bb867052f6..0a194f3707f 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -453,7 +453,10 @@ ul.notes {
}
.note-actions {
+ align-self: flex-start;
flex-shrink: 0;
+ display: inline-flex;
+ align-items: center;
// For PhantomJS that does not support flex
float: right;
margin-left: 10px;
@@ -463,18 +466,12 @@ ul.notes {
float: none;
margin-left: 0;
}
-
- .note-action-button {
- margin-left: 8px;
- }
-
- .more-actions-toggle {
- margin-left: 2px;
- }
}
.more-actions {
- display: inline-block;
+ float: right; // phantomjs fallback
+ display: flex;
+ align-items: flex-end;
.tooltip {
white-space: nowrap;
@@ -482,16 +479,10 @@ ul.notes {
}
.more-actions-toggle {
- padding: 0;
-
&:hover .icon,
&:focus .icon {
color: $blue-600;
}
-
- .icon {
- padding: 0 6px;
- }
}
.more-actions-dropdown {
@@ -519,28 +510,42 @@ ul.notes {
@include notes-media('max', $screen-md-max) {
float: none;
margin-left: 0;
+ }
+}
- .note-action-button {
- margin-left: 0;
- }
+.note-actions-item {
+ margin-left: 15px;
+ display: flex;
+ align-items: center;
+
+ &.more-actions {
+ // compensate for narrow icon
+ margin-left: 10px;
}
}
.note-action-button {
- display: inline;
- line-height: 20px;
+ line-height: 1;
+ padding: 0;
+ min-width: 16px;
+ color: $gray-darkest;
.fa {
- color: $gray-darkest;
position: relative;
- font-size: 17px;
+ font-size: 16px;
}
+
+
svg {
height: 16px;
width: 16px;
- fill: $gray-darkest;
+ top: 0;
vertical-align: text-top;
+
+ path {
+ fill: currentColor;
+ }
}
.award-control-icon-positive,
@@ -613,10 +618,7 @@ ul.notes {
.note-role {
position: relative;
- top: -2px;
- display: inline-block;
- padding-left: 7px;
- padding-right: 7px;
+ padding: 0 7px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index ad17078c98a..b3527fe8cd9 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -1,6 +1,6 @@
.fade-enter-active,
.fade-leave-active {
- transition: opacity .5s;
+ transition: opacity $sidebar-transition-duration;
}
.monaco-loader {
@@ -28,11 +28,6 @@
.project-refs-form,
.project-refs-target-form {
display: inline-block;
-
- &.disabled {
- opacity: 0.5;
- pointer-events: none;
- }
}
.fade-enter,
@@ -90,7 +85,7 @@
}
.blob-viewer-container {
- height: calc(100vh - 63px);
+ height: calc(100vh - 62px);
overflow: auto;
}
@@ -114,6 +109,7 @@
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
white-space: nowrap;
+ cursor: pointer;
&.remove {
animation: swipeRightDissapear ease-in 0.1s;
@@ -133,10 +129,10 @@
a {
@include str-truncated(100px);
color: $black;
- display: inline-block;
width: 100px;
text-align: center;
vertical-align: middle;
+ text-decoration: none;
&.close {
width: auto;
@@ -146,15 +142,15 @@
}
}
- i.fa.fa-times,
- i.fa.fa-circle {
+ .close-icon,
+ .unsaved-icon {
float: right;
margin-top: 3px;
margin-left: 15px;
color: $gray-darkest;
}
- i.fa.fa-circle {
+ .unsaved-icon {
color: $brand-success;
}
@@ -204,7 +200,7 @@
background: $gray-light;
padding: 20px;
- span.help-block {
+ .help-block {
padding-top: 7px;
margin-top: 0;
}
@@ -232,6 +228,7 @@
vertical-align: top;
width: 20%;
border-right: 1px solid $white-normal;
+ min-height: 475px;
height: calc(100vh + 20px);
overflow: auto;
}
@@ -261,7 +258,6 @@
text-transform: uppercase;
font-weight: bold;
color: $gray-darkest;
- width: 185px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -270,7 +266,7 @@
}
}
- .fa {
+ .file-icon {
margin-right: 5px;
}
@@ -280,118 +276,22 @@
}
a {
+ @include str-truncated(250px);
color: $almost-black;
display: inline-block;
vertical-align: middle;
}
-
- ul {
- list-style-type: none;
- padding: 0;
-
- li {
- border-bottom: 1px solid $border-gray-normal;
- padding: 10px 20px;
-
- a {
- color: $almost-black;
- }
-
- .fa {
- font-size: $code_font_size;
- margin-right: 5px;
- }
- }
- }
- }
-
-}
-
-.animation-container {
- background: $repo-editor-grey;
- height: 40px;
- overflow: hidden;
- position: relative;
-
- &.animation-container-small {
- height: 12px;
- }
-
- &::before {
- animation-duration: 1s;
- animation-fill-mode: forwards;
- animation-iteration-count: infinite;
- animation-name: blockTextShine;
- animation-timing-function: linear;
- background-image: $repo-editor-linear-gradient;
- background-repeat: no-repeat;
- background-size: 800px 45px;
- content: ' ';
- display: block;
- height: 100%;
- position: relative;
- }
-
- div {
- background: $white-light;
- height: 6px;
- left: 0;
- position: absolute;
- right: 0;
- }
-
- .line-of-code-1 {
- left: 0;
- top: 8px;
- }
-
- .line-of-code-2 {
- left: 150px;
- top: 0;
- height: 10px;
- }
-
- .line-of-code-3 {
- left: 0;
- top: 23px;
- }
-
- .line-of-code-4 {
- left: 0;
- top: 38px;
- }
-
- .line-of-code-5 {
- left: 200px;
- top: 28px;
- height: 10px;
- }
-
- .line-of-code-6 {
- top: 14px;
- left: 230px;
- height: 10px;
}
}
.render-error {
- min-height: calc(100vh - 63px);
+ min-height: calc(100vh - 62px);
p {
width: 100%;
}
}
-@keyframes blockTextShine {
- 0% {
- transform: translateX(-468px);
- }
-
- 100% {
- transform: translateX(468px);
- }
-}
-
@keyframes swipeRightAppear {
0% {
transform: scaleX(0.00);
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 11236cbf2e7..0028e207f3e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -29,6 +29,10 @@
margin-right: 15px;
}
+ .tree-ref-target-holder {
+ display: inline-block;
+ }
+
.repo-breadcrumb {
li:last-of-type {
position: relative;
diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb
index 52e06f4945a..1ab107168c0 100644
--- a/app/controllers/concerns/cycle_analytics_params.rb
+++ b/app/controllers/concerns/cycle_analytics_params.rb
@@ -6,6 +6,13 @@ module CycleAnalyticsParams
end
def start_date(params)
- params[:start_date] == '30' ? 30.days.ago : 90.days.ago
+ case params[:start_date]
+ when '7'
+ 7.days.ago
+ when '30'
+ 30.days.ago
+ else
+ 90.days.ago
+ end
end
end
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index c0ac47e363d..96ce686c989 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -34,7 +34,7 @@ class Groups::ApplicationController < ApplicationController
def build_canonical_path(group)
params[:group_id] = group.to_param
-
+
url_for(params)
end
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index b4213574561..7444826a5d1 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -142,13 +142,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
-
+
def fail_login
error_message = @user.errors.full_messages.to_sentence
return redirect_to omniauth_error_path(oauth['provider'], error: error_message)
end
-
+
def fail_ldap_login
flash[:alert] = 'Access denied for your LDAP account.'
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index a2e8c10857d..2b8f3977e6e 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -198,6 +198,10 @@ class Projects::BlobController < Projects::ApplicationController
json = blob_json(@blob)
return render_404 unless json
+ path_segments = @path.split('/')
+ path_segments.pop
+ tree_path = path_segments.join('/')
+
render json: json.merge(
path: blob.path,
name: blob.name,
@@ -212,6 +216,7 @@ class Projects::BlobController < Projects::ApplicationController
raw_path: project_raw_path(project, @id),
blame_path: project_blame_path(project, @id),
commits_path: project_commits_path(project, @id),
+ tree_path: project_tree_path(project, File.join(@ref, tree_path)),
permalink: project_blob_path(project, File.join(@commit.id, @path))
)
end
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index b69d46f2c41..26f3c114108 100644
--- a/app/controllers/projects/cycle_analytics/events_controller.rb
+++ b/app/controllers/projects/cycle_analytics/events_controller.rb
@@ -2,7 +2,7 @@ module Projects
module CycleAnalytics
class EventsController < Projects::ApplicationController
include CycleAnalyticsParams
-
+
before_action :authorize_read_cycle_analytics!
before_action :authorize_read_build!, only: [:test, :staging]
before_action :authorize_read_issue!, only: [:issue, :production]
@@ -11,33 +11,33 @@ module Projects
def issue
render_events(cycle_analytics[:issue].events)
end
-
+
def plan
render_events(cycle_analytics[:plan].events)
end
-
+
def code
render_events(cycle_analytics[:code].events)
end
-
+
def test
options(events_params)[:branch] = events_params[:branch_name]
-
+
render_events(cycle_analytics[:test].events)
end
-
+
def review
render_events(cycle_analytics[:review].events)
end
-
+
def staging
render_events(cycle_analytics[:staging].events)
end
-
+
def production
render_events(cycle_analytics[:production].events)
end
-
+
private
def render_events(events)
@@ -46,14 +46,14 @@ module Projects
format.json { render json: { events: events } }
end
end
-
+
def cycle_analytics
@cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
end
-
+
def events_params
return {} unless params[:events].present?
-
+
params[:events].permit(:start_date, :branch_name)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index f4d4cca8dd8..8893a514207 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -212,7 +212,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create_merge_request
- result = MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
+ result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
if result[:status] == :success
render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4de814d0ca8..2a3b73577a5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -218,8 +218,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
if can?(current_user, :read_environment, environment) && environment.has_metrics?
metrics_project_environment_deployment_path(environment.project, environment, deployment)
end
-
- metrics_monitoring_url =
+
+ metrics_monitoring_url =
if can?(current_user, :read_environment, environment)
environment_metrics_path(environment)
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 8cd61f738e1..4123a96911f 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -59,7 +59,7 @@ module GroupsHelper
end
def remove_group_message(group)
- _("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+ _("You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name }
end
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
index fee1edc2a1b..6edaf78de1b 100644
--- a/app/helpers/pipeline_schedules_helper.rb
+++ b/app/helpers/pipeline_schedules_helper.rb
@@ -1,10 +1,10 @@
module PipelineSchedulesHelper
def timezone_data
- ActiveSupport::TimeZone.all.map do |timezone|
- {
- name: timezone.name,
- offset: timezone.utc_offset,
- identifier: timezone.tzinfo.identifier
+ ActiveSupport::TimeZone.all.map do |timezone|
+ {
+ name: timezone.name,
+ offset: timezone.utc_offset,
+ identifier: timezone.tzinfo.identifier
}
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 6c5f98f74dc..bee4950e414 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -80,7 +80,7 @@ module ProjectsHelper
end
def remove_project_message(project)
- _("You are going to remove %{project_name_with_namespace}.\nRemoved project CANNOT be restored!\nAre you ABSOLUTELY sure?") %
+ _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
end
@@ -235,6 +235,8 @@ module ProjectsHelper
# If no limit is applied we'll just issue a COUNT since the result set could
# be too large to load into memory.
def any_projects?(projects)
+ return projects.any? if projects.is_a?(Array)
+
if projects.limit_value
projects.to_a.any?
else
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 7b617b359ea..d76c61c369f 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -11,11 +11,11 @@ module Emails
@member_source_type = member_source_type
@member_id = member_id
- admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ admins = member_source.members.owners_and_masters.pluck(:notification_email)
# A project in a group can have no explicit owners/masters, in that case
# we fallbacks to the group's owners/masters.
if admins.empty? && member_source.respond_to?(:group) && member_source.group
- admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ admins = member_source.group.members.owners_and_masters.pluck(:notification_email)
end
mail(to: admins,
diff --git a/app/models/blob_viewer/notebook.rb b/app/models/blob_viewer/notebook.rb
index 8632b8a9885..e00b47e6c17 100644
--- a/app/models/blob_viewer/notebook.rb
+++ b/app/models/blob_viewer/notebook.rb
@@ -2,7 +2,7 @@ module BlobViewer
class Notebook < Base
include Rich
include ClientSide
-
+
self.partial_name = 'notebook'
self.extensions = %w(ipynb)
self.binary = false
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 8be2dee6479..4692fb5644a 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -194,10 +194,7 @@ module Ci
# * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug
- ref.to_s
- .downcase
- .gsub(/[^a-z0-9]/, '-')[0..62]
- .gsub(/(\A-+|-+\z)/, '')
+ Gitlab::Utils.slugify(ref.to_s)
end
# Variables whose value does not depend on environment
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index ae8486bd9ac..b37b9bfbdac 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -12,7 +12,7 @@ class DeployKeysProject < ActiveRecord::Base
def destroy_orphaned_deploy_key
return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned?
-
+
self.deploy_key.destroy
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index bd5735ed82e..2816a68257c 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -212,21 +212,39 @@ class Group < Namespace
end
def user_ids_for_project_authorizations
- users_with_parents.pluck(:id)
+ members_with_parents.pluck(:user_id)
end
def members_with_parents
- GroupMember.active.where(source_id: ancestors.pluck(:id).push(id)).where.not(user_id: nil)
+ # Avoids an unnecessary SELECT when the group has no parents
+ source_ids =
+ if parent_id
+ self_and_ancestors.reorder(nil).select(:id)
+ else
+ id
+ end
+
+ GroupMember
+ .active_without_invites
+ .where(source_id: source_ids)
+ end
+
+ def members_with_descendants
+ GroupMember
+ .active_without_invites
+ .where(source_id: self_and_descendants.reorder(nil).select(:id))
end
def users_with_parents
- User.where(id: members_with_parents.select(:user_id))
+ User
+ .where(id: members_with_parents.select(:user_id))
+ .reorder(nil)
end
def users_with_descendants
- members_with_descendants = GroupMember.non_request.where(source_id: descendants.pluck(:id).push(id))
-
- User.where(id: members_with_descendants.select(:user_id))
+ User
+ .where(id: members_with_descendants.select(:user_id))
+ .reorder(nil)
end
def max_member_access_for_user(user)
diff --git a/app/models/member.rb b/app/models/member.rb
index b26b5017183..ee2cb13697b 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -41,9 +41,20 @@ class Member < ActiveRecord::Base
is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil))
user_is_active = User.arel_table[:state].eq(:active)
- includes(:user).references(:users)
- .where(is_external_invite.or(user_is_active))
+ user_ok = Arel::Nodes::Grouping.new(is_external_invite).or(user_is_active)
+
+ left_join_users
+ .where(user_ok)
+ .where(requested_at: nil)
+ .reorder(nil)
+ end
+
+ # Like active, but without invites. For when a User is required.
+ scope :active_without_invites, -> do
+ left_join_users
+ .where(users: { state: 'active' })
.where(requested_at: nil)
+ .reorder(nil)
end
scope :invite, -> { where.not(invite_token: nil) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f90194041b1..ac08dc0ee1f 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -443,7 +443,8 @@ class MergeRequest < ActiveRecord::Base
end
def reload_diff_if_branch_changed
- if source_branch_changed? || target_branch_changed?
+ if (source_branch_changed? || target_branch_changed?) &&
+ (source_branch_head && target_branch_head)
reload_diff
end
end
@@ -792,11 +793,7 @@ class MergeRequest < ActiveRecord::Base
end
def fetch_ref
- target_project.repository.fetch_ref(
- source_project.repository.path_to_repo,
- "refs/heads/#{source_branch}",
- ref_path
- )
+ write_ref
update_column(:ref_fetched, true)
end
@@ -939,4 +936,17 @@ class MergeRequest < ActiveRecord::Base
true
end
+
+ private
+
+ def write_ref
+ target_project.repository.with_repo_branch_commit(
+ source_project.repository, source_branch) do |commit|
+ if commit
+ target_project.repository.write_ref(ref_path, commit.sha)
+ else
+ raise Rugged::ReferenceError, 'source repository is empty'
+ end
+ end
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 6073fb94a3f..e7bc1d1b080 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -156,6 +156,14 @@ class Namespace < ActiveRecord::Base
.base_and_ancestors
end
+ def self_and_ancestors
+ return self.class.where(id: id) unless parent_id
+
+ Gitlab::GroupHierarchy
+ .new(self.class.where(id: id))
+ .base_and_ancestors
+ end
+
# Returns all the descendants of the current namespace.
def descendants
Gitlab::GroupHierarchy
@@ -163,6 +171,12 @@ class Namespace < ActiveRecord::Base
.base_and_descendants
end
+ def self_and_descendants
+ Gitlab::GroupHierarchy
+ .new(self.class.where(id: id))
+ .base_and_descendants
+ end
+
def user_ids_for_project_authorizations
[owner_id]
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e04663a31f3..22b347cc8f9 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -196,7 +196,6 @@ class Project < ActiveRecord::Base
accepts_nested_attributes_for :import_data
delegate :name, to: :owner, allow_nil: true, prefix: true
- delegate :count, to: :forks, prefix: true
delegate :members, to: :team, prefix: true
delegate :add_user, :add_users, to: :team
delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
@@ -1048,9 +1047,7 @@ class Project < ActiveRecord::Base
def change_head(branch)
if repository.branch_exists?(branch)
repository.before_change_head
- repository.rugged.references.create('HEAD',
- "refs/heads/#{branch}",
- force: true)
+ repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.copy_gitattributes(branch)
repository.after_change_head
reload_default_branch
@@ -1285,12 +1282,16 @@ class Project < ActiveRecord::Base
status.zero?
end
+ def full_path_slug
+ Gitlab::Utils.slugify(full_path.to_s)
+ end
+
def predefined_variables
[
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: path, public: true },
{ key: 'CI_PROJECT_PATH', value: full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: full_path.parameterize, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: web_url, public: true }
]
@@ -1398,6 +1399,10 @@ class Project < ActiveRecord::Base
# @deprecated cannot remove yet because it has an index with its name in elasticsearch
alias_method :path_with_namespace, :full_path
+ def forks_count
+ Projects::ForksCountService.new(self).count
+ end
+
private
def cross_namespace_reference?(from)
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 090fbd61e6f..31de204d824 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -14,7 +14,7 @@ class RedirectRoute < ActiveRecord::Base
else
'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
end
-
+
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 0ac3c382f17..c1e4fcf94a4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -226,7 +226,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
begin
- rugged.references.create(keep_around_ref_name(sha), sha, force: true)
+ write_ref(keep_around_ref_name(sha), sha)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
rescue Rugged::OSError => ex
@@ -239,6 +239,10 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
+ def write_ref(ref_path, sha)
+ rugged.references.create(ref_path, sha, force: true)
+ end
+
def diverging_commit_counts(branch)
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
@@ -987,12 +991,10 @@ class Repository
if start_repository == self
start_branch_name
else
- tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
-
- fetch_ref(
+ tmp_ref = fetch_ref(
start_repository.path_to_repo,
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
- tmp_ref
+ "refs/tmp/#{SecureRandom.hex}/head"
)
start_repository.commit(start_branch_name).sha
@@ -1023,7 +1025,12 @@ class Repository
def fetch_ref(source_path, source_ref, target_ref)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
- run_git(args)
+ message, status = run_git(args)
+
+ # Make sure ref was created, and raise Rugged::ReferenceError when not
+ raise Rugged::ReferenceError, message if status != 0
+
+ target_ref
end
def create_ref(ref, ref_path)
diff --git a/app/models/user.rb b/app/models/user.rb
index 0a2cfeb7f3e..0e2654ff757 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -726,9 +726,9 @@ class User < ActiveRecord::Base
end
def sanitize_attrs
- %w[username skype linkedin twitter].each do |attr|
- value = public_send(attr) # rubocop:disable GitlabSecurity/PublicSend
- public_send("#{attr}=", Sanitize.clean(value)) if value.present? # rubocop:disable GitlabSecurity/PublicSend
+ %i[skype linkedin twitter].each do |attr|
+ value = self[attr]
+ self[attr] = Sanitize.clean(value) if value.present?
end
end
diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb
index dc283ba3e7a..b3e5fd21e97 100644
--- a/app/serializers/project_entity.rb
+++ b/app/serializers/project_entity.rb
@@ -1,6 +1,6 @@
class ProjectEntity < Grape::Entity
include RequestAwareEntity
-
+
expose :id
expose :name
diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb
index 23b65aa4a4c..69702ae1493 100644
--- a/app/serializers/tree_root_entity.rb
+++ b/app/serializers/tree_root_entity.rb
@@ -1,8 +1,21 @@
# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`.
class TreeRootEntity < Grape::Entity
+ include RequestAwareEntity
+
expose :path
-
+
expose :trees, using: TreeEntity
expose :blobs, using: BlobEntity
expose :submodules, using: SubmoduleEntity
+
+ expose :parent_tree_url do |tree|
+ path = tree.path.sub(%r{\A/}, '')
+ next unless path.present?
+
+ path_segments = path.split('/')
+ path_segments.pop
+ parent_tree_path = path_segments.join('/')
+
+ project_tree_path(request.project, File.join(request.ref, parent_tree_path))
+ end
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index fa0c0b7175c..194413bf321 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -25,7 +25,6 @@ module MergeRequests
end
def after_create(issuable)
- event_service.open_mr(issuable, current_user)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
update_merge_requests_head_pipeline(issuable)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 11ad4838471..54eb75ab9bf 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -128,6 +128,8 @@ module Projects
project.repository.before_delete
Repository.new(wiki_path, project, disk_path: repo_path).before_delete
+
+ Projects::ForksCountService.new(project).delete_cache
end
end
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index a2b23ea6171..ad67e68a86a 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -21,11 +21,17 @@ module Projects
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
+ refresh_forks_count
+
new_project
end
private
+ def refresh_forks_count
+ Projects::ForksCountService.new(@project).refresh_cache
+ end
+
def allowed_visibility_level
project_level = @project.visibility_level
diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb
new file mode 100644
index 00000000000..e2e2b1da91d
--- /dev/null
+++ b/app/services/projects/forks_count_service.rb
@@ -0,0 +1,30 @@
+module Projects
+ # Service class for getting and caching the number of forks of a project.
+ class ForksCountService
+ def initialize(project)
+ @project = project
+ end
+
+ def count
+ Rails.cache.fetch(cache_key) { uncached_count }
+ end
+
+ def refresh_cache
+ Rails.cache.write(cache_key, uncached_count)
+ end
+
+ def delete_cache
+ Rails.cache.delete(cache_key)
+ end
+
+ private
+
+ def uncached_count
+ @project.forks.count
+ end
+
+ def cache_key
+ ['projects', @project.id, 'forks_count']
+ end
+ end
+end
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index f385e426827..f30b40423c8 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -13,7 +13,13 @@ module Projects
::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
end
+ refresh_forks_count(@project.forked_from_project)
+
@project.forked_project_link.destroy
end
+
+ def refresh_forks_count(project)
+ Projects::ForksCountService.new(project).refresh_cache
+ end
end
end
diff --git a/app/views/layouts/nav/_new_admin_sidebar.html.haml b/app/views/layouts/nav/_new_admin_sidebar.html.haml
index 0b4a9d92bea..3cbcd841aff 100644
--- a/app/views/layouts/nav/_new_admin_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_admin_sidebar.html.haml
@@ -1,150 +1,151 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .context-header
- = link_to admin_root_path, title: 'Admin Overview' do
- .avatar-container.s40.settings-avatar
- = icon('wrench')
- .project-title Admin Area
- %ul.sidebar-top-level-items
- = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
- .nav-icon-container
- = custom_icon('overview')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview' do
- %span
- Dashboard
- = nav_link(controller: [:admin, :projects]) do
- = link_to admin_projects_path, title: 'Projects' do
- %span
- Projects
- = nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users' do
- %span
- Users
- = nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link path: 'jobs#index' do
- = link_to admin_jobs_path, title: 'Jobs' do
- %span
- Jobs
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path, title: 'Runners' do
- %span
- Runners
- = nav_link path: 'cohorts#index' do
- = link_to admin_cohorts_path, title: 'Cohorts' do
- %span
- Cohorts
+ .nav-sidebar-inner-scroll
+ .context-header
+ = link_to admin_root_path, title: 'Admin Overview' do
+ .avatar-container.s40.settings-avatar
+ = icon('wrench')
+ .project-title Admin Area
+ %ul.sidebar-top-level-items
+ = nav_link(controller: %w(dashboard admin projects users groups jobs runners cohorts), html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('overview')
+ %span.nav-item-name
+ Overview
- = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
- = link_to admin_conversational_development_index_path, title: 'Monitoring' do
- .nav-icon-container
- = custom_icon('monitoring')
- %span.nav-item-name
- Monitoring
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
+ = link_to admin_root_path, title: 'Overview' do
+ %span
+ Dashboard
+ = nav_link(controller: [:admin, :projects]) do
+ = link_to admin_projects_path, title: 'Projects' do
+ %span
+ Projects
+ = nav_link(controller: :users) do
+ = link_to admin_users_path, title: 'Users' do
+ %span
+ Users
+ = nav_link(controller: :groups) do
+ = link_to admin_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link path: 'jobs#index' do
+ = link_to admin_jobs_path, title: 'Jobs' do
+ %span
+ Jobs
+ = nav_link path: ['runners#index', 'runners#show'] do
+ = link_to admin_runners_path, title: 'Runners' do
+ %span
+ Runners
+ = nav_link path: 'cohorts#index' do
+ = link_to admin_cohorts_path, title: 'Cohorts' do
+ %span
+ Cohorts
- %ul.sidebar-sub-level-items
- = nav_link(controller: :conversational_development_index) do
- = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
- %span
- ConvDev Index
- = nav_link(controller: :system_info) do
- = link_to admin_system_info_path, title: 'System Info' do
- %span
- System Info
- = nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs' do
- %span
- Background Jobs
- = nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs' do
- %span
- Logs
- = nav_link(controller: :health_check) do
- = link_to admin_health_check_path, title: 'Health Check' do
- %span
- Health Check
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
- %span
- Requests Profiles
+ = nav_link(controller: %w(conversational_development_index system_info background_jobs logs health_check requests_profiles)) do
+ = link_to admin_conversational_development_index_path, title: 'Monitoring' do
+ .nav-icon-container
+ = custom_icon('monitoring')
+ %span.nav-item-name
+ Monitoring
- = nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Messages' do
- .nav-icon-container
- = custom_icon('messages')
- %span.nav-item-name
- Messages
- = nav_link(controller: [:hooks, :hook_logs]) do
- = link_to admin_hooks_path, title: 'Hooks' do
- .nav-icon-container
- = custom_icon('system_hooks')
- %span.nav-item-name
- System Hooks
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :conversational_development_index) do
+ = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
+ %span
+ ConvDev Index
+ = nav_link(controller: :system_info) do
+ = link_to admin_system_info_path, title: 'System Info' do
+ %span
+ System Info
+ = nav_link(controller: :background_jobs) do
+ = link_to admin_background_jobs_path, title: 'Background Jobs' do
+ %span
+ Background Jobs
+ = nav_link(controller: :logs) do
+ = link_to admin_logs_path, title: 'Logs' do
+ %span
+ Logs
+ = nav_link(controller: :health_check) do
+ = link_to admin_health_check_path, title: 'Health Check' do
+ %span
+ Health Check
+ = nav_link(controller: :requests_profiles) do
+ = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
+ %span
+ Requests Profiles
- = nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications' do
- .nav-icon-container
- = custom_icon('applications')
- %span.nav-item-name
- Applications
+ = nav_link(controller: :broadcast_messages) do
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
+ .nav-icon-container
+ = custom_icon('messages')
+ %span.nav-item-name
+ Messages
+ = nav_link(controller: [:hooks, :hook_logs]) do
+ = link_to admin_hooks_path, title: 'Hooks' do
+ .nav-icon-container
+ = custom_icon('system_hooks')
+ %span.nav-item-name
+ System Hooks
- = nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse Reports" do
- .nav-icon-container
- = custom_icon('abuse_reports')
- %span.nav-item-name
- Abuse Reports
- %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ .nav-icon-container
+ = custom_icon('applications')
+ %span.nav-item-name
+ Applications
- - if akismet_enabled?
- = nav_link(controller: :spam_logs) do
- = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = nav_link(controller: :abuse_reports) do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
.nav-icon-container
- = custom_icon('spam_logs')
+ = custom_icon('abuse_reports')
%span.nav-item-name
- Spam Logs
+ Abuse Reports
+ %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- = nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
- .nav-icon-container
- = custom_icon('key')
- %span.nav-item-name
- Deploy Keys
+ - if akismet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ .nav-icon-container
+ = custom_icon('spam_logs')
+ %span.nav-item-name
+ Spam Logs
- = nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates' do
- .nav-icon-container
- = custom_icon('service_templates')
- %span.nav-item-name
- Service Templates
+ = nav_link(controller: :deploy_keys) do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+ .nav-icon-container
+ = custom_icon('key')
+ %span.nav-item-name
+ Deploy Keys
- = nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels' do
- .nav-icon-container
- = custom_icon('labels')
- %span.nav-item-name
- Labels
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ .nav-icon-container
+ = custom_icon('service_templates')
+ %span.nav-item-name
+ Service Templates
- = nav_link(controller: :appearances) do
- = link_to admin_appearances_path, title: 'Appearances' do
- .nav-icon-container
- = custom_icon('appearance')
- %span.nav-item-name
- Appearance
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels' do
+ .nav-icon-container
+ = custom_icon('labels')
+ %span.nav-item-name
+ Labels
- %li.divider
- = nav_link(controller: :application_settings) do
- = link_to admin_application_settings_path, title: 'Settings' do
- .nav-icon-container
- = custom_icon('settings')
- %span.nav-item-name
- Settings
+ = nav_link(controller: :appearances) do
+ = link_to admin_appearances_path, title: 'Appearances' do
+ .nav-icon-container
+ = custom_icon('appearance')
+ %span.nav-item-name
+ Appearance
+
+ %li.divider
+ = nav_link(controller: :application_settings) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ Settings
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_group_sidebar.html.haml b/app/views/layouts/nav/_new_group_sidebar.html.haml
index c7dabbd8237..ed5793f09fe 100644
--- a/app/views/layouts/nav/_new_group_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_group_sidebar.html.haml
@@ -1,89 +1,90 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .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
- %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: 'Group overview' do
- .nav-icon-container
- = custom_icon('project')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Group details' do
- %span
- Details
-
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- %span
- Activity
-
- = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
- = link_to issues_group_path(@group), title: 'Issues' do
- .nav-icon-container
- = custom_icon('issues')
- %span.nav-item-name
- - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- Issues
- %span.badge.count= number_with_delimiter(issues.count)
-
- %ul.sidebar-sub-level-items
- = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
- = link_to issues_group_path(@group), title: 'List' do
- %span
- List
+ .nav-sidebar-inner-scroll
+ .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
+ %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: 'Group overview' do
+ .nav-icon-container
+ = custom_icon('project')
+ %span.nav-item-name
+ Overview
- = nav_link(path: 'labels#index') do
- = link_to group_labels_path(@group), title: 'Labels' do
- %span
- Labels
+ %ul.sidebar-sub-level-items
+ = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to group_path(@group), title: 'Group details' do
+ %span
+ Details
- = nav_link(path: 'milestones#index') do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- %span
- Milestones
+ = nav_link(path: 'groups#activity') do
+ = link_to activity_group_path(@group), title: 'Activity' do
+ %span
+ Activity
- = nav_link(path: 'groups#merge_requests') do
- = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
- .nav-icon-container
- = custom_icon('mr_bold')
- %span.nav-item-name
- - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
- Merge Requests
- %span.badge.count= number_with_delimiter(merge_requests.count)
- = nav_link(path: 'group_members#index') do
- = link_to group_group_members_path(@group), title: 'Members' do
- .nav-icon-container
- = custom_icon('members')
- %span.nav-item-name
- Members
- - if current_user && can?(current_user, :admin_group, @group)
- = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
- = link_to edit_group_path(@group), title: 'Settings' do
+ = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do
+ = link_to issues_group_path(@group), title: 'Issues' do
.nav-icon-container
- = custom_icon('settings')
+ = custom_icon('issues')
%span.nav-item-name
- Settings
+ - issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
+ Issues
+ %span.badge.count= number_with_delimiter(issues.count)
+
%ul.sidebar-sub-level-items
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'General' do
+ = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
+ = link_to issues_group_path(@group), title: 'List' do
%span
- General
+ List
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
+ = nav_link(path: 'labels#index') do
+ = link_to group_labels_path(@group), title: 'Labels' do
%span
- Projects
+ Labels
- = nav_link(controller: :ci_cd) do
- = link_to group_settings_ci_cd_path(@group), title: 'CI / CD' do
+ = nav_link(path: 'milestones#index') do
+ = link_to group_milestones_path(@group), title: 'Milestones' do
%span
- CI / CD
+ Milestones
+
+ = nav_link(path: 'groups#merge_requests') do
+ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do
+ .nav-icon-container
+ = custom_icon('mr_bold')
+ %span.nav-item-name
+ - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
+ Merge Requests
+ %span.badge.count= number_with_delimiter(merge_requests.count)
+ = nav_link(path: 'group_members#index') do
+ = link_to group_group_members_path(@group), title: 'Members' do
+ .nav-icon-container
+ = custom_icon('members')
+ %span.nav-item-name
+ Members
+ - if current_user && can?(current_user, :admin_group, @group)
+ = nav_link(path: %w[groups#projects groups#edit ci_cd#show]) do
+ = link_to edit_group_path(@group), title: 'Settings' do
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ 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: 'CI / CD' do
+ %span
+ CI / CD
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_profile_sidebar.html.haml b/app/views/layouts/nav/_new_profile_sidebar.html.haml
index edae009a28e..4234df56d1d 100644
--- a/app/views/layouts/nav/_new_profile_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_profile_sidebar.html.haml
@@ -1,84 +1,85 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- .context-header
- = link_to profile_path, title: 'Profile Settings' do
- .avatar-container.s40.settings-avatar
- = icon('user')
- .project-title User Settings
- %ul.sidebar-top-level-items
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ .nav-sidebar-inner-scroll
+ .context-header
= link_to profile_path, title: 'Profile Settings' do
- .nav-icon-container
- = custom_icon('profile')
- %span.nav-item-name
- Profile
- = nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account' do
- .nav-icon-container
- = custom_icon('account')
- %span.nav-item-name
- Account
- - if current_application_settings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
+ .avatar-container.s40.settings-avatar
+ = icon('user')
+ .project-title User Settings
+ %ul.sidebar-top-level-items
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: 'Profile Settings' do
.nav-icon-container
- = custom_icon('applications')
+ = custom_icon('profile')
%span.nav-item-name
- Applications
- = nav_link(controller: :chat_names) do
- = link_to profile_chat_names_path, title: 'Chat' do
- .nav-icon-container
- = custom_icon('chat')
- %span.nav-item-name
- Chat
- = nav_link(controller: :personal_access_tokens) do
- = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
- .nav-icon-container
- = custom_icon('access_tokens')
- %span.nav-item-name
- Access Tokens
- = nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails' do
- .nav-icon-container
- = custom_icon('emails')
- %span.nav-item-name
- Emails
- - unless current_user.ldap_user?
- = nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password' do
+ Profile
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
+ = link_to profile_account_path, title: 'Account' do
.nav-icon-container
- = custom_icon('lock')
+ = custom_icon('account')
%span.nav-item-name
- Password
- = nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications' do
- .nav-icon-container
- = custom_icon('notifications')
- %span.nav-item-name
- Notifications
+ Account
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ .nav-icon-container
+ = custom_icon('applications')
+ %span.nav-item-name
+ Applications
+ = nav_link(controller: :chat_names) do
+ = link_to profile_chat_names_path, title: 'Chat' do
+ .nav-icon-container
+ = custom_icon('chat')
+ %span.nav-item-name
+ Chat
+ = nav_link(controller: :personal_access_tokens) do
+ = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+ .nav-icon-container
+ = custom_icon('access_tokens')
+ %span.nav-item-name
+ Access Tokens
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path, title: 'Emails' do
+ .nav-icon-container
+ = custom_icon('emails')
+ %span.nav-item-name
+ Emails
+ - unless current_user.ldap_user?
+ = nav_link(controller: :passwords) do
+ = link_to edit_profile_password_path, title: 'Password' do
+ .nav-icon-container
+ = custom_icon('lock')
+ %span.nav-item-name
+ Password
+ = nav_link(controller: :notifications) do
+ = link_to profile_notifications_path, title: 'Notifications' do
+ .nav-icon-container
+ = custom_icon('notifications')
+ %span.nav-item-name
+ Notifications
- = nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys' do
- .nav-icon-container
- = custom_icon('key')
- %span.nav-item-name
- SSH Keys
- = nav_link(controller: :gpg_keys) do
- = link_to profile_gpg_keys_path, title: 'GPG Keys' do
- .nav-icon-container
- = custom_icon('key_2')
- %span.nav-item-name
- GPG Keys
- = nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences' do
- .nav-icon-container
- = custom_icon('preferences')
- %span.nav-item-name
- Preferences
- = nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Authentication log' do
- .nav-icon-container
- = custom_icon('authentication_log')
- %span.nav-item-name
- Authentication log
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ .nav-icon-container
+ = custom_icon('key')
+ %span.nav-item-name
+ SSH Keys
+ = nav_link(controller: :gpg_keys) do
+ = link_to profile_gpg_keys_path, title: 'GPG Keys' do
+ .nav-icon-container
+ = custom_icon('key_2')
+ %span.nav-item-name
+ GPG Keys
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences' do
+ .nav-icon-container
+ = custom_icon('preferences')
+ %span.nav-item-name
+ Preferences
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Authentication log' do
+ .nav-icon-container
+ = custom_icon('authentication_log')
+ %span.nav-item-name
+ Authentication log
- = render 'shared/sidebar_toggle_button'
+ = render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/_new_project_sidebar.html.haml b/app/views/layouts/nav/_new_project_sidebar.html.haml
index e0477c29ebe..0ef81375c3a 100644
--- a/app/views/layouts/nav/_new_project_sidebar.html.haml
+++ b/app/views/layouts/nav/_new_project_sidebar.html.haml
@@ -1,261 +1,262 @@
.nav-sidebar{ class: ("sidebar-icons-only" if collapsed_sidebar?) }
- - can_edit = can?(current_user, :admin_project, @project)
- .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
- %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 overview', class: 'shortcuts-project' do
- .nav-icon-container
- = custom_icon('project')
- %span.nav-item-name
- Overview
-
- %ul.sidebar-sub-level-items
- = nav_link(path: 'projects#show') do
- = 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
- %span= _('Activity')
-
- - if can?(current_user, :read_cycle_analytics, @project)
- = nav_link(path: 'cycle_analytics#show') do
- = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
- %span= _('Cycle Analytics')
-
- - 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_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ .nav-sidebar-inner-scroll
+ - can_edit = can?(current_user, :admin_project, @project)
+ .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
+ %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 overview', class: 'shortcuts-project' do
.nav-icon-container
- = custom_icon('doc_text')
+ = custom_icon('project')
%span.nav-item-name
- Repository
+ Overview
%ul.sidebar-sub-level-items
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_tree_path(@project) do
- #{ _('Files') }
-
- = nav_link(controller: [:commit, :commits]) do
- = link_to project_commits_path(@project, current_ref) do
- #{ _('Commits') }
-
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
- #{ _('Branches') }
-
- = nav_link(controller: [:tags, :releases]) do
- = link_to project_tags_path(@project) do
- #{ _('Tags') }
-
- = nav_link(path: 'graphs#show') do
- = link_to project_graph_path(@project, current_ref) do
- #{ _('Contributors') }
-
- = nav_link(controller: %w(network)) do
- = link_to project_network_path(@project, current_ref) do
- #{ s_('ProjectNetworkGraph|Graph') }
-
- = nav_link(controller: :compare) 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_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_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
- .nav-icon-container
- = custom_icon('container_registry')
- %span.nav-item-name
- Registry
-
- - if project_nav_tab? :issues
- = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
- .nav-icon-container
- = custom_icon('issues')
- %span.nav-item-name
- Issues
- - if @project.issues_enabled?
- %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
-
- %ul.sidebar-sub-level-items
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), title: 'Issues' do
- %span
- List
-
- = nav_link(controller: :boards) do
- = link_to project_boards_path(@project), title: 'Board' do
- %span
- Board
-
- = nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: 'Labels' do
- %span
- Labels
-
- = nav_link(controller: :milestones) do
- = link_to project_milestones_path(@project), title: 'Milestones' do
- %span
- Milestones
-
- - if project_nav_tab? :merge_requests
- = 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
- .nav-icon-container
- = custom_icon('mr_bold')
- %span.nav-item-name
- Merge Requests
- %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
-
- - if project_nav_tab? :pipelines
- = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
- = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do
- .nav-icon-container
- = custom_icon('pipeline')
- %span.nav-item-name
- CI / CD
-
- %ul.sidebar-sub-level-items
- - if project_nav_tab? :pipelines
- = nav_link(path: ['pipelines#index', 'pipelines#show']) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
-
- - if project_nav_tab? :builds
- = nav_link(controller: [:jobs, :artifacts]) do
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ = nav_link(path: 'projects#show') do
+ = 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
+ %span= _('Activity')
+
+ - if can?(current_user, :read_cycle_analytics, @project)
+ = nav_link(path: 'cycle_analytics#show') do
+ = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
+ %span= _('Cycle Analytics')
+
+ - 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_tree_path(@project), title: 'Repository', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('doc_text')
+ %span.nav-item-name
+ Repository
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
+ = link_to project_tree_path(@project) do
+ #{ _('Files') }
+
+ = nav_link(controller: [:commit, :commits]) do
+ = link_to project_commits_path(@project, current_ref) do
+ #{ _('Commits') }
+
+ = nav_link(html_options: {class: branches_tab_class}) do
+ = link_to project_branches_path(@project) do
+ #{ _('Branches') }
+
+ = nav_link(controller: [:tags, :releases]) do
+ = link_to project_tags_path(@project) do
+ #{ _('Tags') }
+
+ = nav_link(path: 'graphs#show') do
+ = link_to project_graph_path(@project, current_ref) do
+ #{ _('Contributors') }
+
+ = nav_link(controller: %w(network)) do
+ = link_to project_network_path(@project, current_ref) do
+ #{ s_('ProjectNetworkGraph|Graph') }
+
+ = nav_link(controller: :compare) 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_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_index_path(@project), title: 'Container Registry', class: 'shortcuts-container-registry' do
+ .nav-icon-container
+ = custom_icon('container_registry')
+ %span.nav-item-name
+ Registry
+
+ - if project_nav_tab? :issues
+ = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
+ = link_to project_issues_path(@project), title: 'Issues', class: 'shortcuts-issues' do
+ .nav-icon-container
+ = custom_icon('issues')
+ %span.nav-item-name
+ Issues
+ - if @project.issues_enabled?
+ %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :issues) do
+ = link_to project_issues_path(@project), title: 'Issues' do
%span
- Jobs
+ List
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipeline_schedules) do
- = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+ = nav_link(controller: :boards) do
+ = link_to project_boards_path(@project), title: 'Board' do
%span
- Schedules
+ Board
- - if project_nav_tab? :environments
- = nav_link(controller: :environments) do
- = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ = nav_link(controller: :labels) do
+ = link_to project_labels_path(@project), title: 'Labels' do
%span
- Environments
+ Labels
- - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
- = nav_link(path: 'pipelines#charts') do
- = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ = nav_link(controller: :milestones) do
+ = link_to project_milestones_path(@project), title: 'Milestones' do
%span
- Charts
+ Milestones
+
+ - if project_nav_tab? :merge_requests
+ = 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
+ .nav-icon-container
+ = custom_icon('mr_bold')
+ %span.nav-item-name
+ Merge Requests
+ %span.badge.count.merge_counter.js-merge-counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts]) do
+ = link_to project_pipelines_path(@project), title: 'CI / CD', class: 'shortcuts-pipelines' do
+ .nav-icon-container
+ = custom_icon('pipeline')
+ %span.nav-item-name
+ CI / CD
+
+ %ul.sidebar-sub-level-items
+ - if project_nav_tab? :pipelines
+ = nav_link(path: ['pipelines#index', 'pipelines#show']) do
+ = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
+ %span
+ Pipelines
- - if project_nav_tab? :wiki
- = nav_link(controller: :wikis) do
- = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
- .nav-icon-container
- = custom_icon('wiki')
- %span.nav-item-name
- Wiki
+ - if project_nav_tab? :builds
+ = nav_link(controller: [:jobs, :artifacts]) do
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ %span
+ Jobs
- - if project_nav_tab? :snippets
- = nav_link(controller: :snippets) do
- = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
- .nav-icon-container
- = custom_icon('snippets')
- %span.nav-item-name
- Snippets
+ - if project_nav_tab? :pipelines
+ = nav_link(controller: :pipeline_schedules) do
+ = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
+ %span
+ Schedules
- - if project_nav_tab? :settings
- = 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
- .nav-icon-container
- = custom_icon('settings')
- %span.nav-item-name
- Settings
+ - if project_nav_tab? :environments
+ = nav_link(controller: :environments) do
+ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
+ %span
+ Environments
- %ul.sidebar-sub-level-items
- - can_edit = can?(current_user, :admin_project, @project)
- - if can_edit
- = nav_link(path: %w[projects#edit]) do
- = link_to edit_project_path(@project), title: 'General' do
- %span
- General
- = nav_link(controller: :project_members) do
- = link_to project_project_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 project_settings_repository_path(@project), title: 'Repository' do
+ - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
+ = nav_link(path: 'pipelines#charts') do
+ = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
+ %span
+ Charts
+
+ - if project_nav_tab? :wiki
+ = nav_link(controller: :wikis) do
+ = link_to get_project_wiki_path(@project), title: 'Wiki', class: 'shortcuts-wiki' do
+ .nav-icon-container
+ = custom_icon('wiki')
+ %span.nav-item-name
+ Wiki
+
+ - if project_nav_tab? :snippets
+ = nav_link(controller: :snippets) do
+ = link_to project_snippets_path(@project), title: 'Snippets', class: 'shortcuts-snippets' do
+ .nav-icon-container
+ = custom_icon('snippets')
+ %span.nav-item-name
+ Snippets
+
+ - if project_nav_tab? :settings
+ = 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
+ .nav-icon-container
+ = custom_icon('settings')
+ %span.nav-item-name
+ Settings
+
+ %ul.sidebar-sub-level-items
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if can_edit
+ = nav_link(path: %w[projects#edit]) do
+ = link_to edit_project_path(@project), title: 'General' do
+ %span
+ General
+ = nav_link(controller: :project_members) do
+ = link_to project_project_members_path(@project), title: 'Members' do
%span
- Repository
- - if @project.feature_available?(:builds, current_user)
- = nav_link(controller: :ci_cd) do
- = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
+ Members
+ - if can_edit
+ = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
+ = link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
- CI / CD
- - if Gitlab.config.pages.enabled
- = nav_link(controller: :pages) do
- = link_to project_pages_path(@project), title: 'Pages' do
+ Integrations
+ = nav_link(controller: :repository) do
+ = link_to project_settings_repository_path(@project), title: 'Repository' do
%span
- Pages
-
- - else
- = nav_link(path: %w[members#show]) do
- = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
- .nav-icon-container
- = custom_icon('members')
- %span.nav-item-name
- Members
-
- = render 'shared/sidebar_toggle_button'
-
- -# Shortcut to Project > Activity
- %li.hidden
- = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
- %span
- Activity
-
- -# Shortcut to Repository > Graph (formerly, Network)
- - if project_nav_tab? :network
+ Repository
+ - if @project.feature_available?(:builds, current_user)
+ = nav_link(controller: :ci_cd) do
+ = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do
+ %span
+ CI / CD
+ - if Gitlab.config.pages.enabled
+ = nav_link(controller: :pages) do
+ = link_to project_pages_path(@project), title: 'Pages' do
+ %span
+ Pages
+
+ - else
+ = nav_link(path: %w[members#show]) do
+ = link_to project_settings_members_path(@project), title: 'Members', class: 'shortcuts-tree' do
+ .nav-icon-container
+ = custom_icon('members')
+ %span.nav-item-name
+ Members
+
+ = render 'shared/sidebar_toggle_button'
+
+ -# Shortcut to Project > Activity
%li.hidden
- = 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_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
- Charts
-
- -# Shortcut to Issues > New Issue
- %li.hidden
- = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
- Create a new issue
-
- -# Shortcut to Pipelines > Jobs
- - if project_nav_tab? :builds
+ = link_to activity_project_path(@project), title: 'Activity', class: 'shortcuts-project-activity' do
+ %span
+ Activity
+
+ -# Shortcut to Repository > Graph (formerly, Network)
+ - if project_nav_tab? :network
+ %li.hidden
+ = 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_project_graph_path(@project, current_ref), title: 'Charts', class: 'shortcuts-repository-charts' do
+ Charts
+
+ -# Shortcut to Issues > New Issue
%li.hidden
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
- Jobs
-
- -# Shortcut to commits page
- - if project_nav_tab? :commits
+ = link_to new_project_issue_path(@project), class: 'shortcuts-new-issue' do
+ Create a new issue
+
+ -# Shortcut to Pipelines > Jobs
+ - if project_nav_tab? :builds
+ %li.hidden
+ = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ Jobs
+
+ -# Shortcut to commits page
+ - if project_nav_tab? :commits
+ %li.hidden
+ = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
+ Commits
+
+ -# Shortcut to issue boards
%li.hidden
- = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
- Commits
-
- -# Shortcut to issue boards
- %li.hidden
- = link_to 'Issue Boards', project_boards_path(@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/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml
index 22674b671c9..83821326aec 100644
--- a/app/views/projects/commit/_ajax_signature.html.haml
+++ b/app/views/projects/commit/_ajax_signature.html.haml
@@ -1,3 +1,2 @@
- 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/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index c704635ead3..3467e357c49 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -40,6 +40,9 @@
%i.fa.fa-chevron-down
%ul.dropdown-menu.dropdown-menu-align-right
%li
+ %a{ "href" => "#", "data-value" => "7" }
+ {{ n__('Last %d day', 'Last %d days', 7) }}
+ %li
%a{ "href" => "#", "data-value" => "30" }
{{ n__('Last %d day', 'Last %d days', 30) }}
%li
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 178ab3df2e5..376f672f424 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -6,7 +6,7 @@
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) }
.files-changed-inner
- .inline-parallel-buttons
+ .inline-parallel-buttons.hidden-xs.hidden-sm
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index efc0ea31917..02fd54c97fb 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -10,7 +10,7 @@
%strong.cgreen #{sum_added_lines} additions
and
%strong.cred #{sum_removed_lines} deletions
- .diff-stats-additions-deletions-collapsed.pull-right{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
+ .diff-stats-additions-deletions-collapsed.pull-right.hidden-xs.hidden-sm{ "aria-hidden": "true", "aria-describedby": "diff-stats" }
%strong.cgreen<
+#{sum_added_lines}
%strong.cred<
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 756faf4625e..13809da6523 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,7 +1,7 @@
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
- if @can_bulk_update
- = button_tag "Edit Issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
+ = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
= 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) }),
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index e92f2712347..e73dab8ad4a 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- if @can_bulk_update
- = button_tag "Edit Merge Requests", class: "btn js-bulk-update-toggle"
+ = button_tag "Edit merge requests", class: "btn append-right-10 js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "btn btn-new", title: "New merge request" do
New merge request
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 9c42be4e0ff..cb737d129f0 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -17,24 +17,32 @@
"inline-template" => true,
"ref" => "note_#{note.id}" }
- %button.note-action-button.line-resolve-btn{ type: "button",
- class: ("is-disabled" unless can_resolve),
- ":class" => "{ 'is-active': isResolved }",
- ":aria-label" => "buttonText",
- "@click" => "resolve",
- ":title" => "buttonText",
- ":ref" => "'button'" }
+ .note-actions-item
+ %button.note-action-button.line-resolve-btn{ type: "button",
+ class: ("is-disabled" unless can_resolve),
+ ":class" => "{ 'is-active': isResolved }",
+ ":aria-label" => "buttonText",
+ "@click" => "resolve",
+ ":title" => "buttonText",
+ ":ref" => "'button'" }
- = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
- %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
+ = icon('spin spinner', 'v-show' => 'loading', class: 'loading', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
+ %div{ 'v-show' => '!loading' }= render 'shared/icons/icon_status_success.svg'
- if current_user
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
- = icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ .note-actions-item
+ = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
+ = icon('spinner spin')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+ %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
- = render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
+ - if note_editable
+ .note-actions-item
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ %span.link-highlight
+ = custom_icon('icon_pencil')
+
+ = render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 75a4687e1e3..5930209a682 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -1,14 +1,11 @@
- is_current_user = current_user == note.author
- if note_editable || !is_current_user
- .dropdown.more-actions
+ .dropdown.more-actions.note-actions-item
= button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body' } do
- = icon('ellipsis-v', class: 'icon')
+ %span.icon
+ = custom_icon('ellipsis_v')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
- - if note_editable
- %li
- = button_tag 'Edit comment', class: 'js-note-edit btn btn-transparent'
- %li.divider
- unless is_current_user
%li
= link_to new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) do
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 427b059cb82..853e2a6e7ec 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -2,8 +2,9 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
- if show_new_repo?
- = icon('long-arrow-right', title: 'to target branch')
- = render 'shared/target_switcher', destination: 'tree', path: @path
+ .tree-ref-target-holder.js-tree-ref-target-holder
+ = icon('long-arrow-right', title: 'to target branch')
+ = render 'shared/target_switcher', destination: 'tree', path: @path
- unless show_new_repo?
= render 'projects/tree/old_tree_header'
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 4498c8f8349..7ad743b3b81 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -6,7 +6,7 @@
- @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_project_path(@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, visit: 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/_target_switcher.html.haml b/app/views/shared/_target_switcher.html.haml
index 3672b552f10..9236868652f 100644
--- a/app/views/shared/_target_switcher.html.haml
+++ b/app/views/shared/_target_switcher.html.haml
@@ -1,5 +1,5 @@
- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag nil, method: :get, class: "project-refs-target-form" do
+= form_tag nil, method: :get, style: { display: 'none' }, class: "project-refs-target-form" do
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
diff --git a/app/views/shared/icons/_ellipsis_v.svg b/app/views/shared/icons/_ellipsis_v.svg
new file mode 100644
index 00000000000..9117a9bb9ec
--- /dev/null
+++ b/app/views/shared/icons/_ellipsis_v.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 1600"><path d="M1088 1248v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V736q0-40 28-68t68-28h192q40 0 68 28t28 68zm0-512v192q0 40-28 68t-68 28H800q-40 0-68-28t-28-68V224q0-40 28-68t68-28h192q40 0 68 28t28 68z"/></svg>
diff --git a/app/views/shared/icons/_node_express.svg b/app/views/shared/icons/_express.svg
index f2c94319f19..f2c94319f19 100644
--- a/app/views/shared/icons/_node_express.svg
+++ b/app/views/shared/icons/_express.svg
diff --git a/app/views/shared/icons/_java_spring.svg b/app/views/shared/icons/_spring.svg
index 508349aa456..508349aa456 100644
--- a/app/views/shared/icons/_java_spring.svg
+++ b/app/views/shared/icons/_spring.svg
diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml
index 0fc40cf0801..87fa2007d16 100644
--- a/app/views/shared/repo/_repo.html.haml
+++ b/app/views/shared/repo/_repo.html.haml
@@ -1,2 +1,7 @@
-#repo{ data: { url: content_url, project_name: project.name, refs_url: refs_project_path(project, format: :json), project_url: project_path(project), project_id: project.id, can_commit: (!!can_push_branch?(project, @ref)).to_s } }
- %repo
+#repo{ data: { url: content_url,
+ project_name: project.name,
+ refs_url: refs_project_path(project, format: :json),
+ project_url: project_path(project),
+ project_id: project.id,
+ can_commit: (!!can_push_branch?(project, @ref)).to_s,
+ on_top_of_branch: (!!on_top_of_branch?(project, @ref)).to_s } }
diff --git a/app/views/snippets/notes/_actions.html.haml b/app/views/snippets/notes/_actions.html.haml
index 098a88c48c5..3a50324770d 100644
--- a/app/views/snippets/notes/_actions.html.haml
+++ b/app/views/snippets/notes/_actions.html.haml
@@ -1,10 +1,17 @@
- if current_user
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
- = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
- = icon('spinner spin')
- %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
- %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+ .note-actions-item
+ = link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored} has-tooltip", data: { position: 'right' } do
+ = icon('spinner spin')
+ %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
+ %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
+ %span{ class: 'link-highlight award-control-icon-super-positive' }= custom_icon('emoji_smile')
+
+ - if note_editable
+ .note-actions-item
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
+ %span.link-highlight
+ = custom_icon('icon_pencil')
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml b/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml
new file mode 100644
index 00000000000..278ef2a8acb
--- /dev/null
+++ b/changelogs/unreleased/34049-public-commits-should-not-require-authentication.yml
@@ -0,0 +1,4 @@
+---
+title: Added tests for commits API unauthenticated user and public/private project
+merge_request: 13287
+author: Jacopo Beschi @jacopo-beschi
diff --git a/changelogs/unreleased/34371-pipeline-schedule-vue-files.yml b/changelogs/unreleased/34371-pipeline-schedule-vue-files.yml
new file mode 100644
index 00000000000..7de30d82601
--- /dev/null
+++ b/changelogs/unreleased/34371-pipeline-schedule-vue-files.yml
@@ -0,0 +1,6 @@
+---
+title: Improves performance of vue code by using vue files and moving svg out of data
+ function in pipeline schedule callout
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml b/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml
new file mode 100644
index 00000000000..08171f6bcec
--- /dev/null
+++ b/changelogs/unreleased/34527-make-edit-comment-button-always-available-outside-of-dropdown.yml
@@ -0,0 +1,4 @@
+---
+title: move edit comment button outside of dropdown
+merge_request:
+author:
diff --git a/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml b/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml
new file mode 100644
index 00000000000..ddaaf4a2507
--- /dev/null
+++ b/changelogs/unreleased/34533-speed-up-group-project-authorizations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix timeouts when creating projects in groups with many members
+merge_request: 13508
+author:
+type: fixed
diff --git a/changelogs/unreleased/34643-fix-project-path-slugify.yml b/changelogs/unreleased/34643-fix-project-path-slugify.yml
new file mode 100644
index 00000000000..f7018a1aca5
--- /dev/null
+++ b/changelogs/unreleased/34643-fix-project-path-slugify.yml
@@ -0,0 +1,4 @@
+---
+title: Fix CI_PROJECT_PATH_SLUG slugify
+merge_request: 13350
+author: Ivan Chernov
diff --git a/changelogs/unreleased/36385-pipeline-graph-dropdown.yml b/changelogs/unreleased/36385-pipeline-graph-dropdown.yml
new file mode 100644
index 00000000000..1a43c66debd
--- /dev/null
+++ b/changelogs/unreleased/36385-pipeline-graph-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Prevents jobs dropdown from closing in pipeline graph
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-btn-alignment.yml b/changelogs/unreleased/fix-btn-alignment.yml
new file mode 100644
index 00000000000..e5dce3d3a0e
--- /dev/null
+++ b/changelogs/unreleased/fix-btn-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Fix inconsistent spacing for edit buttons on issues and merge request page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-edit-merge-request-button-case.yml b/changelogs/unreleased/fix-edit-merge-request-button-case.yml
new file mode 100644
index 00000000000..8550f3e3c1b
--- /dev/null
+++ b/changelogs/unreleased/fix-edit-merge-request-button-case.yml
@@ -0,0 +1,5 @@
+---
+title: Fix edit merge request and issues button inconsistent letter casing
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/forks-count-cache.yml b/changelogs/unreleased/forks-count-cache.yml
new file mode 100644
index 00000000000..da8c53c2abd
--- /dev/null
+++ b/changelogs/unreleased/forks-count-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Cache the number of forks of a project
+merge_request: 13535
+author:
+type: other
diff --git a/changelogs/unreleased/issue_31790.yml b/changelogs/unreleased/issue_31790.yml
new file mode 100644
index 00000000000..df02cad423a
--- /dev/null
+++ b/changelogs/unreleased/issue_31790.yml
@@ -0,0 +1,4 @@
+---
+title: Fix API responses when dealing with txt files
+merge_request:
+author:
diff --git a/changelogs/unreleased/seven-days-cycle-analytics.yml b/changelogs/unreleased/seven-days-cycle-analytics.yml
new file mode 100644
index 00000000000..ff660bdd603
--- /dev/null
+++ b/changelogs/unreleased/seven-days-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Add a `Last 7 days` option for Cycle Analytics view
+merge_request: 13443
+author: Mehdi Lahmam (@mehlah)
+type: added
diff --git a/config/initializers/0_acts_as_taggable.rb b/config/initializers/0_acts_as_taggable.rb
index 54e9fcc31db..50dc47673ab 100644
--- a/config/initializers/0_acts_as_taggable.rb
+++ b/config/initializers/0_acts_as_taggable.rb
@@ -5,5 +5,5 @@ ActsAsTaggableOn.strict_case_match = true
ActsAsTaggableOn.tags_counter = false
# validate that counter cache is disabled
-raise "Counter cache is not disabled" if
+raise "Counter cache is not disabled" if
ActsAsTaggableOn::Tagging.reflections["tag"].options[:counter_cache]
diff --git a/config/initializers/active_record_mysql_timestamp.rb b/config/initializers/active_record_mysql_timestamp.rb
new file mode 100644
index 00000000000..af74c4ff6fb
--- /dev/null
+++ b/config/initializers/active_record_mysql_timestamp.rb
@@ -0,0 +1,30 @@
+# Make sure that MySQL won't try to use CURRENT_TIMESTAMP when the timestamp
+# column is NOT NULL. See https://gitlab.com/gitlab-org/gitlab-ce/issues/36405
+# And also: https://bugs.mysql.com/bug.php?id=75098
+# This patch was based on:
+# https://github.com/rails/rails/blob/15ef55efb591e5379486ccf53dd3e13f416564f6/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb#L34-L36
+
+if Gitlab::Database.mysql?
+ require 'active_record/connection_adapters/abstract/schema_creation'
+
+ module MySQLTimestampFix
+ def add_column_options!(sql, options)
+ # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
+ # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
+ # column to contain NULL, explicitly declare it with the NULL attribute.
+ # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
+ if sql.end_with?('timestamp') && !options[:primary_key]
+ if options[:null] != false
+ sql << ' NULL'
+ elsif options[:column].default.nil?
+ sql << ' DEFAULT 0'
+ end
+ end
+
+ super
+ end
+ end
+
+ ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
+ .prepend(MySQLTimestampFix)
+end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index 9ed96ddb0b4..943e01f1496 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,15 +1,15 @@
app = Rails.application
if app.config.serve_static_files
- # The `ActionDispatch::Static` middleware intercepts requests for static files
- # by checking if they exist in the `/public` directory.
+ # The `ActionDispatch::Static` middleware intercepts requests for static files
+ # by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same,
# except ignoring `/uploads`, letting those go through to the GitLab Rails app.
app.config.middleware.swap(
- ActionDispatch::Static,
- Gitlab::Middleware::Static,
- app.paths["public"].first,
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
app.config.static_cache_control
)
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index fc4f02453d7..0c32528311e 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -2,7 +2,7 @@
# as the ActionDispatch::Request object. This is necessary for libraries
# like rack_attack where they don't use ActionDispatch, and we want them
# to block/throttle requests on private networks.
-# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145
+# Rack Attack specific issue: https://github.com/kickstarter/rack-attack/issues/145
module Rack
class Request
def trusted_proxy?(ip)
diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml
index 5eb01d62924..0642a0b2fe9 100644
--- a/config/prometheus/additional_metrics.yml
+++ b/config/prometheus/additional_metrics.yml
@@ -1,3 +1,33 @@
+- group: Response metrics (NGINX Ingress)
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - nginx_upstream_requests_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[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{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
+ label: Average
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "HTTP 500 Errors / Sec"
+ required_metrics:
+ - nginx_upstream_responses_total
+ weight: 1
+ queries:
+ - query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m]))'
+ label: HTTP Errors
+ unit: "errors / sec"
- group: Response metrics (HA Proxy)
priority: 10
metrics:
@@ -68,18 +98,18 @@
- nginx_upstream_response_msecs_avg
weight: 1
queries:
- - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) * 1000'
+ - query_range: 'avg(nginx_upstream_response_msecs_avg{%{environment_filter}})'
label: Upstream
unit: ms
- title: "HTTP Error Rate"
- y_label: "Error Rate (%)"
+ y_label: "HTTP 500 Errors / Sec"
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]))'
+ - query_range: 'sum(rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m]))'
label: HTTP Errors
- unit: "%"
+ unit: "errors / sec"
- group: System metrics (Kubernetes)
priority: 5
metrics:
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 57b7c55423d..9ffdebbcff1 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -3,7 +3,7 @@
resource :repository, only: [:create] do
member do
get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
-
+
# deprecated since GitLab 9.5
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 8e1b80cd39f..6a347c2e660 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -223,6 +223,9 @@ var config = {
names: ['main', 'locale', 'common', 'webpack_runtime'],
}),
+ // enable scope hoisting
+ new webpack.optimize.ModuleConcatenationPlugin(),
+
// copy pre-compiled vendor libraries verbatim
new CopyWebpackPlugin([
{
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index c304e0706dc..30244ee4431 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -28,6 +28,8 @@ Gitlab::Seeder.quiet do
project = Project.find_by_full_path('gitlab-org/gitlab-test')
+ next if project.empty_repo? # We don't have repository on CI
+
params = {
source_branch: 'feature',
target_branch: 'master',
diff --git a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
index ecdd1bd7e5e..f64dfa7675f 100644
--- a/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
+++ b/db/migrate/20160713205315_add_domain_blacklist_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddDomainBlacklistToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20160817133006_add_koding_to_application_settings.rb b/db/migrate/20160817133006_add_koding_to_application_settings.rb
index 915d3d78e40..46120652d8e 100644
--- a/db/migrate/20160817133006_add_koding_to_application_settings.rb
+++ b/db/migrate/20160817133006_add_koding_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Migration/SaferBooleanColumn
class AddKodingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161020075830_default_request_access_projects.rb b/db/migrate/20161020075830_default_request_access_projects.rb
index cb790291b24..a3a53350e8d 100644
--- a/db/migrate/20161020075830_default_request_access_projects.rb
+++ b/db/migrate/20161020075830_default_request_access_projects.rb
@@ -1,7 +1,7 @@
class DefaultRequestAccessProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
-
+
def up
change_column_default :projects, :request_access_enabled, false
end
diff --git a/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
index e644a174964..522437b92b4 100644
--- a/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
+++ b/db/migrate/20161103191444_add_sidekiq_throttling_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddSidekiqThrottlingToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
index 1c59241d0fe..38f5781745b 100644
--- a/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
+++ b/db/migrate/20161128161412_add_html_emails_enabled_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddHtmlEmailsEnabledToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb b/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
index 3677f978cc2..7f56ecf4c9e 100644
--- a/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
+++ b/db/migrate/20161206003819_add_plant_uml_enabled_to_application_settings.rb
@@ -1,6 +1,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
+# rubocop:disable Migration/SaferBooleanColumn
class AddPlantUmlEnabledToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
index 5e8b667b86d..d358020d182 100644
--- a/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
+++ b/db/migrate/20170602154736_add_help_page_hide_commercial_content_to_application_settings.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Migration/SaferBooleanColumn
class AddHelpPageHideCommercialContentToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
diff --git a/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb b/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb
index 13e8ef52f22..5551fb51a6e 100644
--- a/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb
+++ b/db/migrate/20170809134534_add_broadcast_message_not_null_constraints.rb
@@ -9,9 +9,21 @@ class AddBroadcastMessageNotNullConstraints < ActiveRecord::Migration
COLUMNS = %i[starts_at ends_at created_at updated_at message_html]
- def change
+ class BroadcastMessage < ActiveRecord::Base
+ self.table_name = 'broadcast_messages'
+ end
+
+ def up
COLUMNS.each do |column|
+ BroadcastMessage.where(column => nil).delete_all
+
change_column_null :broadcast_messages, column, false
end
end
+
+ def down
+ COLUMNS.each do |column|
+ change_column_null :broadcast_messages, column, true
+ end
+ end
end
diff --git a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
index 705e11ed47d..3a4d6c4916b 100644
--- a/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
+++ b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
@@ -21,7 +21,7 @@ class UpdateRetriedForCiBuild < ActiveRecord::Migration
private
def up_mysql
- # This is a trick to overcome MySQL limitation:
+ # This is a trick to overcome MySQL limitation:
# Mysql2::Error: Table 'ci_builds' is specified twice, both as a target for 'UPDATE' and as a separate source for data
# However, this leads to create a temporary table from `max(ci_builds.id)` which is slow and do full database update
execute <<-SQL.strip_heredoc
diff --git a/db/post_migrate/20170523083112_migrate_old_artifacts.rb b/db/post_migrate/20170523083112_migrate_old_artifacts.rb
index f2690bd0017..3a77b9751d3 100644
--- a/db/post_migrate/20170523083112_migrate_old_artifacts.rb
+++ b/db/post_migrate/20170523083112_migrate_old_artifacts.rb
@@ -7,7 +7,7 @@ class MigrateOldArtifacts < ActiveRecord::Migration
# This uses special heuristic to find potential candidates for data migration
# Read more about this here: https://gitlab.com/gitlab-org/gitlab-ce/issues/32036#note_30422345
-
+
def up
builds_with_artifacts.find_each do |build|
build.migrate_artifacts!
@@ -51,14 +51,14 @@ class MigrateOldArtifacts < ActiveRecord::Migration
private
def source_artifacts_path
- @source_artifacts_path ||=
+ @source_artifacts_path ||=
File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'),
ci_id.to_s, id.to_s)
end
def target_artifacts_path
- @target_artifacts_path ||=
+ @target_artifacts_path ||=
File.join(Gitlab.config.artifacts.path,
created_at.utc.strftime('%Y_%m'),
project_id.to_s, id.to_s)
diff --git a/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb b/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb
new file mode 100644
index 00000000000..6132b553177
--- /dev/null
+++ b/db/post_migrate/20170815060945_remove_duplicate_mr_events.rb
@@ -0,0 +1,26 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDuplicateMrEvents < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+
+ class Event < ActiveRecord::Base
+ self.table_name = 'events'
+ end
+
+ def up
+ base_condition = "action = 1 AND target_type = 'MergeRequest' AND created_at > '2017-08-13'"
+ Event.select('target_id, count(*)')
+ .where(base_condition)
+ .group('target_id').having('count(*) > 1').each do |event|
+ duplicates = Event.where("#{base_condition} AND target_id = #{event.target_id}").pluck(:id)
+ duplicates.shift
+
+ Event.where(id: duplicates).delete_all
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3206e106552..2ea6ae29dc7 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: 20170809161910) do
+ActiveRecord::Schema.define(version: 20170815060945) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/README.md b/doc/README.md
index 4175750d497..547541c4876 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -32,6 +32,7 @@ Shortcuts to GitLab's most visited docs:
- [User documentation](user/index.md)
- [Administrator documentation](#administrator-documentation)
+- [Technical Articles](articles/index.md)
## Getting started with GitLab
diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md
new file mode 100644
index 00000000000..c64851bad2b
--- /dev/null
+++ b/doc/articles/artifactory_and_gitlab/index.md
@@ -0,0 +1,278 @@
+# How to deploy Maven projects to Artifactory with GitLab CI/CD
+
+> **Article [Type](../../development/writing_documentation.md#types-of-technical-articles):** tutorial ||
+> **Level:** intermediary ||
+> **Author:** [Fabio Busatto](https://gitlab.com/bikebilly) ||
+> **Publication date:** 2017-08-15
+
+## Introduction
+
+In this article, we will show how you can leverage the power of [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/)
+to build a [Maven](https://maven.apache.org/) project, deploy it to [Artifactory](https://www.jfrog.com/artifactory/), and then use it from another Maven application as a dependency.
+
+You'll create two different projects:
+
+- `simple-maven-dep`: the app built and deployed to Artifactory (available at https://gitlab.com/gitlab-examples/maven/simple-maven-dep)
+- `simple-maven-app`: the app using the previous one as a dependency (available at https://gitlab.com/gitlab-examples/maven/simple-maven-app)
+
+We assume that you already have a GitLab account on [GitLab.com](https://gitlab.com/), and that you know the basic usage of Git and [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/).
+We also assume that an Artifactory instance is available and reachable from the internet, and that you have valid credentials to deploy on it.
+
+## Create the simple Maven dependency
+
+First of all, you need an application to work with: in this specific case we will
+use a simple one, but it could be any Maven application. This will be the
+dependency you want to package and deploy to Artifactory, in order to be
+available to other projects.
+
+### Prepare the dependency application
+
+For this article you'll use a Maven app that can be cloned from our example
+project:
+
+1. Log in to your GitLab account
+1. Create a new project by selecting **Import project from ➔ Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
+ ```
+1. Click **Create project**
+
+This application is nothing more than a basic class with a stub for a JUnit based test suite.
+It exposes a method called `hello` that accepts a string as input, and prints a hello message on the screen.
+
+The project structure is really simple, and you should consider these two resources:
+
+- `pom.xml`: project object model (POM) configuration file
+- `src/main/java/com/example/dep/Dep.java`: source of our application
+
+### Configure the Artifactory deployment
+
+The application is ready to use, but you need some additional steps to deploy it to Artifactory:
+
+1. Log in to Artifactory with your user's credentials.
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel.
+1. Copy to clipboard the configuration snippet under the **Deploy** paragraph.
+1. Change the `url` value in order to have it configurable via secret variables.
+1. Copy the snippet in the `pom.xml` file for your project, just after the
+ `dependencies` section. The snippet should look like this:
+
+ ```xml
+ <distributionManagement>
+ <repository>
+ <id>central</id>
+ <name>83d43b5afeb5-releases</name>
+ <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
+ </repository>
+ </distributionManagement>
+ ```
+
+Another step you need to do before you can deploy the dependency to Artifactory
+is to configure the authentication data. It is a simple task, but Maven requires
+it to stay in a file called `settings.xml` that has to be in the `.m2` subdirectory
+in the user's homedir.
+
+Since you want to use GitLab Runner to automatically deploy the application, you
+should create the file in the project's home directory and set a command line
+parameter in `.gitlab-ci.yml` to use the custom location instead of the default one:
+
+1. Create a folder called `.m2` in the root of your repository
+1. Create a file called `settings.xml` in the `.m2` folder
+1. Copy the following content into a `settings.xml` file:
+
+ ```xml
+ <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
+ xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <servers>
+ <server>
+ <id>central</id>
+ <username>${env.MAVEN_REPO_USER}</username>
+ <password>${env.MAVEN_REPO_PASS}</password>
+ </server>
+ </servers>
+ </settings>
+ ```
+
+ Username and password will be replaced by the correct values using secret variables.
+
+### Configure GitLab CI/CD for `simple-maven-dep`
+
+Now it's time we set up [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and deploy the dependency!
+
+GitLab CI/CD uses a file in the root of the repo, named `.gitlab-ci.yml`, to read the definitions for jobs
+that will be executed by the configured GitLab Runners. You can read more about this file in the [GitLab Documentation](https://docs.gitlab.com/ee/ci/yaml/).
+
+First of all, remember to set up secret variables for your deployment. Navigate to your project's **Settings > CI/CD** page
+and add the following secret variables (replace them with your current values, of course):
+
+- **MAVEN_REPO_URL**: `http://artifactory.example.com:8081/artifactory` (your Artifactory URL)
+- **MAVEN_REPO_USER**: `gitlab` (your Artifactory username)
+- **MAVEN_REPO_PASS**: `AKCp2WXr3G61Xjz1PLmYa3arm3yfBozPxSta4taP3SeNu2HPXYa7FhNYosnndFNNgoEds8BCS` (your Artifactory Encrypted Password)
+
+Now it's time to define jobs in `.gitlab-ci.yml` and push it to the repo:
+
+```yaml
+image: maven:latest
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+deploy:
+ stage: deploy
+ script:
+ - mvn $MAVEN_CLI_OPTS deploy
+ only:
+ - master
+```
+
+GitLab Runner will use the latest [Maven Docker image](https://hub.docker.com/_/maven/), which already contains all the tools and the dependencies you need to manage the project,
+in order to run the jobs.
+
+Environment variables are set to instruct Maven to use the `homedir` of the repo instead of the user's home when searching for configuration and dependencies.
+
+Caching the `.m2/repository folder` (where all the Maven files are stored), and the `target` folder (where our application will be created), is useful for speeding up the process
+by running all Maven phases in a sequential order, therefore, executing `mvn test` will automatically run `mvn compile` if necessary.
+
+Both `build` and `test` jobs leverage the `mvn` command to compile the application and to test it as defined in the test suite that is part of the application.
+
+Deploy to Artifactory is done as defined by the secret variables we have just set up.
+The deployment occurs only if we're pushing or merging to `master` branch, so that the development versions are tested but not published.
+
+Done! Now you have all the changes in the GitLab repo, and a pipeline has already been started for this commit. In the **Pipelines** tab you can see what's happening.
+If the deployment has been successful, the deploy job log will output:
+
+```
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 1.983 s
+```
+
+>**Note**:
+the `mvn` command downloads a lot of files from the internet, so you'll see a lot of extra activity in the log the first time you run it.
+
+Yay! You did it! Checking in Artifactory will confirm that you have a new artifact available in the `libs-release-local` repo.
+
+## Create the main Maven application
+
+Now that you have the dependency available on Artifactory, it's time to use it!
+Let's see how we can have it as a dependency to our main application.
+
+### Prepare the main application
+
+We'll use again a Maven app that can be cloned from our example project:
+
+1. Create a new project by selecting **Import project from ➔ Repo by URL**
+1. Add the following URL:
+
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
+ ```
+1. Click **Create project**
+
+This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
+file you can see that it imports the `com.example.dep.Dep` class and calls the `hello` method passing `GitLab` as a parameter.
+
+Since Maven doesn't know how to resolve the dependency, you need to modify the configuration:
+
+1. Go back to Artifactory
+1. Browse the `libs-release-local` repository
+1. Select the `simple-maven-dep-1.0.jar` file
+1. Find the configuration snippet from the **Dependency Declaration** section of the main panel
+1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
+ The snippet should look like this:
+
+ ```xml
+ <dependency>
+ <groupId>com.example.dep</groupId>
+ <artifactId>simple-maven-dep</artifactId>
+ <version>1.0</version>
+ </dependency>
+ ```
+
+### Configure the Artifactory repository location
+
+At this point you defined the dependency for the application, but you still miss where you can find the required files.
+You need to create a `.m2/settings.xml` file as you did for the dependency project, and let Maven know the location using environment variables.
+
+Here is how you can get the content of the file directly from Artifactory:
+
+1. From the main screen, click on the `libs-release-local` item in the **Set Me Up** panel
+1. Click on **Generate Maven Settings**
+1. Click on **Generate Settings**
+1. Copy to clipboard the configuration file
+1. Save the file as `.m2/settings.xml` in your repo
+
+Now you are ready to use the Artifactory repository to resolve dependencies and use `simple-maven-dep` in your main application!
+
+### Configure GitLab CI/CD for `simple-maven-app`
+
+You need a last step to have everything in place: configure the `.gitlab-ci.yml` file for this project, as you already did for `simple-maven-dep`.
+
+You want to leverage [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/) to automatically build, test and run your awesome application,
+and see if you can get the greeting as expected!
+
+All you need to do is to add the following `.gitlab-ci.yml` to the repo:
+
+```yaml
+image: maven:latest
+
+stages:
+ - build
+ - test
+ - run
+
+variables:
+ MAVEN_CLI_OPTS: "-s .m2/settings.xml --batch-mode"
+ MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
+
+cache:
+ paths:
+ - .m2/repository/
+ - target/
+
+build:
+ stage: build
+ script:
+ - mvn $MAVEN_CLI_OPTS compile
+
+test:
+ stage: test
+ script:
+ - mvn $MAVEN_CLI_OPTS test
+
+run:
+ stage: run
+ script:
+ - mvn $MAVEN_CLI_OPTS package
+ - mvn $MAVEN_CLI_OPTS exec:java -Dexec.mainClass="com.example.app.App"
+```
+
+It is very similar to the configuration used for `simple-maven-dep`, but instead of the `deploy` job there is a `run` job.
+Probably something that you don't want to use in real projects, but here it is useful to see the application executed automatically.
+
+And that's it! In the `run` job output log you will find a friendly hello to GitLab!
+
+## Conclusion
+
+In this article we covered the basic steps to use an Artifactory Maven repository to automatically publish and consume artifacts.
+
+A similar approach could be used to interact with any other Maven compatible Binary Repository Manager.
+Obviously, you can improve these examples, optimizing the `.gitlab-ci.yml` file to better suit your needs, and adapting to your workflow.
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 130e8f542b4..25a24bc1d32 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** admin guide ||
> **Level:** intermediary ||
> **Author:** [Chris Wilson](https://gitlab.com/MrChrisW) ||
-> **Publication date:** 2017/05/03
+> **Publication date:** 2017-05-03
## Introduction
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
index 66d866b2d09..37b60501ce2 100644
--- a/doc/articles/how_to_install_git/index.md
+++ b/doc/articles/how_to_install_git/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** user guide ||
> **Level:** beginner ||
> **Author:** [Sean Packham](https://gitlab.com/SeanPackham) ||
-> **Publication date:** 2017/05/15
+> **Publication date:** 2017-05-15
To begin contributing to GitLab projects
you will need to install the Git client on your computer.
diff --git a/doc/articles/index.md b/doc/articles/index.md
index 558c624fe39..3039faca411 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -26,6 +26,7 @@ Build, test, and deploy the software you develop with [GitLab CI/CD](../ci/READM
| Article title | Category | Publishing date |
| :------------ | :------: | --------------: |
+| [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md) | Tutorial | 2017-08-15 |
| [Making CI Easier with GitLab](https://about.gitlab.com/2017/07/13/making-ci-easier-with-gitlab/) | Concepts | 2017/07/13 |
| [Dockerizing GitLab Review Apps](https://about.gitlab.com/2017/07/11/dockerizing-review-apps/) | Concepts | 2017/07/11 |
| [Continuous Integration: From Jenkins to GitLab Using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/) | Concepts | 2017/07/27 |
diff --git a/doc/articles/openshift_and_gitlab/index.md b/doc/articles/openshift_and_gitlab/index.md
index 7f76e577efa..c0bbcfe2a8a 100644
--- a/doc/articles/openshift_and_gitlab/index.md
+++ b/doc/articles/openshift_and_gitlab/index.md
@@ -3,7 +3,7 @@
> **Article [Type](../../development/writing_documentation.html#types-of-technical-articles):** tutorial ||
> **Level:** intermediary ||
> **Author:** [Achilleas Pipinellis](https://gitlab.com/axil) ||
-> **Publication date:** 2016/06/28
+> **Publication date:** 2016-06-28
## Introduction
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 10ea9467942..c722d895f42 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -111,7 +111,8 @@ Here is an collection of tutorials and guides on setting up your CI pipeline.
- [Phoenix](examples/test-phoenix-application.md)
- [Run PHP Composer & NPM scripts then deploy them to a staging server](examples/deployment/composer-npm-deploy.md)
- [Analyze code quality with the Code Climate CLI](examples/code_climate.md)
-- **Blog posts**
+- **Articles**
+ - [How to deploy Maven projects to Artifactory with GitLab CI/CD](../articles/artifactory_and_gitlab/index.md)
- [Automated Debian packaging](https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/)
- [Spring boot application with GitLab CI and Kubernetes](https://about.gitlab.com/2016/12/14/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/)
- [Setting up GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/)
diff --git a/doc/ci/autodeploy/img/auto_monitoring.png b/doc/ci/autodeploy/img/auto_monitoring.png
new file mode 100644
index 00000000000..5661b50841b
--- /dev/null
+++ b/doc/ci/autodeploy/img/auto_monitoring.png
Binary files differ
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 9fa2b2c4969..a714689ebd5 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -69,3 +69,28 @@ PostgreSQL provisioning can be disabled by setting the variable `DISABLE_POSTGRE
[review-app]: ../review_apps/index.md
[container-registry]: https://docs.gitlab.com/ce/user/project/container_registry.html
[postgresql]: https://www.postgresql.org/
+
+## Auto Monitoring
+
+> Introduced in [GitLab 9.5](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438).
+
+Apps auto-deployed using one the [Kubernetes templates](#supported-templates) can also be automatically monitored for:
+
+* Response Metrics: latency, throughput, error rate
+* System Metrics: CPU utilization, memory utilization
+
+Metrics are gathered from [nginx-ingress](../../user/project/integrations/prometheus_library/nginx_ingress.md) and [Kubernetes](../../user/project/integrations/prometheus_library/kubernetes.md).
+
+To view the metrics, open the [Monitoring dashboard for a deployed environment](../environments.md#monitoring-environments).
+
+![Auto Metrics](img/auto_monitoring.png)
+
+### Configuring Auto Monitoring
+
+If GitLab has been deployed using the [omnibus-gitlab](../../install/kubernetes/gitlab_omnibus.md) Helm chart, no configuration is required.
+
+If you have installed GitLab using a different method:
+
+1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster
+1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml).
+1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the NGINX Ingress deployment to be scraped by Prometheus using `prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 6a7f694d705..28b27921f8b 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -607,10 +607,9 @@ exist, you should see something like:
- With GitLab 9.2, all deployments to an environment are shown directly on the
monitoring dashboard
-If you have enabled Prometheus for collecting metrics, you can monitor the performance behavior of your app
-through the environments.
+If you have enabled [Prometheus for monitoring system and response metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html), you can monitor the performance behavior of your app running in each environment.
-Once configured, GitLab will attempt to retrieve performance metrics for any
+Once configured, GitLab will attempt to retrieve [supported performance metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus_library/metrics.html) for any
environment which has had a successful deployment. If monitoring data was
successfully retrieved, a Monitoring button will appear on the environment's
detail page.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 6ade3231fac..9c72fda0229 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -511,7 +511,24 @@ A forEach will cause side effects, it will be mutating the array being iterated.
$('span').tooltip('fixTitle');
```
+### The Javascript/Vue Accord
+The goal of this accord is to make sure we are all on the same page.
+1. When writing Vue, you may not use jQuery in your application.
+1.1 If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery.
+1.2 You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html).
+1.3 If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners.
+1.4 We will avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
+
+1. You may query the `window` object 1 time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application.
+
+1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with it's priority to be determined by maintainers.
+
+1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests.
+
+1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you must use the *store pattern* which can be found in the [Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch).
+
+1. Once you have chosen a centralized state management solution you must use it for your entire application. i.e. Don't mix and match your state management solutions.
## SCSS
- [SCSS](style_guide_scss.md)
diff --git a/doc/development/testing.md b/doc/development/testing.md
index c7eac3cf40c..efd56484b12 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -279,6 +279,43 @@ end
- Avoid scenario titles that add no information, such as "successfully".
- Avoid scenario titles that repeat the feature title.
+### Table-based / Parameterized tests
+
+This style of testing is used to exercise one piece of code with a comprehensive
+range of inputs. By specifying the test case once, alongside a table of inputs
+and the expected output for each, your tests can be made easier to read and more
+compact.
+
+We use the [rspec-parameterized](https://github.com/tomykaira/rspec-parameterized)
+gem. A short example, using the table syntax and checking Ruby equality for a
+range of inputs, might look like this:
+
+```ruby
+describe "#==" do
+ using Rspec::Parameterized::TableSyntax
+
+ let(:project1) { create(:project) }
+ let(:project2) { create(:project) }
+ where(:a, :b, :result) do
+ 1 | 1 | true
+ 1 | 2 | false
+ true | true | true
+ true | false | false
+ project1 | project1 | true
+ project2 | project2 | true
+ project 1 | project2 | false
+ end
+
+ with_them do
+ it { expect(a == b).to eq(result) }
+
+ it 'is isomorphic' do
+ expect(b == a).to eq(result)
+ end
+ end
+end
+```
+
### Matchers
Custom matchers should be created to clarify the intent and/or hide the
diff --git a/doc/install/installation.md b/doc/install/installation.md
index b14cb2d44c4..66eb7675896 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
- # Make sure Git is version 2.8.4 or higher
+ # Make sure Git is version 2.13.0 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index b7e86ea7c81..bd3a85272d0 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -126,7 +126,7 @@ Let's Encrypt limits a single TLD to five certificate requests within a single w
## Installing GitLab using the Helm Chart
> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
-Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab), you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
+Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab) and [added the Helm repository](index.md#add-the-gitlab-helm-repository), you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
For example:
```bash
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 08da721c71d..ceec8b74373 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -153,6 +153,14 @@ Find this option under your project's settings.
GitLab administrators can use the admin interface to move any project to any namespace if needed.
+## Sharing a project with a group
+
+You can [share your projects with a group](../project/members/share_project_with_groups.md)
+and give your group members access to the project all at once.
+
+Alternatively, with [GitLab Enterprise Edition Starter](https://about.gitlab.com/gitlab-ee/),
+you can [lock the sharing with group feature](#share-with-group-lock-ees-eep).
+
## Manage group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
@@ -189,7 +197,7 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
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).
+a project with another group](../project/members/share_project_with_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).
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 3d47e644ad2..dcf210e1085 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -5,17 +5,17 @@ particular group or project. If a user is both in a group's project and the
project itself, the highest permission level is used.
On public and internal projects the Guest role is not enforced. All users will
-be able to create issues, leave comments, and pull or download the project code.
+be able to create issues, leave comments, and clone or download the project code.
-When a member leaves the team the all assigned Issues and Merge Requests
+When a member leaves the team all the assigned [Issues](project/issues/index.md) and [Merge Requests](project/merge_requests/index.md)
will be unassigned automatically.
-GitLab administrators receive all permissions.
+GitLab [administrators](../README.md#administrator-documentation) receive all permissions.
-To add or import a user, you can follow the [project users and members
-documentation](../workflow/add-user/add-user.md).
+To add or import a user, you can follow the
+[project members documentation](../user/project/members/index.md).
-## Project
+## Project members permissions
The following table depicts the various user permission levels in a project.
@@ -75,7 +75,58 @@ The following table depicts the various user permission levels in a project.
| Remove protected branches [^3] | | | | | |
| Remove pages | | | | | ✓ |
-## Group
+## Project features permissions
+
+### Wiki and issues
+
+Project features like wiki and issues can be hidden from users depending on
+which visibility level you select on project settings.
+
+- Disabled: disabled for everyone
+- Only team members: only team members will see even if your project is public or internal
+- Everyone with access: everyone can see depending on your project visibility level
+
+### Protected branches
+
+To prevent people from messing with history or pushing code without
+review, we've created protected branches. Read through the documentation on
+[protected branches](project/protected_branches.md)
+to learn more.
+
+Additionally, you can allow or forbid users with Master and/or
+Developer permissions to push to a protected branch. Read through the documentation on
+[Allowed to Merge and Allowed to Push settings](project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
+to learn more.
+
+### Cycle Analytics permissions
+
+Find the current permissions on the Cycle Analytics dashboard on
+the [documentation on Cycle Analytics permissions](project/cycle_analytics.md#permissions).
+
+### Issue Board permissions
+
+Developers and users with higher permission level can use all
+the functionality of the Issue Board, that is create/delete lists
+and drag issues around. Read though the
+[documentation on Issue Boards permissions](project/issue_board.md#permissions)
+to learn more.
+
+### File Locking permissions (EEP)
+
+The user that locks a file or directory is the only one that can edit and push their changes back to the repository where the locked objects are located.
+
+Read through the documentation on [permissions for File Locking](https://docs.gitlab.com/ee/user/project/file_lock.html#permissions-on-file-locking) to learn more.
+
+File Locking is available in
+[GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/) only.
+
+### Confidential Issues permissions
+
+Confidential issues can be accessed by reporters and higher permission levels,
+as well as by guest users that create a confidential issue. To learn more,
+read through the documentation on [permissions and access to confidential issues](project/issues/confidential_issues.md#permissions-and-access-to-confidential-issues).
+
+## Group members permissions
Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
@@ -91,7 +142,16 @@ group.
| Remove group | | | | | ✓ |
| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-## External Users
+### Subgroup permissions
+
+When you add a member to a subgroup, they inherit the membership and
+permission level from the parent group. This model allows access to
+nested groups if you have membership in one of its parents.
+
+To learn more, read through the documentation on
+[subgroups memberships](group/subgroups/index.md#membership).
+
+## External users permissions
In cases where it is desired that a user has access only to some internal or
private projects, there is the option of creating **External Users**. This
@@ -115,18 +175,9 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
-## Project features
-
-Project features like wiki and issues can be hidden from users depending on
-which visibility level you select on project settings.
-
-- Disabled: disabled for everyone
-- Only team members: only team members will see even if your project is public or internal
-- Everyone with access: everyone can see depending on your project visibility level
-
-## GitLab CI
+## GitLab CI/CD permissions
-GitLab CI permissions rely on the role the user has in GitLab. There are four
+GitLab CI/CD permissions rely on the role the user has in GitLab. There are four
permission levels in total:
- admin
@@ -134,7 +185,7 @@ permission levels in total:
- developer
- guest/reporter
-The admin user can perform any action on GitLab CI in scope of the GitLab
+The admin user can perform any action on GitLab CI/CD in scope of the GitLab
instance and project. In addition, all admins can use the admin interface under
`/admin/runners`.
@@ -150,7 +201,7 @@ instance and project. In addition, all admins can use the admin interface under
| See events in the system | | | | ✓ |
| Admin interface | | | | ✓ |
-### Jobs permissions
+### Job permissions
>**Note:**
GitLab 8.12 has a completely redesigned job permissions system.
@@ -174,6 +225,26 @@ users:
| Push container images to current project | | ✓ | ✓ | ✓ |
| Push container images to other projects | | | | |
+### New CI job permissions model
+
+GitLab 8.12 has a completely redesigned job permissions system. To learn more,
+read through the documentation on the [new CI/CD permissions model](project/new_ci_build_permissions_model.md#new-ci-job-permissions-model).
+
+## LDAP users permissions
+
+Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
+Read through the documentation on [LDAP users permissions](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/index.html#updating-user-permissions-new-feature) to learn more.
+
+## Auditor users permissions (EEP)
+
+An Auditor user should be able to access all projects and groups of a GitLab instance
+with the permissions described on the documentation on [auditor users permissions](https://docs.gitlab.com/ee/administration/auditor_users.html#permissions-and-restrictions-of-an-auditor-user).
+
+Auditor users are available in [GitLab Enterprise Edition Premium](https://about.gitlab.com/gitlab-ee/)
+only.
+
+----
+
[^1]: Guest users can only view the confidential issues they created themselves
[^2]: If **Public pipelines** is enabled in **Project Settings > Pipelines**
[^3]: Not allowed for Guest, Reporter, Developer, Master, or Owner
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 91a19600951..0dd0faf35e9 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -98,7 +98,11 @@ from your fork to the upstream project
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
-## Leave a project
+## Project's members
+
+Learn how to [add members to your projects](members/index.md).
+
+### Leave a project
**Leave project** will only display on the project's dashboard
when a project is part of a group (under a
diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png
index e69376f74c4..63aa0e99a50 100644
--- a/doc/user/project/integrations/img/jira_service_page.png
+++ b/doc/user/project/integrations/img/jira_service_page.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 4f583879a4e..93aec56f8dc 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -10,7 +10,12 @@ JIRA](https://www.programmableweb.com/news/how-and-why-to-integrate-gitlab-jira/
## Configuration
-Each GitLab project can be configured to connect to a different JIRA instance.
+Each GitLab project can be configured to connect to a different JIRA instance. That
+means one GitLab project maps to _all_ JIRA projects in that JIRA instance once
+the configuration is set up. Therefore, you don't have to explicitly associate
+one GitLab project to any JIRA project. Once the configuration is set up, any JIRA
+projects in the JIRA instance are already mapped to the GitLab project.
+
If you have one JIRA instance you can pre-fill the settings page with a default
template, see the [Services Templates][services-templates] docs.
@@ -103,7 +108,6 @@ in the table below.
| ----- | ----------- |
| `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. 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). |
| `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.** |
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 6f15765751c..5fefb3b69c4 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -40,7 +40,7 @@ Installing and configuring Prometheus to monitor applications is fairly straight
### 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 supported metrics. Once enabled, Prometheus will automatically begin monitoring Kubernetes Nodes and any [annotated Pods](https://prometheus.io/docs/operating/configuration/#<kubernetes_sd_config>).
+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].
@@ -133,6 +133,8 @@ to integrate with.
Once configured, GitLab will attempt to retrieve performance metrics for any
environment which has had a successful deployment.
+GitLab will automatically scan the Prometheus server for known metrics and attempt to identify the metrics for a particular environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html).
+
[Learn more about monitoring environments.](../../../ci/environments.md#monitoring-environments)
## Determining the performance impact of a merge
@@ -174,7 +176,7 @@ If the "Attempting to load performance data" screen continues to appear, it coul
[prometheus-docker-image]: https://hub.docker.com/r/prom/prometheus/
[prometheus-yml]:samples/prometheus.yml
[gitlab.com-ip-range]: https://gitlab.com/gitlab-com/infrastructure/issues/434
-[ci-environment-slug]: https://docs.gitlab.com/ce/ci/variables/#predefined-variables-environment-variables
+[ci-environment-slug]: ../../../ci/variables/#predefined-variables-environment-variables
[ce-8935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935
[ce-10408]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10408
[promgldocs]: ../../../administration/monitoring/prometheus/index.md
diff --git a/doc/user/project/integrations/prometheus_library/metrics.md b/doc/user/project/integrations/prometheus_library/metrics.md
index 6bdffce9c55..f09ecf9ff2d 100644
--- a/doc/user/project/integrations/prometheus_library/metrics.md
+++ b/doc/user/project/integrations/prometheus_library/metrics.md
@@ -4,6 +4,7 @@
GitLab offers automatic detection of select [Prometheus exporters](https://prometheus.io/docs/instrumenting/exporters/). Currently supported exporters are:
* [Kubernetes](kubernetes.md)
* [NGINX](nginx.md)
+* [NGINX Ingress Controller](nginx_ingress.md)
* [HAProxy](haproxy.md)
* [Amazon Cloud Watch](cloudwatch.md)
@@ -14,10 +15,7 @@ We have tried to surface the most important metrics for each exporter, and will
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`.
+GitLab uses the defined queries and fills in the environment specific variables. Typically this involves looking for the [$CI_ENVIRONMENT_SLUG](https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables), but may also include other information such as the project's Kubernetes namespace. Each search query is defined in the [exporter specific documentation](#prometheus-metrics-library).
## Adding to the library
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index b3470773996..12e3321f5f3 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -8,8 +8,8 @@ GitLab has support for automatically detecting and monitoring NGINX. This is pro
| 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])) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{%{environment_filter}}) |
+| HTTP Error Rate (HTTP Errors / sec) | rate(nginx_responses_total{status_code="5xx", %{environment_filter}}[2m])) |
## Configuring Prometheus to monitor for NGINX metrics
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
new file mode 100644
index 00000000000..84ee8bc45e5
--- /dev/null
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -0,0 +1,25 @@
+# Monitoring NGINX Ingress Controller
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5
+
+GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress/blob/master/controllers/nginx/Changelog.md#09-beta1) of the ingress.
+
+## Metrics supported
+
+| Name | Query |
+| ---- | ----- |
+| Throughput (req/sec) | sum(rate(nginx_upstream_requests_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
+| Latency (ms) | avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}) |
+| HTTP Error Rate (HTTP Errors / sec) | sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) |
+
+## Configuring Prometheus to monitor for NGINX ingress metrics
+
+The easiest way to get started is to use at least version 0.9.0 of [NGINX ingress](https://github.com/kubernetes/ingress/tree/master/controllers/nginx). If you are using NGINX as your Kubernetes ingress, there is [direct support](https://github.com/kubernetes/ingress/pull/423) for enabling Prometheus monitoring in the 0.9.0 release.
+
+If you have deployed with the [gitlab-omnibus](https://docs.gitlab.com/ee/install/kubernetes/gitlab_omnibus.md) Helm chart, these metrics will be automatically enabled and annotated for Prometheus monitoring.
+
+## 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 search metrics with appropriate labels. In this case, the `upstream` label must be of the form `<Kubernetes Namespace>-<CI_ENVIRONMENT_SLUG>-*`.
+
+If you have used [Auto Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html) to deploy your app, this format will be used automatically and metrics will be detected with no action on your part.
diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/user/project/members/img/access_requests_management.png
index 3693bed869b..3693bed869b 100644
--- a/doc/workflow/add-user/img/access_requests_management.png
+++ b/doc/user/project/members/img/access_requests_management.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_new_user_to_project_settings.png b/doc/user/project/members/img/add_new_user_to_project_settings.png
index 40db600455f..40db600455f 100644
--- a/doc/workflow/add-user/img/add_new_user_to_project_settings.png
+++ b/doc/user/project/members/img/add_new_user_to_project_settings.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/user/project/members/img/add_user_email_accept.png
index 763b3ff463d..763b3ff463d 100644
--- a/doc/workflow/add-user/img/add_user_email_accept.png
+++ b/doc/user/project/members/img/add_user_email_accept.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/user/project/members/img/add_user_email_ready.png
index 0066eb3427b..0066eb3427b 100644
--- a/doc/workflow/add-user/img/add_user_email_ready.png
+++ b/doc/user/project/members/img/add_user_email_ready.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/user/project/members/img/add_user_email_search.png
index 66bcd6aad80..66bcd6aad80 100644
--- a/doc/workflow/add-user/img/add_user_email_search.png
+++ b/doc/user/project/members/img/add_user_email_search.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/user/project/members/img/add_user_give_permissions.png
index 376a3eefccc..376a3eefccc 100644
--- a/doc/workflow/add-user/img/add_user_give_permissions.png
+++ b/doc/user/project/members/img/add_user_give_permissions.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/user/project/members/img/add_user_import_members_from_another_project.png
index 0c32001098e..0c32001098e 100644
--- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
+++ b/doc/user/project/members/img/add_user_import_members_from_another_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/user/project/members/img/add_user_imported_members.png
index 51fd7688890..51fd7688890 100644
--- a/doc/workflow/add-user/img/add_user_imported_members.png
+++ b/doc/user/project/members/img/add_user_imported_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/user/project/members/img/add_user_list_members.png
index e0fa404288d..e0fa404288d 100644
--- a/doc/workflow/add-user/img/add_user_list_members.png
+++ b/doc/user/project/members/img/add_user_list_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/user/project/members/img/add_user_members_menu.png
index 8e61d15fe65..8e61d15fe65 100644
--- a/doc/workflow/add-user/img/add_user_members_menu.png
+++ b/doc/user/project/members/img/add_user_members_menu.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/user/project/members/img/add_user_search_people.png
index 41767a9167c..41767a9167c 100644
--- a/doc/workflow/add-user/img/add_user_search_people.png
+++ b/doc/user/project/members/img/add_user_search_people.png
Binary files differ
diff --git a/doc/workflow/groups/max_access_level.png b/doc/user/project/members/img/max_access_level.png
index 63f33f9d91d..63f33f9d91d 100644
--- a/doc/workflow/groups/max_access_level.png
+++ b/doc/user/project/members/img/max_access_level.png
Binary files differ
diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/user/project/members/img/other_group_sees_shared_project.png
index 67af27043eb..67af27043eb 100644
--- a/doc/workflow/groups/other_group_sees_shared_project.png
+++ b/doc/user/project/members/img/other_group_sees_shared_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/user/project/members/img/request_access_button.png
index 608baccb0ca..608baccb0ca 100644
--- a/doc/workflow/add-user/img/request_access_button.png
+++ b/doc/user/project/members/img/request_access_button.png
Binary files differ
diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/user/project/members/img/share_project_with_groups.png
index 3cb4796f9f7..3cb4796f9f7 100644
--- a/doc/workflow/groups/share_project_with_groups.png
+++ b/doc/user/project/members/img/share_project_with_groups.png
Binary files differ
diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/user/project/members/img/withdraw_access_request_button.png
index 6edd786b151..6edd786b151 100644
--- a/doc/workflow/add-user/img/withdraw_access_request_button.png
+++ b/doc/user/project/members/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
new file mode 100644
index 00000000000..b8dd96087f1
--- /dev/null
+++ b/doc/user/project/members/index.md
@@ -0,0 +1,116 @@
+# Project's members
+
+You can manage the groups and users and their access levels in all of your
+projects. You can also personalize the access level you give each user,
+per-project.
+
+You should have `master` or `owner` [permissions](../../permissions.md) to add
+or import a new user to your project.
+
+To view, edit, add, and remove project's members, go to your
+project's **Settings > Members**.
+
+---
+
+## Add a user
+
+Right next to **People**, start typing the name or username of the user you
+want to add.
+
+![Search for people](img/add_user_search_people.png)
+
+---
+
+Select the user and the [permission level](../../user/permissions.md)
+that you'd like to give the user. Note that you can select more than one user.
+
+![Give user permissions](img/add_user_give_permissions.png)
+
+---
+
+Once done, hit **Add users to project** and they will be immediately added to
+your project with the permissions you gave them above.
+
+![List members](img/add_user_list_members.png)
+
+---
+
+From there on, you can either remove an existing user or change their access
+level to the project.
+
+## Import users from another project
+
+You can import another project's users in your own project by hitting the
+**Import members** button on the upper right corner of the **Members** menu.
+
+In the dropdown menu, you can see only the projects you are Master on.
+
+![Import members from another project](img/add_user_import_members_from_another_project.png)
+
+---
+
+Select the one you want and hit **Import project members**. A flash message
+notifying you that the import was successful will appear, and the new members
+are now in the project's members list. Notice that the permissions that they
+had on the project you imported from are retained.
+
+![Members list of new members](img/add_user_imported_members.png)
+
+---
+
+## Invite people using their e-mail address
+
+If a user you want to give access to doesn't have an account on your GitLab
+instance, you can invite them just by typing their e-mail address in the
+user search field.
+
+![Invite user by mail](img/add_user_email_search.png)
+
+---
+
+As you can imagine, you can mix inviting multiple people and adding existing
+GitLab users to the project.
+
+![Invite user by mail ready to submit](img/add_user_email_ready.png)
+
+---
+
+Once done, hit **Add users to project** and watch that there is a new member
+with the e-mail address we used above. From there on, you can resend the
+invitation, change their access level or even delete them.
+
+![Invite user members list](img/add_user_email_accept.png)
+
+---
+
+Once the user accepts the invitation, they will be prompted to create a new
+GitLab account using the same e-mail address the invitation was sent to.
+
+## Request access to a project
+
+As a project owner you can enable or disable non members to request access to
+your project. Go to the project settings and click on **Allow users to request access**.
+
+As a user, you can request to be a member of a project. Go to the project 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)
+
+---
+
+Project owners & 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)
+
+## Share project with group
+
+Alternatively, you can [share a project with an entire group](share_project_with_groups.md) instead of adding users one by one.
diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
new file mode 100644
index 00000000000..4c1ddcdcba8
--- /dev/null
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -0,0 +1,41 @@
+# Share Projects with other Groups
+
+You can share projects with other [groups](../../group/index.md). This makes it
+possible to add a group of users to a project with a single action.
+
+## Groups as collections of users
+
+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.
+
+## Sharing a project with a group of users
+
+The primary mechanism to give a group of users, say 'Engineering', access to a project,
+say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
+Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
+This is where the group sharing feature can be of use.
+
+To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section.
+
+![The 'Groups' section in the project settings screen](img/share_project_with_groups.png)
+
+Now you can add the 'Engineering' group with the maximum access level of your choice.
+After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard.
+
+!['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+
+## Maximum access level
+
+!['Project Acme' is shared with 'Engineering' with a maximum access level of 'Developer'](img/max_access_level.png)
+
+In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'.
+
+## Share project 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](../members/share_project_with_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).
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 4b2c435a120..5e5ae880518 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -20,6 +20,8 @@ documentation.
For security reasons, when using the command line, we strongly recommend
you to [connect with GitLab via SSH](../../../ssh/README.md).
+## Files
+
## Create and edit files
Host your codebase in GitLab repositories by pushing your files to GitLab.
@@ -47,6 +49,10 @@ it's easier to do so [via GitLab UI](web_editor.md):
To get started with the command line, please read through the
[command line basics documentation](../../../gitlab-basics/command-line-commands.md).
+### Find files
+
+Use GitLab's [file finder](../../../workflow/file_finder.md) to search for files in a repository.
+
## Branches
When you submit changes in a new branch, you create a new version
diff --git a/doc/user/search/img/group_issues_filter.png b/doc/user/search/img/group_issues_filter.png
new file mode 100644
index 00000000000..45eced79b99
--- /dev/null
+++ b/doc/user/search/img/group_issues_filter.png
Binary files differ
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index 6d59dcc6c75..79f34fd29ba 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -40,6 +40,14 @@ The same process is valid for merge requests. Navigate to your project's **Merge
and click **Search or filter results...**. Merge requests can be filtered by author, assignee,
milestone, and label.
+## Issues and merge requests per group
+
+Similar to **Issues and merge requests per project**, you can also search for issues
+within a group. Navigate to a group's **Issues** tab and query search results in
+the same way as you do for projects.
+
+![filter issues in a group](img/group_issues_filter.png)
+
## Search history
You can view recent searches by clicking on the little arrow-clock icon, which is to the left of the search input. Click the search entry to run that search again. This feature is available for issues and merge requests. Searches are stored locally in your browser.
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 78861625f8a..2170b079f62 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -16,7 +16,7 @@ Comments on snippets was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/is
## Project snippets
-Project snippets are always related to a specific project - see [Project features](../workflow/project_features.md) for more information.
+Project snippets are always related to a specific project - see [Project's features](project/index.md#project-39-s-features) for more information.
## Personal snippets
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 925bbf76d49..673e08287a3 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -16,14 +16,13 @@
- [File finder](file_finder.md)
- [Labels](../user/project/labels.md)
- [Notification emails](notifications.md)
-- [Project Features](project_features.md)
+- [Projects](../user/project/index.md)
- [Project forking workflow](forking_workflow.md)
-- [Project users](add-user/add-user.md)
+- [Project users](../user/project/members/index.md)
- [Protected branches](../user/project/protected_branches.md)
- [Protected tags](../user/project/protected_tags.md)
- [Quick Actions](../user/project/quick_actions.md)
-- [Sharing a project with a group](share_with_group.md)
-- [Share projects with other groups](share_projects_with_other_groups.md)
+- [Sharing projects with groups](../user/project/members/share_project_with_groups.md)
- [Time tracking](time_tracking.md)
- [Web Editor](../user/project/repository/web_editor.md)
- [Releases](releases.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index e541111d7b3..35cc080d2b7 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -1,114 +1 @@
-# Project users
-
-You can manage the groups and users and their access levels in all of your
-projects. You can also personalize the access level you give each user,
-per-project.
-
-You should have `master` or `owner` permissions to add or import a new user
-to your project.
-
-The first step to add or import a user, go to your project and click on
-**Members** in the drop-down menu on the right side of your screen.
-
-![Members](img/add_user_members_menu.png)
-
----
-
-## Add a user
-
-Right next to **People**, start typing the name or username of the user you
-want to add.
-
-![Search for people](img/add_user_search_people.png)
-
----
-
-Select the user and the [permission level](../../user/permissions.md)
-that you'd like to give the user. Note that you can select more than one user.
-
-![Give user permissions](img/add_user_give_permissions.png)
-
----
-
-Once done, hit **Add users to project** and they will be immediately added to
-your project with the permissions you gave them above.
-
-![List members](img/add_user_list_members.png)
-
----
-
-From there on, you can either remove an existing user or change their access
-level to the project.
-
-## Import users from another project
-
-You can import another project's users in your own project by hitting the
-**Import members** button on the upper right corner of the **Members** menu.
-
-In the dropdown menu, you can see only the projects you are Master on.
-
-![Import members from another project](img/add_user_import_members_from_another_project.png)
-
----
-
-Select the one you want and hit **Import project members**. A flash message
-notifying you that the import was successful will appear, and the new members
-are now in the project's members list. Notice that the permissions that they
-had on the project you imported from are retained.
-
-![Members list of new members](img/add_user_imported_members.png)
-
----
-
-## Invite people using their e-mail address
-
-If a user you want to give access to doesn't have an account on your GitLab
-instance, you can invite them just by typing their e-mail address in the
-user search field.
-
-![Invite user by mail](img/add_user_email_search.png)
-
----
-
-As you can imagine, you can mix inviting multiple people and adding existing
-GitLab users to the project.
-
-![Invite user by mail ready to submit](img/add_user_email_ready.png)
-
----
-
-Once done, hit **Add users to project** and watch that there is a new member
-with the e-mail address we used above. From there on, you can resend the
-invitation, change their access level or even delete them.
-
-![Invite user members list](img/add_user_email_accept.png)
-
----
-
-Once the user accepts the invitation, they will be prompted to create a new
-GitLab account using the same e-mail address the invitation was sent to.
-
-## Request access to a project
-
-As a project owner you can enable or disable non members to request access to
-your project. Go to the project settings and click on **Allow users to request access**.
-
-As a user, you can request to be a member of a project. Go to the project 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)
-
----
-
-Project owners & 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)
+This document was moved to [../../user/project/members/index.md](../../user/project/members/index.md)
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index 3f5de2bd4b1..feb88712f5a 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -1,45 +1 @@
-# Project features
-
-When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
-
-Below you will find a more elaborate explanation of each of these.
-
-## Issues
-
-Issues is a really powerful, but lightweight issue tracking system.
-
-You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
-
-They integrate deeply into GitLab and are easily referenced from anywhere by using `#` and the issue number.
-
-## Merge Requests
-
-Using a merge request, you can review and discuss code before it is merged in the branch of your code.
-
-As with issues, it can be assigned; people, issues, etc. can be referenced; milestones attached.
-
-We see it as an integral part of working together on code and couldn't work without it.
-
-## Wiki
-
-This is a separate system for documentation, built right into GitLab.
-
-It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
-
-[Read more about Wikis.](../user/project/wiki/index.md)
-
-## Snippets
-
-Snippets are little bits of code or text.
-
-This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
-
-For example, a specific config file that is used by the team that is only valid for the people that work on the code.
-
-## Git LFS
-
->**Note:** Project-specific LFS setting was added on 8.12 and is available only to admins.
-
-Git Large File Storage allows you to easily manage large binary files with Git.
-With this setting admins can better control which projects are allowed to use
-LFS.
+This document was moved to [../user/project/index.md](../user/project/index.md)
diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md
index 40d756bc199..2eb4d24958a 100644
--- a/doc/workflow/share_projects_with_other_groups.md
+++ b/doc/workflow/share_projects_with_other_groups.md
@@ -1,32 +1 @@
-# Share Projects with other Groups
-
-You can share projects with other groups. This makes it possible to add a group of users
-to a project with a single action.
-
-## Groups as collections of users
-
-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.
-
-## Sharing a project with a group of users
-
-The primary mechanism to give a group of users, say 'Engineering', access to a project,
-say 'Project Acme', in GitLab is to make the 'Engineering' group the owner of 'Project
-Acme'. But what if 'Project Acme' already belongs to another group, say 'Open Source'?
-This is where the group sharing feature can be of use.
-
-To share 'Project Acme' with the 'Engineering' group, go to the project settings page for 'Project Acme' and use the left navigation menu to go to the 'Groups' section.
-
-![The 'Groups' section in the project settings screen](groups/share_project_with_groups.png)
-
-Now you can add the 'Engineering' group with the maximum access level of your choice.
-After sharing 'Project Acme' with 'Engineering', the project is listed on the group dashboard.
-
-!['Project Acme' is listed as a shared project for 'Engineering'](groups/other_group_sees_shared_project.png)
-
-## Maximum access level
-
-!['Project Acme' is shared with 'Engineering' with a maximum access level of 'Developer'](groups/max_access_level.png)
-
-In the screenshot above, the maximum access level of 'Developer' for members from 'Engineering' means that users with higher access levels in 'Engineering' ('Master' or 'Owner') will only have 'Developer' access to 'Project Acme'.
+This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md
index 3b7690973cb..2eb4d24958a 100644
--- a/doc/workflow/share_with_group.md
+++ b/doc/workflow/share_with_group.md
@@ -1,13 +1 @@
-# Sharing a project with a group
-
-If you want to share a single project in a group with another group,
-you can do so easily. By setting the permission you can quickly
-give a select group of users access to a project in a restricted manner.
-
-In a project go to the project settings -> groups.
-
-Now you can select a group that you want to share this project with and with
-which maximum access level. Users in that group are able to access this project
-with their set group access level, up to the maximum level that you've set.
-
-![Share a project with a group](share_with_group.png)
+This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb
index 10ebe705365..4f44f932a6d 100644
--- a/features/steps/profile/emails.rb
+++ b/features/steps/profile/emails.rb
@@ -28,7 +28,7 @@ class Spinach::Features::ProfileEmails < Spinach::FeatureSteps
expect(email).to be_nil
expect(page).not_to have_content("my@email.com")
end
-
+
step 'I click link "Remove" for "my@email.com"' do
# there should only be one remove button at this time
click_link "Remove"
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 810cd75591b..7254fbc2e4e 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -299,9 +299,6 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I change the comment "Line is wrong" to "Typo, please fix" on diff' do
page.within('.diff-file:nth-of-type(5) .note') do
- find('.more-actions').click
- find('.more-actions .dropdown-menu li', match: :first)
-
find('.js-note-edit').click
page.within('.current-note-edit-form', visible: true) do
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 80187b83fee..492da38355c 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -11,8 +11,8 @@ module SharedNote
note = find('.note')
note.hover
- note.find('.more-actions').click
- note.find('.more-actions .dropdown-menu li', match: :first)
+ find('.more-actions').click
+ find('.more-actions .dropdown-menu li', match: :first)
find(".js-note-delete").click
end
@@ -147,9 +147,6 @@ module SharedNote
note = find('.note')
note.hover
- note.find('.more-actions').click
- note.find('.more-actions .dropdown-menu li', match: :first)
-
note.find('.js-note-edit').click
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 716e3f11744..e8dd61e493f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -83,7 +83,7 @@ module API
expose :created_at, :last_activity_at
end
- class Project < BasicProjectDetails
+ class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
expose :_links do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 450334fee84..e2ac7142bc4 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,5 +1,8 @@
module API
class Files < Grape::API
+ # Prevents returning plain/text responses for files with .txt extension
+ after_validation { content_type "application/json" }
+
helpers do
def commit_params(attrs)
{
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 89dda88d3f5..15c3832b032 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -351,6 +351,8 @@ module API
if user_project.forked_from_project.nil?
user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+
+ ::Projects::ForksCountService.new(forked_from_project).refresh_cache
else
render_api_error!("Project already forked", 409)
end
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index d742f2e18d0..dccf4fa27a7 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -61,7 +61,7 @@ module API
service_args = [user_project, current_user, protected_branch_params]
protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute
-
+
if protected_branch.persisted?
present protected_branch, with: Entities::ProtectedBranch, project: user_project
else
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index eb090453b48..449876c10d9 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -388,6 +388,8 @@ module API
if user_project.forked_from_project.nil?
user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+
+ ::Projects::ForksCountService.new(forked_from_project).refresh_cache
else
render_api_error!("Project already forked", 409)
end
diff --git a/lib/banzai/filter/image_lazy_load_filter.rb b/lib/banzai/filter/image_lazy_load_filter.rb
index 7a81d583b82..bcb4f332267 100644
--- a/lib/banzai/filter/image_lazy_load_filter.rb
+++ b/lib/banzai/filter/image_lazy_load_filter.rb
@@ -6,9 +6,9 @@ module Banzai
doc.xpath('descendant-or-self::img').each do |img|
img['class'] ||= '' << 'lazy'
img['data-src'] = img['src']
- img['src'] = LazyImageTagHelper.placeholder_image
+ img['src'] = LazyImageTagHelper.placeholder_image
end
-
+
doc
end
end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index fd7b97d3167..5bef29eb1da 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -7,7 +7,7 @@ class ProjectUrlConstrainer
return false unless DynamicPathValidator.valid_project_path?(full_path)
# We intentionally allow SELECT(*) here so result of this query can be used
- # as cache for further Project.find_by_full_path calls within request
+ # as cache for further Project.find_by_full_path calls within request
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
index e544aefa63a..b028169f500 100644
--- a/lib/declarative_policy/base.rb
+++ b/lib/declarative_policy/base.rb
@@ -221,7 +221,7 @@ module DeclarativePolicy
end
# computes the given ability and prints a helpful debugging output
- # showing which
+ # showing which
def debug(ability, *a)
runner(ability).debug(*a)
end
diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb
index 1089bc9f89e..e6173d45af3 100644
--- a/lib/gitlab/auth/ip_rate_limiter.rb
+++ b/lib/gitlab/auth/ip_rate_limiter.rb
@@ -11,11 +11,11 @@ module Gitlab
def enabled?
config.enabled
end
-
+
def reset!
Rack::Attack::Allow2Ban.reset(ip, config)
end
-
+
def register_fail!
# Allow2Ban.filter will return false if this IP has not failed too often yet
@banned = Rack::Attack::Allow2Ban.filter(ip, config) do
@@ -23,17 +23,17 @@ module Gitlab
ip_can_be_banned?
end
end
-
+
def banned?
@banned
end
-
+
private
-
+
def config
Gitlab.config.rack_attack.git_basic_auth
end
-
+
def ip_can_be_banned?
config.ip_whitelist.exclude?(ip)
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index a375ccbece0..a788fb3fcbc 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -60,7 +60,7 @@ module Gitlab
begin
path = read_string(gz).force_encoding('UTF-8')
meta = read_string(gz).force_encoding('UTF-8')
-
+
next unless path.valid_encoding? && meta.valid_encoding?
next unless path =~ match_pattern
next if path =~ INVALID_PATH_PATTERN
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 59e95191464..7780f4e4d4f 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -54,7 +54,7 @@ module Gitlab
# [[commit_sha, path], [commit_sha, path], ...]. If blob_size_limit < 0 then the
# full blob contents are returned. If blob_size_limit >= 0 then each blob will
# contain no more than limit bytes in its data attribute.
- #
+ #
# Keep in mind that this method may allocate a lot of memory. It is up
# to the caller to limit the number of blobs and blob_size_limit.
#
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index afe4fb58ad0..1d5ca68137a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -18,6 +18,28 @@ module Gitlab
InvalidBlobName = Class.new(StandardError)
InvalidRef = Class.new(StandardError)
+ class << self
+ # Unlike `new`, `create` takes the storage path, not the storage name
+ def create(storage_path, name, bare: true, symlink_hooks_to: nil)
+ repo_path = File.join(storage_path, name)
+ repo_path += '.git' unless repo_path.end_with?('.git')
+
+ FileUtils.mkdir_p(repo_path, mode: 0770)
+
+ # Equivalent to `git --git-path=#{repo_path} init [--bare]`
+ repo = Rugged::Repository.init_at(repo_path, bare)
+ repo.close
+
+ if symlink_hooks_to.present?
+ hooks_path = File.join(repo_path, 'hooks')
+ FileUtils.rm_rf(hooks_path)
+ FileUtils.ln_s(symlink_hooks_to, hooks_path)
+ end
+
+ true
+ end
+ end
+
# Full path to repo
attr_reader :path
@@ -811,6 +833,8 @@ module Gitlab
return unless commit_object && commit_object.type == :COMMIT
gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
+ return unless gitmodules
+
found_module = GitmodulesParser.new(gitmodules.data).parse[path]
found_module && found_module['url']
diff --git a/lib/gitlab/import_export/attributes_finder.rb b/lib/gitlab/import_export/attributes_finder.rb
index d230de781d5..56042ddecbf 100644
--- a/lib/gitlab/import_export/attributes_finder.rb
+++ b/lib/gitlab/import_export/attributes_finder.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class AttributesFinder
-
def initialize(included_attributes:, excluded_attributes:, methods:)
@included_attributes = included_attributes || {}
@excluded_attributes = excluded_attributes || {}
diff --git a/lib/gitlab/ldap/auth_hash.rb b/lib/gitlab/ldap/auth_hash.rb
index 95378e5a769..4fbc5fa5262 100644
--- a/lib/gitlab/ldap/auth_hash.rb
+++ b/lib/gitlab/ldap/auth_hash.rb
@@ -17,7 +17,7 @@ module Gitlab
value = value.first if value
break if value.present?
end
-
+
return super unless value
Gitlab::Utils.force_utf8(value)
diff --git a/lib/gitlab/middleware/rails_queue_duration.rb b/lib/gitlab/middleware/rails_queue_duration.rb
index 5d2d7d0026c..63c3372da51 100644
--- a/lib/gitlab/middleware/rails_queue_duration.rb
+++ b/lib/gitlab/middleware/rails_queue_duration.rb
@@ -8,7 +8,7 @@ module Gitlab
def initialize(app)
@app = app
end
-
+
def call(env)
trans = Gitlab::Metrics.current_transaction
proxy_start = env['HTTP_GITLAB_WORKHORSE_PROXY_START'].presence
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index cf461adf697..732fbf68dad 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -25,7 +25,9 @@ module Gitlab
end
TEMPLATES_TABLE = [
- ProjectTemplate.new('rails', 'Ruby on Rails')
+ ProjectTemplate.new('rails', 'Ruby on Rails'),
+ ProjectTemplate.new('spring', 'Spring'),
+ ProjectTemplate.new('express', 'NodeJS Express')
].freeze
class << self
diff --git a/lib/gitlab/redis/cache.rb b/lib/gitlab/redis/cache.rb
index b0da516ff83..9bf019b72e6 100644
--- a/lib/gitlab/redis/cache.rb
+++ b/lib/gitlab/redis/cache.rb
@@ -7,9 +7,6 @@ module Gitlab
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
@@ -22,7 +19,7 @@ module Gitlab
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__)
+ file_name = config_file_path('redis.cache.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb
index f9249d05565..e1695aafbeb 100644
--- a/lib/gitlab/redis/queues.rb
+++ b/lib/gitlab/redis/queues.rb
@@ -8,9 +8,6 @@ module Gitlab
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
@@ -23,7 +20,7 @@ module Gitlab
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__)
+ file_name = config_file_path('redis.queues.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_QUEUES_URL when config file is absent
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 395dcf082da..10bec7a90da 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -7,9 +7,6 @@ module Gitlab
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
@@ -22,7 +19,7 @@ module Gitlab
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__)
+ file_name = config_file_path('redis.shared_state.yml')
return file_name if File.file?(file_name)
# this will force use of DEFAULT_REDIS_SHARED_STATE_URL when config file is absent
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
index c43b37dde74..8ad06480575 100644
--- a/lib/gitlab/redis/wrapper.rb
+++ b/lib/gitlab/redis/wrapper.rb
@@ -8,9 +8,6 @@ module Gitlab
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
@@ -49,13 +46,21 @@ module Gitlab
DEFAULT_REDIS_URL
end
+ # Return the absolute path to a Rails configuration file
+ #
+ # We use this instead of `Rails.root` because for certain tasks
+ # utilizing these classes, `Rails` might not be available.
+ def config_file_path(filename)
+ File.expand_path("../../../config/#{filename}", __dir__)
+ 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__)
+ file_name = config_file_path('resque.yml')
return file_name if File.file?(file_name)
# nil will force use of DEFAULT_REDIS_URL when config file is absent
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 0cb28732402..280a9abf03e 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -73,8 +73,10 @@ module Gitlab
#
# 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"])
+ Gitlab::Git::Repository.create(storage, name, bare: true, symlink_hooks_to: gitlab_shell_hooks_path)
+ rescue => err
+ Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
+ false
end
# Import repository
@@ -273,7 +275,11 @@ module Gitlab
protected
def gitlab_shell_path
- Gitlab.config.gitlab_shell.path
+ File.expand_path(Gitlab.config.gitlab_shell.path)
+ end
+
+ def gitlab_shell_hooks_path
+ File.expand_path(Gitlab.config.gitlab_shell.hooks_path)
end
def gitlab_shell_user_home
diff --git a/lib/gitlab/slash_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb
index ea611a4d629..ab855319077 100644
--- a/lib/gitlab/slash_commands/presenters/help.rb
+++ b/lib/gitlab/slash_commands/presenters/help.rb
@@ -14,7 +14,7 @@ module Gitlab
if text.start_with?('help')
header_with_list("Available commands", full_commands(trigger))
else
- header_with_list("Unknown command, these commands are available", full_commands(trigger))
+ header_with_list("Unknown command, these commands are available", full_commands(trigger))
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index fa182c4deda..9670c93759e 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -14,6 +14,19 @@ module Gitlab
str.force_encoding(Encoding::UTF_8)
end
+ # A slugified version of the string, suitable for inclusion in URLs and
+ # domain names. Rules:
+ #
+ # * Lowercased
+ # * Anything not matching [a-z0-9-] is replaced with a -
+ # * Maximum length is 63 bytes
+ # * First/Last Character is not a hyphen
+ def slugify(str)
+ return str.downcase
+ .gsub(/[^a-z0-9]/, '-')[0..62]
+ .gsub(/(\A-+|-+\z)/, '')
+ end
+
def to_boolean(value)
return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3f25e463412..a362a3a0bc6 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -45,7 +45,6 @@ module Gitlab
raise "Unsupported action: #{action}"
end
if feature_enabled
- params[:GitalyAddress] = server[:address] # This field will be deprecated
params[:GitalyServer] = server
end
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index 1f504485e4c..08677a98fc1 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -15,13 +15,17 @@ namespace :gitlab do
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake])
- command = status.zero? ? 'gmake' : 'make'
+ command = status.zero? ? ['gmake'] : ['make']
+
+ if Rails.env.test?
+ command += %W[BUNDLE_PATH=#{Bundler.bundle_path}]
+ end
Dir.chdir(args.dir) do
create_gitaly_configuration
# In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present?
- Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + [command]) }
+ Bundler.with_original_env { run_command!(%w[/usr/bin/env -u RUBYOPT -u BUNDLE_GEMFILE] + command) }
end
end
end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index a7e30423c7a..f44abc2b81b 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -21,13 +21,18 @@ namespace :gitlab do
params = {
import_url: template.clone_url,
namespace_id: admin.namespace.id,
- path: template.title,
+ path: template.name,
skip_wiki: true
}
- puts "Creating project for #{template.name}"
+ puts "Creating project for #{template.title}"
project = Projects::CreateService.new(admin, params).execute
+ unless project.persisted?
+ puts project.errors.messages
+ exit(1)
+ end
+
loop do
if project.finished?
puts "Import finished for #{template.name}"
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 85d806e6f20..5c531f0cd7d 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -1163,22 +1163,11 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "На път сте да премахнете „%{group_name}“. Ако я премахнете, групата НЕ може да бъде възстановена! НАИСТИНА ли искате това?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "На път сте да премахнете „%{project_name_with_namespace}“. Ако го премахнете, той НЕ може да бъде възстановен!"
"НАИСТИНА ли искате това?"
msgid ""
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index 46bf4e33997..0ac591d4927 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -17,17 +17,34 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n"
+msgid "%d commit"
+msgid_plural "%d commits"
+msgstr[0] ""
+msgstr[1] ""
+
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] ""
-msgid "%d commit"
-msgid_plural "%d commits"
+msgid "%{commit_author_link} committed %{commit_timeago}"
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds."
+msgstr ""
+
+msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
+msgstr ""
+
+msgid "%{storage_name}: failed storage access attempt on host:"
+msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
msgstr[0] ""
msgstr[1] ""
-msgid "%{commit_author_link} committed %{commit_timeago}"
+msgid "(checkout the %{link} for information on how to install it)."
msgstr ""
msgid "1 pipeline"
@@ -41,6 +58,9 @@ msgstr ""
msgid "About auto deploy"
msgstr ""
+msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
+msgstr ""
+
msgid "Active"
msgstr ""
@@ -68,6 +88,18 @@ msgstr ""
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
+msgid "Are you sure you want to discard your changes?"
+msgstr ""
+
+msgid "Are you sure you want to reset registration token?"
+msgstr ""
+
+msgid "Are you sure you want to reset the health check token?"
+msgstr ""
+
+msgid "Are you sure?"
+msgstr ""
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr ""
@@ -109,6 +141,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
+msgid "Cancel edit"
+msgstr ""
+
msgid "ChangeTypeActionLabel|Pick into branch"
msgstr ""
@@ -234,6 +269,9 @@ msgstr ""
msgid "Create New Directory"
msgstr ""
+msgid "Create a new branch"
+msgstr ""
+
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
@@ -311,9 +349,15 @@ msgstr[1] ""
msgid "Description"
msgstr ""
+msgid "Details"
+msgstr ""
+
msgid "Directory name"
msgstr ""
+msgid "Discard changes"
+msgstr ""
+
msgid "Don't show again"
msgstr ""
@@ -397,12 +441,36 @@ msgstr ""
msgid "From merge request merge until deploy to production"
msgstr ""
+msgid "Git storage health information has been reset"
+msgstr ""
+
+msgid "GitLab Runner section"
+msgstr ""
+
msgid "Go to your fork"
msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
+msgid "Health Check"
+msgstr ""
+
+msgid "Health information can be retrieved from the following endpoints. More information is available"
+msgstr ""
+
+msgid "HealthCheck|Access token is"
+msgstr ""
+
+msgid "HealthCheck|Healthy"
+msgstr ""
+
+msgid "HealthCheck|No Health Problems Detected"
+msgstr ""
+
+msgid "HealthCheck|Unhealthy"
+msgstr ""
+
msgid "Home"
msgstr ""
@@ -412,6 +480,9 @@ msgstr ""
msgid "Import repository"
msgstr ""
+msgid "Install a Runner compatible with GitLab CI"
+msgstr ""
+
msgid "Interval Pattern"
msgstr ""
@@ -470,6 +541,9 @@ msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
+msgid "More information is available|here"
+msgstr ""
+
msgid "New Issue"
msgid_plural "New Issues"
msgstr[0] ""
@@ -682,6 +756,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project details"
+msgstr ""
+
msgid "Project export could not be deleted."
msgstr ""
@@ -754,9 +831,21 @@ msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Repository"
+msgstr ""
+
msgid "Request Access"
msgstr ""
+msgid "Reset git storage health information"
+msgstr ""
+
+msgid "Reset health check access token"
+msgstr ""
+
+msgid "Reset runners registration token"
+msgstr ""
+
msgid "Revert this commit"
msgstr ""
@@ -781,6 +870,9 @@ msgstr ""
msgid "Select a timezone"
msgstr ""
+msgid "Select existing branch"
+msgstr ""
+
msgid "Select target branch"
msgstr ""
@@ -807,12 +899,18 @@ msgstr[1] ""
msgid "Source code"
msgstr ""
+msgid "Specify the following URL during the Runner setup:"
+msgstr ""
+
msgid "StarProject|Star"
msgstr ""
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
+msgid "Start the Runner!"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
@@ -875,6 +973,9 @@ 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 "There are problems accessing Git storage: "
+msgstr ""
+
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr ""
@@ -1044,6 +1145,9 @@ msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
+msgid "Use the following registration token during setup:"
+msgstr ""
+
msgid "Use your global notification setting"
msgstr ""
@@ -1059,6 +1163,9 @@ msgstr ""
msgid "VisibilityLevel|Public"
msgstr ""
+msgid "VisibilityLevel|Unknown"
+msgstr ""
+
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
@@ -1068,16 +1175,10 @@ 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?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! 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?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index d688478972d..94ae131186b 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -1165,30 +1165,14 @@ msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon."
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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vi forigos „%{group_name}“. Oni NE POVAS malfari la forigon de grupo! Ĉ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?"
-msgstr ""
-"Vi forigos „%{project_name_with_namespace}“.\n"
-"Oni NE POVAS malfari la forigon de projekto!\n"
-"Ĉu vi estas ABSOLUTE certa?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vi forigos „%{project_name_with_namespace}“. Oni NE POVAS malfari la forigon de projekto! Ĉu vi estas ABSOLUTE certa?"
-msgid ""
-"You are going to remove the fork relationship to source project "
-"%{forked_from_project}. Are you ABSOLUTELY sure?"
-msgstr ""
-"Vi forigos la rilaton de la disbranĉigo al la originala projekto, "
-"„%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
+msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
+msgstr "Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{forked_from_project}“. Ĉu vi estas ABSOLUTE certa?"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 5c669d51a68..e43fd5fea15 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -1071,23 +1071,11 @@ msgstr "No hay suficientes datos para mostrar en esta etapa."
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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Va a eliminar %{group_name}. ¡El grupo eliminado NO puede ser restaurado! ¿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?"
-msgstr ""
-"Va a eliminar %{project_name_with_namespace}.\n"
-"¡El proyecto eliminado NO puede ser restaurado!\n"
-"¿Estás TOTALMENTE seguro?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Va a eliminar %{project_name_with_namespace}. ¡El proyecto eliminado NO puede ser restaurado! ¿Estás TOTALMENTE seguro?"
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{forked_from_project}. ¿Estás TOTALMENTE seguro?"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 90e2462039c..83f31f7a3b2 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -1175,22 +1175,11 @@ 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 %{group_name}. Removed groups CANNOT be restored! 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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Vous êtes sur le point de supprimer %{project_name_with_namespace}. Les projets supprimés NE PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr ?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index babef3ed0af..e60504e1395 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1072,16 +1072,10 @@ 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?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! 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?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr ""
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 7ba23d84405..e719a3988e3 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -1169,23 +1169,11 @@ 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 %{group_name}.\n"
-"Removed groups CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
-msgstr ""
-"Stai per rimuovere il gruppo %{group_name}.\n"
-"I gruppi rimossi NON possono esser ripristinati!\n"
-"Sei ASSOLUTAMENTE sicuro?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Stai per rimuovere il gruppo %{group_name}. I gruppi rimossi NON POSSONO esser ripristinati! Sei ASSOLUTAMENTE sicuro?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Stai per rimuovere %{project_name_with_namespace}. I progetti rimossi NON POSSONO essere ripristinati! Sei assolutamente sicuro?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 0b1db651c11..bfa97aa21d7 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -1119,22 +1119,11 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{group_name} グループを削除しようとしています。 削除されたグループは絶対に元に戻せません!本当によろしいですか?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace} プロジェクトを削除しようとしています。削除されたプロジェクトは絶対に元には戻せません!本当によろしいですか?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 0a6fbac0880..340c8955d20 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -1121,21 +1121,11 @@ 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"
-"\"정말로\" 확실합니까?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{group_name} 그룹을 제거하려고합니다. \"정말로\" 확실합니까?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "%{project_name_with_namespace} 프로젝트를 삭제하려고합니다. "삭제된 프로젝트를 복원 할 수 없습니다! \"정말로\" 확실합니까?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 2eaadb64124..a2df8ea549c 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -1164,30 +1164,12 @@ msgstr "Esta etapa não possui dados suficientes para exibição."
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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Você vai remover %{group_name}. Grupos removidos NÃO PODEM ser restaurados! Você está ABSOLUTAMENTE certo?"
-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 remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Você irá remover %{project_name_with_namespace}. O projeto removido NÃO PODE ser restaurado! Tem certeza ABSOLUTA?"
msgid ""
"You are going to transfer %{project_name_with_namespace} to another owner. "
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index 78f7b059077..6661232850a 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -1179,23 +1179,11 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Вы собираетесь удалить %{group_name}. Удаленные группы НЕ МОГУТ быть восстановлены! Вы АБСОЛЮТНО уверены?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Вы хотите удалить %{project_name_with_namespace}. Удаленный проект НЕ МОЖЕТ быть восстановлен! Вы АБСОЛЮТНО уверены?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 78144d3755d..0ac0499e315 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -1173,23 +1173,11 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ви хочете видалити %{group_name}. Видалені групи НЕ МОЖНА буду відновити! Ви АБСОЛЮТНО впевнені?"
-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 %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "Ви хочете видалити %{project_name_with_namespace}. Видалений проект НЕ МОЖЕ бути відновлений! Ви АБСОЛЮТНО впевнені?"
msgid ""
"You are going to remove the fork relationship to source project "
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 4a550db55d2..a3d0027212c 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -1101,18 +1101,10 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "即将删除 %{group_name}。已删除的群组无法恢复!确定继续吗?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "即将要删除 %{project_name_with_namespace}。已删除的项目无法恢复!确定继续吗?"
msgid ""
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 69b2bf80dbf..f4d33862a36 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -1100,18 +1100,10 @@ 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 %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "即將刪除 %{group_name}。已刪除的群組無法恢復!確定繼續嗎?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "即將要刪除 %{project_name_with_namespace}。已刪除的項目無法恢複!確定繼續嗎?"
msgid ""
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 4fd728659c6..205d4712316 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -1111,18 +1111,10 @@ 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"
-"真的「100%確定」要這麼做嗎?"
+msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
+msgstr "即將要刪除 %{group_name}。被刪除的群組完全無法救回來喔!真的「100%確定」要這麼做嗎?"
-msgid ""
-"You are going to remove %{project_name_with_namespace}.\n"
-"Removed project CANNOT be restored!\n"
-"Are you ABSOLUTELY sure?"
+msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "即將要刪除 %{project_name_with_namespace}。被刪除的專案完全無法救回來喔!真的「100%確定」要這麼做嗎?"
msgid ""
diff --git a/package.json b/package.json
index c5247a63e67..cbb9be3a27f 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"vue-loader": "^11.3.4",
"vue-resource": "^1.3.4",
"vue-template-compiler": "^2.2.6",
- "webpack": "^3.4.0",
+ "webpack": "^3.5.4",
"webpack-bundle-analyzer": "^2.8.2",
"webpack-stats-plugin": "^0.1.5"
},
diff --git a/rubocop/cop/migration/safer_boolean_column.rb b/rubocop/cop/migration/safer_boolean_column.rb
new file mode 100644
index 00000000000..0335c25d85d
--- /dev/null
+++ b/rubocop/cop/migration/safer_boolean_column.rb
@@ -0,0 +1,94 @@
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # This cop requires a default value and disallows nulls for boolean
+ # columns on small tables.
+ #
+ # In general, this prevents 3-state-booleans.
+ # https://robots.thoughtbot.com/avoid-the-threestate-boolean-problem
+ #
+ # In particular, for the `application_settings` table, this ensures that
+ # upgraded installations get a proper default for the new boolean setting.
+ # A developer might otherwise mistakenly assume that a value in
+ # `ApplicationSetting.defaults` is sufficient.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/2750 for more
+ # information.
+ class SaferBooleanColumn < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ DEFAULT_OFFENSE = 'Boolean columns on the `%s` table should have a default. You may wish to use `add_column_with_default`.'.freeze
+ NULL_OFFENSE = 'Boolean columns on the `%s` table should disallow nulls.'.freeze
+ DEFAULT_AND_NULL_OFFENSE = 'Boolean columns on the `%s` table should have a default and should disallow nulls. You may wish to use `add_column_with_default`.'.freeze
+
+ SMALL_TABLES = %i[
+ application_settings
+ ].freeze
+
+ def_node_matcher :add_column?, <<~PATTERN
+ (send nil :add_column $...)
+ PATTERN
+
+ def on_send(node)
+ return unless in_migration?(node)
+
+ matched = add_column?(node)
+
+ return unless matched
+
+ table, _, type = matched.to_a.take(3).map(&:children).map(&:first)
+ opts = matched[3]
+
+ return unless SMALL_TABLES.include?(table) && type == :boolean
+
+ no_default = no_default?(opts)
+ nulls_allowed = nulls_allowed?(opts)
+
+ offense = if no_default && nulls_allowed
+ DEFAULT_AND_NULL_OFFENSE
+ elsif no_default
+ DEFAULT_OFFENSE
+ elsif nulls_allowed
+ NULL_OFFENSE
+ end
+
+ add_offense(node, :expression, format(offense, table)) if offense
+ end
+
+ def no_default?(opts)
+ return true unless opts
+
+ each_hash_node_pair(opts) do |key, value|
+ return value == 'nil' if key == :default
+ end
+ end
+
+ def nulls_allowed?(opts)
+ return true unless opts
+
+ each_hash_node_pair(opts) do |key, value|
+ return value != 'false' if key == :null
+ end
+ end
+
+ def each_hash_node_pair(hash_node, &block)
+ hash_node.each_node(:pair) do |pair|
+ key = hash_pair_key(pair)
+ value = hash_pair_value(pair)
+ yield(key, value)
+ end
+ end
+
+ def hash_pair_key(pair)
+ pair.children[0].children[0]
+ end
+
+ def hash_pair_value(pair)
+ pair.children[1].source
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 3fbd5b0163c..1b6e8991a17 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -13,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/safer_boolean_column'
require_relative 'cop/migration/hash_index'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 23601c457b0..b571b11dcac 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1,7 +1,7 @@
require('spec_helper')
describe Projects::IssuesController do
- let(:project) { create(:project_empty_repo) }
+ let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
@@ -841,7 +841,7 @@ describe Projects::IssuesController do
describe 'POST #toggle_award_emoji' do
before do
sign_in(user)
- project.team << [user, :developer]
+ project.add_developer(user)
end
it "toggles the award emoji" do
@@ -855,6 +855,8 @@ describe Projects::IssuesController do
end
describe 'POST create_merge_request' do
+ let(:project) { create(:project, :repository) }
+
before do
project.add_developer(user)
sign_in(user)
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 29ad1af9fd9..e5abfd67d60 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -10,6 +10,10 @@ FactoryGirl.define do
after(:build) do |deployment, evaluator|
deployment.project ||= deployment.environment.project
+
+ unless deployment.project.repository_exists?
+ allow(deployment.project.repository).to receive(:fetch_ref)
+ end
end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 1bc530d06db..cbec716d6ea 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -68,6 +68,17 @@ FactoryGirl.define do
merge_user author
end
+ after(:build) do |merge_request|
+ target_project = merge_request.target_project
+ source_project = merge_request.source_project
+
+ # Fake `write_ref` if we don't have repository
+ # We have too many existing tests replying on this behaviour
+ unless [target_project, source_project].all?(&:repository_exists?)
+ allow(merge_request).to receive(:write_ref)
+ end
+ end
+
factory :merged_merge_request, traits: [:merged]
factory :closed_merge_request, traits: [:closed]
factory :reopened_merge_request, traits: [:opened]
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 5db42175c15..dbb0ae9c86e 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -74,7 +74,7 @@ feature 'Admin updates settings' do
context 'sign-in restrictions', :js do
it 'de-activates oauth sign-in source' do
find('.btn', text: 'GitLab.com').click
-
+
expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
end
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 5c60cca10b9..bfe9dac3bd4 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -24,6 +24,12 @@ feature 'Cycle Analytics', js: true do
expect(page).to have_content('Introducing Cycle Analytics')
end
+ it 'shows pipeline summary' do
+ expect(new_issues_counter).to have_content('-')
+ expect(commits_counter).to have_content('-')
+ expect(deploys_counter).to have_content('-')
+ end
+
it 'shows active stage with empty message' do
expect(page).to have_selector('.stage-nav-item.active', text: 'Issue')
expect(page).to have_content("We don't have enough data to show this stage.")
@@ -42,6 +48,12 @@ feature 'Cycle Analytics', js: true do
visit project_cycle_analytics_path(project)
end
+ it 'shows pipeline summary' do
+ expect(new_issues_counter).to have_content('1')
+ expect(commits_counter).to have_content('2')
+ expect(deploys_counter).to have_content('1')
+ end
+
it 'shows data on each stage' do
expect_issue_to_be_present
@@ -63,6 +75,20 @@ feature 'Cycle Analytics', js: true do
click_stage('Production')
expect_issue_to_be_present
end
+
+ context "when I change the time period observed" do
+ before do
+ _two_weeks_old_issue = create(:issue, project: project, created_at: 2.weeks.ago)
+
+ click_button('Last 30 days')
+ click_link('Last 7 days')
+ wait_for_requests
+ end
+
+ it 'shows only relevant data' do
+ expect(new_issues_counter).to have_content('1')
+ end
+ end
end
context "when my preferred language is Spanish" do
@@ -109,6 +135,18 @@ feature 'Cycle Analytics', js: true do
end
end
+ def new_issues_counter
+ find(:xpath, "//p[contains(text(),'New Issue')]/preceding-sibling::h3")
+ end
+
+ def commits_counter
+ find(:xpath, "//p[contains(text(),'Commits')]/preceding-sibling::h3")
+ end
+
+ def deploys_counter
+ find(:xpath, "//p[contains(text(),'Deploy')]/preceding-sibling::h3")
+ end
+
def expect_issue_to_be_present
expect(find('.stage-events')).to have_content(issue.title)
expect(find('.stage-events')).to have_content(issue.author.name)
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 847e3856ba5..b2229b44f99 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -353,7 +353,7 @@ feature 'Issues > Labels bulk assignment' do
context 'cannot bulk assign labels' do
it do
- expect(page).not_to have_button 'Edit Issues'
+ expect(page).not_to have_button 'Edit issues'
expect(page).not_to have_css '.check-all-issues'
expect(page).not_to have_css '.issue-check'
end
@@ -411,7 +411,7 @@ feature 'Issues > Labels bulk assignment' do
def enable_bulk_update
visit project_issues_path(project)
- click_button 'Edit Issues'
+ click_button 'Edit issues'
end
def disable_bulk_update
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 9f08ecc214b..62dbc3efb01 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -133,8 +133,6 @@ feature 'Issue notes polling', :js do
def click_edit_action(note)
note_element = find("#note_#{note.id}")
- open_more_actions_dropdown(note)
-
note_element.find('.js-note-edit').click
end
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 5a7c4f54cb6..bcc6e9bab0f 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -14,7 +14,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'sets to closed' do
visit project_issues_path(project)
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
find('.js-issue-status').click
@@ -27,7 +27,7 @@ feature 'Multiple issue updating from issues#index', :js do
create_closed
visit project_issues_path(project, state: 'closed')
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
find('.js-issue-status').click
@@ -41,7 +41,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'updates to current user' do
visit project_issues_path(project)
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
click_update_assignee_button
@@ -57,7 +57,7 @@ feature 'Multiple issue updating from issues#index', :js do
create_assigned
visit project_issues_path(project)
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
click_update_assignee_button
@@ -73,7 +73,7 @@ feature 'Multiple issue updating from issues#index', :js do
it 'updates milestone' do
visit project_issues_path(project)
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
@@ -89,7 +89,7 @@ feature 'Multiple issue updating from issues#index', :js do
expect(first('.issue')).to have_content milestone.title
- click_button 'Edit Issues'
+ click_button 'Edit issues'
find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 3c8e37ff920..3ffc80622f5 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -708,7 +708,7 @@ describe 'Issues' do
end
describe 'confidential issue#show', js: true do
- it 'shows confidential sibebar information as confidential and can be turned off' do
+ it 'shows confidential sibebar information as confidential and can be turned off' do
issue = create(:issue, :confidential, project: project)
visit project_issue_path(project, issue)
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 2d9419d6124..c4f02311f13 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -157,7 +157,7 @@ feature 'Diff note avatars', js: true do
end
page.within find("[id='#{position.line_code(project.repository)}']") do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').trigger('click')
expect(page).to have_selector('img.js-diff-comment-avatar', count: 3)
expect(find('.diff-comments-more-count')).to have_content '+1'
diff --git a/spec/features/merge_requests/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 1d52a4659ad..9912e8165e6 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 'Merge Request filtering by Labels', js: true do
+feature 'Merge Request filtering by Labels', :js do
include FilteredSearchHelpers
include MergeRequestHelpers
@@ -12,9 +12,9 @@ feature 'Merge Request filtering by Labels', js: true do
let!(:feature) { create(:label, project: project, title: 'feature') }
let!(:enhancement) { create(:label, project: project, title: 'enhancement') }
- let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "bugfix1") }
- let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "bugfix2") }
- let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "feature1") }
+ let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
+ let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "wip") }
+ let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "improve/awesome") }
before do
mr1.labels << bug
@@ -25,7 +25,7 @@ feature 'Merge Request filtering by Labels', js: true do
mr3.title = "Feature1"
mr3.labels << feature
- project.team << [user, :master]
+ project.add_master(user)
sign_in(user)
visit project_merge_requests_path(project)
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index f0019be86ad..3686131fee4 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -12,7 +12,7 @@ describe 'Filter merge requests' do
let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
before do
- project.team << [user, :master]
+ project.add_master(user)
group.add_developer(user)
sign_in(user)
create(:merge_request, source_project: project, target_project: project)
@@ -170,7 +170,7 @@ describe 'Filter merge requests' do
describe 'filter merge requests by text' do
before do
- create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "bug")
+ create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "wip")
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
@@ -179,7 +179,7 @@ describe 'Filter merge requests' do
title: "Bug 2",
source_project: project,
target_project: project,
- source_branch: "bug2",
+ source_branch: "fix",
milestone: milestone,
author: user,
assignee: user)
@@ -259,12 +259,12 @@ describe 'Filter merge requests' do
end
end
- describe 'filter merge requests and sort', js: true do
+ describe 'filter merge requests and sort', :js do
before do
bug_label = create(:label, project: project, title: 'bug')
- mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "Frontend")
- mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "bug2")
+ mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "wip")
+ mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "fix")
mr1.labels << bug_label
mr2.labels << bug_label
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
index c1b90e5f875..eed95816bdf 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', js: true do
+feature 'Merge requests filter clear button', :js do
include FilteredSearchHelpers
include MergeRequestHelpers
include IssueHelpers
@@ -9,8 +9,8 @@ feature 'Merge requests filter clear button', js: true do
let!(:user) { create(:user) }
let!(:milestone) { create(:milestone, project: project) }
let!(:bug) { create(:label, project: project, name: 'bug')}
- let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
- let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
+ let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "improve/awesome", milestone: milestone, author: user, assignee: user) }
+ let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "fix") }
let(:merge_request_css) { '.merge-request' }
let(:clear_search_css) { '.filtered-search-box .clear-search' }
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index cf30a687df8..e6dc284cba7 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -98,7 +98,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
def change_status(text)
- click_button 'Edit Merge Requests'
+ click_button 'Edit merge requests'
find('#check-all-issues').click
find('.js-issue-status').click
find('.dropdown-menu-status a', text: text).click
@@ -106,7 +106,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
def change_assignee(text)
- click_button 'Edit Merge Requests'
+ click_button 'Edit merge requests'
find('#check-all-issues').click
find('.js-update-assignee').click
wait_for_requests
@@ -119,7 +119,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
def change_milestone(text)
- click_button 'Edit Merge Requests'
+ click_button 'Edit merge requests'
find('#check-all-issues').click
find('.issues-bulk-update .js-milestone-select').click
find('.dropdown-menu-milestone a', text: text).click
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 d62b035b40b..20008b4e7f9 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -24,12 +24,10 @@ describe 'Projects > Merge requests > User lists merge requests' do
milestone: create(:milestone, due_date: '2013-12-12'),
created_at: 2.minutes.ago,
updated_at: 2.minutes.ago)
- # lfs in itself is not a great choice for the title if one wants to match the whole body content later on
- # just think about the scenario when faker generates 'Chester Runolfsson' as the user's name
create(:merge_request,
- title: 'merge_lfs',
+ title: 'merge-test',
source_project: project,
- source_branch: 'merge_lfs',
+ source_branch: 'merge-test',
created_at: 3.minutes.ago,
updated_at: 10.seconds.ago)
end
@@ -38,7 +36,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
expect(current_path).to eq(project_merge_requests_path(project))
- expect(page).to have_content 'merge_lfs'
+ expect(page).to have_content 'merge-test'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
expect(count_merge_requests).to eq(1)
@@ -47,7 +45,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
it 'filters on a specific assignee' do
visit_merge_requests(project, assignee_id: user.id)
- expect(page).not_to have_content 'merge_lfs'
+ expect(page).not_to have_content 'merge-test'
expect(page).to have_content 'fix'
expect(page).to have_content 'markdown'
expect(count_merge_requests).to eq(2)
@@ -57,14 +55,14 @@ describe 'Projects > Merge requests > User lists merge requests' do
visit_merge_requests(project, sort: sort_value_recently_created)
expect(first_merge_request).to include('fix')
- expect(last_merge_request).to include('merge_lfs')
+ expect(last_merge_request).to include('merge-test')
expect(count_merge_requests).to eq(3)
end
it 'sorts by oldest' do
visit_merge_requests(project, sort: sort_value_oldest_created)
- expect(first_merge_request).to include('merge_lfs')
+ expect(first_merge_request).to include('merge-test')
expect(last_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end
@@ -72,7 +70,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
- expect(first_merge_request).to include('merge_lfs')
+ expect(first_merge_request).to include('merge-test')
expect(count_merge_requests).to eq(3)
end
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 74d21822a59..d7cda73ab40 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -75,7 +75,6 @@ describe 'Merge requests > User posts notes', :js do
describe 'editing the note' do
before do
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
end
@@ -104,7 +103,6 @@ describe 'Merge requests > User posts notes', :js do
wait_for_requests
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
@@ -132,7 +130,6 @@ describe 'Merge requests > User posts notes', :js do
describe 'deleting an attachment' do
before do
find('.note').hover
- open_more_actions_dropdown(note)
find('.js-note-edit').click
end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
index 20303359c46..624f13922ed 100644
--- a/spec/features/milestones/show_spec.rb
+++ b/spec/features/milestones/show_spec.rb
@@ -8,7 +8,7 @@ describe 'Milestone show' do
let(:issue_params) { { project: project, assignees: [user], author: user, milestone: milestone, labels: labels } }
before do
- project.add_user(user, :developer)
+ project.add_user(user, :developer)
sign_in(user)
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 4238d25e9ee..9bcd5beabb8 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -20,7 +20,7 @@ feature 'Template Undo Button', js: true do
end
end
- context 'creating a non-matching file' do
+ context 'creating a non-matching file' do
before do
visit project_new_blob_path(project, 'master')
select_file_template_type('LICENSE')
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index c9ba1a8c088..8abd4403065 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Projected Tags', js: true do
+feature 'Protected Tags', js: true do
let(:user) { create(:user, :admin) }
let(:project) { create(:project, :repository) }
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index f1d0905738b..c0c293dee78 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -91,11 +91,7 @@ describe 'Comments on personal snippets', :js do
context 'when editing a note' do
it 'changes the text' do
- open_more_actions_dropdown(snippet_notes[0])
-
- page.within("#notes-list li#note_#{snippet_notes[0].id}") do
- click_on 'Edit comment'
- end
+ find('.js-note-edit').click
page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'new content'
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index c14826df55a..580258f77eb 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -52,8 +52,8 @@ feature 'Task Lists' do
before do
Warden.test_mode!
- project.team << [user, :master]
- project.team << [user2, :guest]
+ project.add_master(user)
+ project.add_guest(user2)
login_as(user)
end
diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb
index 0c063f6d5ee..3a8a1e7de74 100644
--- a/spec/finders/environments_finder_spec.rb
+++ b/spec/finders/environments_finder_spec.rb
@@ -12,7 +12,7 @@ describe EnvironmentsFinder do
context 'tagged deployment' do
before do
- create(:deployment, environment: environment, ref: '1.0', tag: true, sha: project.commit.id)
+ create(:deployment, environment: environment, ref: 'v1.1.0', tag: true, sha: project.commit.id)
end
it 'returns environment when with_tags is set' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 37a5e6b474e..d1efa318d14 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -432,9 +432,7 @@ describe ProjectsHelper do
end
describe '#any_projects?' do
- before do
- create(:project)
- end
+ let!(:project) { create(:project) }
it 'returns true when projects will be returned' do
expect(helper.any_projects?(Project.all)).to eq(true)
@@ -444,6 +442,14 @@ describe ProjectsHelper do
expect(helper.any_projects?(Project.none)).to eq(false)
end
+ it 'returns true when using a non-empty Array' do
+ expect(helper.any_projects?([project])).to eq(true)
+ end
+
+ it 'returns false when using an empty Array' do
+ expect(helper.any_projects?([])).to eq(false)
+ end
+
it 'only executes a single query when a LIMIT is applied' do
relation = Project.limit(1)
recorder = ActiveRecord::QueryRecorder.new do
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index 7a46e47bb15..7968c9425f2 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -7,7 +7,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:prometheus_service, project: project) }
-
+
render_views
before(:all) do
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 0a3c64d5d31..80915c32a74 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -7,7 +7,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
-
render_views
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index 65a7459c5ed..2e81a1b056b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -10,6 +10,7 @@ import {
mousePos,
getHideSubItemsInterval,
documentMouseMove,
+ getHeaderHeight,
} from '~/fly_out_nav';
import bp from '~/breakpoints';
@@ -59,7 +60,7 @@ describe('Fly out sidebar navigation', () => {
describe('getHideSubItemsInterval', () => {
beforeEach(() => {
- el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 50px;"></div>';
+ el.innerHTML = '<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>';
});
it('returns 0 if currentOpenMenu is nil', () => {
@@ -112,6 +113,7 @@ describe('Fly out sidebar navigation', () => {
clientX: el.getBoundingClientRect().left + 20,
clientY: el.getBoundingClientRect().top + 10,
});
+ console.log(el);
expect(
getHideSubItemsInterval(),
@@ -245,7 +247,7 @@ describe('Fly out sidebar navigation', () => {
expect(
subItems.style.transform,
- ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top)}px, 0px)`);
+ ).toBe(`translate3d(0px, ${Math.floor(el.getBoundingClientRect().top) - getHeaderHeight()}px, 0px)`);
});
it('sets is-above when element is above', () => {
diff --git a/spec/javascripts/gpg_badges_spec.js b/spec/javascripts/gpg_badges_spec.js
new file mode 100644
index 00000000000..7a826487bf9
--- /dev/null
+++ b/spec/javascripts/gpg_badges_spec.js
@@ -0,0 +1,48 @@
+import GpgBadges from '~/gpg_badges';
+
+describe('GpgBadges', () => {
+ const dummyCommitSha = 'n0m0rec0ffee';
+ const dummyBadgeHtml = 'dummy html';
+ const dummyResponse = {
+ signatures: [{
+ commit_sha: dummyCommitSha,
+ html: dummyBadgeHtml,
+ }],
+ };
+
+ beforeEach(() => {
+ setFixtures(`
+ <div class="parent-container">
+ <div class="js-loading-gpg-badge" data-commit-sha="${dummyCommitSha}"></div>
+ </div>
+ `);
+ });
+
+ it('displays a loading spinner', () => {
+ spyOn($, 'get').and.returnValue({
+ done() {
+ // intentionally left blank
+ },
+ });
+
+ GpgBadges.fetch();
+
+ expect(document.querySelector('.js-loading-gpg-badge:empty')).toBe(null);
+ const spinners = document.querySelectorAll('.js-loading-gpg-badge i.fa.fa-spinner.fa-spin');
+ expect(spinners.length).toBe(1);
+ });
+
+ it('replaces the loading spinner', () => {
+ spyOn($, 'get').and.returnValue({
+ done(callback) {
+ callback(dummyResponse);
+ },
+ });
+
+ GpgBadges.fetch();
+
+ expect(document.querySelector('.js-loading-gpg-badge')).toBe(null);
+ const parentContainer = document.querySelector('.parent-container');
+ expect(parentContainer.innerHTML.trim()).toEqual(dummyBadgeHtml);
+ });
+});
diff --git a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js b/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js
index 6120d224ac0..ed481cb60a1 100644
--- a/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js
+++ b/spec/javascripts/pipeline_schedules/pipeline_schedule_callout_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
-import PipelineSchedulesCallout from '~/pipeline_schedules/components/pipeline_schedules_callout';
+import PipelineSchedulesCallout from '~/pipeline_schedules/components/pipeline_schedules_callout.vue';
const PipelineSchedulesCalloutComponent = Vue.extend(PipelineSchedulesCallout);
const cookieKey = 'pipeline_schedules_callout_dismissed';
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index db2b7d51626..249a2f36fcd 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -1,57 +1,57 @@
import Vue from 'vue';
import repoCommitSection from '~/repo/components/repo_commit_section.vue';
import RepoStore from '~/repo/stores/repo_store';
-import RepoHelper from '~/repo/helpers/repo_helper';
import Api from '~/api';
describe('RepoCommitSection', () => {
const branch = 'master';
const projectUrl = 'projectUrl';
- const openedFiles = [{
+ const changedFiles = [{
id: 0,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file0.ext`,
+ path: 'dir/file0.ext',
newContent: 'a',
}, {
id: 1,
changed: true,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file1.ext`,
+ path: 'dir/file1.ext',
newContent: 'b',
- }, {
+ }];
+ const openedFiles = changedFiles.concat([{
id: 2,
url: `/namespace/${projectUrl}/blob/${branch}/dir/file2.ext`,
+ path: 'dir/file2.ext',
changed: false,
- }];
+ }]);
RepoStore.projectUrl = projectUrl;
- function createComponent() {
+ function createComponent(el) {
const RepoCommitSection = Vue.extend(repoCommitSection);
- return new RepoCommitSection().$mount();
+ return new RepoCommitSection().$mount(el);
}
it('renders a commit section', () => {
RepoStore.isCommitable = true;
+ RepoStore.currentBranch = branch;
RepoStore.targetBranch = branch;
RepoStore.openedFiles = openedFiles;
- spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
-
const vm = createComponent();
- const changedFiles = [...vm.$el.querySelectorAll('.changed-files > li')];
+ const changedFileElements = [...vm.$el.querySelectorAll('.changed-files > li')];
const commitMessage = vm.$el.querySelector('#commit-message');
- const submitCommit = vm.$el.querySelector('.submit-commit');
+ const submitCommit = vm.$refs.submitCommit;
const targetBranch = vm.$el.querySelector('.target-branch');
expect(vm.$el.querySelector(':scope > form')).toBeTruthy();
- expect(vm.$el.querySelector('.staged-files').textContent).toEqual('Staged files (2)');
- expect(changedFiles.length).toEqual(2);
+ expect(vm.$el.querySelector('.staged-files').textContent.trim()).toEqual('Staged files (2)');
+ expect(changedFileElements.length).toEqual(2);
- changedFiles.forEach((changedFile, i) => {
- const filePath = RepoHelper.getFilePathFromFullPath(openedFiles[i].url, branch);
-
- expect(changedFile.textContent).toEqual(filePath);
+ changedFileElements.forEach((changedFile, i) => {
+ expect(changedFile.textContent.trim()).toEqual(changedFiles[i].path);
});
expect(commitMessage.tagName).toEqual('TEXTAREA');
@@ -59,9 +59,9 @@ describe('RepoCommitSection', () => {
expect(submitCommit.type).toEqual('submit');
expect(submitCommit.disabled).toBeTruthy();
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeFalsy();
- expect(vm.$el.querySelector('.commit-summary').textContent).toEqual('Commit 2 files');
- expect(targetBranch.querySelector(':scope > label').textContent).toEqual('Target branch');
- expect(targetBranch.querySelector('.help-block').textContent).toEqual(branch);
+ expect(vm.$el.querySelector('.commit-summary').textContent.trim()).toEqual('Commit 2 files');
+ expect(targetBranch.querySelector(':scope > label').textContent.trim()).toEqual('Target branch');
+ expect(targetBranch.querySelector('.help-block').textContent.trim()).toEqual(branch);
});
it('does not render if not isCommitable', () => {
@@ -89,14 +89,20 @@ describe('RepoCommitSection', () => {
const projectId = 'projectId';
const commitMessage = 'commitMessage';
RepoStore.isCommitable = true;
+ RepoStore.currentBranch = branch;
+ RepoStore.targetBranch = branch;
RepoStore.openedFiles = openedFiles;
RepoStore.projectId = projectId;
- spyOn(RepoHelper, 'getBranch').and.returnValue(branch);
+ // We need to append to body to get form `submit` events working
+ // Otherwise we run into, "Form submission canceled because the form is not connected"
+ // See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ const el = document.createElement('div');
+ document.body.appendChild(el);
- const vm = createComponent();
+ const vm = createComponent(el);
const commitMessageEl = vm.$el.querySelector('#commit-message');
- const submitCommit = vm.$el.querySelector('.submit-commit');
+ const submitCommit = vm.$refs.submitCommit;
vm.commitMessage = commitMessage;
@@ -124,10 +130,8 @@ describe('RepoCommitSection', () => {
expect(actions[1].action).toEqual('update');
expect(actions[0].content).toEqual(openedFiles[0].newContent);
expect(actions[1].content).toEqual(openedFiles[1].newContent);
- expect(actions[0].file_path)
- .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[0].url, branch));
- expect(actions[1].file_path)
- .toEqual(RepoHelper.getFilePathFromFullPath(openedFiles[1].url, branch));
+ expect(actions[0].file_path).toEqual(openedFiles[0].path);
+ expect(actions[1].file_path).toEqual(openedFiles[1].path);
done();
});
@@ -140,7 +144,6 @@ describe('RepoCommitSection', () => {
const vm = {
submitCommitsLoading: true,
changedFiles: new Array(10),
- openedFiles: new Array(10),
commitMessage: 'commitMessage',
editMode: true,
};
@@ -149,7 +152,6 @@ describe('RepoCommitSection', () => {
expect(vm.submitCommitsLoading).toEqual(false);
expect(vm.changedFiles).toEqual([]);
- expect(vm.openedFiles).toEqual([]);
expect(vm.commitMessage).toEqual('');
expect(vm.editMode).toEqual(false);
});
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
index df2f9697acc..29dc2d21e4b 100644
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ b/spec/javascripts/repo/components/repo_edit_button_spec.js
@@ -12,18 +12,22 @@ describe('RepoEditButton', () => {
it('renders an edit button that toggles the view state', (done) => {
RepoStore.isCommitable = true;
RepoStore.changedFiles = [];
+ RepoStore.binary = false;
+ RepoStore.openedFiles = [{}, {}];
const vm = createComponent();
expect(vm.$el.tagName).toEqual('BUTTON');
expect(vm.$el.textContent).toMatch('Edit');
- spyOn(vm, 'editClicked').and.callThrough();
+ spyOn(vm, 'editCancelClicked').and.callThrough();
+ spyOn(vm, 'toggleProjectRefsForm');
vm.$el.click();
Vue.nextTick(() => {
- expect(vm.editClicked).toHaveBeenCalled();
+ expect(vm.editCancelClicked).toHaveBeenCalled();
+ expect(vm.toggleProjectRefsForm).toHaveBeenCalled();
expect(vm.$el.textContent).toMatch('Cancel edit');
done();
});
@@ -38,14 +42,10 @@ describe('RepoEditButton', () => {
});
describe('methods', () => {
- describe('editClicked', () => {
- it('sets dialog to open when there are changedFiles', () => {
+ describe('editCancelClicked', () => {
+ it('sets dialog to open when there are changedFiles');
- });
-
- it('toggles editMode and calls toggleBlobView', () => {
-
- });
+ it('toggles editMode and calls toggleBlobView');
});
});
});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 35e0c995163..85d55d171f9 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -1,26 +1,49 @@
import Vue from 'vue';
import repoEditor from '~/repo/components/repo_editor.vue';
-import RepoStore from '~/repo/stores/repo_store';
describe('RepoEditor', () => {
- function createComponent() {
+ beforeEach(() => {
const RepoEditor = Vue.extend(repoEditor);
- return new RepoEditor().$mount();
- }
+ this.vm = new RepoEditor().$mount();
+ });
+
+ it('renders an ide container', (done) => {
+ this.vm.openedFiles = ['idiidid'];
+ this.vm.binary = false;
- it('renders an ide container', () => {
- const monacoInstance = jasmine.createSpyObj('monacoInstance', ['onMouseUp', 'onKeyUp', 'setModel', 'updateOptions']);
- const monaco = {
- editor: jasmine.createSpyObj('editor', ['create']),
- };
- RepoStore.monaco = monaco;
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(false);
+ expect(this.vm.$el.id).toEqual('ide');
+ expect(this.vm.$el.tagName).toBe('DIV');
+ done();
+ });
+ });
- monaco.editor.create.and.returnValue(monacoInstance);
- spyOn(repoEditor.watch, 'blobRaw');
+ describe('when there are no open files', () => {
+ it('does not render the ide', (done) => {
+ this.vm.openedFiles = [];
+
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(true);
+ expect(this.vm.$el.tagName).not.toBeDefined();
+ done();
+ });
+ });
+ });
- const vm = createComponent();
+ describe('when open file is binary and not raw', () => {
+ it('does not render the IDE', (done) => {
+ this.vm.binary = true;
+ this.vm.activeFile = {
+ raw: false,
+ };
- expect(vm.$el.id).toEqual('ide');
+ Vue.nextTick(() => {
+ expect(this.vm.shouldHideEditor).toBe(true);
+ expect(this.vm.$el.tagName).not.toBeDefined();
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
index e1f25e4485f..dfab51710c3 100644
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js
@@ -23,6 +23,7 @@ describe('RepoFileButtons', () => {
RepoStore.activeFile = activeFile;
RepoStore.activeFileLabel = activeFileLabel;
RepoStore.editMode = true;
+ RepoStore.binary = false;
const vm = createComponent();
const raw = vm.$el.querySelector('.raw');
@@ -31,13 +32,13 @@ describe('RepoFileButtons', () => {
expect(vm.$el.id).toEqual('repo-file-buttons');
expect(raw.href).toMatch(`/${activeFile.raw_path}`);
- expect(raw.textContent).toEqual('Raw');
+ expect(raw.textContent.trim()).toEqual('Raw');
expect(blame.href).toMatch(`/${activeFile.blame_path}`);
- expect(blame.textContent).toEqual('Blame');
+ expect(blame.textContent.trim()).toEqual('Blame');
expect(history.href).toMatch(`/${activeFile.commits_path}`);
- expect(history.textContent).toEqual('History');
- expect(vm.$el.querySelector('.permalink').textContent).toEqual('Permalink');
- expect(vm.$el.querySelector('.preview').textContent).toEqual(activeFileLabel);
+ expect(history.textContent.trim()).toEqual('History');
+ expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
+ expect(vm.$el.querySelector('.preview').textContent.trim()).toEqual(activeFileLabel);
});
it('triggers rawPreviewToggle on preview click', () => {
@@ -71,12 +72,4 @@ describe('RepoFileButtons', () => {
expect(vm.$el.querySelector('.preview')).toBeFalsy();
});
-
- it('does not render if not isMini', () => {
- RepoStore.openedFiles = [];
-
- const vm = createComponent();
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index 90616ae13ca..518a2d25ecf 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -39,9 +39,9 @@ describe('RepoFile', () => {
expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px');
expect(name.title).toEqual(file.url);
expect(name.href).toMatch(`/${file.url}`);
- expect(name.textContent).toEqual(file.name);
- expect(vm.$el.querySelector('.commit-message').textContent).toBe(file.lastCommitMessage);
- expect(vm.$el.querySelector('.commit-update').textContent).toBe(updated);
+ expect(name.textContent.trim()).toEqual(file.name);
+ expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(file.lastCommitMessage);
+ expect(vm.$el.querySelector('.commit-update').textContent.trim()).toBe(updated);
expect(fileIcon.classList.contains(file.icon)).toBeTruthy();
expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`);
});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
index d84f4c5609e..a030314d749 100644
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ b/spec/javascripts/repo/components/repo_loading_file_spec.js
@@ -13,7 +13,7 @@ describe('RepoLoadingFile', () => {
function assertLines(lines) {
lines.forEach((line, n) => {
const index = n + 1;
- expect(line.classList.contains(`line-of-code-${index}`)).toBeTruthy();
+ expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
});
}
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js
index 0d216c9c026..abcff8e537e 100644
--- a/spec/javascripts/repo/components/repo_sidebar_spec.js
+++ b/spec/javascripts/repo/components/repo_sidebar_spec.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import Helper from '~/repo/helpers/repo_helper';
+import RepoService from '~/repo/services/repo_service';
import RepoStore from '~/repo/stores/repo_store';
import repoSidebar from '~/repo/components/repo_sidebar.vue';
@@ -13,6 +15,7 @@ describe('RepoSidebar', () => {
RepoStore.files = [{
id: 0,
}];
+ RepoStore.openedFiles = [];
const vm = createComponent();
const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody');
@@ -58,4 +61,51 @@ describe('RepoSidebar', () => {
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
});
+
+ describe('methods', () => {
+ describe('fileClicked', () => {
+ it('should fetch data for new file', () => {
+ spyOn(Helper, 'getContent').and.callThrough();
+ const file1 = {
+ id: 0,
+ url: '',
+ };
+ RepoStore.files = [file1];
+ RepoStore.isRoot = true;
+ const vm = createComponent();
+
+ vm.fileClicked(file1);
+
+ expect(Helper.getContent).toHaveBeenCalledWith(file1);
+ });
+
+ it('should hide files in directory if already open', () => {
+ spyOn(RepoStore, 'removeChildFilesOfTree').and.callThrough();
+ const file1 = {
+ id: 0,
+ type: 'tree',
+ url: '',
+ opened: true,
+ };
+ RepoStore.files = [file1];
+ RepoStore.isRoot = true;
+ const vm = createComponent();
+
+ vm.fileClicked(file1);
+
+ expect(RepoStore.removeChildFilesOfTree).toHaveBeenCalledWith(file1);
+ });
+ });
+
+ describe('goToPreviousDirectoryClicked', () => {
+ it('should hide files in directory if already open', () => {
+ const prevUrl = 'foo/bar';
+ const vm = createComponent();
+
+ vm.goToPreviousDirectoryClicked(prevUrl);
+
+ expect(RepoService.url).toEqual(prevUrl);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index f3572804b4a..d2a790ad73a 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -12,7 +12,6 @@ describe('RepoTab', () => {
it('renders a close link and a name link', () => {
const tab = {
- loading: false,
url: 'url',
name: 'name',
};
@@ -22,38 +21,21 @@ describe('RepoTab', () => {
const close = vm.$el.querySelector('.close');
const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
- spyOn(vm, 'xClicked');
+ spyOn(vm, 'closeTab');
spyOn(vm, 'tabClicked');
expect(close.querySelector('.fa-times')).toBeTruthy();
- expect(name.textContent).toEqual(tab.name);
+ expect(name.textContent.trim()).toEqual(tab.name);
close.click();
name.click();
- expect(vm.xClicked).toHaveBeenCalledWith(tab);
+ expect(vm.closeTab).toHaveBeenCalledWith(tab);
expect(vm.tabClicked).toHaveBeenCalledWith(tab);
});
- it('renders a spinner if tab is loading', () => {
- const tab = {
- loading: true,
- url: 'url',
- };
- const vm = createComponent({
- tab,
- });
- const close = vm.$el.querySelector('.close');
- const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
-
- expect(close).toBeFalsy();
- expect(name).toBeFalsy();
- expect(vm.$el.querySelector('.fa.fa-spinner.fa-spin')).toBeTruthy();
- });
-
it('renders an fa-circle icon if tab is changed', () => {
const tab = {
- loading: false,
url: 'url',
name: 'name',
changed: true,
@@ -66,22 +48,22 @@ describe('RepoTab', () => {
});
describe('methods', () => {
- describe('xClicked', () => {
+ describe('closeTab', () => {
const vm = jasmine.createSpyObj('vm', ['$emit']);
it('returns undefined and does not $emit if file is changed', () => {
const file = { changed: true };
- const returnVal = repoTab.methods.xClicked.call(vm, file);
+ const returnVal = repoTab.methods.closeTab.call(vm, file);
expect(returnVal).toBeUndefined();
expect(vm.$emit).not.toHaveBeenCalled();
});
- it('$emits xclicked event with file obj', () => {
+ it('$emits tabclosed event with file obj', () => {
const file = { changed: false };
- repoTab.methods.xClicked.call(vm, file);
+ repoTab.methods.closeTab.call(vm, file);
- expect(vm.$emit).toHaveBeenCalledWith('xclicked', file);
+ expect(vm.$emit).toHaveBeenCalledWith('tabclosed', file);
});
});
});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index fdb12cfc00f..a02b54efafc 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -18,44 +18,25 @@ describe('RepoTabs', () => {
it('renders a list of tabs', () => {
RepoStore.openedFiles = openedFiles;
- RepoStore.tabsOverflow = true;
const vm = createComponent();
const tabs = [...vm.$el.querySelectorAll(':scope > li')];
expect(vm.$el.id).toEqual('tabs');
- expect(vm.$el.classList.contains('overflown')).toBeTruthy();
expect(tabs.length).toEqual(3);
expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy();
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
});
- it('does not render a tabs list if not isMini', () => {
- RepoStore.openedFiles = [];
-
- const vm = createComponent();
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
-
- it('does not apply overflown class if not tabsOverflow', () => {
- RepoStore.openedFiles = openedFiles;
- RepoStore.tabsOverflow = false;
-
- const vm = createComponent();
-
- expect(vm.$el.classList.contains('overflown')).toBeFalsy();
- });
-
describe('methods', () => {
- describe('xClicked', () => {
+ describe('tabClosed', () => {
it('calls removeFromOpenedFiles with file obj', () => {
const file = {};
spyOn(RepoStore, 'removeFromOpenedFiles');
- repoTabs.methods.xClicked(file);
+ repoTabs.methods.tabClosed(file);
expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file);
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 90eac1ed1ab..2e16adffb5b 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -41,7 +41,7 @@ describe('Confidential Issue Sidebar Block', () => {
).toBe(true);
expect(
- vm2.$el.innerHTML.includes('None'),
+ vm2.$el.innerHTML.includes('This issue is not confidential'),
).toBe(true);
});
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index ebe5af56160..e5555546fa8 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -295,7 +295,7 @@ describe Gitlab::Ci::Trace::Stream do
end
context 'malicious regexp' do
- let(:data) { malicious_text }
+ let(:data) { malicious_text }
let(:regex) { malicious_regexp }
include_examples 'malicious regexp'
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 854aaa34c73..0560c47f03f 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -6,10 +6,10 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:user) { create(:user, :admin) }
let(:start_time_attrs) { Issue.arel_table[:created_at] }
let(:end_time_attrs) { [Issue::Metrics.arel_table[:first_associated_with_milestone_at]] }
- let(:options) do
+ let(:options) do
{ start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs,
- from: 30.days.ago }
+ from: 30.days.ago }
end
subject do
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index c531d4b055f..ac33cd8a2c9 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -310,8 +310,8 @@ describe Gitlab::Git::Commit, seed_helper: true do
commits.map(&:id)
end
- it 'has 33 elements' do
- expect(subject.size).to eq(33)
+ it 'has 34 elements' do
+ expect(subject.size).to eq(34)
end
it 'includes the expected commits' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 8d24d6f55b8..4ef5d9070a2 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -289,7 +289,13 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(submodule_url('six')).to eq('git://github.com/randx/six.git') }
end
- context 'no submodules at commit' do
+ context 'no .gitmodules at commit' do
+ let(:ref) { '9596bc54a6f0c0c98248fe97077eb5ccf48a98d0' }
+
+ it { expect(submodule_url('six')).to eq(nil) }
+ end
+
+ context 'no gitlink entry' do
let(:ref) { '6d39438' }
it { expect(submodule_url('six')).to eq(nil) }
@@ -986,7 +992,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#branch_count' do
it 'returns the number of branches' do
- expect(repository.branch_count).to eq(9)
+ expect(repository.branch_count).to eq(10)
end
end
diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb
index f5fd5a96bc9..d643dc5342d 100644
--- a/spec/lib/gitlab/key_fingerprint_spec.rb
+++ b/spec/lib/gitlab/key_fingerprint_spec.rb
@@ -30,8 +30,8 @@ describe Gitlab::KeyFingerprint, lib: true do
MD5_FINGERPRINTS = {
rsa: '06:b2:8a:92:df:0e:11:2c:ca:7b:8f:a4:ba:6e:4b:fd',
- ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
- ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
+ ecdsa: '45:ff:5b:98:9a:b6:8a:41:13:c1:30:8b:09:5e:7b:4e',
+ ed25519: '2e:65:6a:c8:cf:bf:b2:8b:9a:bd:6d:9f:11:5c:12:16',
dss: '57:98:86:02:5f:9c:f4:9b:ad:5a:1e:51:92:0e:fd:2b'
}.freeze
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 57a91193004..8370adf9211 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -4,8 +4,8 @@ describe Gitlab::LDAP::AuthHash do
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
- uid: '123456',
- provider: 'ldapmain',
+ uid: '123456',
+ provider: 'ldapmain',
info: info,
extra: {
raw_info: raw_info
@@ -33,11 +33,11 @@ describe Gitlab::LDAP::AuthHash do
context "without overridden attributes" do
it "has the correct username" do
- expect(auth_hash.username).to eq("123456")
+ expect(auth_hash.username).to eq("123456")
end
it "has the correct name" do
- expect(auth_hash.name).to eq("Smith, J.")
+ expect(auth_hash.name).to eq("Smith, J.")
end
end
@@ -54,11 +54,11 @@ describe Gitlab::LDAP::AuthHash do
end
it "has the correct username" do
- expect(auth_hash.username).to eq("johnsmith@example.com")
+ expect(auth_hash.username).to eq("johnsmith@example.com")
end
it "has the correct name" do
- expect(auth_hash.name).to eq("John Smith")
+ expect(auth_hash.name).to eq("John Smith")
end
end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 15edb820908..2cf0f7516de 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -481,7 +481,7 @@ describe Gitlab::OAuth::User do
email: 'admin@othermail.com'
}
end
-
+
it 'generates the username with a counter' do
expect(gl_user.username).to eq('admin1')
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 12e75cdd5d0..d19bd611919 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -4,7 +4,9 @@ describe Gitlab::ProjectTemplate do
describe '.all' do
it 'returns a all templates' do
expected = [
- described_class.new('rails', 'Ruby on Rails')
+ described_class.new('rails', 'Ruby on Rails'),
+ described_class.new('spring', 'Spring'),
+ described_class.new('express', 'NodeJS Express')
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index d7df4e35c31..5589db92b1d 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
queries: [{ query_range: 'query_range_empty' }]
- group: group_b
priority: 1
- metrics:
+ metrics:
- title: title
required_metrics: ['metric_a']
weight: 1
@@ -148,7 +148,7 @@ describe Gitlab::Prometheus::AdditionalMetricsParser do
- group: group_a
priority: 1
metrics:
- - title:
+ - title:
required_metrics: []
weight: 1
queries: []
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index e1becd0a614..0c22a0d62cc 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -17,4 +17,11 @@ describe Gitlab::Redis::Wrapper do
let(:class_redis_url) { Gitlab::Redis::Wrapper::DEFAULT_REDIS_URL }
include_examples "redis_shared_examples"
+
+ describe '.config_file_path' do
+ it 'returns the absolute path to the configuration file' do
+ expect(described_class.config_file_path('foo.yml'))
+ .to eq Rails.root.join('config', 'foo.yml').to_s
+ end
+ end
end
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 2345874cf10..cfadee0bcf5 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -94,28 +94,41 @@ describe Gitlab::Shell do
end
describe 'projects commands' do
- let(:projects_path) { 'tmp/tests/shell-projects-test/bin/gitlab-projects' }
+ let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
+ let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
+ let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }
before do
- allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-projects-test')
+ allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
+ allow(Gitlab.config.gitlab_shell).to receive(:hooks_path).and_return(gitlab_shell_hooks_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
describe '#add_repository' do
- it 'returns true when the command succeeds' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
- nil, popen_vars).and_return([nil, 0])
+ it 'creates a repository' do
+ created_path = File.join(TestEnv.repos_path, 'project', 'path.git')
+ hooks_path = File.join(created_path, 'hooks')
+
+ begin
+ result = gitlab_shell.add_repository(TestEnv.repos_path, 'project/path')
+
+ repo_stat = File.stat(created_path) rescue nil
+ hooks_stat = File.lstat(hooks_path) rescue nil
+ hooks_dir = File.realpath(hooks_path)
+ ensure
+ FileUtils.rm_rf(created_path)
+ end
- expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be true
+ expect(result).to be_truthy
+ expect(repo_stat.mode & 0o777).to eq(0o770)
+ expect(hooks_stat.symlink?).to be_truthy
+ expect(hooks_dir).to eq(gitlab_shell_hooks_path)
end
it 'returns false when the command fails' do
- expect(Gitlab::Popen).to receive(:popen)
- .with([projects_path, 'add-project', 'current/storage', 'project/path.git'],
- nil, popen_vars).and_return(["error", 1])
+ expect(FileUtils).to receive(:mkdir_p).and_raise(Errno::EEXIST)
- expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be false
+ expect(gitlab_shell.add_repository('current/storage', 'project/path')).to be_falsy
end
end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 111c873f79c..92787bb262e 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,7 +1,21 @@
require 'spec_helper'
describe Gitlab::Utils do
- delegate :to_boolean, :boolean_to_yes_no, to: :described_class
+ delegate :to_boolean, :boolean_to_yes_no, :slugify, to: :described_class
+
+ describe '.slugify' do
+ {
+ 'TEST' => 'test',
+ 'project_with_underscores' => 'project-with-underscores',
+ 'namespace/project' => 'namespace-project',
+ 'a' * 70 => 'a' * 63,
+ 'test_trailing_' => 'test-trailing'
+ }.each do |original, expected|
+ it "slugifies #{original} to #{expected}" do
+ expect(slugify(original)).to eq(expected)
+ end
+ end
+ end
describe '.to_boolean' do
it 'accepts booleans' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index e78892d4232..b66afafa174 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -202,7 +202,6 @@ describe Gitlab::Workhorse do
context 'when Gitaly is enabled' do
let(:gitaly_params) do
{
- GitalyAddress: Gitlab::GitalyClient.address('default'),
GitalyServer: {
address: Gitlab::GitalyClient.address('default'),
token: Gitlab::GitalyClient.token('default')
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index cfe1ca481b2..81366d15b34 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -10,7 +10,7 @@ describe MigrateOldArtifacts do
before do
allow(Gitlab.config.artifacts).to receive(:path).and_return(directory)
end
-
+
after do
FileUtils.remove_entry_secure(directory)
end
@@ -95,7 +95,7 @@ describe MigrateOldArtifacts do
FileUtils.copy(
Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
File.join(legacy_path(build), "ci_build_artifacts.zip"))
-
+
FileUtils.copy(
Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
File.join(legacy_path(build), "ci_build_artifacts_metadata.gz"))
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
new file mode 100644
index 00000000000..e393374028f
--- /dev/null
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb')
+
+describe RemoveDuplicateMrEvents, truncate: true do
+ let(:migration) { described_class.new }
+
+ describe '#up' do
+ let(:user) { create(:user) }
+ let(:merge_requests) { create_list(:merge_request, 2) }
+ let(:issue) { create(:issue) }
+ let!(:events) do
+ [
+ create(:event, :created, author: user, target: merge_requests.first),
+ create(:event, :created, author: user, target: merge_requests.first),
+ create(:event, :updated, author: user, target: merge_requests.first),
+ create(:event, :created, author: user, target: merge_requests.second),
+ create(:event, :created, author: user, target: issue),
+ create(:event, :created, author: user, target: issue)
+ ]
+ end
+
+ it 'removes duplicated merge request create records' do
+ expect { migration.up }.to change { Event.count }.from(6).to(5)
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 86afa856ea7..767f0ad9e65 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1220,7 +1220,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
{ key: 'CI_PROJECT_NAME', value: project.path, public: true },
{ key: 'CI_PROJECT_PATH', value: project.full_path, public: true },
- { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path.parameterize, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
{ 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 },
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 1a00c50690c..69286eff984 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -315,6 +315,20 @@ describe Namespace do
end
end
+ describe '#self_and_ancestors', :nested_groups do
+ let(:group) { create(:group) }
+ let(:nested_group) { create(:group, parent: group) }
+ let(:deep_nested_group) { create(:group, parent: nested_group) }
+ let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+
+ it 'returns the correct ancestors' do
+ expect(very_deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
+ expect(deep_nested_group.self_and_ancestors).to contain_exactly(group, nested_group, deep_nested_group)
+ expect(nested_group.self_and_ancestors).to contain_exactly(group, nested_group)
+ expect(group.self_and_ancestors).to contain_exactly(group)
+ end
+ end
+
describe '#descendants', :nested_groups do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
@@ -331,6 +345,22 @@ describe Namespace do
end
end
+ describe '#self_and_descendants', :nested_groups do
+ let!(:group) { create(:group, path: 'git_lab') }
+ let!(:nested_group) { create(:group, parent: group) }
+ let!(:deep_nested_group) { create(:group, parent: nested_group) }
+ let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
+ let!(:another_group) { create(:group, path: 'gitllab') }
+ let!(:another_group_nested) { create(:group, path: 'foo', parent: another_group) }
+
+ it 'returns the correct descendants' do
+ expect(very_deep_nested_group.self_and_descendants).to contain_exactly(very_deep_nested_group)
+ expect(deep_nested_group.self_and_descendants).to contain_exactly(deep_nested_group, very_deep_nested_group)
+ expect(nested_group.self_and_descendants).to contain_exactly(nested_group, deep_nested_group, very_deep_nested_group)
+ expect(group.self_and_descendants).to contain_exactly(group, nested_group, deep_nested_group, very_deep_nested_group)
+ end
+ end
+
describe '#users_with_descendants', :nested_groups do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d9ab44dc49f..eba71ba2f72 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2310,4 +2310,14 @@ describe Project do
end
end
end
+
+ describe '#forks_count' do
+ it 'returns the number of forks' do
+ project = build(:project)
+
+ allow(project.forks).to receive(:count).and_return(1)
+
+ expect(project.forks_count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 62f40c12c0a..4926d5d6c49 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -961,6 +961,27 @@ describe Repository, models: true do
end
end
+ context 'when temporary ref failed to be created from other project' do
+ let(:target_project) { create(:project, :empty_repo) }
+
+ before do
+ expect(target_project.repository).to receive(:run_git)
+ end
+
+ it 'raises Rugged::ReferenceError' do
+ raise_reference_error = raise_error(Rugged::ReferenceError) do |err|
+ expect(err.cause).to be_nil
+ end
+
+ expect do
+ GitOperationService.new(user, target_project.repository)
+ .with_branch('feature',
+ start_project: project,
+ &:itself)
+ end.to raise_reference_error
+ end
+ end
+
context 'when the update adds more than one commit' do
let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 992a6e8d76a..dafe3f466a2 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -16,11 +16,13 @@ describe API::Commits do
end
describe 'GET /projects/:id/repository/commits' do
- context 'authorized user' do
+ let(:route) { "/projects/#{project_id}/repository/commits" }
+
+ shared_examples_for 'project commits' do
it "returns project commits" do
commit = project.repository.commit
- get api("/projects/#{project_id}/repository/commits", user)
+ get api(route, current_user)
expect(response).to have_http_status(200)
expect(response).to match_response_schema('public_api/v4/commits')
@@ -32,7 +34,7 @@ describe API::Commits do
it 'include correct pagination headers' do
commit_count = project.repository.count_commits(ref: 'master').to_s
- get api("/projects/#{project_id}/repository/commits", user)
+ get api(route, current_user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count)
@@ -40,140 +42,151 @@ describe API::Commits do
end
end
- context "unauthorized user" do
- it "does not return project commits" do
- get api("/projects/#{project_id}/repository/commits")
+ context 'when unauthenticated', 'and project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ it_behaves_like 'project commits'
+ end
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
end
end
- context "since optional parameter" do
- it "returns project commits since provided parameter" do
- commits = project.repository.commits("master")
- after = commits.second.created_at
+ context 'when authenticated', 'as a master' do
+ let(:current_user) { user }
- get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+ it_behaves_like 'project commits'
- expect(json_response.size).to eq 2
- expect(json_response.first["id"]).to eq(commits.first.id)
- expect(json_response.second["id"]).to eq(commits.second.id)
- end
+ context "since optional parameter" do
+ it "returns project commits since provided parameter" do
+ commits = project.repository.commits("master")
+ after = commits.second.created_at
- it 'include correct pagination headers' do
- commits = project.repository.commits("master")
- after = commits.second.created_at
- commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
- get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+ expect(json_response.size).to eq 2
+ expect(json_response.first["id"]).to eq(commits.first.id)
+ expect(json_response.second["id"]).to eq(commits.second.id)
+ end
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ after = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', after: after).to_s
+
+ get api("/projects/#{project_id}/repository/commits?since=#{after.utc.iso8601}", user)
+
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context "until optional parameter" do
- it "returns project commits until provided parameter" do
- commits = project.repository.commits("master")
- before = commits.second.created_at
+ context "until optional parameter" do
+ it "returns project commits until provided parameter" do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
- get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- if commits.size >= 20
- expect(json_response.size).to eq(20)
- else
- expect(json_response.size).to eq(commits.size - 1)
- end
+ if commits.size >= 20
+ expect(json_response.size).to eq(20)
+ else
+ expect(json_response.size).to eq(commits.size - 1)
+ end
- expect(json_response.first["id"]).to eq(commits.second.id)
- expect(json_response.second["id"]).to eq(commits.third.id)
- end
+ expect(json_response.first["id"]).to eq(commits.second.id)
+ expect(json_response.second["id"]).to eq(commits.third.id)
+ end
- it 'include correct pagination headers' do
- commits = project.repository.commits("master")
- before = commits.second.created_at
- commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
+ it 'include correct pagination headers' do
+ commits = project.repository.commits("master")
+ before = commits.second.created_at
+ commit_count = project.repository.count_commits(ref: 'master', before: before).to_s
- get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
+ get api("/projects/#{project_id}/repository/commits?until=#{before.utc.iso8601}", user)
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context "invalid xmlschema date parameters" do
- it "returns an invalid parameter error message" do
- get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
+ context "invalid xmlschema date parameters" do
+ it "returns an invalid parameter error message" do
+ get api("/projects/#{project_id}/repository/commits?since=invalid-date", user)
- expect(response).to have_http_status(400)
- expect(json_response['error']).to eq('since is invalid')
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('since is invalid')
+ end
end
- end
- context "path optional parameter" do
- it "returns project commits matching provided path parameter" do
- path = 'files/ruby/popen.rb'
- commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
+ context "path optional parameter" do
+ it "returns project commits matching provided path parameter" do
+ path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
- expect(json_response.size).to eq(3)
- expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- end
+ expect(json_response.size).to eq(3)
+ expect(json_response.first["id"]).to eq("570e7b2abdd848b95f2f578043fc23bd6f6fd24d")
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ end
- it 'include correct pagination headers' do
- path = 'files/ruby/popen.rb'
- commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
+ it 'include correct pagination headers' do
+ path = 'files/ruby/popen.rb'
+ commit_count = project.repository.count_commits(ref: 'master', path: path).to_s
- get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
+ get api("/projects/#{project_id}/repository/commits?path=#{path}", user)
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eql('1')
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eql('1')
+ end
end
- end
- context 'with pagination params' do
- let(:page) { 1 }
- let(:per_page) { 5 }
- let(:ref_name) { 'master' }
- let!(:request) do
- get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
- end
+ context 'with pagination params' do
+ let(:page) { 1 }
+ let(:per_page) { 5 }
+ let(:ref_name) { 'master' }
+ let!(:request) do
+ get api("/projects/#{project_id}/repository/commits?page=#{page}&per_page=#{per_page}&ref_name=#{ref_name}", user)
+ end
- it 'returns correct headers' do
- commit_count = project.repository.count_commits(ref: ref_name).to_s
+ it 'returns correct headers' do
+ commit_count = project.repository.count_commits(ref: ref_name).to_s
- expect(response).to include_pagination_headers
- expect(response.headers['X-Total']).to eq(commit_count)
- expect(response.headers['X-Page']).to eq('1')
- expect(response.headers['Link']).to match(/page=1&per_page=5/)
- expect(response.headers['Link']).to match(/page=2&per_page=5/)
- end
+ expect(response).to include_pagination_headers
+ expect(response.headers['X-Total']).to eq(commit_count)
+ expect(response.headers['X-Page']).to eq('1')
+ expect(response.headers['Link']).to match(/page=1&per_page=5/)
+ expect(response.headers['Link']).to match(/page=2&per_page=5/)
+ end
- context 'viewing the first page' do
- it 'returns the first 5 commits' do
- commit = project.repository.commit
+ context 'viewing the first page' do
+ it 'returns the first 5 commits' do
+ commit = project.repository.commit
- expect(json_response.size).to eq(per_page)
- expect(json_response.first['id']).to eq(commit.id)
- expect(response.headers['X-Page']).to eq('1')
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('1')
+ end
end
- end
- context 'viewing the third page' do
- let(:page) { 3 }
+ context 'viewing the third page' do
+ let(:page) { 3 }
- it 'returns the third 5 commits' do
- commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
+ it 'returns the third 5 commits' do
+ commit = project.repository.commits('HEAD', offset: (page - 1) * per_page).first
- expect(json_response.size).to eq(per_page)
- expect(json_response.first['id']).to eq(commit.id)
- expect(response.headers['X-Page']).to eq('3')
+ expect(json_response.size).to eq(per_page)
+ expect(json_response.first['id']).to eq(commit.id)
+ expect(response.headers['X-Page']).to eq('3')
+ end
end
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 55c998b13b8..ea97c556430 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -33,6 +33,15 @@ describe API::Files do
expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
end
+ it 'returns json when file has txt extension' do
+ file_path = "bar%2Fbranch-test.txt"
+
+ get api(route(file_path), current_user), params
+
+ expect(response).to have_http_status(200)
+ expect(response.content_type).to eq('application/json')
+ end
+
it 'returns file by commit sha' do
# This file is deleted on HEAD
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
@@ -220,6 +229,7 @@ describe API::Files do
post api(route("new_file_with_author%2Etxt"), user), valid_params
expect(response).to have_http_status(201)
+ expect(response.content_type).to eq('application/json')
last_commit = project.repository.commit.raw
expect(last_commit.author_email).to eq(author_email)
expect(last_commit.author_name).to eq(author_name)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 9a6072e7eb7..0db645863fb 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -31,7 +31,7 @@ describe API::MergeRequests do
it 'returns authentication error' do
get api('/merge_requests')
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -43,7 +43,7 @@ describe API::MergeRequests do
it 'returns an array of all merge requests' do
get api('/merge_requests', user), scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |mr| mr['id'] })
@@ -56,7 +56,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.map { |mr| mr['id'] })
@@ -68,7 +68,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -79,7 +79,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), author_id: user2.id, scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -90,7 +90,7 @@ describe API::MergeRequests do
get api('/merge_requests', user), assignee_id: user2.id, scope: :all
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -101,7 +101,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), scope: 'assigned-to-me'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -112,7 +112,7 @@ describe API::MergeRequests do
get api('/merge_requests', user2), scope: 'created-by-me'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request3.id)
@@ -125,7 +125,7 @@ describe API::MergeRequests do
it "returns authentication error" do
get api("/projects/#{project.id}/merge_requests")
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
end
end
@@ -145,7 +145,7 @@ describe API::MergeRequests do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -166,7 +166,7 @@ describe API::MergeRequests do
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 have_gitlab_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
@@ -182,7 +182,7 @@ describe API::MergeRequests do
it "returns an array of all merge_requests" do
get api("/projects/#{project.id}/merge_requests?state", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -192,7 +192,7 @@ describe API::MergeRequests do
it "returns an array of open merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=opened", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -202,7 +202,7 @@ describe API::MergeRequests do
it "returns an array of closed merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -212,7 +212,7 @@ describe API::MergeRequests do
it "returns an array of merged merge_requests" do
get api("/projects/#{project.id}/merge_requests?state=merged", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -222,7 +222,7 @@ describe API::MergeRequests do
it 'returns merge_request by "iids" array' do
get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid]
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq merge_request_closed.title
@@ -232,14 +232,14 @@ describe API::MergeRequests do
it 'matches V4 response schema' do
get api("/projects/#{project.id}/merge_requests", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/merge_requests')
end
it 'returns an empty array if no issue matches milestone' do
get api("/projects/#{project.id}/merge_requests", user), milestone: '1.0.0'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -247,7 +247,7 @@ describe API::MergeRequests do
it 'returns an empty array if milestone does not exist' do
get api("/projects/#{project.id}/merge_requests", user), milestone: 'foo'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -262,7 +262,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests matching state in milestone' do
get api("/projects/#{project.id}/merge_requests", user), milestone: '0.9', state: 'closed'
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(merge_request_closed.id)
@@ -271,7 +271,7 @@ describe API::MergeRequests do
it 'returns an array of labeled merge requests' do
get api("/projects/#{project.id}/merge_requests?labels=#{label.title}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['labels']).to eq([label2.title, label.title])
@@ -280,7 +280,7 @@ describe API::MergeRequests do
it 'returns an array of labeled merge requests where all labels match' do
get api("/projects/#{project.id}/merge_requests?labels=#{label.title},foo,bar", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -288,7 +288,7 @@ describe API::MergeRequests do
it 'returns an empty array if no merge request matches labels' do
get api("/projects/#{project.id}/merge_requests?labels=foo,bar", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
@@ -307,7 +307,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
expect(json_response.first['id']).to eq(mr2.id)
@@ -322,7 +322,7 @@ describe API::MergeRequests do
it "returns an array of merge_requests in ascending order" do
get api("/projects/#{project.id}/merge_requests?sort=asc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -333,7 +333,7 @@ describe API::MergeRequests do
it "returns an array of merge_requests in descending order" do
get api("/projects/#{project.id}/merge_requests?sort=desc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -344,7 +344,7 @@ describe API::MergeRequests do
it "returns an array of merge_requests ordered by updated_at" do
get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -355,7 +355,7 @@ describe API::MergeRequests do
it "returns an array of merge_requests ordered by created_at" do
get api("/projects/#{project.id}/merge_requests?order_by=created_at&sort=asc", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
@@ -370,7 +370,7 @@ describe API::MergeRequests do
it 'exposes known attributes' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['id']).to eq(merge_request.id)
expect(json_response['iid']).to eq(merge_request.iid)
expect(json_response['project_id']).to eq(merge_request.project.id)
@@ -398,7 +398,7 @@ describe API::MergeRequests do
it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq(merge_request.title)
expect(json_response['iid']).to eq(merge_request.iid)
expect(json_response['work_in_progress']).to eq(false)
@@ -409,13 +409,13 @@ describe API::MergeRequests do
it "returns a 404 error if merge_request_iid not found" do
get api("/projects/#{project.id}/merge_requests/999", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns a 404 error if merge_request `id` is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
context 'Work in Progress' do
@@ -423,7 +423,7 @@ describe API::MergeRequests do
it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.iid}", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['work_in_progress']).to eq(true)
end
end
@@ -434,7 +434,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/commits", user)
commit = merge_request.commits.first
- expect(response.status).to eq 200
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(merge_request.commits.size)
@@ -444,13 +444,13 @@ describe API::MergeRequests do
it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/commits", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/commits", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -458,19 +458,19 @@ describe API::MergeRequests do
it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/changes", user)
- expect(response.status).to eq 200
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
end
it 'returns a 404 when merge_request_iid not found' do
get api("/projects/#{project.id}/merge_requests/999/changes", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns a 404 when merge_request id is used instead of iid' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -485,7 +485,7 @@ describe API::MergeRequests do
labels: 'label, label2',
milestone_id: milestone.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['labels']).to eq(%w(label label2))
expect(json_response['milestone']['id']).to eq(milestone.id)
@@ -495,25 +495,25 @@ describe API::MergeRequests do
it "returns 422 when source_branch equals target_branch" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", target_branch: "master", author: user
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
title: "Test merge_request", source_branch: "markdown", author: user
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
target_branch: 'master', source_branch: 'markdown'
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it 'allows special label names' do
@@ -523,7 +523,7 @@ describe API::MergeRequests do
target_branch: 'master',
author: user,
labels: 'label, label?, label&foo, ?, &'
- expect(response.status).to eq(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['labels']).to include 'label'
expect(json_response['labels']).to include 'label?'
expect(json_response['labels']).to include 'label&foo'
@@ -549,7 +549,7 @@ describe API::MergeRequests do
target_branch: 'master',
author: user
end.to change { MergeRequest.count }.by(0)
- expect(response).to have_http_status(409)
+ expect(response).to have_gitlab_http_status(409)
end
end
@@ -580,15 +580,17 @@ describe API::MergeRequests do
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
- before do |each|
- fork_project.team << [user2, :reporter]
+ before do
+ fork_project.add_reporter(user2)
+
+ allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
it "returns merge_request" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
end
@@ -599,7 +601,7 @@ describe API::MergeRequests do
expect(fork_project.forked_from_project).to eq(project)
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
end
@@ -613,25 +615,25 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
context 'when target_branch is specified' do
@@ -642,7 +644,7 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do
@@ -652,14 +654,14 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user2,
target_project_id: unrelated_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
end
it "returns 201 when target_branch is specified and for the same project" do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
@@ -674,7 +676,7 @@ describe API::MergeRequests do
it "denies the deletion of the merge request" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", developer)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -682,19 +684,19 @@ describe API::MergeRequests do
it "destroys the merge request owners can destroy" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
- expect(response).to have_http_status(204)
+ expect(response).to have_gitlab_http_status(204)
end
it "returns 404 for an invalid merge request IID" do
delete api("/projects/#{project.id}/merge_requests/12345", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
delete api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
end
@@ -705,7 +707,7 @@ describe API::MergeRequests do
it "returns merge_request in case of success" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
end
it "returns 406 if branch can't be merged" do
@@ -714,21 +716,21 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(406)
+ expect(response).to have_gitlab_http_status(406)
expect(json_response['message']).to eq('Branch cannot be merged')
end
it "returns 405 if merge_request is not open" do
merge_request.close
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
it "returns 405 if merge_request is a work in progress" do
merge_request.update_attribute(:title, "WIP: #{merge_request.title}")
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -737,7 +739,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user)
- expect(response).to have_http_status(405)
+ expect(response).to have_gitlab_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
@@ -745,21 +747,21 @@ describe API::MergeRequests do
user2 = create(:user)
project.team << [user2, :reporter]
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user2)
- expect(response).to have_http_status(401)
+ expect(response).to have_gitlab_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized')
end
it "returns 409 if the SHA parameter doesn't match" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
- expect(response).to have_http_status(409)
+ expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
end
it "succeeds if the SHA parameter matches" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
end
it "enables merge when pipeline succeeds if the pipeline is active" do
@@ -768,7 +770,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end
@@ -780,7 +782,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
expect(json_response['merge_when_pipeline_succeeds']).to eq(true)
end
@@ -788,13 +790,13 @@ describe API::MergeRequests do
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345/merge", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -803,39 +805,39 @@ describe API::MergeRequests do
it "returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['state']).to eq('closed')
end
end
it "updates title and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('New title')
end
it "updates description and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq('New description')
end
it "updates milestone_id and returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "returns merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['target_branch']).to eq('wiki')
end
it "returns merge_request that removes the source branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(json_response['force_remove_source_branch']).to be_truthy
end
@@ -856,7 +858,7 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
merge_request.reload
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
expect(merge_request.state).to eq('opened')
end
@@ -864,20 +866,20 @@ describe API::MergeRequests do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
merge_request.reload
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
expect(merge_request.state).to eq('opened')
end
it "returns 404 for an invalid merge request IID" do
put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -890,7 +892,7 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{mr.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -900,7 +902,7 @@ describe API::MergeRequests do
it 'returns an empty array when there are no issues to be closed' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
@@ -916,7 +918,7 @@ describe API::MergeRequests do
get api("/projects/#{jira_project.id}/merge_requests/#{merge_request.iid}/closes_issues", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -936,19 +938,19 @@ describe API::MergeRequests do
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/closes_issues", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
it "returns 404 for an invalid merge request IID" do
get api("/projects/#{project.id}/merge_requests/12345/closes_issues", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/closes_issues", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
end
@@ -956,26 +958,26 @@ describe API::MergeRequests do
it 'subscribes to a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", admin)
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['subscribed']).to eq(true)
end
it 'returns 304 if already subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", user)
- expect(response).to have_http_status(304)
+ expect(response).to have_gitlab_http_status(304)
end
it 'returns 404 if the merge request is not found' do
post api("/projects/#{project.id}/merge_requests/123/subscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/subscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 403 if user has no access to read code' do
@@ -984,7 +986,7 @@ describe API::MergeRequests do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/subscribe", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
@@ -992,26 +994,26 @@ describe API::MergeRequests do
it 'unsubscribes from a merge request' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", user)
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['subscribed']).to eq(false)
end
it 'returns 304 if not subscribed' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", admin)
- expect(response).to have_http_status(304)
+ expect(response).to have_gitlab_http_status(304)
end
it 'returns 404 if the merge request is not found' do
post api("/projects/#{project.id}/merge_requests/123/unsubscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 404 if the merge request id is used instead of iid' do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/unsubscribe", user)
- expect(response).to have_http_status(404)
+ expect(response).to have_gitlab_http_status(404)
end
it 'returns 403 if user has no access to read code' do
@@ -1020,7 +1022,7 @@ describe API::MergeRequests do
post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/unsubscribe", guest)
- expect(response).to have_http_status(403)
+ expect(response).to have_gitlab_http_status(403)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 6cb27d16fe5..a89a58ff713 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1065,6 +1065,14 @@ describe API::Projects do
expect(project_fork_target.forked?).to be_truthy
end
+ it 'refreshes the forks count cachce' do
+ expect(project_fork_source.forks_count).to be_zero
+
+ post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+
+ expect(project_fork_source.forks_count).to eq(1)
+ end
+
it 'fails if forked_from project which does not exist' do
post api("/projects/#{project_fork_target.id}/fork/9999", admin)
expect(response).to have_http_status(404)
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index e4f9c47fb33..1aa8a95780e 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -96,7 +96,7 @@ describe API::ProtectedBranches do
describe 'POST /projects/:id/protected_branches' do
let(:branch_name) { 'new_branch' }
- context 'when authenticated as a master' do
+ context 'when authenticated as a master' do
before do
project.add_master(user)
end
@@ -221,7 +221,7 @@ describe API::ProtectedBranches do
context 'when branch has a wildcard in its name' do
let(:protected_name) { 'feature*' }
-
+
it "unprotects a wildcard branch" do
delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 97275b80d03..737c028ad53 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -45,7 +45,7 @@ describe API::Settings, 'Settings' do
help_page_hide_commercial_content: true,
help_page_support_url: 'http://example.com/help',
project_export_enabled: false
-
+
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['password_authentication_enabled']).to be_falsey
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index ec684e7b9cd..86f38dd4ec1 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -315,15 +315,17 @@ describe API::MergeRequests do
let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
- before do |each|
- fork_project.team << [user2, :reporter]
+ before do
+ fork_project.add_reporter(user2)
+
+ allow_any_instance_of(MergeRequest).to receive(:write_ref)
end
it "returns merge_request" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
end
@@ -334,7 +336,7 @@ describe API::MergeRequests do
expect(fork_project.forked_from_project).to eq(project)
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
end
@@ -348,25 +350,25 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
- expect(response).to have_http_status(400)
+ expect(response).to have_gitlab_http_status(400)
end
context 'when target_branch is specified' do
@@ -377,7 +379,7 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user,
target_project_id: fork_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
it 'returns 422 if targeting a different fork' do
@@ -387,14 +389,14 @@ describe API::MergeRequests do
source_branch: 'markdown',
author: user2,
target_project_id: unrelated_project.id
- expect(response).to have_http_status(422)
+ expect(response).to have_gitlab_http_status(422)
end
end
it "returns 201 when target_branch is specified and for the same project" do
post v3_api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: fork_project.id
- expect(response).to have_http_status(201)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index fca5b5b5d82..a514166274a 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -1004,6 +1004,14 @@ describe API::V3::Projects do
expect(project_fork_target.forked?).to be_truthy
end
+ it 'refreshes the forks count cachce' do
+ expect(project_fork_source.forks_count).to be_zero
+
+ post v3_api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
+
+ expect(project_fork_source.forks_count).to eq(1)
+ end
+
it 'fails if forked_from project which does not exist' do
post v3_api("/projects/#{project_fork_target.id}/fork/9999", admin)
expect(response).to have_http_status(404)
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index ebd67eb1e94..7ccba4ba3ec 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -130,7 +130,7 @@ describe Ci::API::Builds do
register_builds info: { platform: :darwin }
expect(response).to have_http_status(201)
-
+
expect(json_response["options"]).to be_empty
end
end
diff --git a/spec/rubocop/cop/migration/safer_boolean_column_spec.rb b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
new file mode 100644
index 00000000000..1006594499a
--- /dev/null
+++ b/spec/rubocop/cop/migration/safer_boolean_column_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/safer_boolean_column'
+
+describe RuboCop::Cop::Migration::SaferBooleanColumn do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ described_class::SMALL_TABLES.each do |table|
+ context "for the #{table} table" do
+ sources_and_offense = [
+ ["add_column :#{table}, :column, :boolean, default: true", 'should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: false", 'should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: nil", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, null: false", 'should have a default'],
+ ["add_column :#{table}, :column, :boolean, null: true", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: nil, null: false", 'should have a default'],
+ ["add_column :#{table}, :column, :boolean, default: nil, null: true", 'should have a default and should disallow nulls'],
+ ["add_column :#{table}, :column, :boolean, default: false, null: true", 'should disallow nulls']
+ ]
+
+ sources_and_offense.each do |source, offense|
+ context "given the source \"#{source}\"" do
+ it "registers the offense matching \"#{offense}\"" do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses.first.message).to match(offense)
+ end
+ end
+ end
+ end
+
+ inoffensive_sources = [
+ "add_column :#{table}, :column, :boolean, default: true, null: false",
+ "add_column :#{table}, :column, :boolean, default: false, null: false"
+ ]
+
+ inoffensive_sources.each do |source|
+ context "given the source \"#{source}\"" do
+ it "registers no offense" do
+ inspect_source(cop, source)
+
+ aggregate_failures do
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+ end
+ end
+ end
+
+ it 'registers no offense for tables not listed in SMALL_TABLES' do
+ inspect_source(cop, "add_column :large_table, :column, :boolean")
+
+ expect(cop.offenses).to be_empty
+ end
+
+ it 'registers no offense for non-boolean columns' do
+ table = described_class::SMALL_TABLES.sample
+ inspect_source(cop, "add_column :#{table}, :column, :string")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ context 'outside of migration' do
+ it 'registers no offense' do
+ table = described_class::SMALL_TABLES.sample
+ inspect_source(cop, "add_column :#{table}, :column, :boolean")
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 730df4e0336..53d4fcfed18 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -66,7 +66,7 @@ describe Ci::CreatePipelineService do
context 'when there is no pipeline for source branch' do
it "does not update merge request head pipeline" do
- merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project)
+ merge_request = create(:merge_request, source_branch: 'feature', target_branch: "branch_1", source_project: project)
head_pipeline = pipeline
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 049b082277a..08267d6e6a0 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -20,6 +20,10 @@ describe CreateDeploymentService do
let(:service) { described_class.new(job) }
+ before do
+ allow_any_instance_of(Deployment).to receive(:create_ref)
+ end
+
describe '#execute' do
subject { service.execute }
diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb
index fac66791ffb..67a86c50fc1 100644
--- a/spec/services/issues/resolve_discussions_spec.rb
+++ b/spec/services/issues/resolve_discussions_spec.rb
@@ -20,7 +20,7 @@ describe Issues::ResolveDiscussions do
describe "for resolving discussions" do
let(:discussion) { create(:diff_note_on_merge_request, project: project, note: "Almost done").to_discussion }
let(:merge_request) { discussion.noteable }
- let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "other") }
+ let(:other_merge_request) { create(:merge_request, source_project: project, source_branch: "fix") }
describe "#merge_request_for_resolving_discussion" do
let(:service) { DummyService.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid) }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 8fef480274d..a1f3bec42cc 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -48,6 +48,16 @@ describe MergeRequests::CreateService do
expect(Todo.where(attributes).count).to be_zero
end
+ it 'creates exactly 1 create MR event' do
+ attributes = {
+ action: Event::CREATED,
+ target_id: @merge_request.id,
+ target_type: @merge_request.class.name
+ }
+
+ expect(Event.where(attributes).count).to eq(1)
+ end
+
context 'when merge request is assigned to someone' do
let(:opts) do
{
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 672d86e4028..25599dea19f 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
describe MergeRequests::GetUrlsService do
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
- let(:source_branch) { "my_branch" }
+ let(:source_branch) { "merge-test" }
let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
@@ -111,9 +111,9 @@ describe MergeRequests::GetUrlsService do
end
context 'pushing new branch and existing branch (with merge request created) at once' do
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: "existing_branch") }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: "markdown") }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
- let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/existing_branch" }
+ let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
@@ -124,7 +124,7 @@ describe MergeRequests::GetUrlsService do
url: new_merge_request_url,
new_merge_request: true
}, {
- branch_name: "existing_branch",
+ branch_name: "markdown",
url: show_merge_request_url,
new_merge_request: false
}])
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 85b05ef6d05..c867139d1de 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -142,13 +142,13 @@ describe Projects::DestroyService do
context 'when `execute` raises unexpected error' do
before do
expect_any_instance_of(Project)
- .to receive(:destroy!).and_raise(Exception.new("Other error message"))
+ .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
+ end.to raise_error(Exception, 'Other error message')
expect(project.reload.pending_delete).to be(false)
expect(project.delete_error).to include("Other error message")
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index c90536ba346..21c4b30734c 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -50,6 +50,14 @@ describe Projects::ForkService do
expect(@from_project.avatar.file).to be_exists
end
+
+ it 'flushes the forks count cache of the source project' do
+ expect(@from_project.forks_count).to be_zero
+
+ fork_project(@from_project, @to_user)
+
+ expect(@from_project.forks_count).to eq(1)
+ end
end
end
diff --git a/spec/services/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
new file mode 100644
index 00000000000..cf299c5d09b
--- /dev/null
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe Projects::ForksCountService do
+ let(:project) { build(:project, id: 42) }
+ let(:service) { described_class.new(project) }
+
+ describe '#count' do
+ it 'returns the number of forks' do
+ allow(service).to receive(:uncached_count).and_return(1)
+
+ expect(service.count).to eq(1)
+ end
+
+ it 'caches the forks count', :use_clean_rails_memory_store_caching do
+ expect(service).to receive(:uncached_count).once.and_return(1)
+
+ 2.times { service.count }
+ end
+ end
+
+ describe '#refresh_cache', :use_clean_rails_memory_store_caching do
+ it 'refreshes the cache' do
+ expect(service).to receive(:uncached_count).once.and_return(1)
+
+ service.refresh_cache
+
+ expect(service.count).to eq(1)
+ end
+ end
+
+ describe '#delete_cache', :use_clean_rails_memory_store_caching do
+ it 'removes the cache' do
+ expect(service).to receive(:uncached_count).twice.and_return(1)
+
+ service.count
+ service.delete_cache
+ service.count
+ end
+ end
+end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 2ae8d5f7c54..4f1ab697460 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -29,4 +29,14 @@ describe Projects::UnlinkForkService do
subject.execute
end
+
+ it 'refreshes the forks count cache of the source project' do
+ source = fork_project.forked_from_project
+
+ expect(source.forks_count).to eq(1)
+
+ subject.execute
+
+ expect(source.forks_count).to be_zero
+ end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 365cb6b8f09..0726e135b20 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -144,7 +144,7 @@ describe WebHookService do
describe '#async_execute' do
let(:system_hook) { create(:system_hook) }
-
+
it 'enqueue WebHookWorker' do
expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0cddbe89833..3eea39d4bf4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -8,6 +8,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'rspec/retry'
+require 'rspec-parameterized'
rspec_profiling_is_configured =
ENV['RSPEC_PROFILING_POSTGRES_URL'].present? ||
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 27e079c01dd..cb483ae9a5a 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -7,15 +7,18 @@ shared_examples 'reportable note' do
let(:more_actions_selector) { '.more-actions.dropdown' }
let(:abuse_report_path) { new_abuse_report_path(user_id: note.author.id, ref_url: noteable_note_url(note)) }
+ it 'has an edit button' do
+ expect(comment).to have_selector('.js-note-edit')
+ end
+
it 'has a `More actions` dropdown' do
expect(comment).to have_selector(more_actions_selector)
end
- it 'dropdown has Edit, Report and Delete links' do
+ it 'dropdown has Report and Delete links' do
dropdown = comment.find(more_actions_selector)
open_dropdown(dropdown)
- expect(dropdown).to have_button('Edit comment')
expect(dropdown).to have_link('Report as abuse', href: abuse_report_path)
expect(dropdown).to have_link('Delete comment', href: note_url(note, project))
end
diff --git a/spec/support/generate-seed-repo-rb b/spec/support/generate-seed-repo-rb
index c89389b90ca..ef3c8e7087f 100755
--- a/spec/support/generate-seed-repo-rb
+++ b/spec/support/generate-seed-repo-rb
@@ -1,16 +1,16 @@
#!/usr/bin/env ruby
-#
+#
# # generate-seed-repo-rb
-#
+#
# This script generates the seed_repo.rb file used by lib/gitlab/git
# tests. The seed_repo.rb file needs to be updated anytime there is a
# Git push to https://gitlab.com/gitlab-org/gitlab-git-test.
-#
+#
# Usage:
-#
+#
# ./spec/support/generate-seed-repo-rb > spec/support/seed_repo.rb
-#
-#
+#
+#
require 'erb'
require 'tempfile'
diff --git a/spec/support/gitlab-git-test.git/objects/3e/20715310a699808282e772720b9c04a0696bcc b/spec/support/gitlab-git-test.git/objects/3e/20715310a699808282e772720b9c04a0696bcc
new file mode 100644
index 00000000000..86bf37ac887
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/3e/20715310a699808282e772720b9c04a0696bcc
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/objects/95/96bc54a6f0c0c98248fe97077eb5ccf48a98d0 b/spec/support/gitlab-git-test.git/objects/95/96bc54a6f0c0c98248fe97077eb5ccf48a98d0
new file mode 100644
index 00000000000..d90cb028e9b
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/95/96bc54a6f0c0c98248fe97077eb5ccf48a98d0
@@ -0,0 +1,2 @@
+xOn1 䜯 9&O "noYD6ՒҪ?j;wQ GrN(HPrArR7tpM#M”cNrsI
+%p>۫pz?Y3XBB̰GB4 p?kv۞y~W])[a<CP_ \ No newline at end of file
diff --git a/spec/support/gitlab-git-test.git/packed-refs b/spec/support/gitlab-git-test.git/packed-refs
index ce5ab1f705b..507e4ce785a 100644
--- a/spec/support/gitlab-git-test.git/packed-refs
+++ b/spec/support/gitlab-git-test.git/packed-refs
@@ -8,6 +8,7 @@
46e1395e609395de004cacd4b142865ab0e52a29 refs/heads/gitattributes-updated
4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6 refs/heads/master
5937ac0a7beb003549fc5fd26fc247adbce4a52e refs/heads/merge-test
+9596bc54a6f0c0c98248fe97077eb5ccf48a98d0 refs/heads/missing-gitmodules
f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0
^6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0
diff --git a/spec/support/gpg_helpers.rb b/spec/support/gpg_helpers.rb
index 96ea6f28b30..65b38626a51 100644
--- a/spec/support/gpg_helpers.rb
+++ b/spec/support/gpg_helpers.rb
@@ -1,4 +1,6 @@
module GpgHelpers
+ SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'.freeze
+
module User1
extend self
diff --git a/spec/support/issuables_list_metadata_shared_examples.rb b/spec/support/issuables_list_metadata_shared_examples.rb
index a60d3b0d22d..75982432ab4 100644
--- a/spec/support/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/issuables_list_metadata_shared_examples.rb
@@ -2,12 +2,12 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
before do
@issuable_ids = []
- 2.times do |n|
+ %w[fix improve/awesome].each do |source_branch|
issuable =
if issuable_type == :issue
create(issuable_type, project: project)
else
- create(issuable_type, source_project: project, source_branch: "#{n}-feature")
+ create(issuable_type, source_project: project, source_branch: source_branch)
end
@issuable_ids << issuable.id
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index ff60bd0c0ae..bb6b7c63ee9 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -1,6 +1,6 @@
# AccessMatchersForController
#
-# For testing authorize_xxx in controller.
+# For testing authorize_xxx in controller.
module AccessMatchersForController
extend RSpec::Matchers::DSL
include Warden::Test::Helpers
diff --git a/spec/support/seed_repo.rb b/spec/support/seed_repo.rb
index cfe7fc980a8..b4868e82cd7 100644
--- a/spec/support/seed_repo.rb
+++ b/spec/support/seed_repo.rb
@@ -97,6 +97,7 @@ module SeedRepo
gitattributes-updated
master
merge-test
+ missing-gitmodules
].freeze
TAGS = %w[
v1.0.0
diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index 43ac1a72152..1e9b20435ec 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -54,17 +54,17 @@ describe 'gitlab:gitaly namespace rake task' do
before do
FileUtils.mkdir_p(clone_path)
expect(Dir).to receive(:chdir).with(clone_path).and_call_original
+ allow(Bundler).to receive(:bundle_path).and_return('/fake/bundle_path')
end
context 'gmake is available' do
before do
expect(main_object).to receive(:checkout_or_clone_version)
- allow(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
end
it 'calls gmake in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
- expect(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[gmake BUNDLE_PATH=/fake/bundle_path]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
@@ -73,15 +73,26 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' do
before do
expect(main_object).to receive(:checkout_or_clone_version)
- allow(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+ expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
end
it 'calls make in the gitaly directory' do
- expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
- expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+ expect(main_object).to receive(:run_command!).with(command_preamble + %w[make BUNDLE_PATH=/fake/bundle_path]).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
+
+ context 'when Rails.env is not "test"' do
+ before do
+ allow(Rails.env).to receive(:test?).and_return(false)
+ end
+
+ it 'calls make in the gitaly directory without BUNDLE_PATH' do
+ expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
+
+ run_rake_task('gitlab:gitaly:install', clone_path)
+ end
+ end
end
end
end
diff --git a/spec/views/projects/commits/_commit.html.haml_spec.rb b/spec/views/projects/commits/_commit.html.haml_spec.rb
new file mode 100644
index 00000000000..4c247361bd7
--- /dev/null
+++ b/spec/views/projects/commits/_commit.html.haml_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe 'projects/commits/_commit.html.haml' do
+ context 'with a singed commit' do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:ref) { GpgHelpers::SIGNED_COMMIT_SHA }
+ let(:commit) { repository.commit(ref) }
+
+ it 'does not display a loading spinner for GPG status' do
+ render partial: 'projects/commits/commit', locals: {
+ project: project,
+ ref: ref,
+ commit: commit
+ }
+
+ within '.gpg-status-box' do
+ expect(page).not_to have_css('i.fa.fa-spinner.fa-spin')
+ end
+ end
+ end
+end
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 aea20d826d0..9c0be249a50 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
@@ -24,18 +24,16 @@ describe 'projects/notes/_more_actions_dropdown' do
expect(rendered).not_to have_selector('.dropdown.more-actions')
end
- it 'shows Report as abuse, Edit and Delete buttons if editable and not current users comment' do
+ it 'shows Report as abuse and Delete buttons if editable and not current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: not_author_user, note_editable: true, note: note
expect(rendered).to have_link('Report as abuse')
- expect(rendered).to have_button('Edit comment')
expect(rendered).to have_link('Delete comment')
end
- it 'shows Edit and Delete buttons if editable and current users comment' do
+ it 'shows Delete button if editable and current users comment' do
render 'projects/notes/more_actions_dropdown', current_user: author_user, note_editable: true, note: note
- expect(rendered).to have_button('Edit comment')
expect(rendered).to have_link('Delete comment')
end
end
diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz
new file mode 100644
index 00000000000..6353f6605d5
--- /dev/null
+++ b/vendor/project_templates/express.tar.gz
Binary files differ
diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz
new file mode 100644
index 00000000000..d7c0ab74d01
--- /dev/null
+++ b/vendor/project_templates/spring.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index c9e1b630a9e..5fc28f8b5ba 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6275,9 +6275,9 @@ webpack-stats-plugin@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-0.1.5.tgz#29e5f12ebfd53158d31d656a113ac1f7b86179d9"
-webpack@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63"
+webpack@^3.5.4:
+ version "3.5.4"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.5.4.tgz#5583eb263ed27b78b5bd17bfdfb0eb1b1cd1bf81"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"