summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitattributes3
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml148
-rw-r--r--.mailmap35
-rw-r--r--.rubocop.yml21
-rw-r--r--.rubocop_todo.yml15
-rw-r--r--.ruby-version2
-rw-r--r--.simplecov4
-rw-r--r--CHANGELOG160
-rw-r--r--CONTRIBUTING.md11
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile23
-rw-r--r--Gemfile.lock130
-rw-r--r--PROCESS.md2
-rw-r--r--app/assets/images/bg-header.pngbin90 -> 0 bytes
-rw-r--r--app/assets/images/bg_fallback.pngbin167 -> 0 bytes
-rw-r--r--app/assets/images/chosen-sprite.pngbin367 -> 0 bytes
-rw-r--r--app/assets/images/diff_note_add.pngbin418 -> 0 bytes
-rw-r--r--app/assets/images/icon-search.pngbin222 -> 0 bytes
-rw-r--r--app/assets/images/icon_sprite.pngbin2636 -> 0 bytes
-rw-r--r--app/assets/images/images.pngbin5806 -> 0 bytes
-rw-r--r--app/assets/images/move.pngbin197 -> 0 bytes
-rw-r--r--app/assets/images/progress_bar.gifbin494 -> 0 bytes
-rw-r--r--app/assets/images/slider_handles.pngbin1341 -> 0 bytes
-rw-r--r--app/assets/images/switch_icon.pngbin231 -> 0 bytes
-rw-r--r--app/assets/images/trans_bg.gifbin49 -> 0 bytes
-rw-r--r--app/assets/javascripts/application.js6
-rw-r--r--app/assets/javascripts/awards_handler.js14
-rw-r--r--app/assets/javascripts/build.js2
-rw-r--r--app/assets/javascripts/diff.js29
-rw-r--r--app/assets/javascripts/dispatcher.js12
-rw-r--r--app/assets/javascripts/dropzone_input.js2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js4
-rw-r--r--app/assets/javascripts/gl_dropdown.js46
-rw-r--r--app/assets/javascripts/gl_form.js2
-rw-r--r--app/assets/javascripts/importer_status.js8
-rw-r--r--app/assets/javascripts/issuable.js11
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js38
-rw-r--r--app/assets/javascripts/lib/utils/md5.js211
-rw-r--r--app/assets/javascripts/lib/utils/utf8_encode.js70
-rw-r--r--app/assets/javascripts/preview_markdown.js (renamed from app/assets/javascripts/markdown_preview.js)4
-rw-r--r--app/assets/javascripts/project.js10
-rw-r--r--app/assets/javascripts/protected_branch_access_dropdown.js.es624
-rw-r--r--app/assets/javascripts/protected_branch_create.js.es656
-rw-r--r--app/assets/javascripts/protected_branch_dropdown.js.es675
-rw-r--r--app/assets/javascripts/protected_branch_edit.js.es661
-rw-r--r--app/assets/javascripts/protected_branch_edit_list.js.es617
-rw-r--r--app/assets/javascripts/protected_branch_select.js72
-rw-r--r--app/assets/javascripts/protected_branches.js35
-rw-r--r--app/assets/javascripts/users_select.js36
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/lists.scss11
-rw-r--r--app/assets/stylesheets/framework/nav.scss2
-rw-r--r--app/assets/stylesheets/framework/panels.scss5
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/commits.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss5
-rw-r--r--app/assets/stylesheets/pages/environments.scss30
-rw-r--r--app/assets/stylesheets/pages/groups.scss8
-rw-r--r--app/assets/stylesheets/pages/issues.scss30
-rw-r--r--app/assets/stylesheets/pages/labels.scss11
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss10
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss35
-rw-r--r--app/assets/stylesheets/pages/tree.scss4
-rw-r--r--app/controllers/admin/groups_controller.rb4
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb17
-rw-r--r--app/controllers/admin/spam_logs_controller.rb10
-rw-r--r--app/controllers/application_controller.rb56
-rw-r--r--app/controllers/autocomplete_controller.rb1
-rw-r--r--app/controllers/concerns/diff_for_path.rb6
-rw-r--r--app/controllers/concerns/issuable_collections.rb79
-rw-r--r--app/controllers/concerns/issues_action.rb10
-rw-r--r--app/controllers/concerns/merge_requests_action.rb10
-rw-r--r--app/controllers/concerns/spammable_actions.rb25
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/explore/application_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb4
-rw-r--r--app/controllers/help_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb7
-rw-r--r--app/controllers/profiles/passwords_controller.rb1
-rw-r--r--app/controllers/projects/badges_controller.rb18
-rw-r--r--app/controllers/projects/blob_controller.rb16
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/builds_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb6
-rw-r--r--app/controllers/projects/compare_controller.rb16
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb20
-rw-r--r--app/controllers/projects/environments_controller.rb23
-rw-r--r--app/controllers/projects/git_http_client_controller.rb110
-rw-r--r--app/controllers/projects/git_http_controller.rb142
-rw-r--r--app/controllers/projects/issues_controller.rb25
-rw-r--r--app/controllers/projects/lfs_api_controller.rb94
-rw-r--r--app/controllers/projects/lfs_storage_controller.rb92
-rw-r--r--app/controllers/projects/merge_requests_controller.rb25
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb8
-rw-r--r--app/controllers/projects/protected_branches_controller.rb33
-rw-r--r--app/controllers/projects/tags_controller.rb3
-rw-r--r--app/controllers/projects/wikis_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb6
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/projects_finder.rb3
-rw-r--r--app/finders/todos_finder.rb20
-rw-r--r--app/helpers/application_helper.rb9
-rw-r--r--app/helpers/avatars_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/commits_helper.rb4
-rw-r--r--app/helpers/diff_helper.rb48
-rw-r--r--app/helpers/explore_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb32
-rw-r--r--app/helpers/lfs_helper.rb67
-rw-r--r--app/helpers/members_helper.rb6
-rw-r--r--app/helpers/notes_helper.rb19
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/selects_helper.rb30
-rw-r--r--app/helpers/sorting_helper.rb12
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb18
-rw-r--r--app/models/ability.rb26
-rw-r--r--app/models/blob.rb11
-rw-r--r--app/models/ci/build.rb54
-rw-r--r--app/models/ci/pipeline.rb131
-rw-r--r--app/models/commit.rb47
-rw-r--r--app/models/commit_status.rb37
-rw-r--r--app/models/compare.rb66
-rw-r--r--app/models/concerns/faster_cache_keys.rb16
-rw-r--r--app/models/concerns/issuable.rb2
-rw-r--r--app/models/concerns/spammable.rb68
-rw-r--r--app/models/concerns/statuseable.rb31
-rw-r--r--app/models/concerns/token_authenticatable.rb30
-rw-r--r--app/models/deployment.rb6
-rw-r--r--app/models/diff_note.rb12
-rw-r--r--app/models/discussion.rb6
-rw-r--r--app/models/environment.rb18
-rw-r--r--app/models/issue.rb38
-rw-r--r--app/models/key.rb5
-rw-r--r--app/models/label_link.rb6
-rw-r--r--app/models/legacy_diff_note.rb14
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/members/project_member.rb7
-rw-r--r--app/models/merge_request.rb42
-rw-r--r--app/models/merge_request_diff.rb14
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/note.rb1
-rw-r--r--app/models/project.rb60
-rw-r--r--app/models/project_services/campfire_service.rb51
-rw-r--r--app/models/project_services/hipchat_service.rb21
-rw-r--r--app/models/project_services/pivotaltracker_service.rb31
-rw-r--r--app/models/project_team.rb77
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/protected_branch.rb6
-rw-r--r--app/models/protected_branch/merge_access_level.rb24
-rw-r--r--app/models/protected_branch/push_access_level.rb27
-rw-r--r--app/models/repository.rb50
-rw-r--r--app/models/spam_log.rb4
-rw-r--r--app/models/spam_report.rb5
-rw-r--r--app/models/user.rb8
-rw-r--r--app/models/user_agent_detail.rb9
-rw-r--r--app/services/akismet_service.rb79
-rw-r--r--app/services/auth/container_registry_authentication_service.rb12
-rw-r--r--app/services/ci/create_builds_service.rb62
-rw-r--r--app/services/ci/create_pipeline_builds_service.rb42
-rw-r--r--app/services/ci/create_pipeline_service.rb96
-rw-r--r--app/services/ci/create_trigger_request_service.rb17
-rw-r--r--app/services/ci/process_pipeline_service.rb77
-rw-r--r--app/services/compare_service.rb6
-rw-r--r--app/services/create_commit_builds_service.rb69
-rw-r--r--app/services/create_spam_log_service.rb13
-rw-r--r--app/services/delete_branch_service.rb2
-rw-r--r--app/services/delete_tag_service.rb2
-rw-r--r--app/services/delete_user_service.rb9
-rw-r--r--app/services/destroy_group_service.rb16
-rw-r--r--app/services/files/base_service.rb1
-rw-r--r--app/services/files/update_service.rb23
-rw-r--r--app/services/git_push_service.rb17
-rw-r--r--app/services/git_tag_push_service.rb6
-rw-r--r--app/services/ham_service.rb26
-rw-r--r--app/services/import_export_clean_up_service.rb24
-rw-r--r--app/services/issues/create_service.rb37
-rw-r--r--app/services/members/destroy_service.rb5
-rw-r--r--app/services/merge_requests/base_service.rb9
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/get_urls_service.rb63
-rw-r--r--app/services/merge_requests/merge_request_diff_cache_service.rb8
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/refresh_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb8
-rw-r--r--app/services/projects/enable_deploy_key_service.rb17
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/protected_branches/create_service.rb27
-rw-r--r--app/services/protected_branches/update_service.rb13
-rw-r--r--app/services/repository_archive_clean_up_service.rb4
-rw-r--r--app/services/spam_service.rb78
-rw-r--r--app/services/system_note_service.rb154
-rw-r--r--app/services/todo_service.rb3
-rw-r--r--app/services/user_agent_detail_service.rb13
-rw-r--r--app/views/admin/application_settings/_form.html.haml9
-rw-r--r--app/views/admin/background_jobs/_head.html.haml4
-rw-r--r--app/views/admin/builds/_build.html.haml10
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/labels/_form.html.haml3
-rw-r--r--app/views/admin/requests_profiles/index.html.haml26
-rw-r--r--app/views/admin/spam_logs/_spam_log.html.haml5
-rw-r--r--app/views/admin/users/show.html.haml8
-rw-r--r--app/views/devise/sessions/_new_crowd.html.haml2
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/bitbucket/status.html.haml4
-rw-r--r--app/views/import/fogbugz/status.html.haml3
-rw-r--r--app/views/import/github/status.html.haml7
-rw-r--r--app/views/import/gitlab/status.html.haml3
-rw-r--r--app/views/import/gitorious/status.html.haml3
-rw-r--r--app/views/import/google_code/status.html.haml3
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml2
-rw-r--r--app/views/layouts/project.html.haml6
-rw-r--r--app/views/notify/new_issue_email.text.erb2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml3
-rw-r--r--app/views/projects/badges/badge.svg.erb36
-rw-r--r--app/views/projects/blob/_actions.html.haml3
-rw-r--r--app/views/projects/blob/_image.html.haml16
-rw-r--r--app/views/projects/blob/diff.html.haml34
-rw-r--r--app/views/projects/blob/edit.html.haml9
-rw-r--r--app/views/projects/branches/_commit.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml40
-rw-r--r--app/views/projects/builds/_sidebar.html.haml7
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml15
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml6
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml2
-rw-r--r--app/views/projects/commit/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml6
-rw-r--r--app/views/projects/deployments/_commit.html.haml8
-rw-r--r--app/views/projects/deployments/_deployment.html.haml1
-rw-r--r--app/views/projects/diffs/_content.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml21
-rw-r--r--app/views/projects/diffs/_file.html.haml17
-rw-r--r--app/views/projects/diffs/_line.html.haml10
-rw-r--r--app/views/projects/diffs/_match_line.html.haml7
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml10
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml5
-rw-r--r--app/views/projects/diffs/_warning.html.haml2
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/environments/_environment.html.haml8
-rw-r--r--app/views/projects/environments/_form.html.haml29
-rw-r--r--app/views/projects/environments/edit.html.haml6
-rw-r--r--app/views/projects/environments/index.html.haml7
-rw-r--r--app/views/projects/environments/new.html.haml14
-rw-r--r--app/views/projects/environments/show.html.haml6
-rw-r--r--app/views/projects/graphs/ci/_build_times.haml7
-rw-r--r--app/views/projects/graphs/ci/_builds.haml7
-rw-r--r--app/views/projects/graphs/commits.html.haml4
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/issues/_head.html.haml2
-rw-r--r--app/views/projects/issues/_issue_by_email.html.haml27
-rw-r--r--app/views/projects/issues/_related_branches.html.haml4
-rw-r--r--app/views/projects/issues/index.html.haml15
-rw-r--r--app/views/projects/issues/show.html.haml11
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml3
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml13
-rw-r--r--app/views/projects/new.html.haml38
-rw-r--r--app/views/projects/pipelines/new.html.haml2
-rw-r--r--app/views/projects/pipelines_settings/_badge.html.haml27
-rw-r--r--app/views/projects/pipelines_settings/show.html.haml25
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml36
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml36
-rw-r--r--app/views/projects/protected_branches/_dropdown.html.haml12
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml15
-rw-r--r--app/views/projects/protected_branches/index.html.haml32
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml2
-rw-r--r--app/views/projects/tree/_tree_row.html.haml6
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml11
-rw-r--r--app/views/search/results/_note.html.haml8
-rw-r--r--app/views/shared/_labels_row.html.haml6
-rw-r--r--app/views/shared/icons/_icon_status_cancel.svg14
-rw-r--r--app/views/shared/icons/_icon_status_failed.svg14
-rw-r--r--app/views/shared/icons/_icon_status_pending.svg15
-rw-r--r--app/views/shared/icons/_icon_status_running.svg14
-rw-r--r--app/views/shared/icons/_icon_status_success.svg17
-rw-r--r--app/views/shared/icons/_icon_status_warning.svg17
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml4
-rw-r--r--app/views/shared/web_hooks/_form.html.haml2
-rw-r--r--app/workers/email_receiver_worker.rb54
-rw-r--r--app/workers/emails_on_push_worker.rb19
-rw-r--r--app/workers/group_destroy_worker.rb17
-rw-r--r--app/workers/import_export_project_cleanup_worker.rb9
-rw-r--r--app/workers/irker_worker.rb6
-rw-r--r--app/workers/post_receive.rb4
-rw-r--r--app/workers/project_destroy_worker.rb2
-rw-r--r--app/workers/repository_fork_worker.rb5
-rw-r--r--app/workers/requests_profiles_worker.rb (renamed from app/workers/gitlab_remove_project_export_worker.rb)4
-rw-r--r--config/application.rb3
-rw-r--r--config/dependency_decisions.yml31
-rw-r--r--config/initializers/1_settings.rb9
-rw-r--r--config/initializers/5_backend.rb3
-rw-r--r--config/initializers/6_validations.rb2
-rw-r--r--config/initializers/devise.rb3
-rw-r--r--config/initializers/metrics.rb7
-rw-r--r--config/initializers/mime_types.rb7
-rw-r--r--config/initializers/request_profiler.rb5
-rw-r--r--config/initializers/secret_token.rb103
-rw-r--r--config/initializers/session_store.rb4
-rw-r--r--config/initializers/sidekiq.rb15
-rw-r--r--config/initializers/trusted_proxies.rb2
-rw-r--r--config/mail_room.yml53
-rw-r--r--config/resque.yml.example34
-rw-r--r--config/routes.rb77
-rw-r--r--db/fixtures/development/04_project.rb1
-rw-r--r--db/fixtures/development/14_builds.rb74
-rw-r--r--db/fixtures/development/16_protected_branches.rb12
-rw-r--r--db/migrate/20140407135544_fix_namespaces.rb10
-rw-r--r--db/migrate/20160705054938_add_protected_branches_push_access.rb17
-rw-r--r--db/migrate/20160705054952_add_protected_branches_merge_access.rb17
-rw-r--r--db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb29
-rw-r--r--db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb29
-rw-r--r--db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb19
-rw-r--r--db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb19
-rw-r--r--db/migrate/20160716115711_add_queued_at_to_ci_builds.rb9
-rw-r--r--db/migrate/20160725083350_add_external_url_to_enviroments.rb9
-rw-r--r--db/migrate/20160727163552_create_user_agent_details.rb18
-rw-r--r--db/migrate/20160729173930_remove_project_id_from_spam_logs.rb29
-rw-r--r--db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb20
-rw-r--r--db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb9
-rw-r--r--db/migrate/20160804150737_add_timestamps_to_members_again.rb21
-rw-r--r--db/migrate/20160805041956_add_deleted_at_to_namespaces.rb12
-rw-r--r--db/migrate/20160810102349_remove_ci_runner_trigram_indexes.rb27
-rw-r--r--db/migrate/20160810142633_remove_redundant_indexes.rb112
-rw-r--r--db/schema.rb105
-rw-r--r--doc/README.md6
-rw-r--r--doc/administration/build_artifacts.md90
-rw-r--r--doc/administration/container_registry.md7
-rw-r--r--doc/administration/custom_hooks.md3
-rw-r--r--doc/administration/high_availability/redis.md301
-rw-r--r--doc/administration/housekeeping.md2
-rw-r--r--doc/administration/raketasks/project_import_export.md15
-rw-r--r--doc/administration/repository_checks.md5
-rw-r--r--doc/api/README.md14
-rw-r--r--doc/api/access_requests.md147
-rw-r--r--doc/api/award_emoji.md20
-rw-r--r--doc/api/branches.md12
-rw-r--r--doc/api/build_triggers.md8
-rw-r--r--doc/api/build_variables.md10
-rw-r--r--doc/api/builds.md20
-rw-r--r--doc/api/ci/builds.md12
-rw-r--r--doc/api/ci/runners.md4
-rw-r--r--doc/api/commits.md19
-rw-r--r--doc/api/deploy_key_multiple_projects.md8
-rw-r--r--doc/api/deploy_keys.md58
-rw-r--r--doc/api/enviroments.md117
-rw-r--r--doc/api/groups.md948
-rw-r--r--doc/api/issues.md22
-rw-r--r--doc/api/labels.md12
-rw-r--r--doc/api/licenses.md2
-rw-r--r--doc/api/members.md182
-rw-r--r--doc/api/merge_requests.md10
-rw-r--r--doc/api/milestones.md2
-rw-r--r--doc/api/namespaces.md4
-rw-r--r--doc/api/notes.md6
-rw-r--r--doc/api/oauth2.md54
-rw-r--r--doc/api/projects.md98
-rw-r--r--doc/api/repository_files.md16
-rw-r--r--doc/api/runners.md16
-rw-r--r--doc/api/services.md4
-rw-r--r--doc/api/session.md2
-rw-r--r--doc/api/settings.md4
-rw-r--r--doc/api/sidekiq_metrics.md8
-rw-r--r--doc/api/system_hooks.md8
-rw-r--r--doc/api/tags.md2
-rw-r--r--doc/api/todos.md8
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/build_artifacts/README.md177
-rw-r--r--doc/ci/build_artifacts/img/build_artifacts_browser.pngbin82102 -> 0 bytes
-rw-r--r--doc/ci/build_artifacts/img/build_artifacts_browser_button.pngbin7230 -> 0 bytes
-rw-r--r--doc/ci/docker/using_docker_build.md2
-rw-r--r--doc/ci/examples/php.md4
-rw-r--r--doc/ci/pipelines.md35
-rw-r--r--doc/ci/quick_start/README.md14
-rw-r--r--doc/ci/triggers/README.md22
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/container_registry/README.md9
-rw-r--r--doc/container_registry/img/mitmproxy-docker.pngbin0 -> 407004 bytes
-rw-r--r--doc/container_registry/troubleshooting.md141
-rw-r--r--doc/development/README.md33
-rw-r--r--doc/development/doc_styleguide.md95
-rw-r--r--doc/development/gotchas.md5
-rw-r--r--doc/development/newlines_styleguide.md102
-rw-r--r--doc/development/performance.md9
-rw-r--r--doc/development/rake_tasks.md30
-rw-r--r--doc/development/ui_guide.md36
-rw-r--r--doc/development/what_requires_downtime.md161
-rw-r--r--doc/gitlab-basics/start-using-git.md8
-rw-r--r--doc/install/installation.md25
-rw-r--r--doc/integration/akismet.md34
-rw-r--r--doc/integration/bitbucket.md2
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/gitlab.md2
-rw-r--r--doc/integration/img/spam_log.pngbin0 -> 187190 bytes
-rw-r--r--doc/integration/img/submit_issue.pngbin0 -> 176450 bytes
-rw-r--r--doc/integration/twitter.md2
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md14
-rw-r--r--doc/markdown/markdown.md705
-rw-r--r--doc/monitoring/health_check.md6
-rw-r--r--doc/raketasks/backup_restore.md39
-rw-r--r--doc/raketasks/user_management.md4
-rw-r--r--doc/update/4.0-to-4.1.md2
-rw-r--r--doc/update/4.2-to-5.0.md2
-rw-r--r--doc/update/5.0-to-5.1.md2
-rw-r--r--doc/update/5.2-to-5.3.md2
-rw-r--r--doc/update/5.3-to-5.4.md2
-rw-r--r--doc/update/6.9-to-7.0.md2
-rw-r--r--doc/update/7.0-to-7.1.md2
-rw-r--r--doc/update/7.14-to-8.0.md2
-rw-r--r--doc/update/8.10-to-8.11.md167
-rw-r--r--doc/update/8.9-to-8.10.md2
-rw-r--r--doc/user/admin_area/img/admin_labels.pngbin0 -> 91459 bytes
-rw-r--r--doc/user/admin_area/labels.md9
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md20
-rw-r--r--doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.pngbin0 -> 6227 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_area_settings_button.pngbin0 -> 9184 bytes
-rw-r--r--doc/user/img/markdown_logo.png (renamed from doc/markdown/img/logo.png)bin9509 -> 9509 bytes
-rw-r--r--doc/user/img/markdown_video.mp4 (renamed from doc/markdown/img/video.mp4)bin383631 -> 383631 bytes
-rw-r--r--doc/user/markdown.md786
-rw-r--r--doc/user/project/builds/artifacts.md104
-rw-r--r--doc/user/project/builds/img/build_artifacts_browser.pngbin0 -> 8365 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_browser_button.pngbin0 -> 11041 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_builds_page.pngbin0 -> 55625 bytes
-rw-r--r--doc/user/project/builds/img/build_artifacts_pipelines_page.pngbin0 -> 73038 bytes
-rw-r--r--doc/user/project/labels.md44
-rw-r--r--doc/user/project/protected_branches.md3
-rw-r--r--doc/user/project/settings/import_export.md25
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--doc/workflow/award_emoji.md6
-rw-r--r--doc/workflow/cherry_pick_changes.md3
-rw-r--r--doc/workflow/file_finder.md2
-rw-r--r--doc/workflow/importing/import_projects_from_github.md3
-rw-r--r--doc/workflow/revert_changes.md2
-rw-r--r--doc/workflow/shortcuts.md73
-rw-r--r--doc/workflow/shortcuts.pngbin108209 -> 0 bytes
-rw-r--r--doc/workflow/todos.md2
-rw-r--r--doc/workflow/web_editor.md3
-rw-r--r--features/explore/groups.feature25
-rw-r--r--features/project/merge_requests.feature19
-rw-r--r--features/steps/dashboard/dashboard.rb1
-rw-r--r--features/steps/dashboard/event_filters.rb13
-rw-r--r--features/steps/dashboard/issues.rb5
-rw-r--r--features/steps/dashboard/merge_requests.rb5
-rw-r--r--features/steps/dashboard/new_project.rb2
-rw-r--r--features/steps/explore/groups.rb4
-rw-r--r--features/steps/project/badges/build.rb2
-rw-r--r--features/steps/project/builds/artifacts.rb1
-rw-r--r--features/steps/project/commits/branches.rb2
-rw-r--r--features/steps/project/forked_merge_requests.rb3
-rw-r--r--features/steps/project/issues/issues.rb2
-rw-r--r--features/steps/project/merge_requests.rb5
-rw-r--r--features/steps/project/source/browse_files.rb4
-rw-r--r--features/steps/project/wiki.rb2
-rw-r--r--features/steps/shared/builds.rb8
-rw-r--r--features/steps/shared/issuable.rb4
-rw-r--r--features/support/env.rb5
-rw-r--r--features/support/wait_for_ajax.rb11
-rw-r--r--lib/api/access_requests.rb90
-rw-r--r--lib/api/api.rb15
-rw-r--r--lib/api/branches.rb37
-rw-r--r--lib/api/commits.rb4
-rw-r--r--lib/api/deploy_keys.rb104
-rw-r--r--lib/api/entities.rb41
-rw-r--r--lib/api/environments.rb83
-rw-r--r--lib/api/group_members.rb87
-rw-r--r--lib/api/helpers.rb24
-rw-r--r--lib/api/helpers/members_helpers.rb13
-rw-r--r--lib/api/internal.rb4
-rw-r--r--lib/api/issues.rb21
-rw-r--r--lib/api/members.rb155
-rw-r--r--lib/api/project_members.rb110
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/todos.rb8
-rw-r--r--lib/backup/files.rb2
-rw-r--r--lib/backup/manager.rb2
-rw-r--r--lib/backup/repository.rb8
-rw-r--r--lib/banzai/filter/autolink_filter.rb15
-rw-r--r--lib/banzai/filter/emoji_filter.rb10
-rw-r--r--lib/banzai/filter/markdown_filter.rb14
-rw-r--r--lib/banzai/filter/relative_link_filter.rb12
-rw-r--r--lib/banzai/filter/sanitization_filter.rb4
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb26
-rw-r--r--lib/banzai/filter/video_link_filter.rb3
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb7
-rw-r--r--lib/banzai/renderer.rb16
-rw-r--r--lib/ci/charts.rb96
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb190
-rw-r--r--lib/ci/static_model.rb49
-rw-r--r--lib/extracts_path.rb2
-rw-r--r--lib/gitlab/access.rb1
-rw-r--r--lib/gitlab/akismet_helper.rb47
-rw-r--r--lib/gitlab/backend/grack_auth.rb163
-rw-r--r--lib/gitlab/backend/shell.rb10
-rw-r--r--lib/gitlab/badge/base.rb21
-rw-r--r--lib/gitlab/badge/build.rb46
-rw-r--r--lib/gitlab/badge/build/metadata.rb28
-rw-r--r--lib/gitlab/badge/build/status.rb37
-rw-r--r--lib/gitlab/badge/build/template.rb47
-rw-r--r--lib/gitlab/badge/coverage/metadata.rb30
-rw-r--r--lib/gitlab/badge/coverage/report.rb56
-rw-r--r--lib/gitlab/badge/coverage/template.rb52
-rw-r--r--lib/gitlab/badge/metadata.rb36
-rw-r--r--lib/gitlab/badge/template.rb49
-rw-r--r--lib/gitlab/changes_list.rb25
-rw-r--r--lib/gitlab/checks/change_access.rb26
-rw-r--r--lib/gitlab/ci/config.rb2
-rw-r--r--lib/gitlab/ci/config/node/artifacts.rb35
-rw-r--r--lib/gitlab/ci/config/node/attributable.rb23
-rw-r--r--lib/gitlab/ci/config/node/cache.rb10
-rw-r--r--lib/gitlab/ci/config/node/commands.rb33
-rw-r--r--lib/gitlab/ci/config/node/configurable.rb25
-rw-r--r--lib/gitlab/ci/config/node/entry.rb49
-rw-r--r--lib/gitlab/ci/config/node/factory.rb47
-rw-r--r--lib/gitlab/ci/config/node/global.rb32
-rw-r--r--lib/gitlab/ci/config/node/hidden_job.rb23
-rw-r--r--lib/gitlab/ci/config/node/job.rb123
-rw-r--r--lib/gitlab/ci/config/node/jobs.rb48
-rw-r--r--lib/gitlab/ci/config/node/legacy_validation_helpers.rb4
-rw-r--r--lib/gitlab/ci/config/node/null.rb34
-rw-r--r--lib/gitlab/ci/config/node/stage.rb22
-rw-r--r--lib/gitlab/ci/config/node/trigger.rb26
-rw-r--r--lib/gitlab/ci/config/node/undefined.rb21
-rw-r--r--lib/gitlab/ci/config/node/validatable.rb10
-rw-r--r--lib/gitlab/ci/config/node/validator.rb17
-rw-r--r--lib/gitlab/ci/config/node/validators.rb22
-rw-r--r--lib/gitlab/closing_issue_extractor.rb4
-rw-r--r--lib/gitlab/database.rb6
-rw-r--r--lib/gitlab/diff/file.rb5
-rw-r--r--lib/gitlab/diff/file_collection/base.rb35
-rw-r--r--lib/gitlab/diff/file_collection/commit.rb14
-rw-r--r--lib/gitlab/diff/file_collection/compare.rb14
-rw-r--r--lib/gitlab/diff/file_collection/merge_request.rb73
-rw-r--r--lib/gitlab/diff/highlight.rb7
-rw-r--r--lib/gitlab/diff/inline_diff.rb74
-rw-r--r--lib/gitlab/diff/line.rb14
-rw-r--r--lib/gitlab/email/handler.rb17
-rw-r--r--lib/gitlab/email/handler/base_handler.rb60
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb52
-rw-r--r--lib/gitlab/email/handler/create_note_handler.rb55
-rw-r--r--lib/gitlab/email/message/repository_push.rb21
-rw-r--r--lib/gitlab/email/receiver.rb117
-rw-r--r--lib/gitlab/git.rb18
-rw-r--r--lib/gitlab/git_access.rb8
-rw-r--r--lib/gitlab/git_post_receive.rb1
-rw-r--r--lib/gitlab/github_import/branch_formatter.rb4
-rw-r--r--lib/gitlab/github_import/hook_formatter.rb23
-rw-r--r--lib/gitlab/github_import/importer.rb77
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb22
-rw-r--r--lib/gitlab/import_export.rb6
-rw-r--r--lib/gitlab/import_export/avatar_restorer.rb1
-rw-r--r--lib/gitlab/import_export/command_line_util.rb8
-rw-r--r--lib/gitlab/import_export/file_importer.rb18
-rw-r--r--lib/gitlab/import_export/import_export.yml22
-rw-r--r--lib/gitlab/import_export/json_hash_builder.rb110
-rw-r--r--lib/gitlab/import_export/members_mapper.rb13
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb4
-rw-r--r--lib/gitlab/import_export/reader.rb77
-rw-r--r--lib/gitlab/import_export/relation_factory.rb65
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb12
-rw-r--r--lib/gitlab/import_export/version_checker.rb2
-rw-r--r--lib/gitlab/incoming_email.rb6
-rw-r--r--lib/gitlab/ldap/access.rb2
-rw-r--r--lib/gitlab/ldap/adapter.rb2
-rw-r--r--lib/gitlab/lfs/response.rb329
-rw-r--r--lib/gitlab/lfs/router.rb98
-rw-r--r--lib/gitlab/mail_room.rb47
-rw-r--r--lib/gitlab/metrics.rb8
-rw-r--r--lib/gitlab/metrics/instrumentation.rb13
-rw-r--r--lib/gitlab/metrics/method_call.rb4
-rw-r--r--lib/gitlab/metrics/system.rb8
-rw-r--r--lib/gitlab/metrics/transaction.rb27
-rw-r--r--lib/gitlab/popen.rb2
-rw-r--r--lib/gitlab/redis.rb94
-rw-r--r--lib/gitlab/request_profiler.rb19
-rw-r--r--lib/gitlab/request_profiler/middleware.rb54
-rw-r--r--lib/gitlab/request_profiler/profile.rb43
-rw-r--r--lib/gitlab/sidekiq_middleware/request_store_middleware.rb13
-rw-r--r--lib/gitlab/themes.rb16
-rw-r--r--lib/gitlab/user_access.rb12
-rw-r--r--lib/rouge/formatters/html_gitlab.rb2
-rw-r--r--lib/support/nginx/gitlab7
-rw-r--r--lib/support/nginx/gitlab-ssl7
-rw-r--r--lib/tasks/downtime_check.rake26
-rw-r--r--lib/tasks/gitlab/bulk_add_permission.rake12
-rw-r--r--lib/tasks/gitlab/check.rake28
-rw-r--r--lib/tasks/gitlab/db.rake7
-rw-r--r--lib/tasks/gitlab/info.rake4
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/gitlab/task_helpers.rake14
-rw-r--r--lib/tasks/gitlab/web_hook.rake4
-rw-r--r--lib/tasks/spinach.rake8
-rw-r--r--lib/tasks/test.rake2
-rw-r--r--public/404.html84
-rw-r--r--public/422.html82
-rw-r--r--public/500.html77
-rw-r--r--public/502.html27
-rw-r--r--public/503.html27
-rw-r--r--public/deploy.html98
-rwxr-xr-xscripts/lint-doc.sh15
-rwxr-xr-xscripts/merge-simplecov30
-rwxr-xr-xscripts/prepare_build.sh7
-rw-r--r--spec/config/mail_room_spec.rb43
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb25
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb12
-rw-r--r--spec/controllers/admin/users_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb6
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb13
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/groups_controller_spec.rb30
-rw-r--r--spec/controllers/profiles/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb18
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb48
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb2
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb23
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb58
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb10
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb161
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb30
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/projects/protected_branches_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb2
-rw-r--r--spec/controllers/projects/services_controller_spec.rb4
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb20
-rw-r--r--spec/controllers/projects_controller_spec.rb10
-rw-r--r--spec/factories/broadcast_messages.rb4
-rw-r--r--spec/factories/ci/builds.rb21
-rw-r--r--spec/factories/ci/pipelines.rb2
-rw-r--r--spec/factories/ci/trigger_requests.rb3
-rw-r--r--spec/factories/commit_statuses.rb24
-rw-r--r--spec/factories/environments.rb1
-rw-r--r--spec/factories/protected_branches.rb23
-rw-r--r--spec/factories/user_agent_details.rb7
-rw-r--r--spec/factories_spec.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb4
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb1
-rw-r--r--spec/features/admin/admin_hooks_spec.rb6
-rw-r--r--spec/features/admin/admin_projects_spec.rb6
-rw-r--r--spec/features/admin/admin_users_spec.rb28
-rw-r--r--spec/features/atom/dashboard_spec.rb6
-rw-r--r--spec/features/atom/issues_spec.rb4
-rw-r--r--spec/features/atom/users_spec.rb12
-rw-r--r--spec/features/builds_spec.rb30
-rw-r--r--spec/features/ci_lint_spec.rb2
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/compare_spec.rb6
-rw-r--r--spec/features/dashboard/label_filter_spec.rb2
-rw-r--r--spec/features/dashboard_issues_spec.rb6
-rw-r--r--spec/features/environments_spec.rb4
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb24
-rw-r--r--spec/features/help_pages_spec.rb2
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb171
-rw-r--r--spec/features/issues/award_emoji_spec.rb10
-rw-r--r--spec/features/issues/award_spec.rb8
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb8
-rw-r--r--spec/features/issues/filter_by_labels_spec.rb48
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb6
-rw-r--r--spec/features/issues/filter_issues_spec.rb40
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb8
-rw-r--r--spec/features/issues/new_branch_button_spec.rb2
-rw-r--r--spec/features/issues/todo_spec.rb4
-rw-r--r--spec/features/issues/update_issues_spec.rb12
-rw-r--r--spec/features/issues_spec.rb66
-rw-r--r--spec/features/login_spec.rb4
-rw-r--r--spec/features/merge_requests/award_spec.rb8
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb2
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb12
-rw-r--r--spec/features/merge_requests/diff_notes_spec.rb159
-rw-r--r--spec/features/merge_requests/edit_mr_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb6
-rw-r--r--spec/features/merge_requests/merge_when_build_succeeds_spec.rb2
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb33
-rw-r--r--spec/features/milestone_spec.rb4
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb12
-rw-r--r--spec/features/participants_autocomplete_spec.rb6
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/profiles/password_spec.rb45
-rw-r--r--spec/features/profiles/preferences_spec.rb4
-rw-r--r--spec/features/projects/badges/coverage_spec.rb73
-rw-r--r--spec/features/projects/badges/list_spec.rb46
-rw-r--r--spec/features/projects/branches_spec.rb~HEAD32
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb34
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb29
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb1
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb1
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb3
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb4
-rw-r--r--spec/features/projects/pipelines_spec.rb (renamed from spec/features/pipelines_spec.rb)79
-rw-r--r--spec/features/projects/project_settings_spec.rb41
-rw-r--r--spec/features/projects/ref_switcher_spec.rb29
-rw-r--r--spec/features/projects_spec.rb8
-rw-r--r--spec/features/protected_branches_spec.rb68
-rw-r--r--spec/features/search_spec.rb38
-rw-r--r--spec/features/todos/todos_spec.rb2
-rw-r--r--spec/features/u2f_spec.rb32
-rw-r--r--spec/features/variables_spec.rb9
-rw-r--r--spec/finders/branches_finder_spec.rb14
-rw-r--r--spec/finders/merge_requests_finder_spec.rb4
-rw-r--r--spec/finders/notes_finder_spec.rb4
-rw-r--r--spec/finders/projects_finder_spec.rb73
-rw-r--r--spec/fixtures/config/redis_new_format_host.yml29
-rw-r--r--spec/fixtures/config/redis_new_format_socket.yml6
-rw-r--r--spec/fixtures/config/redis_old_format_host.yml5
-rw-r--r--spec/fixtures/config/redis_old_format_socket.yml3
-rw-r--r--spec/fixtures/emails/valid_new_issue.eml23
-rw-r--r--spec/fixtures/emails/valid_new_issue_empty.eml18
-rw-r--r--spec/fixtures/emails/wrong_authentication_token.eml18
-rw-r--r--spec/fixtures/emails/wrong_mail_key.eml (renamed from spec/fixtures/emails/wrong_reply_key.eml)0
-rw-r--r--spec/fixtures/project_services/campfire/rooms.json22
-rw-r--r--spec/fixtures/project_services/campfire/rooms2.json22
-rw-r--r--spec/helpers/application_helper_spec.rb37
-rw-r--r--spec/helpers/blob_helper_spec.rb28
-rw-r--r--spec/helpers/diff_helper_spec.rb106
-rw-r--r--spec/helpers/emails_helper_spec.rb12
-rw-r--r--spec/helpers/events_helper_spec.rb14
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb20
-rw-r--r--spec/helpers/graph_helper_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb4
-rw-r--r--spec/helpers/issues_helper_spec.rb101
-rw-r--r--spec/helpers/members_helper_spec.rb48
-rw-r--r--spec/helpers/notes_helper_spec.rb57
-rw-r--r--spec/helpers/search_helper_spec.rb2
-rw-r--r--spec/helpers/submodule_helper_spec.rb28
-rw-r--r--spec/helpers/tree_helper_spec.rb4
-rw-r--r--spec/initializers/secret_token_spec.rb200
-rw-r--r--spec/initializers/trusted_proxies_spec.rb6
-rw-r--r--spec/javascripts/datetime_utility_spec.js.coffee31
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb62
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb12
-rw-r--r--spec/lib/ci/charts_spec.rb20
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb85
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb2
-rw-r--r--spec/lib/extracts_path_spec.rb21
-rw-r--r--spec/lib/gitlab/akismet_helper_spec.rb35
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb4
-rw-r--r--spec/lib/gitlab/auth_spec.rb10
-rw-r--r--spec/lib/gitlab/badge/build/metadata_spec.rb27
-rw-r--r--spec/lib/gitlab/badge/build/status_spec.rb94
-rw-r--r--spec/lib/gitlab/badge/build/template_spec.rb82
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb123
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb30
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb93
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb130
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb21
-rw-r--r--spec/lib/gitlab/changes_list_spec.rb30
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb99
-rw-r--r--spec/lib/gitlab/ci/config/node/artifacts_spec.rb45
-rw-r--r--spec/lib/gitlab/ci/config/node/attributable_spec.rb43
-rw-r--r--spec/lib/gitlab/ci/config/node/commands_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/config/node/factory_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/config/node/global_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/config/node/hidden_job_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/node/job_spec.rb86
-rw-r--r--spec/lib/gitlab/ci/config/node/jobs_spec.rb87
-rw-r--r--spec/lib/gitlab/ci/config/node/null_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/config/node/stage_spec.rb38
-rw-r--r--spec/lib/gitlab/ci/config/node/trigger_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/config/node/undefined_spec.rb32
-rw-r--r--spec/lib/gitlab/ci/config/node/validatable_spec.rb4
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb11
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/email/email_shared_blocks.rb41
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb79
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb116
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb13
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb157
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb1
-rw-r--r--spec/lib/gitlab/git_access_spec.rb102
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb14
-rw-r--r--spec/lib/gitlab/github_import/hook_formatter_spec.rb65
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb45
-rw-r--r--spec/lib/gitlab/highlight_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/project.json182
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb35
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb30
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb30
-rw-r--r--spec/lib/gitlab/incoming_email_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics_spec.rb6
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb6
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb12
-rw-r--r--spec/lib/gitlab/redis_spec.rb79
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb10
-rw-r--r--spec/lib/gitlab/search_results_spec.rb12
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb6
-rw-r--r--spec/lib/gitlab/user_access_spec.rb56
-rw-r--r--spec/mailers/emails/profile_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb12
-rw-r--r--spec/models/ability_spec.rb104
-rw-r--r--spec/models/application_setting_spec.rb22
-rw-r--r--spec/models/blob_spec.rb38
-rw-r--r--spec/models/broadcast_message_spec.rb6
-rw-r--r--spec/models/build_spec.rb85
-rw-r--r--spec/models/ci/pipeline_spec.rb424
-rw-r--r--spec/models/ci/trigger_spec.rb4
-rw-r--r--spec/models/commit_spec.rb42
-rw-r--r--spec/models/commit_status_spec.rb12
-rw-r--r--spec/models/compare_spec.rb77
-rw-r--r--spec/models/concerns/faster_cache_keys_spec.rb17
-rw-r--r--spec/models/concerns/mentionable_spec.rb6
-rw-r--r--spec/models/concerns/milestoneish_spec.rb36
-rw-r--r--spec/models/concerns/spammable_spec.rb33
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb24
-rw-r--r--spec/models/diff_note_spec.rb4
-rw-r--r--spec/models/environment_spec.rb52
-rw-r--r--spec/models/forked_project_link_spec.rb10
-rw-r--r--spec/models/global_milestone_spec.rb12
-rw-r--r--spec/models/group_spec.rb6
-rw-r--r--spec/models/hooks/project_hook_spec.rb4
-rw-r--r--spec/models/hooks/system_hook_spec.rb2
-rw-r--r--spec/models/issue_spec.rb253
-rw-r--r--spec/models/key_spec.rb9
-rw-r--r--spec/models/label_spec.rb4
-rw-r--r--spec/models/legacy_diff_note_spec.rb6
-rw-r--r--spec/models/member_spec.rb14
-rw-r--r--spec/models/members/group_member_spec.rb4
-rw-r--r--spec/models/members/project_member_spec.rb9
-rw-r--r--spec/models/merge_request_diff_spec.rb12
-rw-r--r--spec/models/merge_request_spec.rb96
-rw-r--r--spec/models/milestone_spec.rb20
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/models/note_spec.rb6
-rw-r--r--spec/models/project_security_spec.rb18
-rw-r--r--spec/models/project_services/asana_service_spec.rb8
-rw-r--r--spec/models/project_services/assembla_service_spec.rb2
-rw-r--r--spec/models/project_services/campfire_service_spec.rb58
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb2
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb4
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb71
-rw-r--r--spec/models/project_services/irker_service_spec.rb9
-rw-r--r--spec/models/project_services/jira_service_spec.rb10
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb71
-rw-r--r--spec/models/project_services/pushover_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service/note_message_spec.rb1
-rw-r--r--spec/models/project_services/slack_service/wiki_page_message_spec.rb4
-rw-r--r--spec/models/project_services/slack_service_spec.rb20
-rw-r--r--spec/models/project_spec.rb228
-rw-r--r--spec/models/project_team_spec.rb82
-rw-r--r--spec/models/repository_spec.rb126
-rw-r--r--spec/models/service_spec.rb4
-rw-r--r--spec/models/user_agent_detail_spec.rb31
-rw-r--r--spec/models/user_spec.rb28
-rw-r--r--spec/models/wiki_page_spec.rb6
-rw-r--r--spec/requests/api/access_requests_spec.rb246
-rw-r--r--spec/requests/api/api_helpers_spec.rb30
-rw-r--r--spec/requests/api/award_emoji_spec.rb6
-rw-r--r--spec/requests/api/branches_spec.rb40
-rw-r--r--spec/requests/api/builds_spec.rb46
-rw-r--r--spec/requests/api/commit_statuses_spec.rb10
-rw-r--r--spec/requests/api/commits_spec.rb63
-rw-r--r--spec/requests/api/deploy_keys.rb38
-rw-r--r--spec/requests/api/deploy_keys_spec.rb160
-rw-r--r--spec/requests/api/environments_spec.rb129
-rw-r--r--spec/requests/api/files_spec.rb22
-rw-r--r--spec/requests/api/fork_spec.rb12
-rw-r--r--spec/requests/api/group_members_spec.rb199
-rw-r--r--spec/requests/api/groups_spec.rb56
-rw-r--r--spec/requests/api/internal_spec.rb18
-rw-r--r--spec/requests/api/issues_spec.rb111
-rw-r--r--spec/requests/api/keys_spec.rb6
-rw-r--r--spec/requests/api/labels_spec.rb60
-rw-r--r--spec/requests/api/members_spec.rb312
-rw-r--r--spec/requests/api/merge_requests_spec.rb88
-rw-r--r--spec/requests/api/milestones_spec.rb30
-rw-r--r--spec/requests/api/namespaces_spec.rb10
-rw-r--r--spec/requests/api/notes_spec.rb56
-rw-r--r--spec/requests/api/project_hooks_spec.rb36
-rw-r--r--spec/requests/api/project_members_spec.rb166
-rw-r--r--spec/requests/api/project_snippets_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb246
-rw-r--r--spec/requests/api/repositories_spec.rb40
-rw-r--r--spec/requests/api/runners_spec.rb98
-rw-r--r--spec/requests/api/services_spec.rb22
-rw-r--r--spec/requests/api/session_spec.rb12
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/api/system_hooks_spec.rb20
-rw-r--r--spec/requests/api/tags_spec.rb34
-rw-r--r--spec/requests/api/todos_spec.rb12
-rw-r--r--spec/requests/api/triggers_spec.rb47
-rw-r--r--spec/requests/api/users_spec.rb229
-rw-r--r--spec/requests/api/variables_spec.rb38
-rw-r--r--spec/requests/ci/api/builds_spec.rb191
-rw-r--r--spec/requests/ci/api/triggers_spec.rb19
-rw-r--r--spec/requests/git_http_spec.rb18
-rw-r--r--spec/requests/lfs_http_spec.rb59
-rw-r--r--spec/routing/admin_routing_spec.rb5
-rw-r--r--spec/routing/project_routing_spec.rb17
-rw-r--r--spec/routing/routing_spec.rb8
-rw-r--r--spec/services/ci/create_builds_service_spec.rb32
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb214
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb5
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb4
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb288
-rw-r--r--spec/services/create_commit_builds_service_spec.rb241
-rw-r--r--spec/services/create_snippet_service_spec.rb4
-rw-r--r--spec/services/delete_user_service_spec.rb8
-rw-r--r--spec/services/destroy_group_service_spec.rb58
-rw-r--r--spec/services/event_create_service_spec.rb14
-rw-r--r--spec/services/files/update_service_spec.rb84
-rw-r--r--spec/services/git_hooks_service_spec.rb6
-rw-r--r--spec/services/git_push_service_spec.rb26
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb64
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb4
-rw-r--r--spec/services/issues/update_service_spec.rb8
-rw-r--r--spec/services/merge_requests/build_service_spec.rb8
-rw-r--r--spec/services/merge_requests/close_service_spec.rb4
-rw-r--r--spec/services/merge_requests/create_service_spec.rb2
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb134
-rw-r--r--spec/services/merge_requests/merge_request_diff_cache_service_spec.rb17
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb15
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb10
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb10
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb6
-rw-r--r--spec/services/merge_requests/update_service_spec.rb8
-rw-r--r--spec/services/notification_service_spec.rb8
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb14
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/services/projects/enable_deploy_key_service_spec.rb27
-rw-r--r--spec/services/projects/fork_service_spec.rb8
-rw-r--r--spec/services/projects/update_service_spec.rb18
-rw-r--r--spec/services/repair_ldap_blocked_user_service_spec.rb4
-rw-r--r--spec/services/search_service_spec.rb8
-rw-r--r--spec/services/system_note_service_spec.rb12
-rw-r--r--spec/services/test_hook_service_spec.rb2
-rw-r--r--spec/services/todo_service_spec.rb36
-rw-r--r--spec/simplecov_env.rb54
-rw-r--r--spec/spec_helper.rb13
-rw-r--r--spec/support/api/members_shared_examples.rb11
-rw-r--r--spec/support/issue_helpers.rb13
-rw-r--r--spec/support/merge_request_helpers.rb13
-rw-r--r--spec/support/select2_helper.rb2
-rw-r--r--spec/support/test_env.rb5
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb16
-rw-r--r--spec/tasks/gitlab/db_rake_spec.rb8
-rw-r--r--spec/teaspoon_env.rb10
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb2
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb22
-rw-r--r--spec/views/projects/issues/_related_branches.html.haml_spec.rb21
-rw-r--r--spec/views/projects/merge_requests/_heading.html.haml_spec.rb26
-rw-r--r--spec/views/projects/tree/show.html.haml_spec.rb37
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/group_destroy_worker_spec.rb19
-rw-r--r--spec/workers/post_receive_spec.rb14
-rw-r--r--spec/workers/project_destroy_worker_spec.rb24
-rw-r--r--spec/workers/repository_fork_worker_spec.rb10
-rw-r--r--vendor/gitignore/Elm.gitignore2
-rw-r--r--vendor/gitignore/Global/VisualStudioCode.gitignore6
-rw-r--r--vendor/gitignore/Go.gitignore3
-rw-r--r--vendor/gitignore/Leiningen.gitignore3
-rw-r--r--vendor/gitignore/Objective-C.gitignore2
-rw-r--r--vendor/gitignore/Scala.gitignore4
-rw-r--r--vendor/gitignore/SugarCRM.gitignore2
-rw-r--r--vendor/gitignore/TeX.gitignore11
-rw-r--r--vendor/gitignore/Terraform.gitignore3
-rw-r--r--vendor/gitignore/Unity.gitignore3
-rw-r--r--vendor/gitlab-ci-yml/C++.gitlab-ci.yml26
-rw-r--r--vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml2
-rw-r--r--vendor/gitlab-ci-yml/Grails.gitlab-ci.yml40
-rw-r--r--vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml11
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml26
-rw-r--r--vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml32
-rw-r--r--vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml8
1000 files changed, 20091 insertions, 10165 deletions
diff --git a/.gitattributes b/.gitattributes
index 7e800609e6c..17cbaa5eef5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,2 @@
-CHANGELOG merge=union \ No newline at end of file
+CHANGELOG merge=union
+*.js.es6 gitlab-language=javascript
diff --git a/.gitignore b/.gitignore
index ce6a363fe35..1bf9a47aef6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@
/config/secrets.yml
/config/sidekiq.yml
/coverage/*
+/coverage-javascript/
/db/*.sqlite3
/db/*.sqlite3-journal
/db/data.yml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2d33bad5886..be5614520a5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,7 @@
-image: "ruby:2.1"
+image: "ruby:2.3.1"
cache:
- key: "ruby21"
+ key: "ruby-231"
paths:
- vendor/apt
- vendor/ruby
@@ -15,6 +15,7 @@ variables:
USE_DB: "true"
USE_BUNDLE_INSTALL: "true"
GIT_DEPTH: "20"
+ PHANTOMJS_VERSION: "2.1.1"
before_script:
- source ./scripts/prepare_build.sh
@@ -28,6 +29,7 @@ stages:
- prepare
- test
- post-test
+- pages
# Prepare and merge knapsack tests
.knapsack-state: &knapsack-state
@@ -40,6 +42,7 @@ stages:
paths:
- knapsack/
artifacts:
+ expire_in: 31d
paths:
- knapsack/
@@ -81,8 +84,10 @@ update-knapsack:
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
- knapsack rspec
artifacts:
+ expire_in: 31d
paths:
- knapsack/
+ - coverage/
.spinach-knapsack: &spinach-knapsack
stage: test
@@ -97,8 +102,10 @@ update-knapsack:
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
+ expire_in: 31d
paths:
- knapsack/
+ - coverage/
rspec 0 20: *rspec-knapsack
rspec 1 20: *rspec-knapsack
@@ -132,68 +139,68 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack
-# Execute all testing suites against Ruby 2.3
-.ruby-23: &ruby-23
- image: "ruby:2.3"
+# Execute all testing suites against Ruby 2.1
+.ruby-21: &ruby-21
+ image: "ruby:2.1"
<<: *use-db
only:
- master
cache:
- key: "ruby-23"
+ key: "ruby21"
paths:
- vendor/apt
- vendor/ruby
-.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
+.rspec-knapsack-ruby21: &rspec-knapsack-ruby21
<<: *rspec-knapsack
- <<: *ruby-23
+ <<: *ruby-21
-.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
+.spinach-knapsack-ruby21: &spinach-knapsack-ruby21
<<: *spinach-knapsack
- <<: *ruby-23
-
-rspec 0 20 ruby23: *rspec-knapsack-ruby23
-rspec 1 20 ruby23: *rspec-knapsack-ruby23
-rspec 2 20 ruby23: *rspec-knapsack-ruby23
-rspec 3 20 ruby23: *rspec-knapsack-ruby23
-rspec 4 20 ruby23: *rspec-knapsack-ruby23
-rspec 5 20 ruby23: *rspec-knapsack-ruby23
-rspec 6 20 ruby23: *rspec-knapsack-ruby23
-rspec 7 20 ruby23: *rspec-knapsack-ruby23
-rspec 8 20 ruby23: *rspec-knapsack-ruby23
-rspec 9 20 ruby23: *rspec-knapsack-ruby23
-rspec 10 20 ruby23: *rspec-knapsack-ruby23
-rspec 11 20 ruby23: *rspec-knapsack-ruby23
-rspec 12 20 ruby23: *rspec-knapsack-ruby23
-rspec 13 20 ruby23: *rspec-knapsack-ruby23
-rspec 14 20 ruby23: *rspec-knapsack-ruby23
-rspec 15 20 ruby23: *rspec-knapsack-ruby23
-rspec 16 20 ruby23: *rspec-knapsack-ruby23
-rspec 17 20 ruby23: *rspec-knapsack-ruby23
-rspec 18 20 ruby23: *rspec-knapsack-ruby23
-rspec 19 20 ruby23: *rspec-knapsack-ruby23
-
-spinach 0 10 ruby23: *spinach-knapsack-ruby23
-spinach 1 10 ruby23: *spinach-knapsack-ruby23
-spinach 2 10 ruby23: *spinach-knapsack-ruby23
-spinach 3 10 ruby23: *spinach-knapsack-ruby23
-spinach 4 10 ruby23: *spinach-knapsack-ruby23
-spinach 5 10 ruby23: *spinach-knapsack-ruby23
-spinach 6 10 ruby23: *spinach-knapsack-ruby23
-spinach 7 10 ruby23: *spinach-knapsack-ruby23
-spinach 8 10 ruby23: *spinach-knapsack-ruby23
-spinach 9 10 ruby23: *spinach-knapsack-ruby23
+ <<: *ruby-21
+
+rspec 0 20 ruby21: *rspec-knapsack-ruby21
+rspec 1 20 ruby21: *rspec-knapsack-ruby21
+rspec 2 20 ruby21: *rspec-knapsack-ruby21
+rspec 3 20 ruby21: *rspec-knapsack-ruby21
+rspec 4 20 ruby21: *rspec-knapsack-ruby21
+rspec 5 20 ruby21: *rspec-knapsack-ruby21
+rspec 6 20 ruby21: *rspec-knapsack-ruby21
+rspec 7 20 ruby21: *rspec-knapsack-ruby21
+rspec 8 20 ruby21: *rspec-knapsack-ruby21
+rspec 9 20 ruby21: *rspec-knapsack-ruby21
+rspec 10 20 ruby21: *rspec-knapsack-ruby21
+rspec 11 20 ruby21: *rspec-knapsack-ruby21
+rspec 12 20 ruby21: *rspec-knapsack-ruby21
+rspec 13 20 ruby21: *rspec-knapsack-ruby21
+rspec 14 20 ruby21: *rspec-knapsack-ruby21
+rspec 15 20 ruby21: *rspec-knapsack-ruby21
+rspec 16 20 ruby21: *rspec-knapsack-ruby21
+rspec 17 20 ruby21: *rspec-knapsack-ruby21
+rspec 18 20 ruby21: *rspec-knapsack-ruby21
+rspec 19 20 ruby21: *rspec-knapsack-ruby21
+
+spinach 0 10 ruby21: *spinach-knapsack-ruby21
+spinach 1 10 ruby21: *spinach-knapsack-ruby21
+spinach 2 10 ruby21: *spinach-knapsack-ruby21
+spinach 3 10 ruby21: *spinach-knapsack-ruby21
+spinach 4 10 ruby21: *spinach-knapsack-ruby21
+spinach 5 10 ruby21: *spinach-knapsack-ruby21
+spinach 6 10 ruby21: *spinach-knapsack-ruby21
+spinach 7 10 ruby21: *spinach-knapsack-ruby21
+spinach 8 10 ruby21: *spinach-knapsack-ruby21
+spinach 9 10 ruby21: *spinach-knapsack-ruby21
# Other generic tests
-.static-analyses-variables: &static-analyses-variables
+.ruby-static-analysis: &ruby-static-analysis
variables:
SIMPLECOV: "false"
USE_DB: "false"
USE_BUNDLE_INSTALL: "true"
.exec: &exec
- <<: *static-analyses-variables
+ <<: *ruby-static-analysis
stage: test
script:
- bundle exec $CI_BUILD_NAME
@@ -216,20 +223,54 @@ teaspoon:
stage: test
<<: *use-db
script:
+ - curl --silent --location https://deb.nodesource.com/setup_6.x | bash -
+ - apt-get install --assume-yes nodejs
+ - npm install --global istanbul
- teaspoon
+ artifacts:
+ name: coverage-javascript
+ expire_in: 31d
+ paths:
+ - coverage-javascript/default/
+
+lint-doc:
+ stage: test
+ image: "phusion/baseimage:latest"
+ before_script: []
+ script:
+ - scripts/lint-doc.sh
bundler:audit:
stage: test
- <<: *static-analyses-variables
+ <<: *ruby-static-analysis
only:
- master
script:
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
+coverage:
+ stage: post-test
+ services: []
+ variables:
+ USE_DB: "false"
+ USE_BUNDLE_INSTALL: "true"
+ script:
+ - bundle exec scripts/merge-simplecov
+ artifacts:
+ name: coverage
+ expire_in: 31d
+ paths:
+ - coverage/index.html
+ - coverage/assets/
+
+
# Notify slack in the end
notify:slack:
stage: post-test
+ variables:
+ USE_DB: "false"
+ USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure
@@ -238,3 +279,20 @@ notify:slack:
- tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags@gitlab-org/gitlab-ee
+
+pages:
+ before_script: []
+ stage: pages
+ dependencies:
+ - coverage
+ - teaspoon
+ script:
+ - mv public/ .public/
+ - mkdir public/
+ - mv coverage public/coverage-ruby
+ - mv coverage-javascript/default/ public/coverage-javascript/
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000000..bd5ac22132c
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,35 @@
+#
+# This list is used by git-shortlog to make contributions from the
+# same person appearing to be so.
+#
+
+Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@archlinux.gr>
+Achilleas Pipinellis <axilleas@axilleas.me> <axilleas@users.noreply.github.com>
+Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dmitriy.zaporozhets@gmail.com>
+Dmitriy Zaporozhets <dzaporozhets@gitlab.com> <dzaporozhets@sphereconsultinginc.com>
+Douwe Maan <douwe@gitlab.com> <douwe@selenight.nl>
+Douwe Maan <douwe@gitlab.com> <me@douwe.me>
+Grzegorz Bizon <grzegorz@gitlab.com> <grzegorz.bizon@ntsn.pl>
+Grzegorz Bizon <grzegorz@gitlab.com> <grzesiek.bizon@gmail.com>
+Jacob Vosmaer <jacob@gitlab.com> <contact@jacobvosmaer.nl>
+Jacob Vosmaer <jacob@gitlab.com> Jacob Vosmaer (GitLab) <jacob@gitlab.com>
+Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MacBook-Pro.local>
+Jacob Schatz <jschatz@gitlab.com> <jacobschatz@Jacobs-MBP.fios-router.home>
+Jacob Schatz <jschatz@gitlab.com> <jschatz1@gmail.com>
+James Lopez <james@jameslopez.es> <james@gitlab.com>
+James Lopez <james@jameslopez.es> <james.lopez@vodafone.com>
+Kamil Trzciński <kamil@gitlab.com> <ayufan@ayufan.eu>
+Marin Jankovski <maxlazio@gmail.com> <marin@gitlab.com>
+Phil Hughes <me@iamphill.com> <theephil@gmail.com>
+Rémy Coutable <remy@rymai.me> <remy@gitlab.com>
+Robert Schilling <rschilling@student.tugraz.at> <Razer6@users.noreply.github.com>
+Robert Schilling <rschilling@student.tugraz.at> <schilling.ro@gmail.com>
+Robert Speicher <robert@gitlab.com> <rspeicher@gmail.com>
+Stan Hu <stanhu@gmail.com> <stanhu@alum.mit.edu>
+Stan Hu <stanhu@gmail.com> <stanhu@packetzoom.com>
+Stan Hu <stanhu@gmail.com> <stanhu@users.noreply.github.com>
+Stan Hu <stanhu@gmail.com> stanhu <stanhu@gmail.com>
+Sytse Sijbrandij <sytse@gitlab.com> <sytse+admin@gitlab.com>
+Sytse Sijbrandij <sytse@gitlab.com> <sytse@dosire.com>
+Sytse Sijbrandij <sytse@gitlab.com> <sytses@gmail.com>
+Sytse Sijbrandij <sytse@gitlab.com> dosire <sytse@gitlab.com>
diff --git a/.rubocop.yml b/.rubocop.yml
index 6adbda53456..282f4539f03 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -149,19 +149,19 @@ Style/EmptyLinesAroundAccessModifier:
# Keeps track of empty lines around block bodies.
Style/EmptyLinesAroundBlockBody:
- Enabled: false
+ Enabled: true
# Keeps track of empty lines around class bodies.
Style/EmptyLinesAroundClassBody:
- Enabled: false
+ Enabled: true
# Keeps track of empty lines around module bodies.
Style/EmptyLinesAroundModuleBody:
- Enabled: false
+ Enabled: true
# Keeps track of empty lines around method bodies.
Style/EmptyLinesAroundMethodBody:
- Enabled: false
+ Enabled: true
# Avoid the use of END blocks.
Style/EndBlock:
@@ -373,6 +373,10 @@ Style/SpaceAfterNot:
Style/SpaceAfterSemicolon:
Enabled: true
+# Use space around equals in parameter default
+Style/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
# Use a space around keywords if appropriate.
Style/SpaceAroundKeyword:
Enabled: true
@@ -510,6 +514,15 @@ Metrics/PerceivedComplexity:
#################### Lint ################################
+# Checks for useless access modifiers.
+Lint/UselessAccessModifier:
+ Enabled: true
+
+# Checks for attempts to use `private` or `protected` to set the visibility
+# of a class method, which does not work.
+Lint/IneffectiveAccessModifier:
+ Enabled: false
+
# Checks for ambiguous operators in the first argument of a method invocation
# without parentheses.
Lint/AmbiguousOperator:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index b622b9239d4..20daf1619a7 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -19,10 +19,6 @@ Lint/AssignmentInCondition:
Lint/HandleExceptions:
Enabled: false
-# Offense count: 21
-Lint/IneffectiveAccessModifier:
- Enabled: false
-
# Offense count: 2
Lint/Loop:
Enabled: false
@@ -48,10 +44,6 @@ Lint/UnusedBlockArgument:
Lint/UnusedMethodArgument:
Enabled: false
-# Offense count: 11
-Lint/UselessAccessModifier:
- Enabled: false
-
# Offense count: 12
# Cop supports --auto-correct.
Performance/PushSplat:
@@ -347,13 +339,6 @@ Style/SingleLineBlockParams:
Style/SingleLineMethods:
Enabled: false
-# Offense count: 14
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle, SupportedStyles.
-# SupportedStyles: space, no_space
-Style/SpaceAroundEqualsInParameterDefault:
- Enabled: false
-
# Offense count: 119
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
diff --git a/.ruby-version b/.ruby-version
index ebf14b46981..2bf1c1ccf36 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.1.8
+2.3.1
diff --git a/.simplecov b/.simplecov
deleted file mode 100644
index d979288df44..00000000000
--- a/.simplecov
+++ /dev/null
@@ -1,4 +0,0 @@
-# .simplecov
-SimpleCov.start 'rails' do
- merge_timeout 3600
-end
diff --git a/CHANGELOG b/CHANGELOG
index ca4014461f0..fc9291eefd5 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,21 +1,163 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.11.0 (unreleased)
- - Remove magic comments (`# encoding: UTF-8`) from Ruby files !5456 (winniehell)
+ - Add test coverage report badge. !5708
+ - Remove the http_parser.rb dependency by removing the tinder gem. !5758 (tbalthazar)
+ - Ability to specify branches for Pivotal Tracker integration (Egor Lynko)
+ - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres)
+ - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres)
+ - Fix the title of the toggle dropdown button. !5515 (herminiotorres)
+ - Rename `markdown_preview` routes to `preview_markdown`. (Christopher Bartz)
+ - Update to Ruby 2.3.1. !4948
+ - Improve diff performance by eliminating redundant checks for text blobs
+ - Ensure that branch names containing escapable characters (e.g. %20) aren't unescaped indiscriminately. !5770 (ewiltshi)
+ - Convert switch icon into icon font (ClemMakesApps)
+ - API: Endpoints for enabling and disabling deploy keys
+ - API: List access requests, request access, approve, and deny access requests to a project or a group. !4833
+ - Use long options for curl examples in documentation !5703 (winniehell)
+ - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell)
+ - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell)
+ - Ignore URLs starting with // in Markdown links !5677 (winniehell)
+ - Fix CI status icon link underline (ClemMakesApps)
+ - The Repository class is now instrumented
+ - Fix filter label tooltip HTML rendering (ClemMakesApps)
+ - Cache the commit author in RequestStore to avoid extra lookups in PostReceive
+ - Expand commit message width in repo view (ClemMakesApps)
+ - Cache highlighted diff lines for merge requests
+ - Pre-create all builds for a Pipeline when the new Pipeline is created !5295
- Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
+ - Show member roles to all users on members page
+ - Project.visible_to_user is instrumented again
+ - Fix awardable button mutuality loading spinners (ClemMakesApps)
+ - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable
+ - Optimize maximum user access level lookup in loading of notes
+ - Add "No one can push" as an option for protected branches. !5081
+ - Improve performance of AutolinkFilter#text_parse by using XPath
+ - Add experimental Redis Sentinel support !1877
+ - Rendering of SVGs as blobs is now limited to SVGs with a size smaller or equal to 2MB
+ - Fix branches page dropdown sort initial state (ClemMakesApps)
+ - Environments have an url to link to
+ - Various redundant database indexes have been removed
+ - Update `timeago` plugin to use multiple string/locale settings
+ - Remove unused images (ClemMakesApps)
- Limit git rev-list output count to one in forced push check
- - Add green outline to New Branch button !5447 (winniehell)
+ - Show deployment status on merge requests with external URLs
+ - Clean up unused routes (Josef Strzibny)
+ - Fix issue on empty project to allow developers to only push to protected branches if given permission
+ - Add green outline to New Branch button. !5447 (winniehell)
+ - Optimize generating of cache keys for issues and notes
+ - Improve performance of syntax highlighting Markdown code blocks
+ - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects
+ - Remove delay when hitting "Reply..." button on page with a lot of discussions
- Retrieve rendered HTML from cache in one request
+ - Fix renaming repository when name contains invalid chararacters under project settings
+ - Upgrade Grape from 0.13.0 to 0.15.0. !4601
+ - Trigram indexes for the "ci_runners" table have been removed to speed up UPDATE queries
+ - Fix devise deprecation warnings.
+ - Update version_sorter and use new interface for faster tag sorting
+ - Optimize checking if a user has read access to a list of issues !5370
+ - Store all DB secrets in secrets.yml, under descriptive names !5274
- Nokogiri's various parsing methods are now instrumented
- - Make fork counter always clickable !5463 (winniehell)
- - Load project invited groups and members eagerly in ProjectTeam#fetch_members
+ - Add archived badge to project list !5798
+ - Add simple identifier to public SSH keys (muteor)
+ - Admin page now references docs instead of a specific file !5600 (AnAverageHuman)
+ - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363
+ - Fix filter input alignment (ClemMakesApps)
+ - Include old revision in merge request update hooks (Ben Boeckel)
+ - Add build event color in HipChat messages (David Eisner)
+ - Make fork counter always clickable. !5463 (winniehell)
+ - Document that webhook secret token is sent in X-Gitlab-Token HTTP header !5664 (lycoperdon)
+ - Gitlab::Highlight is now instrumented
+ - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
+ - Allow users to import cross-repository pull requests from GitHub
+ - The overhead of instrumented method calls has been reduced
+ - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
+ - Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
+ - Bump gitlab_git to speedup DiffCollection iterations
+ - Rewrite description of a blocked user in admin settings. (Elias Werberich)
+ - Make branches sortable without push permission !5462 (winniehell)
+ - Check for Ci::Build artifacts at database level on pipeline partial
+ - Convert image diff background image to CSS (ClemMakesApps)
+ - Remove unnecessary index_projects_on_builds_enabled index from the projects table
+ - Make "New issue" button in Issue page less obtrusive !5457 (winniehell)
+ - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration
+ - Fix search for notes which belongs to deleted objects
+ - Allow Akismet to be trained by submitting issues as spam or ham !5538
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
- - Add ES6 gem
-
-v 8.10.2 (unreleased)
+ - Allow branch names ending with .json for graph and network page !5579 (winniehell)
+ - Add the `sprockets-es6` gem
+ - Improve OAuth2 client documentation (muteor)
+ - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
+ - Profile requests when a header is passed
+ - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab.
+ - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible
+ - Add commit stats in commit api. !5517 (dixpac)
+ - Add CI configuration button on project page
+ - Make error pages responsive (Takuya Noguchi)
+ - Fix skip_repo parameter being ignored when destroying a namespace
+ - Change requests_profiles resource constraint to catch virtually any file
+ - Bump gitlab_git to lazy load compare commits
+ - Reduce number of queries made for merge_requests/:id/diffs
+ - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y)
+ - Fix bug where destroying a namespace would not always destroy projects
+ - Fix RequestProfiler::Middleware error when code is reloaded in development
+ - Catch what warden might throw when profiling requests to re-throw it
+ - Avoid commit lookup on diff_helper passing existing local variable to the helper method
+ - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac)
+ - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker
+ - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko)
+ - Adds support for pending invitation project members importing projects
+ - Update devise initializer to turn on changed password notification emails. !5648 (tombell)
+ - Avoid to show the original password field when password is automatically set. !5712 (duduribeiro)
+ - Fix importing GitLab projects with an invalid MR source project
+ - Sort folders with submodules in Files view !5521
+ - Each `File::exists?` replaced to `File::exist?` because of deprecate since ruby version 2.2.0
+ - Add auto-completition in pipeline (Katarzyna Kobierska Ula Budziszewska)
+ - Fix a memory leak caused by Banzai::Filter::SanitizationFilter
+ - Speed up todos queries by limiting the projects set we join with
+ - Ensure file editing in UI does not overwrite commited changes without warning user
+
+v 8.10.5
+ - Add a data migration to fix some missing timestamps in the members table. !5670
+ - Revert the "Defend against 'Host' header injection" change in the source NGINX templates. !5706
+ - Cache project count for 5 minutes to reduce DB load. !5746 & !5754
+
+v 8.10.4
+ - Don't close referenced upstream issues from a forked project.
+ - Fixes issue with dropdowns `enter` key not working correctly. !5544
+ - Fix Import/Export project import not working in HA mode. !5618
+ - Fix Import/Export error checking versions. !5638
+
+v 8.10.3
+ - Fix Import/Export issue importing milestones and labels not associated properly. !5426
+ - Fix timing problems running imports on production. !5523
+ - Add a log message when a project is scheduled for destruction for debugging. !5540
+ - Fix hooks missing on imported GitLab projects. !5549
+ - Properly abort a merge when merge conflicts occur. !5569
+ - Fix importer for GitHub Pull Requests when a branch was removed. !5573
+ - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584
+ - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588
+
+v 8.10.2
- User can now search branches by name. !5144
+ - Page is now properly rendered after committing the first file and creating the first branch. !5399
+ - Add branch or tag icon to ref in builds page. !5434
- Fix backup restore. !5459
- Use project ID in repository cache to prevent stale data from persisting across projects. !5460
+ - Fix issue with autocomplete search not working with enter key. !5466
+ - Add iid to MR API response. !5468
+ - Disable MySQL foreign key checks before dropping all tables. !5472
+ - Ensure relative paths for video are rewritten as we do for images. !5474
+ - Ensure current user can retry a build before showing the 'Retry' button. !5476
+ - Add ENV variable to skip repository storages validations. !5478
+ - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486
+ - Don't show comment button in gutter of diffs on MR discussion tab. !5493
+ - Rescue Rugged::OSError (lock exists) when creating references. !5497
+ - Fix expand all diffs button in compare view. !5500
+ - Show release notes in tags list. !5503
+ - Fix a bug where forking a project from a repository storage to another would fail. !5509
+ - Fix missing schema update for `20160722221922`. !5512
+ - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516
v 8.10.1
- Refactor repository storages documentation. !5428
@@ -73,6 +215,9 @@ v 8.10.0
- Fix check for New Branch button on Issue page. !4630 (winniehell)
- Fix GFM autocomplete not working on wiki pages
- Fixed enter key not triggering click on first row when searching in a dropdown
+ - Updated dropdowns in issuable form to use new GitLab dropdown style
+ - Make images fit to the size of the viewport !4810
+ - Fix check for New Branch button on Issue page !4630 (winniehell)
- Fix MR-auto-close text added to description. !4836
- Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags. !5105 (redetection)
@@ -135,6 +280,7 @@ v 8.10.0
- Fix new snippet style bug (elliotec)
- Instrument Rinku usage
- Be explicit to define merge request discussion variables
+ - Use cache for todos counter calling TodoService
- Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
- RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
- Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 14ff05c9aa3..fbc8e15bebf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,6 +41,8 @@ abbreviation.
If you have read this guide and want to know how the GitLab [core team]
operates please see [the GitLab contributing process](PROCESS.md).
+- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+
## Contributor license agreement
By submitting code as an individual you agree to the
@@ -334,6 +336,10 @@ request is as follows:
1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
+1. If your merge request adds one or more migrations, make sure to execute all
+ migrations on a fresh database before the MR is reviewed. If the review leads
+ to large changes in the MR, do this again once the review is complete.
+1. For more complex migrations, write tests.
The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get
@@ -459,8 +465,10 @@ merge request:
- multi-line method chaining style **Option B**: dot `.` on previous line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
+1. [Newlines styleguide][newlines-styleguide]
1. [Testing](doc/development/testing.md)
-1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
+1. [JavaScript (ES6)](https://github.com/airbnb/javascript)
+1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security
@@ -530,6 +538,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
+[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 944880fa15e..619b5376684 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.2.0
+3.3.3
diff --git a/Gemfile b/Gemfile
index f2ac74a5976..8b44b54e22c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
source 'https://rubygems.org'
-gem 'rails', '4.2.7'
+gem 'rails', '4.2.7.1'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.3.2'
+gem 'gitlab_git', '~> 10.4.5'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -69,7 +69,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
-gem 'grape', '~> 0.13.0'
+gem 'grape', '~> 0.15.0'
gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
@@ -154,7 +154,7 @@ gem 'settingslogic', '~> 2.0.9'
# Misc
-gem 'version_sorter', '~> 2.0.0'
+gem 'version_sorter', '~> 2.1.0'
# Cache
gem 'redis-rails', '~> 4.0.0'
@@ -163,9 +163,6 @@ gem 'redis-rails', '~> 4.0.0'
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
-# Campfire integration
-gem 'tinder', '~> 1.10.0'
-
# HipChat integration
gem 'hipchat', '~> 1.5.0'
@@ -225,7 +222,7 @@ gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.6.1'
gem 'gemojione', '~> 3.0'
-gem 'gon', '~> 6.0.1'
+gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
@@ -253,7 +250,7 @@ group :development do
gem 'letter_opener_web', '~> 1.3.0'
gem 'rerun', '~> 0.11.0'
- gem 'bullet', '~> 5.0.0', require: false
+ gem 'bullet', '~> 5.2.0', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0'
@@ -275,7 +272,7 @@ group :development, :test do
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
- gem 'database_cleaner', '~> 1.4.0'
+ gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.5.0'
gem 'rspec-retry', '~> 0.4.5'
@@ -303,7 +300,7 @@ group :development, :test do
gem 'rubocop', '~> 0.41.2', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
- gem 'simplecov', '~> 0.11.0', require: false
+ gem 'simplecov', '0.12.0', require: false
gem 'flog', '~> 4.3.2', require: false
gem 'flay', '~> 2.6.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
@@ -326,7 +323,7 @@ group :production do
gem 'gitlab_meta', '7.0'
end
-gem 'newrelic_rpm', '~> 3.14'
+gem 'newrelic_rpm', '~> 3.16'
gem 'octokit', '~> 4.3.0'
@@ -334,6 +331,8 @@ gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8'
+gem 'ruby-prof', '~> 0.15.9'
+
## CI
gem 'activerecord-session_store', '~> 1.0.0'
gem 'nested_form', '~> 0.3.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index bfa7e38da85..2244c20203b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,34 +3,34 @@ GEM
specs:
RedCloth (4.3.2)
ace-rails-ap (4.0.2)
- actionmailer (4.2.7)
- actionpack (= 4.2.7)
- actionview (= 4.2.7)
- activejob (= 4.2.7)
+ actionmailer (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.7)
- actionview (= 4.2.7)
- activesupport (= 4.2.7)
+ actionpack (4.2.7.1)
+ actionview (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.7)
- activesupport (= 4.2.7)
+ actionview (4.2.7.1)
+ activesupport (= 4.2.7.1)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.7)
- activesupport (= 4.2.7)
+ activejob (4.2.7.1)
+ activesupport (= 4.2.7.1)
globalid (>= 0.3.0)
- activemodel (4.2.7)
- activesupport (= 4.2.7)
+ activemodel (4.2.7.1)
+ activesupport (= 4.2.7.1)
builder (~> 3.1)
- activerecord (4.2.7)
- activemodel (= 4.2.7)
- activesupport (= 4.2.7)
+ activerecord (4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
arel (~> 6.0)
activerecord-session_store (1.0.0)
actionpack (>= 4.0, < 5.1)
@@ -38,7 +38,7 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.1)
- activesupport (4.2.7)
+ activesupport (4.2.7.1)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -59,7 +59,7 @@ GEM
oauth2 (~> 1.0)
asciidoctor (1.5.3)
ast (2.3.0)
- attr_encrypted (3.0.1)
+ attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
attr_required (1.0.0)
autoprefixer-rails (6.2.3)
@@ -104,9 +104,9 @@ GEM
brakeman (3.3.2)
browser (2.2.0)
builder (3.2.2)
- bullet (5.0.0)
+ bullet (5.2.0)
activesupport (>= 3.0.0)
- uniform_notifier (~> 1.9.0)
+ uniform_notifier (~> 1.10.0)
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
@@ -153,11 +153,11 @@ GEM
d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
- database_cleaner (1.4.1)
+ database_cleaner (1.5.3)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
- default_value_for (3.0.1)
- activerecord (>= 3.2.0, < 5.0)
+ default_value_for (3.0.2)
+ activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.1.1)
@@ -278,7 +278,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
- gitlab_git (10.3.2)
+ gitlab_git (10.4.5)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -289,7 +289,7 @@ GEM
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
rubyntlm (~> 0.3)
- globalid (0.3.6)
+ globalid (0.3.7)
activesupport (>= 4.1.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
@@ -303,12 +303,12 @@ GEM
gollum-rugged_adapter (0.4.2)
mime-types (>= 1.15)
rugged (~> 0.24.0, >= 0.21.3)
- gon (6.0.1)
+ gon (6.1.0)
actionpack (>= 3.0)
json
multi_json
request_store (>= 1.0)
- grape (0.13.0)
+ grape (0.15.0)
activesupport
builder
hashie (>= 2.1.0)
@@ -335,11 +335,10 @@ GEM
activesupport (>= 2)
nokogiri (~> 1.4)
htmlentities (4.3.4)
- http_parser.rb (0.5.3)
httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
- httpclient (2.7.0.1)
+ httpclient (2.8.2)
i18n (0.7.0)
ice_nine (0.11.1)
influxdb (0.2.3)
@@ -404,7 +403,7 @@ GEM
nested_form (0.3.2)
net-ldap (0.12.1)
net-ssh (3.0.1)
- newrelic_rpm (3.14.1.311)
+ newrelic_rpm (3.16.0.318)
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
@@ -509,7 +508,7 @@ GEM
rack-cors (0.4.0)
rack-mount (0.8.3)
rack (>= 1.0.0)
- rack-oauth2 (1.2.1)
+ rack-oauth2 (1.2.3)
activesupport (>= 2.3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
@@ -519,16 +518,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.7)
- actionmailer (= 4.2.7)
- actionpack (= 4.2.7)
- actionview (= 4.2.7)
- activejob (= 4.2.7)
- activemodel (= 4.2.7)
- activerecord (= 4.2.7)
- activesupport (= 4.2.7)
+ rails (4.2.7.1)
+ actionmailer (= 4.2.7.1)
+ actionpack (= 4.2.7.1)
+ actionview (= 4.2.7.1)
+ activejob (= 4.2.7.1)
+ activemodel (= 4.2.7.1)
+ activerecord (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.7)
+ railties (= 4.2.7.1)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -538,9 +537,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.7)
- actionpack (= 4.2.7)
- activesupport (= 4.2.7)
+ railties (4.2.7.1)
+ actionpack (= 4.2.7.1)
+ activesupport (= 4.2.7.1)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -575,7 +574,7 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.7)
redis (>= 2.2)
- request_store (1.3.0)
+ request_store (1.3.1)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.1)
@@ -620,6 +619,7 @@ GEM
rubocop (>= 0.40.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
+ ruby-prof (0.15.9)
ruby-progressbar (1.8.1)
ruby-saml (1.3.0)
nokogiri (>= 1.5.10)
@@ -671,10 +671,9 @@ GEM
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0)
- simple_oauth (0.1.9)
- simplecov (0.11.2)
+ simplecov (0.12.0)
docile (~> 1.1.0)
- json (~> 1.8)
+ json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
sinatra (1.4.7)
@@ -741,21 +740,8 @@ GEM
tilt (2.0.5)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
- tinder (1.10.1)
- eventmachine (~> 1.0)
- faraday (~> 0.9.0)
- faraday_middleware (~> 0.9)
- hashie (>= 1.0)
- json (~> 1.8.0)
- mime-types
- multi_json (~> 1.7)
- twitter-stream (~> 0.1)
turbolinks (2.5.3)
coffee-rails
- twitter-stream (0.1.16)
- eventmachine (>= 0.12.8)
- http_parser.rb (~> 0.5.1)
- simple_oauth (~> 0.1.4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
u2f (0.2.1)
@@ -774,10 +760,10 @@ GEM
unicorn-worker-killer (0.4.4)
get_process_mem (~> 0)
unicorn (>= 4, < 6)
- uniform_notifier (1.9.0)
+ uniform_notifier (1.10.0)
uuid (2.3.8)
macaddr (~> 1.0)
- version_sorter (2.0.0)
+ version_sorter (2.1.0)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
@@ -829,7 +815,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
browser (~> 2.2)
- bullet (~> 5.0.0)
+ bullet (~> 5.2.0)
bundler-audit (~> 0.5.0)
byebug (~> 8.2.1)
capybara (~> 2.6.2)
@@ -841,7 +827,7 @@ DEPENDENCIES
connection_pool (~> 2.0)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
- database_cleaner (~> 1.4.0)
+ database_cleaner (~> 1.5.0)
default_value_for (~> 3.0.0)
devise (~> 4.0)
devise-two-factor (~> 3.0.0)
@@ -869,13 +855,13 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_git (~> 10.3.2)
+ gitlab_git (~> 10.4.5)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
- gon (~> 6.0.1)
- grape (~> 0.13.0)
+ gon (~> 6.1.0)
+ grape (~> 0.15.0)
grape-entity (~> 0.4.2)
hamlit (~> 2.5)
health_check (~> 2.1.0)
@@ -901,7 +887,7 @@ DEPENDENCIES
mysql2 (~> 0.3.16)
nested_form (~> 0.3.2)
net-ssh (~> 3.0.1)
- newrelic_rpm (~> 3.14)
+ newrelic_rpm (~> 3.16)
nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0)
octokit (~> 4.3.0)
@@ -928,7 +914,7 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
- rails (= 4.2.7)
+ rails (= 4.2.7.1)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
rblineprof (~> 0.3.6)
@@ -948,6 +934,7 @@ DEPENDENCIES
rubocop (~> 0.41.2)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
+ ruby-prof (~> 0.15.9)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0)
@@ -960,7 +947,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
- simplecov (~> 0.11.0)
+ simplecov (= 0.12.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
@@ -979,7 +966,6 @@ DEPENDENCIES
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2)
thin (~> 1.7.0)
- tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
u2f (~> 0.2.1)
uglifier (~> 2.7.2)
@@ -987,7 +973,7 @@ DEPENDENCIES
unf (~> 0.1.4)
unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2)
- version_sorter (~> 2.0.0)
+ version_sorter (~> 2.1.0)
virtus (~> 1.0.1)
vmstat (~> 2.1.1)
web-console (~> 2.0)
diff --git a/PROCESS.md b/PROCESS.md
index fe3a963110d..8e1a3f7360f 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -8,6 +8,8 @@ treatment, etc.). And so that maintainers know what to expect from contributors
(use the latest version, ensure that the issue is addressed, friendly treatment,
etc.).
+- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+
## Common actions
### Issue team
diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png
deleted file mode 100644
index 639271c6faf..00000000000
--- a/app/assets/images/bg-header.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png
deleted file mode 100644
index 5c55bc79dec..00000000000
--- a/app/assets/images/bg_fallback.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/chosen-sprite.png b/app/assets/images/chosen-sprite.png
deleted file mode 100644
index 3d936b07d44..00000000000
--- a/app/assets/images/chosen-sprite.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png
deleted file mode 100644
index 0084422e330..00000000000
--- a/app/assets/images/diff_note_add.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png
deleted file mode 100644
index 3c1c146541d..00000000000
--- a/app/assets/images/icon-search.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png
deleted file mode 100644
index 2e7a5023398..00000000000
--- a/app/assets/images/icon_sprite.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/images.png b/app/assets/images/images.png
deleted file mode 100644
index bd60de994c4..00000000000
--- a/app/assets/images/images.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/move.png b/app/assets/images/move.png
deleted file mode 100644
index 6a0567f8f25..00000000000
--- a/app/assets/images/move.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/progress_bar.gif b/app/assets/images/progress_bar.gif
deleted file mode 100644
index c3d43fa40b2..00000000000
--- a/app/assets/images/progress_bar.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png
deleted file mode 100644
index 52ad11ab7a1..00000000000
--- a/app/assets/images/slider_handles.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png
deleted file mode 100644
index c6b6c8d9521..00000000000
--- a/app/assets/images/switch_icon.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif
deleted file mode 100644
index 1a1c9c15ec7..00000000000
--- a/app/assets/images/trans_bg.gif
+++ /dev/null
Binary files differ
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 127e568adc9..f1aab067351 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -287,7 +287,7 @@
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
$('.navbar-fixed-top').removeClass('header-pinned-nav');
}
- return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
+ $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
e.preventDefault();
$pinBtn = $(e.currentTarget);
@@ -315,6 +315,8 @@
$tooltip.find('.tooltip-inner').text(tooltipText);
return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
});
- });
+ // Custom time ago
+ gl.utils.shortTimeAgo($('.js-short-timeago'));
+ });
}).call(this);
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index ea683b31f75..2c5b83e4f1e 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -161,23 +161,11 @@
$emojiButton = votesBlock.find("[data-emoji=" + mutualVote + "]").parent();
isAlreadyVoted = $emojiButton.hasClass('active');
if (isAlreadyVoted) {
- this.showEmojiLoader($emojiButton);
- return this.addAward(votesBlock, awardUrl, mutualVote, false, function() {
- return $emojiButton.removeClass('is-loading');
- });
+ this.addAward(votesBlock, awardUrl, mutualVote, false);
}
}
};
- AwardsHandler.prototype.showEmojiLoader = function($emojiButton) {
- var $loader;
- $loader = $emojiButton.find('.fa-spinner');
- if (!$loader.length) {
- $emojiButton.append('<i class="fa fa-spinner fa-spin award-control-icon award-control-icon-loading"></i>');
- }
- return $emojiButton.addClass('is-loading');
- };
-
AwardsHandler.prototype.isActive = function($emojiButton) {
return $emojiButton.hasClass('active');
};
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index e135cb92a30..3d9b824d406 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -128,7 +128,7 @@
$date = $('.js-artifacts-remove');
if ($date.length) {
date = $date.text();
- return $date.text($.timefor(new Date(date), ' '));
+ return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' '));
}
};
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index 298f3852085..3dd7ceba92f 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -10,7 +10,7 @@
$(document).off('click', '.js-unfold');
$(document).on('click', '.js-unfold', (function(_this) {
return function(event) {
- var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
+ var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom;
target = $(event.target);
unfoldBottom = target.hasClass('js-unfold-bottom');
unfold = true;
@@ -31,14 +31,16 @@
unfold = false;
}
}
- link = target.parents('.diff-file').attr('data-blob-diff-path');
+ file = target.parents('.diff-file');
+ link = file.data('blob-diff-path');
params = {
since: since,
to: to,
bottom: unfoldBottom,
offset: offset,
unfold: unfold,
- indent: 1
+ indent: 1,
+ view: file.data('view')
};
return $.get(link, params, function(response) {
return target.parent().replaceWith(response);
@@ -48,26 +50,13 @@
}
Diff.prototype.lineNumbers = function(line) {
- var i, l, len, line_number, line_numbers, lines, results;
if (!line.children().length) {
return [0, 0];
}
- lines = line.children().slice(0, 2);
- line_numbers = (function() {
- var i, len, results;
- results = [];
- for (i = 0, len = lines.length; i < len; i++) {
- l = lines[i];
- results.push($(l).attr('data-linenumber'));
- }
- return results;
- })();
- results = [];
- for (i = 0, len = line_numbers.length; i < len; i++) {
- line_number = line_numbers[i];
- results.push(parseInt(line_number));
- }
- return results;
+
+ return line.find('.diff-line-num').map(function() {
+ return parseInt($(this).data('linenumber'));
+ });
};
return Diff;
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index d212d66da1b..3946e861976 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -171,6 +171,11 @@
break;
case 'search:show':
new Search();
+ break;
+ case 'projects:protected_branches:index':
+ new gl.ProtectedBranchCreate();
+ new gl.ProtectedBranchEditList();
+ break;
}
switch (path.first()) {
case 'admin':
@@ -181,6 +186,12 @@
break;
case 'projects':
new NamespaceSelects();
+ break;
+ case 'labels':
+ switch (path[2]) {
+ case 'edit':
+ new Labels();
+ }
}
break;
case 'dashboard':
@@ -206,6 +217,7 @@
new ProjectNew();
break;
case 'show':
+ new Star();
new ProjectNew();
new ProjectShow();
new NotificationsDropdown();
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 288cce04f87..4a6fea929c7 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,5 +1,5 @@
-/*= require markdown_preview */
+/*= require preview_markdown */
(function() {
this.DropzoneInput = (function() {
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 41f4c1914f2..2e5b15f4b77 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -47,8 +47,8 @@
}
}
},
- setup: function(wrap) {
- this.input = $('.js-gfm-input');
+ setup: function(input) {
+ this.input = input || $('.js-gfm-input');
this.destroyAtWho();
this.setupAtWho();
if (this.dataSource) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index c5d92831fbe..d3394fae3f9 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -28,38 +28,43 @@
};
})(this));
timeout = "";
- this.input.on("keyup", (function(_this) {
- return function(e) {
+ this.input
+ .on('keydown', function (e) {
+ var keyCode = e.which;
+
+ if (keyCode === 13) {
+ e.preventDefault()
+ }
+ })
+ .on('keyup', function(e) {
var keyCode;
keyCode = e.which;
if (ARROW_KEY_CODES.indexOf(keyCode) >= 0) {
return;
}
- if (_this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ if (this.input.val() !== "" && !$inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.addClass(HAS_VALUE_CLASS);
- } else if (_this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
+ } else if (this.input.val() === "" && $inputContainer.hasClass(HAS_VALUE_CLASS)) {
$inputContainer.removeClass(HAS_VALUE_CLASS);
}
if (keyCode === 13) {
return false;
}
- if (_this.options.remote) {
+ if (this.options.remote) {
clearTimeout(timeout);
return timeout = setTimeout(function() {
- var blur_field;
- blur_field = _this.shouldBlur(keyCode);
- if (blur_field && _this.filterInputBlur) {
- _this.input.blur();
+ var blurField = this.shouldBlur(keyCode);
+ if (blurField && this.filterInputBlur) {
+ this.input.blur();
}
- return _this.options.query(_this.input.val(), function(data) {
- return _this.options.callback(data);
- });
- }, 250);
+ return this.options.query(this.input.val(), function(data) {
+ return this.options.callback(data);
+ }.bind(this));
+ }.bind(this), 250);
} else {
- return _this.filter(_this.input.val());
+ return this.filter(this.input.val());
}
- };
- })(this));
+ }.bind(this));
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
@@ -382,6 +387,7 @@
GitLabDropdown.prototype.opened = function() {
var contentHtml;
+ currentIndex = -1;
this.addArrowKeyEvent();
if (this.options.setIndeterminateIds) {
this.options.setIndeterminateIds.call(this);
@@ -601,7 +607,7 @@
return this.dropdown.before($input);
};
- GitLabDropdown.prototype.selectRowAtIndex = function(e, index) {
+ GitLabDropdown.prototype.selectRowAtIndex = function(index) {
var $el, selector;
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
if (this.dropdown.find(".dropdown-toggle-page").length) {
@@ -609,8 +615,6 @@
}
$el = $(selector, this.dropdown);
if ($el.length) {
- e.preventDefault();
- e.stopImmediatePropagation();
return $el.first().trigger('click');
}
};
@@ -619,7 +623,7 @@
var $input, ARROW_KEY_CODES, selector;
ARROW_KEY_CODES = [38, 40];
$input = this.dropdown.find(".dropdown-input-field");
- selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)';
+ selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible';
if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one " + selector;
}
@@ -647,7 +651,7 @@
return false;
}
if (currentKeyCode === 13 && currentIndex !== -1) {
- return _this.selectRowAtIndex(e, currentIndex);
+ return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1);
}
};
})(this));
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 6ac7564a848..528a673eb15 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -21,7 +21,7 @@
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
- GitLab.GfmAutoComplete.setup();
+ GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form);
autosize(this.textarea);
this.addEventListeners();
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index 55b6f132bab..0f840821f53 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -66,4 +66,12 @@
})();
+ $(function() {
+ if ($('.js-importer-status').length) {
+ var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
+ var importPath = $('.js-importer-status').data('import-path');
+
+ new ImporterStatus(jobsImportPath, importPath);
+ }
+ });
}).call(this);
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
index f27f1bad1f7..d0305c6c6a1 100644
--- a/app/assets/javascripts/issuable.js
+++ b/app/assets/javascripts/issuable.js
@@ -5,13 +5,10 @@
this.Issuable = {
init: function() {
- if (!issuable_created) {
- issuable_created = true;
- Issuable.initTemplates();
- Issuable.initSearch();
- Issuable.initChecks();
- return Issuable.initLabelFilterRemove();
- }
+ Issuable.initTemplates();
+ Issuable.initSearch();
+ Issuable.initChecks();
+ return Issuable.initLabelFilterRemove();
},
initTemplates: function() {
return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;"> <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body"> <%- label.title %> </a> <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>"> <i class="fa fa-times"></i> </button> </span> <% }); %>');
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index e817261f210..10afa7e4329 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -8,13 +8,16 @@
base.utils = {};
}
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+
w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
};
+
w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()];
};
- return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
+
+ w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) {
if (setTimeago == null) {
setTimeago = true;
}
@@ -31,6 +34,39 @@
});
}
};
+
+ w.gl.utils.shortTimeAgo = function($el) {
+ var shortLocale, tmpLocale;
+ shortLocale = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: 'ago',
+ suffixFromNow: 'from now',
+ seconds: '1 min',
+ minute: '1 min',
+ minutes: '%d mins',
+ hour: '1 hr',
+ hours: '%d hrs',
+ day: '1 day',
+ days: '%d days',
+ month: '1 month',
+ months: '%d months',
+ year: '1 year',
+ years: '%d years',
+ wordSeparator: ' ',
+ numbers: []
+ };
+ tmpLocale = $.timeago.settings.strings;
+ $el.each(function(el) {
+ var $el1;
+ $el1 = $(this);
+ return $el1.attr('title', gl.utils.formatDate($el.attr('datetime')));
+ });
+ $.timeago.settings.strings = shortLocale;
+ $el.timeago();
+ $.timeago.settings.strings = tmpLocale;
+ };
+
})(window);
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/md5.js b/app/assets/javascripts/lib/utils/md5.js
deleted file mode 100644
index b63716eaad2..00000000000
--- a/app/assets/javascripts/lib/utils/md5.js
+++ /dev/null
@@ -1,211 +0,0 @@
-function md5 (str) {
- // http://kevin.vanzonneveld.net
- // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
- // + namespaced by: Michael White (http://getsprink.com)
- // + tweaked by: Jack
- // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
- // + input by: Brett Zamir (http://brett-zamir.me)
- // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
- // - depends on: utf8_encode
- // * example 1: md5('Kevin van Zonneveld');
- // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9'
- var xl;
-
- var rotateLeft = function (lValue, iShiftBits) {
- return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
- };
-
- var addUnsigned = function (lX, lY) {
- var lX4, lY4, lX8, lY8, lResult;
- lX8 = (lX & 0x80000000);
- lY8 = (lY & 0x80000000);
- lX4 = (lX & 0x40000000);
- lY4 = (lY & 0x40000000);
- lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
- if (lX4 & lY4) {
- return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
- }
- if (lX4 | lY4) {
- if (lResult & 0x40000000) {
- return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
- } else {
- return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
- }
- } else {
- return (lResult ^ lX8 ^ lY8);
- }
- };
-
- var _F = function (x, y, z) {
- return (x & y) | ((~x) & z);
- };
- var _G = function (x, y, z) {
- return (x & z) | (y & (~z));
- };
- var _H = function (x, y, z) {
- return (x ^ y ^ z);
- };
- var _I = function (x, y, z) {
- return (y ^ (x | (~z)));
- };
-
- var _FF = function (a, b, c, d, x, s, ac) {
- a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac));
- return addUnsigned(rotateLeft(a, s), b);
- };
-
- var _GG = function (a, b, c, d, x, s, ac) {
- a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac));
- return addUnsigned(rotateLeft(a, s), b);
- };
-
- var _HH = function (a, b, c, d, x, s, ac) {
- a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac));
- return addUnsigned(rotateLeft(a, s), b);
- };
-
- var _II = function (a, b, c, d, x, s, ac) {
- a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac));
- return addUnsigned(rotateLeft(a, s), b);
- };
-
- var convertToWordArray = function (str) {
- var lWordCount;
- var lMessageLength = str.length;
- var lNumberOfWords_temp1 = lMessageLength + 8;
- var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
- var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
- var lWordArray = new Array(lNumberOfWords - 1);
- var lBytePosition = 0;
- var lByteCount = 0;
- while (lByteCount < lMessageLength) {
- lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- lBytePosition = (lByteCount % 4) * 8;
- lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition));
- lByteCount++;
- }
- lWordCount = (lByteCount - (lByteCount % 4)) / 4;
- lBytePosition = (lByteCount % 4) * 8;
- lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
- lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
- lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
- return lWordArray;
- };
-
- var wordToHex = function (lValue) {
- var wordToHexValue = "",
- wordToHexValue_temp = "",
- lByte, lCount;
- for (lCount = 0; lCount <= 3; lCount++) {
- lByte = (lValue >>> (lCount * 8)) & 255;
- wordToHexValue_temp = "0" + lByte.toString(16);
- wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2);
- }
- return wordToHexValue;
- };
-
- var x = [],
- k, AA, BB, CC, DD, a, b, c, d, S11 = 7,
- S12 = 12,
- S13 = 17,
- S14 = 22,
- S21 = 5,
- S22 = 9,
- S23 = 14,
- S24 = 20,
- S31 = 4,
- S32 = 11,
- S33 = 16,
- S34 = 23,
- S41 = 6,
- S42 = 10,
- S43 = 15,
- S44 = 21;
-
- str = this.utf8_encode(str);
- x = convertToWordArray(str);
- a = 0x67452301;
- b = 0xEFCDAB89;
- c = 0x98BADCFE;
- d = 0x10325476;
-
- xl = x.length;
- for (k = 0; k < xl; k += 16) {
- AA = a;
- BB = b;
- CC = c;
- DD = d;
- a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
- d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
- c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
- b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
- a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
- d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
- c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
- b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
- a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
- d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
- c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
- b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
- a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
- d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
- c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
- b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
- a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
- d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
- c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
- b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
- a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
- d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453);
- c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
- b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
- a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
- d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
- c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
- b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
- a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
- d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
- c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
- b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
- a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
- d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
- c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
- b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
- a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
- d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
- c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
- b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
- a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
- d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
- c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
- b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
- a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
- d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
- c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
- b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
- a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244);
- d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
- c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
- b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
- a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
- d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
- c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
- b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
- a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
- d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
- c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314);
- b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
- a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
- d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
- c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
- b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
- a = addUnsigned(a, AA);
- b = addUnsigned(b, BB);
- c = addUnsigned(c, CC);
- d = addUnsigned(d, DD);
- }
-
- var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);
-
- return temp.toLowerCase();
-}
diff --git a/app/assets/javascripts/lib/utils/utf8_encode.js b/app/assets/javascripts/lib/utils/utf8_encode.js
deleted file mode 100644
index 39ffe44dae0..00000000000
--- a/app/assets/javascripts/lib/utils/utf8_encode.js
+++ /dev/null
@@ -1,70 +0,0 @@
-function utf8_encode (argString) {
- // http://kevin.vanzonneveld.net
- // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
- // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
- // + improved by: sowberry
- // + tweaked by: Jack
- // + bugfixed by: Onno Marsman
- // + improved by: Yves Sucaet
- // + bugfixed by: Onno Marsman
- // + bugfixed by: Ulrich
- // + bugfixed by: Rafal Kukawski
- // + improved by: kirilloid
- // + bugfixed by: kirilloid
- // * example 1: utf8_encode('Kevin van Zonneveld');
- // * returns 1: 'Kevin van Zonneveld'
-
- if (argString === null || typeof argString === "undefined") {
- return "";
- }
-
- var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
- var utftext = '',
- start, end, stringl = 0;
-
- start = end = 0;
- stringl = string.length;
- for (var n = 0; n < stringl; n++) {
- var c1 = string.charCodeAt(n);
- var enc = null;
-
- if (c1 < 128) {
- end++;
- } else if (c1 > 127 && c1 < 2048) {
- enc = String.fromCharCode(
- (c1 >> 6) | 192,
- ( c1 & 63) | 128
- );
- } else if (c1 & 0xF800 != 0xD800) {
- enc = String.fromCharCode(
- (c1 >> 12) | 224,
- ((c1 >> 6) & 63) | 128,
- ( c1 & 63) | 128
- );
- } else { // surrogate pairs
- if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); }
- var c2 = string.charCodeAt(++n);
- if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); }
- c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000;
- enc = String.fromCharCode(
- (c1 >> 18) | 240,
- ((c1 >> 12) & 63) | 128,
- ((c1 >> 6) & 63) | 128,
- ( c1 & 63) | 128
- );
- }
- if (enc !== null) {
- if (end > start) {
- utftext += string.slice(start, end);
- }
- utftext += enc;
- start = end = n + 1;
- }
- }
-
- if (end > start) {
- utftext += string.slice(start, stringl);
- }
-
- return utftext;
-}
diff --git a/app/assets/javascripts/markdown_preview.js b/app/assets/javascripts/preview_markdown.js
index 18fc7bae09a..5fd75799640 100644
--- a/app/assets/javascripts/markdown_preview.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -28,7 +28,7 @@
};
MarkdownPreview.prototype.renderMarkdown = function(text, success) {
- if (!window.markdown_preview_path) {
+ if (!window.preview_markdown_path) {
return;
}
if (text === this.ajaxCache.text) {
@@ -36,7 +36,7 @@
}
return $.ajax({
type: 'POST',
- url: window.markdown_preview_path,
+ url: window.preview_markdown_path,
data: {
text: text
},
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index e6663177161..b97f6d22715 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -89,8 +89,14 @@
toggleLabel: function(obj, $el) {
return $el.text().trim();
},
- clicked: function(e) {
- return $dropdown.closest('form').submit();
+ clicked: function(selected, $el, e) {
+ e.preventDefault()
+ if ($('input[name="ref"]').length) {
+ var $form = $dropdown.closest('form'),
+ action = $form.attr('action'),
+ divider = action.indexOf('?') < 0 ? '?' : '&';
+ Turbolinks.visit(action + '' + divider + '' + $form.serialize());
+ }
}
});
});
diff --git a/app/assets/javascripts/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branch_access_dropdown.js.es6
new file mode 100644
index 00000000000..2fbb088fa04
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_access_dropdown.js.es6
@@ -0,0 +1,24 @@
+(global => {
+ global.gl = global.gl || {};
+
+ gl.ProtectedBranchAccessDropdown = class {
+ constructor(options) {
+ const { $dropdown, data, onSelect } = options;
+
+ $dropdown.glDropdown({
+ data: data,
+ selectable: true,
+ inputId: $dropdown.data('input-id'),
+ fieldName: $dropdown.data('field-name'),
+ toggleLabel(item) {
+ return item.text;
+ },
+ clicked(item, $el, e) {
+ e.preventDefault();
+ onSelect();
+ }
+ });
+ }
+ }
+
+})(window);
diff --git a/app/assets/javascripts/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branch_create.js.es6
new file mode 100644
index 00000000000..00e20a03b04
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_create.js.es6
@@ -0,0 +1,56 @@
+(global => {
+ global.gl = global.gl || {};
+
+ gl.ProtectedBranchCreate = class {
+ constructor() {
+ this.$wrap = this.$form = $('#new_protected_branch');
+ this.buildDropdowns();
+ }
+
+ buildDropdowns() {
+ const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
+ const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+
+ // Cache callback
+ this.onSelectCallback = this.onSelect.bind(this);
+
+ // Allowed to Merge dropdown
+ new gl.ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelectCallback
+ });
+
+ // Allowed to Push dropdown
+ new gl.ProtectedBranchAccessDropdown({
+ $dropdown: $allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelectCallback
+ });
+
+ // Select default
+ $allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
+ $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
+
+ // Protected branch dropdown
+ new ProtectedBranchDropdown({
+ $dropdown: this.$wrap.find('.js-protected-branch-select'),
+ onSelect: this.onSelectCallback
+ });
+ }
+
+ // This will run after clicked callback
+ onSelect() {
+
+ // Enable submit button
+ const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
+ const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
+ const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
+
+ if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
+ this.$form.find('input[type="submit"]').removeAttr('disabled');
+ }
+ }
+ }
+
+})(window);
diff --git a/app/assets/javascripts/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branch_dropdown.js.es6
new file mode 100644
index 00000000000..6738dc8862d
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_dropdown.js.es6
@@ -0,0 +1,75 @@
+class ProtectedBranchDropdown {
+ constructor(options) {
+ this.onSelect = options.onSelect;
+ this.$dropdown = options.$dropdown;
+ this.$dropdownContainer = this.$dropdown.parent();
+ this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
+ this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch');
+
+ this.buildDropdown();
+ this.bindEvents();
+
+ // Hide footer
+ this.$dropdownFooter.addClass('hidden');
+ }
+
+ buildDropdown() {
+ this.$dropdown.glDropdown({
+ data: this.getProtectedBranches.bind(this),
+ filterable: true,
+ remote: false,
+ search: {
+ fields: ['title']
+ },
+ selectable: true,
+ toggleLabel(selected) {
+ return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
+ },
+ fieldName: 'protected_branch[name]',
+ text(protectedBranch) {
+ return _.escape(protectedBranch.title);
+ },
+ id(protectedBranch) {
+ return _.escape(protectedBranch.id);
+ },
+ onFilter: this.toggleCreateNewButton.bind(this),
+ clicked: (item, $el, e) => {
+ e.preventDefault();
+ this.onSelect();
+ }
+ });
+ }
+
+ bindEvents() {
+ this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
+ }
+
+ onClickCreateWildcard() {
+ this.$dropdown.data('glDropdown').remote.execute();
+ this.$dropdown.data('glDropdown').selectRowAtIndex(0);
+ }
+
+ getProtectedBranches(term, callback) {
+ if (this.selectedBranch) {
+ callback(gon.open_branches.concat(this.selectedBranch));
+ } else {
+ callback(gon.open_branches);
+ }
+ }
+
+ toggleCreateNewButton(branchName) {
+ this.selectedBranch = {
+ title: branchName,
+ id: branchName,
+ text: branchName
+ };
+
+ if (branchName) {
+ this.$dropdownContainer
+ .find('.create-new-protected-branch code')
+ .text(branchName);
+ }
+
+ this.$dropdownFooter.toggleClass('hidden', !branchName);
+ }
+}
diff --git a/app/assets/javascripts/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branch_edit.js.es6
new file mode 100644
index 00000000000..8d42e268ebc
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_edit.js.es6
@@ -0,0 +1,61 @@
+(global => {
+ global.gl = global.gl || {};
+
+ gl.ProtectedBranchEdit = class {
+ constructor(options) {
+ this.$wrap = options.$wrap;
+ this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
+ this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
+
+ this.buildDropdowns();
+ }
+
+ buildDropdowns() {
+
+ // Allowed to merge dropdown
+ new gl.ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToMergeDropdown,
+ data: gon.merge_access_levels,
+ onSelect: this.onSelect.bind(this)
+ });
+
+ // Allowed to push dropdown
+ new gl.ProtectedBranchAccessDropdown({
+ $dropdown: this.$allowedToPushDropdown,
+ data: gon.push_access_levels,
+ onSelect: this.onSelect.bind(this)
+ });
+ }
+
+ onSelect() {
+ const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
+ const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
+
+ $.ajax({
+ type: 'POST',
+ url: this.$wrap.data('url'),
+ dataType: 'json',
+ data: {
+ _method: 'PATCH',
+ id: this.$wrap.data('banchId'),
+ protected_branch: {
+ merge_access_level_attributes: {
+ access_level: $allowedToMergeInput.val()
+ },
+ push_access_level_attributes: {
+ access_level: $allowedToPushInput.val()
+ }
+ }
+ },
+ success: () => {
+ this.$wrap.effect('highlight');
+ },
+ error() {
+ $.scrollTo(0);
+ new Flash('Failed to update branch!');
+ }
+ });
+ }
+ }
+
+})(window);
diff --git a/app/assets/javascripts/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branch_edit_list.js.es6
new file mode 100644
index 00000000000..9ff0fd12c76
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_edit_list.js.es6
@@ -0,0 +1,17 @@
+(global => {
+ global.gl = global.gl || {};
+
+ gl.ProtectedBranchEditList = class {
+ constructor() {
+ this.$wrap = $('.protected-branches-list');
+
+ // Build edit forms
+ this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
+ new gl.ProtectedBranchEdit({
+ $wrap: $(el)
+ });
+ });
+ }
+ }
+
+})(window);
diff --git a/app/assets/javascripts/protected_branch_select.js b/app/assets/javascripts/protected_branch_select.js
deleted file mode 100644
index 3a47fc972dc..00000000000
--- a/app/assets/javascripts/protected_branch_select.js
+++ /dev/null
@@ -1,72 +0,0 @@
-(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
-
- this.ProtectedBranchSelect = (function() {
- function ProtectedBranchSelect(currentProject) {
- this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
- this.getProtectedBranches = bind(this.getProtectedBranches, this);
- $('.dropdown-footer').hide();
- this.dropdown = $('.js-protected-branch-select').glDropdown({
- data: this.getProtectedBranches,
- filterable: true,
- remote: false,
- search: {
- fields: ['title']
- },
- selectable: true,
- toggleLabel: function(selected) {
- if (selected && 'id' in selected) {
- return selected.title;
- } else {
- return 'Protected Branch';
- }
- },
- fieldName: 'protected_branch[name]',
- text: function(protected_branch) {
- return _.escape(protected_branch.title);
- },
- id: function(protected_branch) {
- return _.escape(protected_branch.id);
- },
- onFilter: this.toggleCreateNewButton,
- clicked: function() {
- return $('.protect-branch-btn').attr('disabled', false);
- }
- });
- $('.create-new-protected-branch').on('click', (function(_this) {
- return function(event) {
- _this.dropdown.data('glDropdown').remote.execute();
- return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
- };
- })(this));
- }
-
- ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
- if (this.selectedBranch) {
- return callback(gon.open_branches.concat(this.selectedBranch));
- } else {
- return callback(gon.open_branches);
- }
- };
-
- ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
- this.selectedBranch = {
- title: branchName,
- id: branchName,
- text: branchName
- };
- if (branchName === '') {
- $('.protected-branch-select-footer-list').addClass('hidden');
- return $('.dropdown-footer').hide();
- } else {
- $('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
- $('.protected-branch-select-footer-list').removeClass('hidden');
- return $('.dropdown-footer').show();
- }
- };
-
- return ProtectedBranchSelect;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/protected_branches.js b/app/assets/javascripts/protected_branches.js
deleted file mode 100644
index db21a19964d..00000000000
--- a/app/assets/javascripts/protected_branches.js
+++ /dev/null
@@ -1,35 +0,0 @@
-(function() {
- $(function() {
- return $(".protected-branches-list :checkbox").change(function(e) {
- var can_push, id, name, obj, url;
- name = $(this).attr("name");
- if (name === "developers_can_push" || name === "developers_can_merge") {
- id = $(this).val();
- can_push = $(this).is(":checked");
- url = $(this).data("url");
- return $.ajax({
- type: "PATCH",
- url: url,
- dataType: "json",
- data: {
- id: id,
- protected_branch: (
- obj = {},
- obj["" + name] = can_push,
- obj
- )
- },
- success: function() {
- var row;
- row = $(e.target);
- return row.closest('tr').effect('highlight');
- },
- error: function() {
- return new Flash("Failed to update branch!", "alert");
- }
- });
- }
- });
- });
-
-}).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 64a29d36cdf..65d362e072c 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -13,14 +13,15 @@
}
$('.js-user-search').each((function(_this) {
return function(i, dropdown) {
+ var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser;
$dropdown = $(dropdown);
- _this.projectId = $dropdown.data('project-id');
- _this.showCurrentUser = $dropdown.data('current-user');
+ options.projectId = $dropdown.data('project-id');
+ options.showCurrentUser = $dropdown.data('current-user');
showNullUser = $dropdown.data('null-user');
showAnyUser = $dropdown.data('any-user');
firstUser = $dropdown.data('first-user');
- _this.authorId = $dropdown.data('author-id');
+ options.authorId = $dropdown.data('author-id');
selectedId = $dropdown.data('selected');
defaultLabel = $dropdown.data('default-label');
issueURL = $dropdown.data('issueUpdate');
@@ -75,7 +76,7 @@
data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
- return _this.users(term, function(users) {
+ return _this.users(term, options, function(users) {
var anyUser, index, j, len, name, obj, showDivider;
if (term.length === 0) {
showDivider = 0;
@@ -185,10 +186,14 @@
$('.ajax-users-select').each((function(_this) {
return function(i, select) {
var firstUser, showAnyUser, showEmailUser, showNullUser;
- _this.projectId = $(select).data('project-id');
- _this.groupId = $(select).data('group-id');
- _this.showCurrentUser = $(select).data('current-user');
- _this.authorId = $(select).data('author-id');
+ var options = {};
+ options.skipLdap = $(select).hasClass('skip_ldap');
+ options.projectId = $(select).data('project-id');
+ options.groupId = $(select).data('group-id');
+ options.showCurrentUser = $(select).data('current-user');
+ options.pushCodeToProtectedBranches = $(select).data('push-code-to-protected-branches');
+ options.authorId = $(select).data('author-id');
+ options.skipUsers = $(select).data('skip-users');
showNullUser = $(select).data('null-user');
showAnyUser = $(select).data('any-user');
showEmailUser = $(select).data('email-user');
@@ -198,7 +203,7 @@
multiple: $(select).hasClass('multiselect'),
minimumInputLength: 0,
query: function(query) {
- return _this.users(query.term, function(users) {
+ return _this.users(query.term, options, function(users) {
var anyUser, data, emailUser, index, j, len, name, nullUser, obj, ref;
data = {
results: users
@@ -308,7 +313,7 @@
});
};
- UsersSelect.prototype.users = function(query, callback) {
+ UsersSelect.prototype.users = function(query, options, callback) {
var url;
url = this.buildUrl(this.usersPath);
return $.ajax({
@@ -317,10 +322,13 @@
search: query,
per_page: 20,
active: true,
- project_id: this.projectId,
- group_id: this.groupId,
- current_user: this.showCurrentUser,
- author_id: this.authorId
+ project_id: options.projectId || null,
+ group_id: options.groupId || null,
+ skip_ldap: options.skipLdap || null,
+ current_user: options.showCurrentUser || null,
+ push_code_to_protected_branches: options.pushCodeToProtectedBranches || null,
+ author_id: options.authorId || null,
+ skip_users: options.skipUsers || null
},
dataType: "json"
}).done(function(users) {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 473530cf094..f1fe1697d30 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -164,6 +164,10 @@
@include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light);
}
+ &.btn-spam {
+ @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light);
+ }
+
&.btn-danger,
&.btn-remove,
&.btn-red {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index c54eb0d6479..e8eafa15899 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -72,6 +72,14 @@
&.large {
width: 200px;
}
+
+ &.wide {
+ width: 100%;
+
+ + .dropdown-select {
+ width: 100%;
+ }
+ }
}
.dropdown-menu,
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 2c40ec430ca..965fcc06518 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -114,6 +114,12 @@ ul.content-list {
font-size: $list-font-size;
color: $list-text-color;
+ &.no-description {
+ .title {
+ line-height: $list-text-height;
+ }
+ }
+
.title {
font-weight: 600;
}
@@ -134,12 +140,11 @@ ul.content-list {
}
.controls {
- padding-top: 1px;
float: right;
> .control-text {
margin-right: $gl-padding-top;
- line-height: 40px;
+ line-height: $list-text-height;
&:last-child {
margin-right: 0;
@@ -150,7 +155,7 @@ ul.content-list {
> .btn-group {
margin-right: $gl-padding-top;
display: inline-block;
- margin-top: 4px;
+ margin-top: 3px;
margin-bottom: 4px;
&:last-child {
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 364952d3b4a..7852fc9a424 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -182,7 +182,6 @@
> form {
display: inline-block;
- margin-top: -1px;
}
.icon-label {
@@ -193,7 +192,6 @@
height: 35px;
display: inline-block;
position: relative;
- top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 874416e1007..c6f30e144fd 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -23,4 +23,9 @@
margin-top: $gl-padding;
}
}
+
+ .panel-title {
+ font-size: inherit;
+ line-height: inherit;
+ }
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 1882d4e888d..ca720022539 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -43,6 +43,7 @@ $gl-header-color: $gl-title-color;
$list-font-size: $gl-font-size;
$list-title-color: $gl-title-color;
$list-text-color: $gl-text-color;
+$list-text-height: 42px;
/*
* Markdown
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 0298577c494..6a58b445afa 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,8 +1,6 @@
.commits-compare-switch {
@include btn-default;
@include btn-white;
- background: image-url("switch_icon.png") no-repeat center center;
- text-indent: -9999px;
float: left;
margin-right: 9px;
}
@@ -61,6 +59,10 @@
font-size: 0;
}
+ .ci-status-link {
+ display: inline-block;
+ }
+
.btn-clipboard, .btn-transparent {
padding-left: 0;
padding-right: 0;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 21b1c223c88..21cee2e3a70 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -164,7 +164,10 @@
line-height: 0;
img {
border: 1px solid #fff;
- background: image-url('trans_bg.gif');
+ background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%),
+ linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%);
+ background-size: 10px 10px;
+ background-position: 0 0, 5px 5px;
max-width: 100%;
}
&.deleted {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index e160d676e35..55f9d4a0011 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -1,5 +1,35 @@
.environments {
+
.commit-title {
margin: 0;
}
+
+ .fa-play {
+ font-size: 14px;
+ }
+
+ .dropdown-new {
+ color: $table-text-gray;
+ }
+
+ .dropdown-menu {
+
+ .fa {
+ margin-right: 6px;
+ color: $table-text-gray;
+ }
+ }
+
+ .branch-name {
+ color: $gl-dark-link-color;
+ }
+}
+
+.table.builds.environments {
+ min-width: 500px;
+
+ .icon-container {
+ width: 20px;
+ text-align: center;
+ }
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 2a3acc3eb4c..b657ca47d38 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -23,15 +23,9 @@
}
.group-row {
- &.no-description {
- .group-name {
- line-height: 44px;
- }
- }
-
.stats {
float: right;
- line-height: 44px;
+ line-height: $list-text-height;
color: $gl-gray;
span {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ee3b2d2b801..dfe1e3075da 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -99,3 +99,33 @@ form.edit-issue {
.issue-form .select2-container {
width: 250px !important;
}
+
+.issues-footer {
+ padding-top: $gl-padding;
+ padding-bottom: 37px;
+}
+
+.issue-email-modal-btn {
+ padding: 0;
+ color: $gl-link-color;
+ background-color: transparent;
+ border: 0;
+ outline: 0;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+.email-modal-input-group {
+ margin-bottom: 10px;
+
+ .form-control {
+ background-color: $white-light;
+ }
+
+ .btn {
+ background-color: $background-color;
+ border: 1px solid $border-gray-light;
+ }
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 3b1e38fc07d..606459f82cd 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -182,6 +182,17 @@
.btn {
color: inherit;
}
+
+ a.btn {
+ padding: 0;
+
+ .has-tooltip {
+ top: 0;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ line-height: 1.1;
+ }
+ }
}
.label-options-toggle {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index db295935b00..b4636269518 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -69,6 +69,10 @@
&.ci-success {
color: $gl-success;
+
+ a.environment {
+ color: inherit;
+ }
}
&.ci-success_with_warnings {
@@ -126,7 +130,6 @@
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
}
-
}
p:last-child {
@@ -216,6 +219,11 @@
position: relative;
top: 3px;
}
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index c58e2ffe7f5..21919fe4d73 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -18,6 +18,10 @@
.btn {
margin: 4px;
}
+
+ .table.builds {
+ min-width: 1200px;
+ }
}
.content-list {
@@ -35,7 +39,7 @@
}
.table.builds {
- min-width: 1200px;
+ min-width: 900px;
&.pipeline {
min-width: 650px;
@@ -128,7 +132,7 @@
.icon-container {
display: inline-block;
text-align: right;
- width: 20px;
+ width: 15px;
.fa {
position: relative;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index cc3aef5199e..cf9aa02600d 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -512,18 +512,12 @@ pre.light-well {
.project-row {
border-color: $table-border-color;
- &.no-description {
- .project {
- line-height: 40px;
- }
- }
-
.project-full-name {
@include str-truncated;
}
.controls {
- line-height: 40px;
+ line-height: $list-text-height;
a:hover {
text-decoration: none;
@@ -661,14 +655,39 @@ pre.light-well {
}
}
+.new_protected_branch {
+ label {
+ margin-top: 6px;
+ font-weight: normal;
+ }
+}
+
.protected-branches-list {
a {
color: $gl-gray;
- font-weight: 600;
&:hover {
color: $gl-link-color;
}
+
+ &.is-active {
+ font-weight: 600;
+ }
+ }
+
+ .settings-message {
+ margin: 0;
+ border-radius: 0 0 1px 1px;
+ padding: 20px 0;
+ border: none;
+ }
+
+ .table-bordered {
+ border-radius: 1px;
+
+ th:not(:last-child), td:not(:last-child) {
+ border-right: solid 1px transparent;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 390977297fb..9da40fe2b09 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -58,6 +58,10 @@
.tree_commit {
max-width: 320px;
+
+ .str-truncated {
+ max-width: 100%;
+ }
}
.tree_time_ago {
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index f3a88a8e6c8..4ce18321649 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -48,9 +48,9 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).execute
+ DestroyGroupService.new(@group, current_user).async_execute
- redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
+ redirect_to admin_groups_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
private
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
new file mode 100644
index 00000000000..a478176e138
--- /dev/null
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -0,0 +1,17 @@
+class Admin::RequestsProfilesController < Admin::ApplicationController
+ def index
+ @profile_token = Gitlab::RequestProfiler.profile_token
+ @profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
+ end
+
+ def show
+ clean_name = Rack::Utils.clean_path_info(params[:name])
+ profile = Gitlab::RequestProfiler::Profile.find(clean_name)
+
+ if profile
+ render text: profile.content
+ else
+ redirect_to admin_requests_profiles_path, alert: 'Profile not found'
+ end
+ end
+end
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 3a2f0185315..2abfa22712d 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -14,4 +14,14 @@ class Admin::SpamLogsController < Admin::ApplicationController
head :ok
end
end
+
+ def mark_as_ham
+ spam_log = SpamLog.find(params[:id])
+
+ if HamService.new(spam_log).mark_as_ham!
+ redirect_to admin_spam_logs_path, notice: 'Spam log successfully submitted as ham.'
+ else
+ redirect_to admin_spam_logs_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ end
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a1004d9bcea..634d36a4467 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -243,42 +243,6 @@ class ApplicationController < ActionController::Base
end
end
- def set_filters_params
- set_default_sort
-
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
-
- @sort = params[:sort]
- @filter_params = params.dup
-
- if @project
- @filter_params[:project_id] = @project.id
- elsif @group
- @filter_params[:group_id] = @group.id
- else
- # TODO: this filter ignore issues/mr created in public or
- # internal repos where you are not a member. Enable this filter
- # or improve current implementation to filter only issues you
- # created or assigned or mentioned
- # @filter_params[:authorized_only] = true
- end
-
- @filter_params
- end
-
- def get_issues_collection
- set_filters_params
- @issuable_finder = IssuesFinder.new(current_user, @filter_params)
- @issuable_finder.execute
- end
-
- def get_merge_requests_collection
- set_filters_params
- @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params)
- @issuable_finder.execute
- end
-
def import_sources_enabled?
!current_application_settings.import_sources.empty?
end
@@ -363,24 +327,4 @@ class ApplicationController < ActionController::Base
def u2f_app_id
request.base_url
end
-
- private
-
- def set_default_sort
- key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests')
- 'issuable_sort'
- end
-
- cookies[key] = params[:sort] if key && params[:sort].present?
- params[:sort] = cookies[key] if key
- params[:sort] ||= 'id_desc'
- end
-
- def is_a_listing_page_for?(page_type)
- controller_name, action_name = params.values_at(:controller, :action)
-
- (controller_name == "projects/#{page_type}" && action_name == 'index') ||
- (controller_name == 'groups' && action_name == page_type) ||
- (controller_name == 'dashboard' && action_name == page_type)
- end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index c89678cf2d8..d828d163c28 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController
def users
@users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
+ @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.page(params[:page])
diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb
index 026d8b2e1e0..aeec3009f15 100644
--- a/app/controllers/concerns/diff_for_path.rb
+++ b/app/controllers/concerns/diff_for_path.rb
@@ -1,8 +1,8 @@
module DiffForPath
extend ActiveSupport::Concern
- def render_diff_for_path(diffs, diff_refs, project)
- diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff|
+ def render_diff_for_path(diffs)
+ diff_file = diffs.diff_files.find do |diff|
diff.old_path == params[:old_path] && diff.new_path == params[:new_path]
end
@@ -14,7 +14,7 @@ module DiffForPath
locals = {
diff_file: diff_file,
diff_commit: diff_commit,
- diff_refs: diff_refs,
+ diff_refs: diffs.diff_refs,
blob: blob,
project: project
}
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
new file mode 100644
index 00000000000..c802922e0af
--- /dev/null
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -0,0 +1,79 @@
+module IssuableCollections
+ extend ActiveSupport::Concern
+ include SortingHelper
+
+ included do
+ helper_method :issues_finder
+ helper_method :merge_requests_finder
+ end
+
+ private
+
+ def issues_collection
+ issues_finder.execute
+ end
+
+ def merge_requests_collection
+ merge_requests_finder.execute
+ end
+
+ def issues_finder
+ @issues_finder ||= issuable_finder_for(IssuesFinder)
+ end
+
+ def merge_requests_finder
+ @merge_requests_finder ||= issuable_finder_for(MergeRequestsFinder)
+ end
+
+ def issuable_finder_for(finder_class)
+ finder_class.new(current_user, filter_params)
+ end
+
+ def filter_params
+ set_sort_order_from_cookie
+ set_default_scope
+ set_default_state
+
+ @filter_params = params.dup
+ @filter_params[:sort] ||= default_sort_order
+
+ @sort = @filter_params[:sort]
+
+ if @project
+ @filter_params[:project_id] = @project.id
+ elsif @group
+ @filter_params[:group_id] = @group.id
+ else
+ # TODO: this filter ignore issues/mr created in public or
+ # internal repos where you are not a member. Enable this filter
+ # or improve current implementation to filter only issues you
+ # created or assigned or mentioned
+ # @filter_params[:authorized_only] = true
+ end
+
+ @filter_params
+ end
+
+ def set_default_scope
+ params[:scope] = 'all' if params[:scope].blank?
+ end
+
+ def set_default_state
+ params[:state] = 'opened' if params[:state].blank?
+ end
+
+ def set_sort_order_from_cookie
+ key = 'issuable_sort'
+
+ cookies[key] = params[:sort] if params[:sort].present?
+ params[:sort] = cookies[key]
+ end
+
+ def default_sort_order
+ case params[:state]
+ when 'opened', 'all' then sort_value_recently_created
+ when 'merged', 'closed' then sort_value_recently_updated
+ else sort_value_recently_created
+ end
+ end
+end
diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb
index 4feabc32b1c..b89fb94be6e 100644
--- a/app/controllers/concerns/issues_action.rb
+++ b/app/controllers/concerns/issues_action.rb
@@ -1,12 +1,14 @@
module IssuesAction
extend ActiveSupport::Concern
+ include IssuableCollections
def issues
- @issues = get_issues_collection.non_archived
- @issues = @issues.page(params[:page])
- @issues = @issues.preload(:author, :project)
+ @label = issues_finder.labels.first
- @label = @issuable_finder.labels.first
+ @issues = issues_collection
+ .non_archived
+ .preload(:author, :project)
+ .page(params[:page])
respond_to do |format|
format.html
diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb
index 06a6b065e7e..a1b0eee37f9 100644
--- a/app/controllers/concerns/merge_requests_action.rb
+++ b/app/controllers/concerns/merge_requests_action.rb
@@ -1,11 +1,13 @@
module MergeRequestsAction
extend ActiveSupport::Concern
+ include IssuableCollections
def merge_requests
- @merge_requests = get_merge_requests_collection.non_archived
- @merge_requests = @merge_requests.page(params[:page])
- @merge_requests = @merge_requests.preload(:author, :target_project)
+ @label = merge_requests_finder.labels.first
- @label = @issuable_finder.labels.first
+ @merge_requests = merge_requests_collection
+ .non_archived
+ .preload(:author, :target_project)
+ .page(params[:page])
end
end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
new file mode 100644
index 00000000000..29e243c66a3
--- /dev/null
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -0,0 +1,25 @@
+module SpammableActions
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :authorize_submit_spammable!, only: :mark_as_spam
+ end
+
+ def mark_as_spam
+ if SpamService.new(spammable).mark_as_spam!
+ redirect_to spammable, notice: "#{spammable.class.to_s} was submitted to Akismet successfully."
+ else
+ redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
+ end
+ end
+
+ private
+
+ def spammable
+ raise NotImplementedError, "#{self.class} does not implement #{__method__}"
+ end
+
+ def authorize_submit_spammable!
+ access_denied! unless current_user.admin?
+ end
+end
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 19a76a5b5d8..1243bb96d4d 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -37,8 +37,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def todos_counts
{
- count: TodosFinder.new(current_user, state: :pending).execute.count,
- done_count: TodosFinder.new(current_user, state: :done).execute.count
+ count: current_user.todos_pending_count,
+ done_count: current_user.todos_done_count
}
end
end
diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb
index 461fc059a3c..a1ab8b99048 100644
--- a/app/controllers/explore/application_controller.rb
+++ b/app/controllers/explore/application_controller.rb
@@ -1,5 +1,5 @@
class Explore::ApplicationController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
layout 'explore'
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 6780a6d4d87..cb82d62616c 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -87,9 +87,9 @@ class GroupsController < Groups::ApplicationController
end
def destroy
- DestroyGroupService.new(@group, current_user).execute
+ DestroyGroupService.new(@group, current_user).async_execute
- redirect_to root_path, alert: "Group '#{@group.name}' was successfully deleted."
+ redirect_to root_path, alert: "Group '#{@group.name}' was scheduled for deletion."
end
protected
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index f7b44099b78..4eca278599f 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -1,5 +1,5 @@
class HelpController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
layout 'help'
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 25e58724860..944c73d139a 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -82,8 +82,6 @@ class Import::BitbucketController < Import::BaseController
go_to_bitbucket_for_permissions
end
- private
-
def access_params
{
bitbucket_access_token: session[:bitbucket_access_token],
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 23a396e8084..08130ee8176 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -61,8 +61,6 @@ class Import::GitlabController < Import::BaseController
go_to_gitlab_for_permissions
end
- private
-
def access_params
{ gitlab_access_token: session[:gitlab_access_token] }
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 30df1fb2fec..3ec173abcdb 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -12,13 +12,14 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
- imported_file = project_params[:file].path + "-import"
+ import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
- FileUtils.copy_entry(project_params[:file].path, imported_file)
+ FileUtils.mkdir_p(File.dirname(import_upload_path))
+ FileUtils.copy_entry(project_params[:file].path, import_upload_path)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
- File.expand_path(imported_file),
+ import_upload_path,
project_params[:path]).execute
if @project.saved?
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index c780e0983f9..6217ec5ecef 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -50,6 +50,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
flash[:notice] = "Password was successfully updated. Please login with it"
redirect_to new_user_session_path
else
+ @user.reload
render 'edit'
end
end
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index a9f482c8787..6c25cd83a24 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -4,12 +4,26 @@ class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers, except: [:index]
def build
- badge = Gitlab::Badge::Build.new(project, params[:ref])
+ build_status = Gitlab::Badge::Build::Status
+ .new(project, params[:ref])
+ render_badge build_status
+ end
+
+ def coverage
+ coverage_report = Gitlab::Badge::Coverage::Report
+ .new(project, params[:ref], params[:job])
+
+ render_badge coverage_report
+ end
+
+ private
+
+ def render_badge(badge)
respond_to do |format|
format.html { render_404 }
format.svg do
- send_data(badge.data, type: badge.type, disposition: 'inline')
+ render 'badge', locals: { badge: badge.template }
end
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index eda3727a28d..cdf9a04bacf 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -17,6 +17,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
+ before_action :set_last_commit_sha, only: [:edit, :update]
def new
commit unless @repository.empty?
@@ -33,7 +34,6 @@ class Projects::BlobController < Projects::ApplicationController
end
def edit
- @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end
@@ -55,6 +55,10 @@ class Projects::BlobController < Projects::ApplicationController
create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
+
+ rescue Files::UpdateService::FileChangedError
+ @conflict = true
+ render :edit
end
def preview
@@ -76,6 +80,8 @@ class Projects::BlobController < Projects::ApplicationController
end
def diff
+ apply_diff_view_cookie!
+
@form = UnfoldForm.new(params)
@lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path)
@lines = @lines[@form.since - 1..@form.to - 1]
@@ -150,7 +156,8 @@ class Projects::BlobController < Projects::ApplicationController
file_path: @file_path,
commit_message: params[:commit_message],
file_content: params[:content],
- file_content_encoding: params[:encoding]
+ file_content_encoding: params[:encoding],
+ last_commit_sha: params[:last_commit_sha]
}
end
@@ -159,4 +166,9 @@ class Projects::BlobController < Projects::ApplicationController
render nothing: true
end
end
+
+ def set_last_commit_sha
+ @last_commit_sha = Gitlab::Git::Commit.
+ last_for_path(@repository, @ref, @path).sha
+ end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 6126acccaab..48fe81b0d74 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,11 +1,13 @@
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
+ include SortingHelper
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy]
def index
+ @sort = params[:sort].presence || sort_value_name
@branches = BranchesFinder.new(@repository, params).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 553b62741a5..12195c3cbb8 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -6,7 +6,7 @@ class Projects::BuildsController < Projects::ApplicationController
def index
@scope = params[:scope]
- @all_builds = project.builds
+ @all_builds = project.builds.relevant
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 7ae034f9398..f44e9bb3fd7 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -28,7 +28,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def diff_for_path
- render_diff_for_path(@diffs, @commit.diff_refs, @project)
+ render_diff_for_path(@commit.diffs(diff_options))
end
def builds
@@ -134,8 +134,8 @@ class Projects::CommitController < Projects::ApplicationController
end
def define_status_vars
- @statuses = CommitStatus.where(pipeline: pipelines)
- @builds = Ci::Build.where(pipeline: pipelines)
+ @statuses = CommitStatus.where(pipeline: pipelines).relevant
+ @builds = Ci::Build.where(pipeline: pipelines).relevant
end
def assign_change_commit_vars(mr_source_branch)
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 8c004724f02..bee3d56076c 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController
def diff_for_path
return render_404 unless @compare
- render_diff_for_path(@diffs, @diff_refs, @project)
+ render_diff_for_path(@compare.diffs(diff_options))
end
def create
@@ -40,18 +40,12 @@ class Projects::CompareController < Projects::ApplicationController
@compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
if @compare
- @commits = Commit.decorate(@compare.commits, @project)
-
- @start_commit = @project.commit(@start_ref)
- @commit = @project.commit(@head_ref)
- @base_commit = @project.merge_base_commit(@start_ref, @head_ref)
+ @commits = @compare.commits
+ @start_commit = @compare.start_commit
+ @commit = @compare.commit
+ @base_commit = @compare.base_commit
@diffs = @compare.diffs(diff_options)
- @diff_refs = Gitlab::Diff::DiffRefs.new(
- base_sha: @base_commit.try(:sha),
- start_sha: @start_commit.try(:sha),
- head_sha: @commit.try(:sha)
- )
@diff_notes_disabled = true
@grouped_diff_discussions = {}
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 83d5ced9be8..529e0aa2d33 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -12,8 +12,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def new
- redirect_to namespace_project_deploy_keys_path(@project.namespace,
- @project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
end
def create
@@ -21,19 +20,16 @@ class Projects::DeployKeysController < Projects::ApplicationController
set_index_vars
if @key.valid? && @project.deploy_keys << @key
- redirect_to namespace_project_deploy_keys_path(@project.namespace,
- @project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
else
render "index"
end
end
def enable
- @key = accessible_keys.find(params[:id])
- @project.deploy_keys << @key
+ Projects::EnableDeployKeyService.new(@project, current_user, params).execute
- redirect_to namespace_project_deploy_keys_path(@project.namespace,
- @project)
+ redirect_to namespace_project_deploy_keys_path(@project.namespace, @project)
end
def disable
@@ -45,9 +41,9 @@ class Projects::DeployKeysController < Projects::ApplicationController
protected
def set_index_vars
- @enabled_keys ||= @project.deploy_keys
+ @enabled_keys ||= @project.deploy_keys
- @available_keys ||= accessible_keys - @enabled_keys
+ @available_keys ||= current_user.accessible_deploy_keys - @enabled_keys
@available_project_keys ||= current_user.project_deploy_keys - @enabled_keys
@available_public_keys ||= DeployKey.are_public - @enabled_keys
@@ -56,10 +52,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
@available_public_keys -= @available_project_keys
end
- def accessible_keys
- @accessible_keys ||= current_user.accessible_deploy_keys
- end
-
def deploy_key_params
params.require(:deploy_key).permit(:key, :title)
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 4b433796161..58678f96879 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -2,8 +2,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
- before_action :authorize_update_environment!, only: [:destroy]
- before_action :environment, only: [:show, :destroy]
+ before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
+ before_action :environment, only: [:show, :edit, :update, :destroy]
def index
@environments = project.environments
@@ -17,13 +17,24 @@ class Projects::EnvironmentsController < Projects::ApplicationController
@environment = project.environments.new
end
+ def edit
+ end
+
def create
- @environment = project.environments.create(create_params)
+ @environment = project.environments.create(environment_params)
if @environment.persisted?
redirect_to namespace_project_environment_path(project.namespace, project, @environment)
else
- render 'new'
+ render :new
+ end
+ end
+
+ def update
+ if @environment.update(environment_params)
+ redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ else
+ render :edit
end
end
@@ -39,8 +50,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
private
- def create_params
- params.require(:environment).permit(:name)
+ def environment_params
+ params.require(:environment).permit(:name, :external_url)
end
def environment
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
new file mode 100644
index 00000000000..7c21bd181dc
--- /dev/null
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -0,0 +1,110 @@
+# This file should be identical in GitLab Community Edition and Enterprise Edition
+
+class Projects::GitHttpClientController < Projects::ApplicationController
+ include ActionController::HttpAuthentication::Basic
+ include KerberosSpnegoHelper
+
+ attr_reader :user
+
+ # Git clients will not know what authenticity token to send along
+ skip_before_action :verify_authenticity_token
+ skip_before_action :repository
+ before_action :authenticate_user
+ before_action :ensure_project_found!
+
+ private
+
+ def authenticate_user
+ if project && project.public? && download_request?
+ return # Allow access
+ end
+
+ if allow_basic_auth? && basic_auth_provided?
+ login, password = user_name_and_password(request)
+ auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
+
+ if auth_result.type == :ci && download_request?
+ @ci = true
+ elsif auth_result.type == :oauth && !download_request?
+ # Not allowed
+ else
+ @user = auth_result.user
+ end
+
+ if ci? || user
+ return # Allow access
+ end
+ elsif allow_kerberos_spnego_auth? && spnego_provided?
+ @user = find_kerberos_user
+
+ if user
+ send_final_spnego_response
+ return # Allow access
+ end
+ end
+
+ send_challenges
+ render plain: "HTTP Basic: Access denied\n", status: 401
+ end
+
+ def basic_auth_provided?
+ has_basic_credentials?(request)
+ end
+
+ def send_challenges
+ challenges = []
+ challenges << 'Basic realm="GitLab"' if allow_basic_auth?
+ challenges << spnego_challenge if allow_kerberos_spnego_auth?
+ headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
+ end
+
+ def ensure_project_found!
+ render_not_found if project.blank?
+ end
+
+ def project
+ return @project if defined?(@project)
+
+ project_id, _ = project_id_with_suffix
+ if project_id.blank?
+ @project = nil
+ else
+ @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
+ end
+ end
+
+ # This method returns two values so that we can parse
+ # params[:project_id] (untrusted input!) in exactly one place.
+ def project_id_with_suffix
+ id = params[:project_id] || ''
+
+ %w[.wiki.git .git].each do |suffix|
+ if id.end_with?(suffix)
+ # Be careful to only remove the suffix from the end of 'id'.
+ # Accidentally removing it from the middle is how security
+ # vulnerabilities happen!
+ return [id.slice(0, id.length - suffix.length), suffix]
+ end
+ end
+
+ # Something is wrong with params[:project_id]; do not pass it on.
+ [nil, nil]
+ end
+
+ def repository
+ _, suffix = project_id_with_suffix
+ if suffix == '.wiki.git'
+ project.wiki.repository
+ else
+ project.repository
+ end
+ end
+
+ def render_not_found
+ render plain: 'Not Found', status: :not_found
+ end
+
+ def ci?
+ @ci.present?
+ end
+end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 40a8b7940d9..b4373ef89ef 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -1,17 +1,6 @@
# This file should be identical in GitLab Community Edition and Enterprise Edition
-class Projects::GitHttpController < Projects::ApplicationController
- include ActionController::HttpAuthentication::Basic
- include KerberosSpnegoHelper
-
- attr_reader :user
-
- # Git clients will not know what authenticity token to send along
- skip_before_action :verify_authenticity_token
- skip_before_action :repository
- before_action :authenticate_user
- before_action :ensure_project_found!
-
+class Projects::GitHttpController < Projects::GitHttpClientController
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs
@@ -20,9 +9,9 @@ class Projects::GitHttpController < Projects::ApplicationController
elsif receive_pack? && receive_pack_allowed?
render_ok
elsif http_blocked?
- render_not_allowed
+ render_http_not_allowed
else
- render_not_found
+ render_denied
end
end
@@ -31,7 +20,7 @@ class Projects::GitHttpController < Projects::ApplicationController
if upload_pack? && upload_pack_allowed?
render_ok
else
- render_not_found
+ render_denied
end
end
@@ -40,87 +29,14 @@ class Projects::GitHttpController < Projects::ApplicationController
if receive_pack? && receive_pack_allowed?
render_ok
else
- render_not_found
+ render_denied
end
end
private
- def authenticate_user
- if project && project.public? && upload_pack?
- return # Allow access
- end
-
- if allow_basic_auth? && basic_auth_provided?
- login, password = user_name_and_password(request)
- auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
-
- if auth_result.type == :ci && upload_pack?
- @ci = true
- elsif auth_result.type == :oauth && !upload_pack?
- # Not allowed
- else
- @user = auth_result.user
- end
-
- if ci? || user
- return # Allow access
- end
- elsif allow_kerberos_spnego_auth? && spnego_provided?
- @user = find_kerberos_user
-
- if user
- send_final_spnego_response
- return # Allow access
- end
- end
-
- send_challenges
- render plain: "HTTP Basic: Access denied\n", status: 401
- end
-
- def basic_auth_provided?
- has_basic_credentials?(request)
- end
-
- def send_challenges
- challenges = []
- challenges << 'Basic realm="GitLab"' if allow_basic_auth?
- challenges << spnego_challenge if allow_kerberos_spnego_auth?
- headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
- end
-
- def ensure_project_found!
- render_not_found if project.blank?
- end
-
- def project
- return @project if defined?(@project)
-
- project_id, _ = project_id_with_suffix
- if project_id.blank?
- @project = nil
- else
- @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
- end
- end
-
- # This method returns two values so that we can parse
- # params[:project_id] (untrusted input!) in exactly one place.
- def project_id_with_suffix
- id = params[:project_id] || ''
-
- %w[.wiki.git .git].each do |suffix|
- if id.end_with?(suffix)
- # Be careful to only remove the suffix from the end of 'id'.
- # Accidentally removing it from the middle is how security
- # vulnerabilities happen!
- return [id.slice(0, id.length - suffix.length), suffix]
- end
- end
-
- # Something is wrong with params[:project_id]; do not pass it on.
- [nil, nil]
+ def download_request?
+ upload_pack?
end
def upload_pack?
@@ -143,47 +59,37 @@ class Projects::GitHttpController < Projects::ApplicationController
render json: Gitlab::Workhorse.git_http_ok(repository, user)
end
- def repository
- _, suffix = project_id_with_suffix
- if suffix == '.wiki.git'
- project.wiki.repository
- else
- project.repository
- end
- end
-
- def render_not_found
- render plain: 'Not Found', status: :not_found
+ def render_http_not_allowed
+ render plain: access_check.message, status: :forbidden
end
- def render_not_allowed
- render plain: download_access.message, status: :forbidden
- end
-
- def ci?
- @ci.present?
+ def render_denied
+ if user && user.can?(:read_project, project)
+ render plain: 'Access denied', status: :forbidden
+ else
+ # Do not leak information about project existence
+ render_not_found
+ end
end
def upload_pack_allowed?
return false unless Gitlab.config.gitlab_shell.upload_pack
if user
- download_access.allowed?
+ access_check.allowed?
else
ci? || project.public?
end
end
def access
- return @access if defined?(@access)
-
- @access = Gitlab::GitAccess.new(user, project, 'http')
+ @access ||= Gitlab::GitAccess.new(user, project, 'http')
end
- def download_access
- return @download_access if defined?(@download_access)
-
- @download_access = access.check('git-upload-pack')
+ def access_check
+ # Use the magic string '_any' to indicate we do not know what the
+ # changes are. This is also what gitlab-shell does.
+ @access_check ||= access.check(git_command, '_any')
end
def http_blocked?
@@ -193,8 +99,6 @@ class Projects::GitHttpController < Projects::ApplicationController
def receive_pack_allowed?
return false unless Gitlab.config.gitlab_shell.receive_pack
- # Skip user authorization on upload request.
- # It will be done by the pre-receive hook in the repository.
- user.present?
+ access_check.allowed?
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index fa663c9bda4..e9fb11e8f94 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -1,8 +1,12 @@
class Projects::IssuesController < Projects::ApplicationController
+ include NotesHelper
include ToggleSubscriptionAction
include IssuableActions
include ToggleAwardEmoji
+ include IssuableCollections
+ include SpammableActions
+ before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
:related_branches, :can_create_branch]
@@ -23,7 +27,7 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
- @issues = get_issues_collection
+ @issues = issues_collection
if terms.present?
if terms =~ /\A#(\d+)\z/
@@ -70,6 +74,8 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue)
@noteable = @issue
+ preload_max_access_for_authors(@notes, @project)
+
respond_to do |format|
format.html
format.json do
@@ -79,7 +85,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def create
- @issue = Issues::CreateService.new(project, current_user, issue_params).execute
+ @issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute
respond_to do |format|
format.html do
@@ -89,7 +95,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :new
end
end
- format.js do |format|
+ format.js do
@link = @issue.attachment.url.to_js
end
end
@@ -180,6 +186,7 @@ class Projects::IssuesController < Projects::ApplicationController
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
alias_method :awardable, :issue
+ alias_method :spammable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
@@ -197,6 +204,18 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.issues_enabled && @project.default_issues_tracker?
end
+ def redirect_to_external_issue_tracker
+ external = @project.external_issue_tracker
+
+ return unless external
+
+ if action_name == 'new'
+ redirect_to external.new_issue_path
+ else
+ redirect_to external.issues_url
+ end
+ end
+
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
new file mode 100644
index 00000000000..ece49dcd922
--- /dev/null
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -0,0 +1,94 @@
+class Projects::LfsApiController < Projects::GitHttpClientController
+ include LfsHelper
+
+ before_action :require_lfs_enabled!
+ before_action :lfs_check_access!, except: [:deprecated]
+
+ def batch
+ unless objects.present?
+ render_lfs_not_found
+ return
+ end
+
+ if download_request?
+ render json: { objects: download_objects! }
+ elsif upload_request?
+ render json: { objects: upload_objects! }
+ else
+ raise "Never reached"
+ end
+ end
+
+ def deprecated
+ render(
+ json: {
+ message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
+ documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ },
+ status: 501
+ )
+ end
+
+ private
+
+ def objects
+ @objects ||= (params[:objects] || []).to_a
+ end
+
+ def existing_oids
+ @existing_oids ||= begin
+ storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
+ end
+ end
+
+ def download_objects!
+ objects.each do |object|
+ if existing_oids.include?(object[:oid])
+ object[:actions] = download_actions(object)
+ else
+ object[:error] = {
+ code: 404,
+ message: "Object does not exist on the server or you don't have permissions to access it",
+ }
+ end
+ end
+ objects
+ end
+
+ def upload_objects!
+ objects.each do |object|
+ object[:actions] = upload_actions(object) unless existing_oids.include?(object[:oid])
+ end
+ objects
+ end
+
+ def download_actions(object)
+ {
+ download: {
+ href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}",
+ header: {
+ Authorization: request.headers['Authorization']
+ }.compact
+ }
+ }
+ end
+
+ def upload_actions(object)
+ {
+ upload: {
+ href: "#{project.http_url_to_repo}/gitlab-lfs/objects/#{object[:oid]}/#{object[:size]}",
+ header: {
+ Authorization: request.headers['Authorization']
+ }.compact
+ }
+ }
+ end
+
+ def download_request?
+ params[:operation] == 'download'
+ end
+
+ def upload_request?
+ params[:operation] == 'upload'
+ end
+end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
new file mode 100644
index 00000000000..69066cb40e6
--- /dev/null
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -0,0 +1,92 @@
+class Projects::LfsStorageController < Projects::GitHttpClientController
+ include LfsHelper
+
+ before_action :require_lfs_enabled!
+ before_action :lfs_check_access!
+
+ def download
+ lfs_object = LfsObject.find_by_oid(oid)
+ unless lfs_object && lfs_object.file.exists?
+ render_lfs_not_found
+ return
+ end
+
+ send_file lfs_object.file.path, content_type: "application/octet-stream"
+ end
+
+ def upload_authorize
+ render(
+ json: {
+ StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload",
+ LfsOid: oid,
+ LfsSize: size,
+ },
+ content_type: 'application/json; charset=utf-8'
+ )
+ end
+
+ def upload_finalize
+ unless tmp_filename
+ render_lfs_forbidden
+ return
+ end
+
+ if store_file(oid, size, tmp_filename)
+ head 200
+ else
+ render plain: 'Unprocessable entity', status: 422
+ end
+ end
+
+ private
+
+ def download_request?
+ action_name == 'download'
+ end
+
+ def upload_request?
+ %w[upload_authorize upload_finalize].include? action_name
+ end
+
+ def oid
+ params[:oid].to_s
+ end
+
+ def size
+ params[:size].to_i
+ end
+
+ def tmp_filename
+ name = request.headers['X-Gitlab-Lfs-Tmp']
+ return if name.include?('/')
+ return unless oid.present? && name.start_with?(oid)
+ name
+ end
+
+ def store_file(oid, size, tmp_file)
+ # Define tmp_file_path early because we use it in "ensure"
+ tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
+
+ object = LfsObject.find_or_create_by(oid: oid, size: size)
+ file_exists = object.file.exists? || move_tmp_file_to_storage(object, tmp_file_path)
+ file_exists && link_to_project(object)
+ ensure
+ FileUtils.rm_f(tmp_file_path)
+ end
+
+ def move_tmp_file_to_storage(object, path)
+ File.open(path) do |f|
+ object.file = f
+ end
+
+ object.file.store!
+ object.save
+ end
+
+ def link_to_project(object)
+ if object && !object.projects.exists?(storage_project.id)
+ object.projects << storage_project
+ object.save
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d6128b3dfe9..6805cfba850 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -3,7 +3,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include DiffForPath
include DiffHelper
include IssuableActions
+ include NotesHelper
include ToggleAwardEmoji
+ include IssuableCollections
before_action :module_enabled
before_action :merge_request, only: [
@@ -28,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def index
terms = params['issue_search']
- @merge_requests = get_merge_requests_collection
+ @merge_requests = merge_requests_collection
if terms.present?
if terms =~ /\A[#!](\d+)\z/
@@ -83,7 +85,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html { define_discussion_vars }
- format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
+ format.json do
+ @diffs = @merge_request.diffs(diff_options)
+
+ render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") }
+ end
end
end
@@ -101,9 +107,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
define_commit_vars
- diffs = @merge_request.diffs(diff_options)
- render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project)
+ render_diff_for_path(@merge_request.diffs(diff_options))
end
def commits
@@ -164,11 +169,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
- @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
+ @diffs = @merge_request.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
@pipeline = @merge_request.pipeline
- @statuses = @pipeline.statuses if @pipeline
+ @statuses = @pipeline.statuses.relevant if @pipeline
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@@ -370,7 +375,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@commits_count = @merge_request.commits.count
@pipeline = @merge_request.pipeline
- @statuses = @pipeline.statuses if @pipeline
+ @statuses = @pipeline.statuses.relevant if @pipeline
if @merge_request.locked_long_ago?
@merge_request.unlock_mr
@@ -389,6 +394,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
fresh.
discussions
+ preload_noteable_for_regular_notes(@discussions.flat_map(&:notes))
+
# This is not executed lazily
@notes = Banzai::NoteRenderer.render(
@discussions.flat_map(&:notes),
@@ -398,6 +405,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@project_wiki,
@ref
)
+
+ preload_max_access_for_authors(@notes, @project)
end
def define_widget_vars
@@ -417,7 +426,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
}
@use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
- @grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions
+ @grouped_diff_discussions = @merge_request.notes.inc_author_project_award_emoji.grouped_diff_discussions
Banzai::NoteRenderer.render(
@grouped_diff_discussions.values.flat_map(&:notes),
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 487963fdcd7..b0c72cfe4b4 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -19,7 +19,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def create
- @pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute
+ @pipeline = Ci::CreatePipelineService.new(project, current_user, create_params).execute(ignore_skip_ci: true, save_on_errors: false)
unless @pipeline.persisted?
render 'new'
return
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 85ba706e5cd..9136633b87a 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -3,7 +3,13 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show
@ref = params[:ref] || @project.default_branch || 'master'
- @build_badge = Gitlab::Badge::Build.new(@project, @ref)
+
+ @badges = [Gitlab::Badge::Build::Status,
+ Gitlab::Badge::Coverage::Report]
+
+ @badges.map! do |badge|
+ badge.new(@project, @ref).metadata
+ end
end
def update
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 10dca47fded..d28ec6e2eac 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -3,19 +3,24 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :authorize_admin_project!
before_action :load_protected_branch, only: [:show, :update, :destroy]
+ before_action :load_protected_branches, only: [:index]
layout "project_settings"
def index
- @protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_branch = @project.protected_branches.new
- gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } })
+ load_protected_branches_gon_variables
end
def create
- @project.protected_branches.create(protected_branch_params)
- redirect_to namespace_project_protected_branches_path(@project.namespace,
- @project)
+ @protected_branch = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
+ if @protected_branch.persisted?
+ redirect_to namespace_project_protected_branches_path(@project.namespace, @project)
+ else
+ load_protected_branches
+ load_protected_branches_gon_variables
+ render :index
+ end
end
def show
@@ -23,7 +28,9 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
end
def update
- if @protected_branch && @protected_branch.update_attributes(protected_branch_params)
+ @protected_branch = ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch)
+
+ if @protected_branch.valid?
respond_to do |format|
format.json { render json: @protected_branch, status: :ok }
end
@@ -50,6 +57,18 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
end
def protected_branch_params
- params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge)
+ params.require(:protected_branch).permit(:name,
+ merge_access_level_attributes: [:access_level],
+ push_access_level_attributes: [:access_level])
+ end
+
+ def load_protected_branches
+ @protected_branches = @project.protected_branches.order(:name).page(params[:page])
+ end
+
+ def load_protected_branches_gon_variables
+ gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } },
+ push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } },
+ merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } })
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 6dc495247c8..8592579abbd 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -10,11 +10,12 @@ class Projects::TagsController < Projects::ApplicationController
@tags = @repository.tags_sorted_by(@sort)
@tags = Kaminari.paginate_array(@tags).page(params[:page])
- @releases = project.releases.where(tag: @tags)
+ @releases = project.releases.where(tag: @tags.map(&:name))
end
def show
@tag = @repository.find_tag(params[:id])
+
@release = @project.releases.find_or_initialize_by(tag: @tag.name)
@commit = @repository.commit(@tag.target)
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 607fe9c7fed..177ccf5eec9 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -91,7 +91,7 @@ class Projects::WikisController < Projects::ApplicationController
)
end
- def markdown_preview
+ def preview_markdown
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ec7a2e63b9a..47efbd4a939 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -97,7 +97,7 @@ class ProjectsController < Projects::ApplicationController
end
if @project.pending_delete?
- flash[:alert] = "Project queued for delete."
+ flash[:alert] = "Project #{@project.name} queued for deletion."
end
respond_to do |format|
@@ -125,7 +125,7 @@ class ProjectsController < Projects::ApplicationController
def destroy
return access_denied! unless can?(current_user, :remove_project, @project)
- ::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
+ ::Projects::DestroyService.new(@project, current_user, {}).async_execute
flash[:alert] = "Project '#{@project.name}' will be deleted."
redirect_to dashboard_projects_path
@@ -238,7 +238,7 @@ class ProjectsController < Projects::ApplicationController
}
end
- def markdown_preview
+ def preview_markdown
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 75b78a49eab..3327f4f2b87 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -33,7 +33,7 @@ class RegistrationsController < Devise::RegistrationsController
protected
- def build_resource(hash=nil)
+ def build_resource(hash = nil)
super
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 69c92d2bed2..61517d21f9f 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -1,5 +1,5 @@
class SearchController < ApplicationController
- skip_before_action :authenticate_user!, :reject_blocked
+ skip_before_action :authenticate_user!, :reject_blocked!
include SearchHelper
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 17aed816cbd..5d7ecfeacf4 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController
# Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil
- redirect_to user_omniauth_authorize_path(provider.to_sym)
+ redirect_to omniauth_authorize_path(:user, provider)
end
def valid_otp_attempt?(user)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index a0932712bd0..33daac0399e 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -109,7 +109,7 @@ class IssuableFinder
scope.where(title: params[:milestone_title])
else
- nil
+ Milestone.none
end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 2f0a9659d15..c7911736812 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -1,6 +1,7 @@
class ProjectsFinder < UnionFinder
- def execute(current_user = nil, options = {})
+ def execute(current_user = nil, project_ids_relation = nil)
segments = all_projects(current_user)
+ segments.map! { |s| s.where(id: project_ids_relation) } if project_ids_relation
find_union(segments, Project)
end
diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb
index ff866c2faa5..4fe0070552e 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -27,9 +27,11 @@ class TodosFinder
items = by_action_id(items)
items = by_action(items)
items = by_author(items)
- items = by_project(items)
items = by_state(items)
items = by_type(items)
+ # Filtering by project HAS TO be the last because we use
+ # the project IDs yielded by the todos query thus far
+ items = by_project(items)
items.reorder(id: :desc)
end
@@ -91,14 +93,9 @@ class TodosFinder
@project
end
- def projects
- return @projects if defined?(@projects)
-
- if project?
- @projects = project
- else
- @projects = ProjectsFinder.new.execute(current_user)
- end
+ def projects(items)
+ item_project_ids = items.reorder(nil).select(:project_id)
+ ProjectsFinder.new.execute(current_user, item_project_ids)
end
def type?
@@ -136,8 +133,9 @@ class TodosFinder
def by_project(items)
if project?
items = items.where(project: project)
- elsif projects
- items = items.merge(projects).joins(:project)
+ else
+ item_projects = projects(items)
+ items = items.merge(item_projects).joins(:project)
end
items
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 03495cf5ec4..c3613bc67dd 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -163,9 +163,13 @@ module ApplicationHelper
# `html_class` argument is provided.
#
# Returns an HTML-safe String
- def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
+ def time_ago_with_tooltip(time, placement: 'top', html_class: '', skip_js: false, short_format: false)
+ css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
+ css_classes << " #{html_class}" unless html_class.blank?
+ css_classes << ' js-timeago-pending' unless skip_js
+
element = content_tag :time, time.to_s,
- class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}",
+ class: css_classes,
datetime: time.to_time.getutc.iso8601,
title: time.to_time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
@@ -245,7 +249,6 @@ module ApplicationHelper
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
author_id: params[:author_id],
- sort: params[:sort],
issue_search: params[:issue_search],
label_name: params[:label_name]
}
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 6ff40c6b461..aa8acbe7567 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,5 +1,4 @@
module AvatarsHelper
-
def author_avatar(commit_or_event, options = {})
user_avatar(options.merge({
user: commit_or_event.author,
@@ -8,8 +7,6 @@ module AvatarsHelper
}))
end
- private
-
def user_avatar(options = {})
avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name]
@@ -26,5 +23,4 @@ module AvatarsHelper
mail_to(options[:user_email], avatar)
end
end
-
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index abe115d8c68..48c27828219 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -13,7 +13,7 @@ module BlobHelper
blob = project.repository.blob_at(ref, path) rescue nil
- return unless blob && blob_text_viewable?(blob)
+ return unless blob
from_mr = options[:from_merge_request_id]
link_opts = {}
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index f497626e21a..7a02d0b10d9 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -206,10 +206,10 @@ module CommitsHelper
end
end
- def view_file_btn(commit_sha, diff, project)
+ def view_file_btn(commit_sha, diff_new_path, project)
link_to(
namespace_project_blob_path(project.namespace, project,
- tree_join(commit_sha, diff.new_path)),
+ tree_join(commit_sha, diff_new_path)),
class: 'btn view-file js-view-file btn-file-option'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 4c031942793..0725c3f4c56 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -13,12 +13,11 @@ module DiffHelper
end
def diff_view
- diff_views = %w(inline parallel)
-
- if diff_views.include?(cookies[:diff_view])
- cookies[:diff_view]
- else
- diff_views.first
+ @diff_view ||= begin
+ diff_views = %w(inline parallel)
+ diff_view = cookies[:diff_view]
+ diff_view = diff_views.first unless diff_views.include?(diff_view)
+ diff_view.to_sym
end
end
@@ -30,19 +29,26 @@ module DiffHelper
options[:paths] = params.values_at(:old_path, :new_path)
end
- Commit.max_diff_options.merge(options)
+ options
end
- def safe_diff_files(diffs, diff_refs: nil, repository: nil)
- diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
- end
+ def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false)
+ content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}"
+ cls = ['diff-line-num', 'unfold', 'js-unfold']
+ cls << 'js-unfold-bottom' if bottom
- def unfold_bottom_class(bottom)
- bottom ? 'js-unfold js-unfold-bottom' : ''
- end
+ html = ''
+ if old_pos
+ html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos })
+ html << content unless view == :inline
+ end
- def unfold_class(unfold)
- unfold ? 'unfold js-unfold' : ''
+ if new_pos
+ html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos })
+ html << content
+ end
+
+ html.html_safe
end
def diff_line_content(line, line_type = nil)
@@ -71,11 +77,11 @@ module DiffHelper
end
def inline_diff_btn
- diff_btn('Inline', 'inline', diff_view == 'inline')
+ diff_btn('Inline', 'inline', diff_view == :inline)
end
def parallel_diff_btn
- diff_btn('Side-by-side', 'parallel', diff_view == 'parallel')
+ diff_btn('Side-by-side', 'parallel', diff_view == :parallel)
end
def submodule_link(blob, ref, repository = @repository)
@@ -103,11 +109,11 @@ module DiffHelper
end
end
- def diff_file_html_data(project, diff_file)
- commit = commit_for_diff(diff_file)
+ def diff_file_html_data(project, diff_file_path, diff_commit_id)
{
blob_diff_path: namespace_project_blob_diff_path(project.namespace, project,
- tree_join(commit.id, diff_file.file_path))
+ tree_join(diff_commit_id, diff_file_path)),
+ view: diff_view
}
end
@@ -144,8 +150,6 @@ module DiffHelper
toggle_whitespace_link(url, options)
end
- private
-
def hide_whitespace?
params[:w] == '1'
end
diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb
index 337b0aacbb5..2b1f3825adc 100644
--- a/app/helpers/explore_helper.rb
+++ b/app/helpers/explore_helper.rb
@@ -1,5 +1,5 @@
module ExploreHelper
- def filter_projects_path(options={})
+ def filter_projects_path(options = {})
exist_opts = {
sort: params[:sort],
scope: params[:scope],
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 2b0defd1dda..2e82b44437b 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -13,38 +13,6 @@ module IssuesHelper
OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned')
end
- def url_for_project_issues(project = @project, options = {})
- return '' if project.nil?
-
- url =
- if options[:only_path]
- project.issues_tracker.project_path
- else
- project.issues_tracker.project_url
- end
-
- # Ensure we return a valid URL to prevent possible XSS.
- URI.parse(url).to_s
- rescue URI::InvalidURIError
- ''
- end
-
- def url_for_new_issue(project = @project, options = {})
- return '' if project.nil?
-
- url =
- if options[:only_path]
- project.issues_tracker.new_issue_path
- else
- project.issues_tracker.new_issue_url
- end
-
- # Ensure we return a valid URL to prevent possible XSS.
- URI.parse(url).to_s
- rescue URI::InvalidURIError
- ''
- end
-
def url_for_issue(issue_iid, project = @project, options = {})
return '' if project.nil?
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
new file mode 100644
index 00000000000..eb651e3687e
--- /dev/null
+++ b/app/helpers/lfs_helper.rb
@@ -0,0 +1,67 @@
+module LfsHelper
+ def require_lfs_enabled!
+ return if Gitlab.config.lfs.enabled
+
+ render(
+ json: {
+ message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
+ documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ },
+ status: 501
+ )
+ end
+
+ def lfs_check_access!
+ return if download_request? && lfs_download_access?
+ return if upload_request? && lfs_upload_access?
+
+ if project.public? || (user && user.can?(:read_project, project))
+ render_lfs_forbidden
+ else
+ render_lfs_not_found
+ end
+ end
+
+ def lfs_download_access?
+ project.public? || ci? || (user && user.can?(:download_code, project))
+ end
+
+ def lfs_upload_access?
+ user && user.can?(:push_code, project)
+ end
+
+ def render_lfs_forbidden
+ render(
+ json: {
+ message: 'Access forbidden. Check your access level.',
+ documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ },
+ content_type: "application/vnd.git-lfs+json",
+ status: 403
+ )
+ end
+
+ def render_lfs_not_found
+ render(
+ json: {
+ message: 'Not found.',
+ documentation_url: "#{Gitlab.config.gitlab.url}/help",
+ },
+ content_type: "application/vnd.git-lfs+json",
+ status: 404
+ )
+ end
+
+ def storage_project
+ @storage_project ||= begin
+ result = project
+
+ loop do
+ break unless result.forked?
+ result = result.forked_from_project
+ end
+
+ result
+ end
+ end
+end
diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb
index ec106418f2d..877c77050be 100644
--- a/app/helpers/members_helper.rb
+++ b/app/helpers/members_helper.rb
@@ -6,12 +6,6 @@ module MembersHelper
"#{action}_#{member.type.underscore}".to_sym
end
- def default_show_roles(member)
- can?(current_user, action_member_permission(:update, member), member) ||
- can?(current_user, action_member_permission(:destroy, member), member) ||
- can?(current_user, action_member_permission(:admin, member), member.source)
- end
-
def remove_member_message(member, user: nil)
user = current_user if defined?(current_user)
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 0f60dd828ab..26bde2230a9 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -7,7 +7,7 @@ module NotesHelper
end
def note_editable?(note)
- note.editable? && can?(current_user, :admin_note, note)
+ Ability.can_edit_note?(current_user, note)
end
def noteable_json(noteable)
@@ -87,14 +87,17 @@ module NotesHelper
end
end
- def note_max_access_for_user(note)
- @max_access_by_user_id ||= Hash.new do |hash, key|
- project = key[:project]
- hash[key] = project.team.human_max_access(key[:user_id])
- end
+ def preload_max_access_for_authors(notes, project)
+ user_ids = notes.map(&:author_id)
+ project.team.max_member_access_for_user_ids(user_ids)
+ end
- full_key = { project: note.project, user_id: note.author_id }
- @max_access_by_user_id[full_key]
+ def preload_noteable_for_regular_notes(notes)
+ ActiveRecord::Associations::Preloader.new.preload(notes.select { |note| !note.for_commit? }, :noteable)
+ end
+
+ def note_max_access_for_user(note)
+ note.project.team.human_max_access(note.author_id)
end
def discussion_diff_path(discussion)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index a733dff1579..505545fbabb 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -263,6 +263,10 @@ module ProjectsHelper
filename_path(project, :version)
end
+ def ci_configuration_path(project)
+ filename_path(project, :gitlab_ci_yml)
+ end
+
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
namespace_project_wiki_path(proj.namespace, proj, page, url_params)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index a2bba139c17..c0195713f4a 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -107,7 +107,7 @@ module SearchHelper
Sanitize.clean(str)
end
- def search_filter_path(options={})
+ def search_filter_path(options = {})
exist_opts = {
search: params[:search],
project_id: params[:project_id],
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index bb395e37884..5f27e33c6ad 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -5,21 +5,9 @@ module SelectsHelper
css_class << "skip_ldap " if opts[:skip_ldap]
css_class << (opts[:class] || '')
value = opts[:selected] || ''
-
- first_user = opts[:first_user] && current_user ? current_user.username : false
-
html = {
class: css_class,
- data: {
- placeholder: opts[:placeholder] || 'Search for a user',
- null_user: opts[:null_user] || false,
- any_user: opts[:any_user] || false,
- email_user: opts[:email_user] || false,
- first_user: first_user,
- current_user: opts[:current_user] || false,
- "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
- author_id: opts[:author_id] || ''
- }
+ data: users_select_data_attributes(opts)
}
unless opts[:scope] == :all
@@ -68,4 +56,20 @@ module SelectsHelper
hidden_field_tag(id, value, class: css_class)
end
+
+ private
+
+ def users_select_data_attributes(opts)
+ {
+ placeholder: opts[:placeholder] || 'Search for a user',
+ null_user: opts[:null_user] || false,
+ any_user: opts[:any_user] || false,
+ email_user: opts[:email_user] || false,
+ first_user: opts[:first_user] && current_user ? current_user.username : false,
+ current_user: opts[:current_user] || false,
+ "push-code-to-protected-branches" => opts[:push_code_to_protected_branches],
+ author_id: opts[:author_id] || '',
+ skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil,
+ }
+ end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index d86f1999f5c..8b138a8e69f 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -20,13 +20,19 @@ module SortingHelper
end
def projects_sort_options_hash
- {
+ options = {
sort_value_name => sort_title_name,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created,
sort_value_oldest_created => sort_title_oldest_created,
}
+
+ if current_controller?('admin/projects')
+ options.merge!(sort_value_largest_repo => sort_title_largest_repo)
+ end
+
+ options
end
def sort_title_priority
@@ -102,11 +108,11 @@ module SortingHelper
end
def sort_value_oldest_created
- 'id_asc'
+ 'created_asc'
end
def sort_value_recently_created
- 'id_desc'
+ 'created_desc'
end
def sort_value_milestone_soon
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index e3a208f826a..0465327060e 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -1,10 +1,10 @@
module TodosHelper
def todos_pending_count
- @todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
+ @todos_pending_count ||= current_user.todos_pending_count
end
def todos_done_count
- @todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
+ @todos_done_count ||= current_user.todos_done_count
end
def todo_action_name(todo)
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index dbedf417fa5..4a76c679bad 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -4,23 +4,11 @@ module TreeHelper
#
# contents - A Grit::Tree object for the current tree
def render_tree(tree)
- # Render Folders before Files/Submodules
+ # Sort submodules and folders together by name ahead of files
folders, files, submodules = tree.trees, tree.blobs, tree.submodules
-
tree = ""
-
- # Render folders if we have any
- tree << render(partial: 'projects/tree/tree_item', collection: folders,
- locals: { type: 'folder' }) if folders.present?
-
- # Render files if we have any
- tree << render(partial: 'projects/tree/blob_item', collection: files,
- locals: { type: 'file' }) if files.present?
-
- # Render submodules if we have any
- tree << render(partial: 'projects/tree/submodule_item',
- collection: submodules) if submodules.present?
-
+ items = (folders + submodules).sort_by(&:name) + files
+ tree << render(partial: "projects/tree/tree_row", collection: items) if items.present?
tree.html_safe
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f33c8d61d3f..d9113ffd99a 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -6,6 +6,10 @@ class Ability
return [] unless user.is_a?(User)
return [] if user.blocked?
+ abilities_by_subject_class(user: user, subject: subject)
+ end
+
+ def abilities_by_subject_class(user:, subject:)
case subject
when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject)
@@ -47,6 +51,16 @@ class Ability
end
end
+ # Returns an Array of Issues that can be read by the given user.
+ #
+ # issues - The issues to reduce down to those readable by the user.
+ # user - The User for which to check the issues
+ def issues_readable_by_user(issues, user = nil)
+ return issues if user && user.admin?
+
+ issues.select { |issue| issue.visible_to_user?(user) }
+ end
+
# List of possible abilities for anonymous user
def anonymous_abilities(user, subject)
if subject.is_a?(PersonalSnippet)
@@ -388,6 +402,18 @@ class Ability
GroupProjectsFinder.new(group).execute(user).any?
end
+ def can_edit_note?(user, note)
+ return false if !note.editable? || !user.present?
+ return true if note.author == user || user.admin?
+
+ if note.project
+ max_access_level = note.project.team.max_member_access(user.id)
+ max_access_level >= Gitlab::Access::MASTER
+ else
+ false
+ end
+ end
+
def namespace_abilities(user, namespace)
rules = []
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 4279ea2ce57..12cc5aaafba 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -3,6 +3,9 @@ class Blob < SimpleDelegator
CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
+ # The maximum size of an SVG that can be displayed.
+ MAXIMUM_SVG_SIZE = 2.megabytes
+
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
# This method prevents the decorated object from evaluating to "truthy" when
@@ -31,6 +34,14 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG'
end
+ def size_within_svg_limits?
+ size <= MAXIMUM_SVG_SIZE
+ end
+
+ def video?
+ UploaderHelper::VIDEO_EXT.include?(extname.downcase.delete('.'))
+ end
+
def to_partial_path
if lfs_pointer?
'download'
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index cbfa14e81f1..3d6c6ea3209 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -13,9 +13,10 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) }
+ scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
- scope :manual_actions, ->() { where(when: :manual) }
+ scope :manual_actions, ->() { where(when: :manual).relevant }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
@@ -41,40 +42,35 @@ module Ci
end
def retry(build, user = nil)
- new_build = Ci::Build.new(status: 'pending')
- new_build.ref = build.ref
- new_build.tag = build.tag
- new_build.options = build.options
- new_build.commands = build.commands
- new_build.tag_list = build.tag_list
- new_build.project = build.project
- new_build.pipeline = build.pipeline
- new_build.name = build.name
- new_build.allow_failure = build.allow_failure
- new_build.stage = build.stage
- new_build.stage_idx = build.stage_idx
- new_build.trigger_request = build.trigger_request
- new_build.yaml_variables = build.yaml_variables
- new_build.when = build.when
- new_build.user = user
- new_build.environment = build.environment
- new_build.save
+ new_build = Ci::Build.create(
+ ref: build.ref,
+ tag: build.tag,
+ options: build.options,
+ commands: build.commands,
+ tag_list: build.tag_list,
+ project: build.project,
+ pipeline: build.pipeline,
+ name: build.name,
+ allow_failure: build.allow_failure,
+ stage: build.stage,
+ stage_idx: build.stage_idx,
+ trigger_request: build.trigger_request,
+ yaml_variables: build.yaml_variables,
+ when: build.when,
+ user: user,
+ environment: build.environment,
+ status_event: 'enqueue'
+ )
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build
end
end
- state_machine :status, initial: :pending do
+ state_machine :status do
after_transition pending: :running do |build|
build.execute_hooks
end
- # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
- around_transition any => [:success, :failed, :canceled] do |build, block|
- block.call
- build.pipeline.create_next_builds(build) if build.pipeline
- end
-
after_transition any => [:success, :failed, :canceled] do |build|
build.update_coverage
build.execute_hooks
@@ -106,7 +102,7 @@ module Ci
def play(current_user = nil)
# Try to queue a current build
- if self.queue
+ if self.enqueue
self.update(user: current_user)
self
else
@@ -331,7 +327,7 @@ module Ci
end
def valid_token?(token)
- project.valid_runners_token? token
+ project.valid_runners_token?(token)
end
def has_tags?
@@ -460,7 +456,7 @@ module Ci
def build_attributes_from_config
return {} unless pipeline.config_processor
-
+
pipeline.config_processor.build_attributes(name)
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bce6a992af6..8cfba92ae9b 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -13,13 +13,51 @@ module Ci
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
validates_presence_of :sha
+ validates_presence_of :ref
validates_presence_of :status
validate :valid_commit_sha
- # Invalidate object and save if when touched
- after_touch :update_state
after_save :keep_around_commits
+ state_machine :status, initial: :created do
+ event :enqueue do
+ transition created: :pending
+ transition [:success, :failed, :canceled, :skipped] => :running
+ end
+
+ event :run do
+ transition any => :running
+ end
+
+ event :skip do
+ transition any => :skipped
+ end
+
+ event :drop do
+ transition any => :failed
+ end
+
+ event :succeed do
+ transition any => :success
+ end
+
+ event :cancel do
+ transition any => :canceled
+ end
+
+ before_transition [:created, :pending] => :running do |pipeline|
+ pipeline.started_at = Time.now
+ end
+
+ before_transition any => [:success, :failed, :canceled] do |pipeline|
+ pipeline.finished_at = Time.now
+ end
+
+ before_transition do |pipeline|
+ pipeline.update_duration
+ end
+ end
+
# ref can't be HEAD or SHA, can only be branch/tag name
scope :latest_successful_for, ->(ref = default_branch) do
where(ref: ref).success.order(id: :desc).limit(1)
@@ -109,37 +147,6 @@ module Ci
trigger_requests.any?
end
- def create_builds(user, trigger_request = nil)
- ##
- # We persist pipeline only if there are builds available
- #
- return unless config_processor
-
- build_builds_for_stages(config_processor.stages, user,
- 'success', trigger_request) && save
- end
-
- def create_next_builds(build)
- return unless config_processor
-
- # don't create other builds if this one is retried
- latest_builds = builds.latest
- return unless latest_builds.exists?(build.id)
-
- # get list of stages after this build
- next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
- next_stages.delete(build.stage)
-
- # get status for all prior builds
- prior_builds = latest_builds.where.not(stage: next_stages)
- prior_status = prior_builds.status
-
- # build builds for next stage that has builds available
- # and save pipeline if we have builds
- build_builds_for_stages(next_stages, build.user, prior_status,
- build.trigger_request) && save
- end
-
def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest)
end
@@ -151,6 +158,14 @@ module Ci
end
end
+ def config_builds_attributes
+ return [] unless config_processor
+
+ config_processor.
+ builds_for_ref(ref, tag?, trigger_requests.first).
+ sort_by { |build| build[:stage_idx] }
+ end
+
def has_warnings?
builds.latest.ignored.any?
end
@@ -182,10 +197,6 @@ module Ci
end
end
- def skip_ci?
- git_commit_message =~ /\[(ci skip|skip ci)\]/i if git_commit_message
- end
-
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
@@ -207,37 +218,37 @@ module Ci
Note.for_commit_id(sha)
end
+ def process!
+ Ci::ProcessPipelineService.new(project, user).execute(self)
+ end
+
+ def build_updated
+ case latest_builds_status
+ when 'pending' then enqueue
+ when 'running' then run
+ when 'success' then succeed
+ when 'failed' then drop
+ when 'canceled' then cancel
+ when 'skipped' then skip
+ end
+ end
+
def predefined_variables
[
{ key: 'CI_PIPELINE_ID', value: id.to_s, public: true }
]
end
+ def update_duration
+ self.duration = statuses.latest.duration
+ end
+
private
- def build_builds_for_stages(stages, user, status, trigger_request)
- ##
- # Note that `Array#any?` implements a short circuit evaluation, so we
- # build builds only for the first stage that has builds available.
- #
- stages.any? do |stage|
- CreateBuildsService.new(self).
- execute(stage, user, status, trigger_request).
- any?(&:active?)
- end
- end
-
- def update_state
- statuses.reload
- self.status = if yaml_errors.blank?
- statuses.latest.status || 'skipped'
- else
- 'failed'
- end
- self.started_at = statuses.started_at
- self.finished_at = statuses.finished_at
- self.duration = statuses.latest.duration
- save
+ def latest_builds_status
+ return 'failed' unless yaml_errors.blank?
+
+ statuses.latest.status || 'skipped'
end
def keep_around_commits
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2ef3973c160..cc413448ce8 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -104,7 +104,7 @@ class Commit
end
def diff_line_count
- @diff_line_count ||= Commit::diff_line_count(self.diffs)
+ @diff_line_count ||= Commit::diff_line_count(raw_diffs)
@diff_line_count
end
@@ -123,15 +123,17 @@ class Commit
# In case this first line is longer than 100 characters, it is cut off
# after 80 characters and ellipses (`&hellp;`) are appended.
def title
- title = safe_message
+ full_title.length > 100 ? full_title[0..79] << "…" : full_title
+ end
- return no_commit_message if title.blank?
+ # Returns the full commits title
+ def full_title
+ return @full_title if @full_title
- title_end = title.index("\n")
- if (!title_end && title.length > 100) || (title_end && title_end > 100)
- title[0..79] << "…"
+ if safe_message.blank?
+ @full_title = no_commit_message
else
- title.split("\n", 2).first
+ @full_title = safe_message.split("\n", 2).first
end
end
@@ -178,7 +180,18 @@ class Commit
end
def author
- @author ||= User.find_by_any_email(author_email.downcase)
+ if RequestStore.active?
+ key = "commit_author:#{author_email.downcase}"
+ # nil is a valid value since no author may exist in the system
+ if RequestStore.store.has_key?(key)
+ @author = RequestStore.store[key]
+ else
+ @author = find_author_by_any_email
+ RequestStore.store[key] = @author
+ end
+ else
+ @author ||= find_author_by_any_email
+ end
end
def committer
@@ -295,8 +308,8 @@ class Commit
def uri_type(path)
entry = @raw.tree.path(path)
if entry[:type] == :blob
- blob = Gitlab::Git::Blob.new(name: entry[:name])
- blob.image? ? :raw : :blob
+ blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]))
+ blob.image? || blob.video? ? :raw : :blob
else
entry[:type]
end
@@ -304,12 +317,24 @@ class Commit
nil
end
+ def raw_diffs(*args)
+ raw.diffs(*args)
+ end
+
+ def diffs(diff_options = nil)
+ Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
+ end
+
private
+ def find_author_by_any_email
+ User.find_by_any_email(author_email.downcase)
+ end
+
def repo_changes
changes = { added: [], modified: [], removed: [] }
- diffs.each do |diff|
+ raw_diffs(deltas_only: true).each do |diff|
if diff.deleted_file
changes[:removed] << diff.old_path
elsif diff.renamed_file || diff.new_file
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 2d185c28809..703ca90edb6 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -5,7 +5,7 @@ class CommitStatus < ActiveRecord::Base
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
- belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
+ belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
belongs_to :user
delegate :commit, to: :pipeline
@@ -25,28 +25,36 @@ class CommitStatus < ActiveRecord::Base
scope :ordered, -> { order(:name) }
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
- state_machine :status, initial: :pending do
- event :queue do
- transition skipped: :pending
+ state_machine :status do
+ event :enqueue do
+ transition [:created, :skipped] => :pending
end
event :run do
transition pending: :running
end
+ event :skip do
+ transition [:created, :pending] => :skipped
+ end
+
event :drop do
- transition [:pending, :running] => :failed
+ transition [:created, :pending, :running] => :failed
end
event :success do
- transition [:pending, :running] => :success
+ transition [:created, :pending, :running] => :success
end
event :cancel do
- transition [:pending, :running] => :canceled
+ transition [:created, :pending, :running] => :canceled
end
- after_transition pending: :running do |commit_status|
+ after_transition created: [:pending, :running] do |commit_status|
+ commit_status.update_attributes queued_at: Time.now
+ end
+
+ after_transition [:created, :pending] => :running do |commit_status|
commit_status.update_attributes started_at: Time.now
end
@@ -54,7 +62,18 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now
end
- after_transition [:pending, :running] => :success do |commit_status|
+ # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
+ around_transition any => [:success, :failed, :canceled] do |commit_status, block|
+ block.call
+
+ commit_status.pipeline.try(:process!)
+ end
+
+ after_transition do |commit_status, transition|
+ commit_status.pipeline.try(:build_updated) unless transition.loopback?
+ end
+
+ after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end
diff --git a/app/models/compare.rb b/app/models/compare.rb
new file mode 100644
index 00000000000..4856510f526
--- /dev/null
+++ b/app/models/compare.rb
@@ -0,0 +1,66 @@
+class Compare
+ delegate :same, :head, :base, to: :@compare
+
+ attr_reader :project
+
+ def self.decorate(compare, project)
+ if compare.is_a?(Compare)
+ compare
+ else
+ self.new(compare, project)
+ end
+ end
+
+ def initialize(compare, project)
+ @compare = compare
+ @project = project
+ end
+
+ def commits
+ @commits ||= Commit.decorate(@compare.commits, project)
+ end
+
+ def start_commit
+ return @start_commit if defined?(@start_commit)
+
+ commit = @compare.base
+ @start_commit = commit ? ::Commit.new(commit, project) : nil
+ end
+
+ def head_commit
+ return @head_commit if defined?(@head_commit)
+
+ commit = @compare.head
+ @head_commit = commit ? ::Commit.new(commit, project) : nil
+ end
+ alias_method :commit, :head_commit
+
+ def base_commit
+ return @base_commit if defined?(@base_commit)
+
+ @base_commit = if start_commit && head_commit
+ project.merge_base_commit(start_commit.id, head_commit.id)
+ else
+ nil
+ end
+ end
+
+ def raw_diffs(*args)
+ @compare.diffs(*args)
+ end
+
+ def diffs(diff_options = nil)
+ Gitlab::Diff::FileCollection::Compare.new(self,
+ project: project,
+ diff_options: diff_options,
+ diff_refs: diff_refs)
+ end
+
+ def diff_refs
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: base_commit.try(:sha),
+ start_sha: start_commit.try(:sha),
+ head_sha: commit.try(:sha)
+ )
+ end
+end
diff --git a/app/models/concerns/faster_cache_keys.rb b/app/models/concerns/faster_cache_keys.rb
new file mode 100644
index 00000000000..5b14723fa2d
--- /dev/null
+++ b/app/models/concerns/faster_cache_keys.rb
@@ -0,0 +1,16 @@
+module FasterCacheKeys
+ # A faster version of Rails' "cache_key" method.
+ #
+ # Rails' default "cache_key" method uses all kind of complex logic to figure
+ # out the cache key. In many cases this complexity and overhead may not be
+ # needed.
+ #
+ # This method does not do any timestamp parsing as this process is quite
+ # expensive and not needed when generating cache keys. This method also relies
+ # on the table name instead of the cache namespace name as the latter uses
+ # complex logic to generate the exact same value (as when using the table
+ # name) in 99% of the cases.
+ def cache_key
+ "#{self.class.table_name}/#{id}-#{read_attribute_before_type_cast(:updated_at)}"
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index acb6f5a2998..cbae1cd439b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -17,7 +17,7 @@ module Issuable
belongs_to :assignee, class_name: "User"
belongs_to :updated_by, class_name: "User"
belongs_to :milestone
- has_many :notes, as: :noteable, dependent: :destroy do
+ has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do
def authors_loaded?
# We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
new file mode 100644
index 00000000000..ce54fe5d3bf
--- /dev/null
+++ b/app/models/concerns/spammable.rb
@@ -0,0 +1,68 @@
+module Spammable
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def attr_spammable(attr, options = {})
+ spammable_attrs << [attr.to_s, options]
+ end
+ end
+
+ included do
+ has_one :user_agent_detail, as: :subject, dependent: :destroy
+
+ attr_accessor :spam
+
+ after_validation :check_for_spam, on: :create
+
+ cattr_accessor :spammable_attrs, instance_accessor: false do
+ []
+ end
+
+ delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true
+ end
+
+ def submittable_as_spam?
+ if user_agent_detail
+ user_agent_detail.submittable?
+ else
+ false
+ end
+ end
+
+ def spam?
+ @spam
+ end
+
+ def check_for_spam
+ self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
+ end
+
+ def spam_title
+ attr = self.class.spammable_attrs.find do |_, options|
+ options.fetch(:spam_title, false)
+ end
+
+ public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
+ end
+
+ def spam_description
+ attr = self.class.spammable_attrs.find do |_, options|
+ options.fetch(:spam_description, false)
+ end
+
+ public_send(attr.first) if attr && respond_to?(attr.first.to_sym)
+ end
+
+ def spammable_text
+ result = self.class.spammable_attrs.map do |attr|
+ public_send(attr.first)
+ end
+
+ result.reject(&:blank?).join("\n")
+ end
+
+ # Override in Spammable if further checks are necessary
+ def check_for_spam?
+ true
+ end
+end
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
index 44c6b30f278..5d4b0a86899 100644
--- a/app/models/concerns/statuseable.rb
+++ b/app/models/concerns/statuseable.rb
@@ -1,18 +1,22 @@
module Statuseable
extend ActiveSupport::Concern
- AVAILABLE_STATUSES = %w(pending running success failed canceled skipped)
+ AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
+ STARTED_STATUSES = %w[running success failed skipped]
+ ACTIVE_STATUSES = %w[pending running]
+ COMPLETED_STATUSES = %w[success failed canceled]
class_methods do
def status_sql
- builds = all.select('count(*)').to_sql
- success = all.success.select('count(*)').to_sql
- ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored)
+ scope = all.relevant
+ builds = scope.select('count(*)').to_sql
+ success = scope.success.select('count(*)').to_sql
+ ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0'
- pending = all.pending.select('count(*)').to_sql
- running = all.running.select('count(*)').to_sql
- canceled = all.canceled.select('count(*)').to_sql
- skipped = all.skipped.select('count(*)').to_sql
+ pending = scope.pending.select('count(*)').to_sql
+ running = scope.running.select('count(*)').to_sql
+ canceled = scope.canceled.select('count(*)').to_sql
+ skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
@@ -48,7 +52,8 @@ module Statuseable
included do
validates :status, inclusion: { in: AVAILABLE_STATUSES }
- state_machine :status, initial: :pending do
+ state_machine :status, initial: :created do
+ state :created, value: 'created'
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
@@ -57,6 +62,8 @@ module Statuseable
state :skipped, value: 'skipped'
end
+ scope :created, -> { where(status: 'created') }
+ scope :relevant, -> { where.not(status: 'created') }
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
scope :success, -> { where(status: 'success') }
@@ -68,14 +75,14 @@ module Statuseable
end
def started?
- !pending? && !canceled? && started_at
+ STARTED_STATUSES.include?(status) && started_at
end
def active?
- running? || pending?
+ ACTIVE_STATUSES.include?(status)
end
def complete?
- canceled? || success? || failed?
+ COMPLETED_STATUSES.include?(status)
end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 885deaf78d2..24c7b26d223 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -1,12 +1,26 @@
module TokenAuthenticatable
extend ActiveSupport::Concern
+ private
+
+ def write_new_token(token_field)
+ new_token = generate_token(token_field)
+ write_attribute(token_field, new_token)
+ end
+
+ def generate_token(token_field)
+ loop do
+ token = Devise.friendly_token
+ break token unless self.class.unscoped.find_by(token_field => token)
+ end
+ end
+
class_methods do
def authentication_token_fields
@token_fields || []
end
- private
+ private # rubocop:disable Lint/UselessAccessModifier
def add_authentication_token_field(token_field)
@token_fields = [] unless @token_fields
@@ -32,18 +46,4 @@ module TokenAuthenticatable
end
end
end
-
- private
-
- def write_new_token(token_field)
- new_token = generate_token(token_field)
- write_attribute(token_field, new_token)
- end
-
- def generate_token(token_field)
- loop do
- token = Devise.friendly_token
- break token unless self.class.unscoped.find_by(token_field => token)
- end
- end
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 1a7cd60817e..1e338889714 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -36,4 +36,10 @@ class Deployment < ActiveRecord::Base
def manual_actions
deployable.try(:other_actions)
end
+
+ def includes_commit?(commit)
+ return false unless commit
+
+ project.repository.is_ancestor?(commit.id, sha)
+ end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 9671955db36..c816deb4e0c 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -67,7 +67,7 @@ class DiffNote < Note
return false unless supported?
return true if for_commit?
- diff_refs ||= self.noteable.diff_refs
+ diff_refs ||= noteable_diff_refs
self.position.diff_refs == diff_refs
end
@@ -78,6 +78,14 @@ class DiffNote < Note
!self.for_merge_request? || self.noteable.support_new_diff_notes?
end
+ def noteable_diff_refs
+ if noteable.respond_to?(:diff_sha_refs)
+ noteable.diff_sha_refs
+ else
+ noteable.diff_refs
+ end
+ end
+
def set_original_position
self.original_position = self.position.dup
end
@@ -96,7 +104,7 @@ class DiffNote < Note
self.project,
nil,
old_diff_refs: self.position.diff_refs,
- new_diff_refs: self.noteable.diff_refs,
+ new_diff_refs: noteable_diff_refs,
paths: self.position.paths
).execute(self)
end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index 74facfd1c9c..e2218a5f02b 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -49,6 +49,12 @@ class Discussion
self.noteable == target && !diff_discussion?
end
+ def active?
+ return @active if defined?(@active)
+
+ @active = first_note.active?
+ end
+
def expanded?
!diff_discussion? || active?
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index ac3a571a1f3..75e6f869786 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -3,6 +3,8 @@ class Environment < ActiveRecord::Base
has_many :deployments
+ before_validation :nullify_external_url
+
validates :name,
presence: true,
uniqueness: { scope: :project_id },
@@ -10,7 +12,23 @@ class Environment < ActiveRecord::Base
format: { with: Gitlab::Regex.environment_name_regex,
message: Gitlab::Regex.environment_name_regex_message }
+ validates :external_url,
+ uniqueness: { scope: :project_id },
+ length: { maximum: 255 },
+ allow_nil: true,
+ addressable_url: true
+
def last_deployment
deployments.last
end
+
+ def nullify_external_url
+ self.external_url = nil if self.external_url.blank?
+ end
+
+ def includes_commit?(commit)
+ return false unless last_deployment
+
+ last_deployment.includes_commit?(commit)
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 60af8c15340..788611305fe 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -6,6 +6,8 @@ class Issue < ActiveRecord::Base
include Referable
include Sortable
include Taskable
+ include Spammable
+ include FasterCacheKeys
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
@@ -34,6 +36,9 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
+ attr_spammable :title, spam_title: true
+ attr_spammable :description, spam_description: true
+
state_machine :state, initial: :opened do
event :close do
transition [:reopened, :opened] => :closed
@@ -229,7 +234,40 @@ class Issue < ActiveRecord::Base
self.closed_by_merge_requests(current_user).empty?
end
+ # Returns `true` if the current issue can be viewed by either a logged in User
+ # or an anonymous user.
+ def visible_to_user?(user = nil)
+ user ? readable_by?(user) : publicly_visible?
+ end
+
+ # Returns `true` if the given User can read the current Issue.
+ def readable_by?(user)
+ if user.admin?
+ true
+ elsif project.owner == user
+ true
+ elsif confidential?
+ author == user ||
+ assignee == user ||
+ project.team.member?(user, Gitlab::Access::REPORTER)
+ else
+ project.public? ||
+ project.internal? && !user.external? ||
+ project.team.member?(user)
+ end
+ end
+
+ # Returns `true` if this Issue is visible to everybody.
+ def publicly_visible?
+ project.public? && !confidential?
+ end
+
def overdue?
due_date.try(:past?) || false
end
+
+ # Only issues on public projects should be checked for spam
+ def check_for_spam?
+ project.public?
+ end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index b9bc38a0436..568a60b8af3 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -26,8 +26,9 @@ class Key < ActiveRecord::Base
end
def publishable_key
- # Removes anything beyond the keytype and key itself
- self.key.split[0..1].join(' ')
+ # Strip out the keys comment so we don't leak email addresses
+ # Replace with simple ident of user_name (hostname)
+ self.key.split[0..1].push("#{self.user_name} (#{Gitlab.config.gitlab.host})").join(' ')
end
# projects that has this key
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 47bd6eaf35f..51b5c2b1f4c 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,7 +1,9 @@
class LabelLink < ActiveRecord::Base
+ include Importable
+
belongs_to :target, polymorphic: true
belongs_to :label
- validates :target, presence: true
- validates :label, presence: true
+ validates :target, presence: true, unless: :importing?
+ validates :label, presence: true, unless: :importing?
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 04a651d50ab..6ed66001513 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -25,6 +25,14 @@ class LegacyDiffNote < Note
@discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end
+ def project_repository
+ if RequestStore.active?
+ RequestStore.fetch("project:#{project_id}:repository") { self.project.repository }
+ else
+ self.project.repository
+ end
+ end
+
def diff_file_hash
line_code.split('_')[0] if line_code
end
@@ -34,7 +42,7 @@ class LegacyDiffNote < Note
end
def diff_file
- @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff
+ @diff_file ||= Gitlab::Diff::File.new(diff, repository: project_repository) if diff
end
def diff_line
@@ -77,7 +85,7 @@ class LegacyDiffNote < Note
return nil unless noteable
return @diff if defined?(@diff)
- @diff = noteable.diffs(Commit.max_diff_options).find do |d|
+ @diff = noteable.raw_diffs(Commit.max_diff_options).find do |d|
d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash
end
end
@@ -108,7 +116,7 @@ class LegacyDiffNote < Note
# Find the diff on noteable that matches our own
def find_noteable_diff
- diffs = noteable.diffs(Commit.max_diff_options)
+ diffs = noteable.raw_diffs(Commit.max_diff_options)
diffs.find { |d| d.new_path == self.diff.new_path }
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 44db3d977fa..24ab1276ee9 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -53,6 +53,10 @@ class Member < ActiveRecord::Base
default_value_for :notification_level, NotificationSetting.levels[:global]
class << self
+ def access_for_user_ids(user_ids)
+ where(user_id: user_ids).has_access.pluck(:user_id, :access_level).to_h
+ end
+
def find_by_invite_token(invite_token)
invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
find_by(invite_token: invite_token)
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index f39afc61ce9..18e97c969d7 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -8,6 +8,7 @@ class ProjectMember < Member
# Make sure project member points only to project as it source
default_value_for :source_type, SOURCE_TYPE
validates_format_of :source_type, with: /\AProject\z/
+ validates :access_level, inclusion: { in: Gitlab::Access.values }
default_scope { where(source_type: SOURCE_TYPE) }
scope :in_project, ->(project) { where(source_id: project.id) }
@@ -21,19 +22,19 @@ class ProjectMember < Member
# or symbol like :master representing role
#
# Ex.
- # add_users_into_projects(
+ # add_users_to_projects(
# project_ids,
# user_ids,
# ProjectMember::MASTER
# )
#
- # add_users_into_projects(
+ # add_users_to_projects(
# project_ids,
# user_ids,
# :master
# )
#
- def add_users_into_projects(project_ids, user_ids, access, current_user = nil)
+ def add_users_to_projects(project_ids, user_ids, access, current_user = nil)
access_level = if roles_hash.has_key?(access)
roles_hash[access]
elsif roles_hash.values.include?(access.to_i)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index dc758a45bcf..d6a6a9a11ae 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -104,6 +104,7 @@ class MergeRequest < ActiveRecord::Base
scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
+ scope :from_source_branches, ->(branches) { where(source_branch: branches) }
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
@@ -164,8 +165,16 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
- def diffs(*args)
- merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args)
+ def raw_diffs(*args)
+ merge_request_diff ? merge_request_diff.raw_diffs(*args) : compare.raw_diffs(*args)
+ end
+
+ def diffs(diff_options = nil)
+ if self.compare
+ self.compare.diffs(diff_options)
+ else
+ Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options)
+ end
end
def diff_size
@@ -238,11 +247,11 @@ class MergeRequest < ActiveRecord::Base
end
def target_branch_sha
- target_branch_head.try(:sha)
+ @target_branch_sha || target_branch_head.try(:sha)
end
def source_branch_sha
- source_branch_head.try(:sha)
+ @source_branch_sha || source_branch_head.try(:sha)
end
def diff_refs
@@ -255,6 +264,19 @@ class MergeRequest < ActiveRecord::Base
)
end
+ # Return diff_refs instance trying to not touch the git repository
+ def diff_sha_refs
+ if merge_request_diff && merge_request_diff.diff_refs_by_sha?
+ return Gitlab::Diff::DiffRefs.new(
+ base_sha: merge_request_diff.base_commit_sha,
+ start_sha: merge_request_diff.start_commit_sha,
+ head_sha: merge_request_diff.head_commit_sha
+ )
+ else
+ diff_refs
+ end
+ end
+
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -300,6 +322,8 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff.reload_content
+ MergeRequests::MergeRequestDiffCacheService.new.execute(self)
+
new_diff_refs = self.diff_refs
update_diff_notes_positions(
@@ -567,6 +591,14 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success?
end
+ def environments
+ return unless diff_head_commit
+
+ target_project.environments.select do |environment|
+ environment.includes_commit?(diff_head_commit)
+ end
+ end
+
def state_human_name
if merged?
"Merged"
@@ -670,7 +702,7 @@ class MergeRequest < ActiveRecord::Base
end
def support_new_diff_notes?
- diff_refs && diff_refs.complete?
+ diff_sha_refs && diff_sha_refs.complete?
end
def update_diff_notes_positions(old_diff_refs:, new_diff_refs:)
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 3f520c8f3ff..32cc6a3bfea 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -33,12 +33,12 @@ class MergeRequestDiff < ActiveRecord::Base
end
def size
- real_size.presence || diffs.size
+ real_size.presence || raw_diffs.size
end
- def diffs(options={})
+ def raw_diffs(options = {})
if options[:ignore_whitespace_change]
- @diffs_no_whitespace ||= begin
+ @raw_diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new(
repository.raw_repository,
self.start_commit_sha || self.target_branch_sha,
@@ -47,8 +47,8 @@ class MergeRequestDiff < ActiveRecord::Base
compare.diffs(options)
end
else
- @diffs ||= {}
- @diffs[options] ||= load_diffs(st_diffs, options)
+ @raw_diffs ||= {}
+ @raw_diffs[options] ||= load_diffs(st_diffs, options)
end
end
@@ -82,6 +82,10 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(self.head_commit_sha)
end
+ def diff_refs_by_sha?
+ base_commit_sha? && head_commit_sha? && start_commit_sha?
+ end
+
def compare
@compare ||=
begin
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 8b52cc824cd..7c29d27ce97 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -1,4 +1,6 @@
class Namespace < ActiveRecord::Base
+ acts_as_paranoid
+
include Sortable
include Gitlab::ShellAdapter
diff --git a/app/models/note.rb b/app/models/note.rb
index b6b2ac6aa42..ddcd7f9d034 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -5,6 +5,7 @@ class Note < ActiveRecord::Base
include Mentionable
include Awardable
include Importable
+ include FasterCacheKeys
# Attribute containing rendered and redacted Markdown as generated by
# Banzai::ObjectRenderer.
diff --git a/app/models/project.rb b/app/models/project.rb
index 023b1dc3725..e0b28160937 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -379,9 +379,10 @@ class Project < ActiveRecord::Base
joins(join_body).reorder('join_note_counts.amount DESC')
end
- # Deletes gitlab project export files older than 24 hours
- def remove_gitlab_exports!
- Gitlab::Popen.popen(%W(find #{Gitlab::ImportExport.storage_path} -not -path #{Gitlab::ImportExport.storage_path} -mmin +1440 -delete))
+ def cached_count
+ Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
+ Project.count
+ end
end
end
@@ -451,7 +452,9 @@ class Project < ActiveRecord::Base
def add_import_job
if forked?
- job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
+ job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace,
+ self.namespace.path)
else
job_id = RepositoryImportWorker.perform_async(self.id)
end
@@ -584,7 +587,11 @@ class Project < ActiveRecord::Base
end
def to_param
- path
+ if persisted? && errors.include?(:path)
+ path_was
+ else
+ path
+ end
end
def to_reference(_from_project = nil)
@@ -599,6 +606,13 @@ class Project < ActiveRecord::Base
web_url.split('://')[1]
end
+ def new_issue_address(author)
+ if Gitlab::IncomingEmail.enabled? && author
+ Gitlab::IncomingEmail.reply_address(
+ "#{path_with_namespace}+#{author.authentication_token}")
+ end
+ end
+
def build_commit_note(commit)
notes.new(commit_id: commit.id, noteable_type: 'Commit')
end
@@ -857,16 +871,14 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
+ return true if empty_repo? && default_branch_protected?
+
@protected_branches ||= self.protected_branches.to_a
ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
end
- def developers_can_push_to_protected_branch?(branch_name)
- protected_branches.matching(branch_name).any?(&:developers_can_push)
- end
-
- def developers_can_merge_to_protected_branch?(branch_name)
- protected_branches.matching(branch_name).any?(&:developers_can_merge)
+ def user_can_push_to_empty_repo?(user)
+ !default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
end
def forked?
@@ -987,6 +999,10 @@ class Project < ActiveRecord::Base
project_members.find_by(user_id: user)
end
+ def add_user(user, access_level, current_user = nil)
+ team.add_user(user, access_level, current_user)
+ end
+
def default_branch
@default_branch ||= repository.root_ref if repository.exists?
end
@@ -1149,13 +1165,6 @@ class Project < ActiveRecord::Base
@wiki ||= ProjectWiki.new(self, self.owner)
end
- def schedule_delete!(user_id, params)
- # Queue this task for after the commit, so once we mark pending_delete it will run
- run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }
-
- update_attribute(:pending_delete, true)
- end
-
def running_or_pending_build_count(force: false)
Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
builds.running_or_pending.count(:all)
@@ -1245,8 +1254,23 @@ class Project < ActiveRecord::Base
authorized_for_user_by_shared_projects?(user, min_access_level)
end
+ def append_or_update_attribute(name, value)
+ old_values = public_send(name.to_s)
+
+ if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
+ update_attribute(name, old_values + value)
+ else
+ update_attribute(name, value)
+ end
+ end
+
private
+ def default_branch_protected?
+ current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
+ current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
+ end
+
def authorized_for_user_by_group?(user, min_access_level)
member = user.group_members.find_by(source_id: group)
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 511b2eac792..5af93860d09 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -1,4 +1,6 @@
class CampfireService < Service
+ include HTTParty
+
prop_accessor :token, :subdomain, :room
validates :token, presence: true, if: :activated?
@@ -29,18 +31,53 @@ class CampfireService < Service
def execute(data)
return unless supported_events.include?(data[:object_kind])
- room = gate.find_room_by_name(self.room)
- return true unless room
-
+ self.class.base_uri base_uri
message = build_message(data)
-
- room.speak(message)
+ speak(self.room, message, auth)
end
private
- def gate
- @gate ||= Tinder::Campfire.new(subdomain, token: token)
+ def base_uri
+ @base_uri ||= "https://#{subdomain}.campfirenow.com"
+ end
+
+ def auth
+ # use a dummy password, as explained in the Campfire API doc:
+ # https://github.com/basecamp/campfire-api#authentication
+ @auth ||= {
+ basic_auth: {
+ username: token,
+ password: 'X'
+ }
+ }
+ end
+
+ # Post a message into a room, returns the message Hash in case of success.
+ # Returns nil otherwise.
+ # https://github.com/basecamp/campfire-api/blob/master/sections/messages.md#create-message
+ def speak(room_name, message, auth)
+ room = rooms(auth).find { |r| r["name"] == room_name }
+ return nil unless room
+
+ path = "/room/#{room["id"]}/speak.json"
+ body = {
+ body: {
+ message: {
+ type: 'TextMessage',
+ body: message
+ }
+ }
+ }
+ res = self.class.post(path, auth.merge(body))
+ res.code == 201 ? res : nil
+ end
+
+ # Returns a list of rooms, or [].
+ # https://github.com/basecamp/campfire-api/blob/master/sections/rooms.md#get-rooms
+ def rooms(auth)
+ res = self.class.get("/rooms.json", auth)
+ res.code == 200 ? res["rooms"] : []
end
def build_message(push)
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 23e5b16221b..d7c986c1a91 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -46,7 +46,7 @@ class HipchatService < Service
return unless supported_events.include?(data[:object_kind])
message = create_message(data)
return unless message.present?
- gate[room].send('GitLab', message, message_options)
+ gate[room].send('GitLab', message, message_options(data))
end
def test(data)
@@ -67,8 +67,8 @@ class HipchatService < Service
@gate ||= HipChat::Client.new(token, options)
end
- def message_options
- { notify: notify.present? && notify == '1', color: color || 'yellow' }
+ def message_options(data = nil)
+ { notify: notify.present? && notify == '1', color: message_color(data) }
end
def create_message(data)
@@ -240,6 +240,21 @@ class HipchatService < Service
"#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
end
+ def message_color(data)
+ build_status_color(data) || color || 'yellow'
+ end
+
+ def build_status_color(data)
+ return unless data && data[:object_kind] == 'build'
+
+ case data[:commit][:status]
+ when 'success'
+ 'green'
+ else
+ 'red'
+ end
+ end
+
def project_name
project.name_with_namespace.gsub(/\s/, '')
end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index ad19b7795da..5301f9fa0ff 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,7 +1,9 @@
class PivotaltrackerService < Service
include HTTParty
- prop_accessor :token
+ API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
+
+ prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated?
def title
@@ -18,7 +20,17 @@ class PivotaltrackerService < Service
def fields
[
- { type: 'text', name: 'token', placeholder: '' }
+ {
+ type: 'text',
+ name: 'token',
+ placeholder: 'Pivotal Tracker API token.'
+ },
+ {
+ type: 'text',
+ name: 'restrict_to_branch',
+ placeholder: 'Comma-separated list of branches which will be ' \
+ 'automatically inspected. Leave blank to include all branches.'
+ }
]
end
@@ -28,8 +40,8 @@ class PivotaltrackerService < Service
def execute(data)
return unless supported_events.include?(data[:object_kind])
+ return unless allowed_branch?(data[:ref])
- url = 'https://www.pivotaltracker.com/services/v5/source_commits'
data[:commits].each do |commit|
message = {
'source_commit' => {
@@ -40,7 +52,7 @@ class PivotaltrackerService < Service
}
}
PivotaltrackerService.post(
- url,
+ API_ENDPOINT,
body: message.to_json,
headers: {
'Content-Type' => 'application/json',
@@ -49,4 +61,15 @@ class PivotaltrackerService < Service
)
end
end
+
+ private
+
+ def allowed_branch?(ref)
+ return true unless ref.present? && restrict_to_branch.present?
+
+ branch = Gitlab::Git.ref_name(ref)
+ allowed_branches = restrict_to_branch.split(',').map(&:strip)
+
+ branch.present? && allowed_branches.include?(branch)
+ end
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 9d312a53790..d0a714cd6fc 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -34,7 +34,7 @@ class ProjectTeam
end
def add_users(users, access, current_user = nil)
- ProjectMember.add_users_into_projects(
+ ProjectMember.add_users_to_projects(
[project.id],
users,
access,
@@ -132,39 +132,68 @@ class ProjectTeam
Gitlab::Access.options_with_owner.key(max_member_access(user_id))
end
- # This method assumes project and group members are eager loaded for optimal
- # performance.
- def max_member_access(user_id)
- access = []
+ # Determine the maximum access level for a group of users in bulk.
+ #
+ # Returns a Hash mapping user ID -> maximum access level.
+ def max_member_access_for_user_ids(user_ids)
+ user_ids = user_ids.uniq
+ key = "max_member_access:#{project.id}"
- access += project.members.where(user_id: user_id).has_access.pluck(:access_level)
+ access = {}
- if group
- access += group.members.where(user_id: user_id).has_access.pluck(:access_level)
+ if RequestStore.active?
+ RequestStore.store[key] ||= {}
+ access = RequestStore.store[key]
end
- if project.invited_groups.any? && project.allowed_to_share_with_group?
- access << max_invited_level(user_id)
+ # Lookup only the IDs we need
+ user_ids = user_ids - access.keys
+
+ if user_ids.present?
+ user_ids.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
+
+ member_access = project.members.access_for_user_ids(user_ids)
+ merge_max!(access, member_access)
+
+ if group
+ group_access = group.members.access_for_user_ids(user_ids)
+ merge_max!(access, group_access)
+ end
+
+ # Each group produces a list of maximum access level per user. We take the
+ # max of the values produced by each group.
+ if project.invited_groups.any? && project.allowed_to_share_with_group?
+ project.project_group_links.each do |group_link|
+ invited_access = max_invited_level_for_users(group_link, user_ids)
+ merge_max!(access, invited_access)
+ end
+ end
end
- access.compact.max
+ access
+ end
+
+ def max_member_access(user_id)
+ max_member_access_for_user_ids([user_id])[user_id]
end
private
- def max_invited_level(user_id)
- project.project_group_links.map do |group_link|
- invited_group = group_link.group
- access = invited_group.group_members.find_by(user_id: user_id).try(:access_field)
+ # For a given group, return the maximum access level for the user. This is the min of
+ # the invited access level of the group and the access level of the user within the group.
+ # For example, if the group has been given DEVELOPER access but the member has MASTER access,
+ # the user should receive only DEVELOPER access.
+ def max_invited_level_for_users(group_link, user_ids)
+ invited_group = group_link.group
+ capped_access_level = group_link.group_access
+ access = invited_group.group_members.access_for_user_ids(user_ids)
- # If group member has higher access level we should restrict it
- # to max allowed access level
- if access && access > group_link.group_access
- access = group_link.group_access
- end
+ # If the user is not in the list, assume he/she does not have access
+ missing_users = user_ids - access.keys
+ missing_users.each { |id| access[id] = Gitlab::Access::NO_ACCESS }
- access
- end.compact.max
+ # Cap the maximum access by the invited level access
+ access.each { |key, value| access[key] = [value, capped_access_level].min }
end
def fetch_members(level = nil)
@@ -215,4 +244,8 @@ class ProjectTeam
def group
project.group
end
+
+ def merge_max!(first_hash, second_hash)
+ first_hash.merge!(second_hash) { |_key, old, new| old > new ? old : new }
+ end
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index a255710f577..46f70da2452 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -56,6 +56,10 @@ class ProjectWiki
end
end
+ def repository_exists?
+ !!repository.exists?
+ end
+
def empty?
pages.empty?
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index b7011d7afdf..226b3f54342 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -5,6 +5,12 @@ class ProtectedBranch < ActiveRecord::Base
validates :name, presence: true
validates :project, presence: true
+ has_one :merge_access_level, dependent: :destroy
+ has_one :push_access_level, dependent: :destroy
+
+ accepts_nested_attributes_for :push_access_level
+ accepts_nested_attributes_for :merge_access_level
+
def commit
project.commit(self.name)
end
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
new file mode 100644
index 00000000000..b1112ee737d
--- /dev/null
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -0,0 +1,24 @@
+class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
+ belongs_to :protected_branch
+ delegate :project, to: :protected_branch
+
+ validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
+ Gitlab::Access::DEVELOPER] }
+
+ def self.human_access_levels
+ {
+ Gitlab::Access::MASTER => "Masters",
+ Gitlab::Access::DEVELOPER => "Developers + Masters"
+ }.with_indifferent_access
+ end
+
+ def check_access(user)
+ return true if user.is_admin?
+
+ project.team.max_member_access(user.id) >= access_level
+ end
+
+ def humanize
+ self.class.human_access_levels[self.access_level]
+ end
+end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
new file mode 100644
index 00000000000..6a5e49cf453
--- /dev/null
+++ b/app/models/protected_branch/push_access_level.rb
@@ -0,0 +1,27 @@
+class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
+ belongs_to :protected_branch
+ delegate :project, to: :protected_branch
+
+ validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER,
+ Gitlab::Access::DEVELOPER,
+ Gitlab::Access::NO_ACCESS] }
+
+ def self.human_access_levels
+ {
+ Gitlab::Access::MASTER => "Masters",
+ Gitlab::Access::DEVELOPER => "Developers + Masters",
+ Gitlab::Access::NO_ACCESS => "No one"
+ }.with_indifferent_access
+ end
+
+ def check_access(user)
+ return false if access_level == Gitlab::Access::NO_ACCESS
+ return true if user.is_admin?
+
+ project.team.max_member_access(user.id) >= access_level
+ end
+
+ def humanize
+ self.class.human_access_levels[self.access_level]
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e9d5f4c91f8..e56bac509a4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -70,7 +70,12 @@ class Repository
def commit(ref = 'HEAD')
return nil unless exists?
- commit = Gitlab::Git::Commit.find(raw_repository, ref)
+ commit =
+ if ref.is_a?(Gitlab::Git::Commit)
+ ref
+ else
+ Gitlab::Git::Commit.find(raw_repository, ref)
+ end
commit = ::Commit.new(commit, @project) if commit
commit
rescue Rugged::OdbError
@@ -158,7 +163,7 @@ class Repository
before_remove_branch
branch = find_branch(branch_name)
- oldrev = branch.try(:target)
+ oldrev = branch.try(:target).try(:id)
newrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
@@ -211,6 +216,9 @@ class Repository
rugged.references.create(keep_around_ref_name(sha), sha, force: true)
rescue Rugged::ReferenceError => ex
Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
+ rescue Rugged::OSError => ex
+ raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
+ Rails.logger.error "Unable to create keep-around reference for repository #{path}: #{ex}"
end
end
@@ -256,10 +264,10 @@ class Repository
# Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes
number_commits_behind = raw_repository.
- count_commits_between(branch.target, root_ref_hash)
+ count_commits_between(branch.target.sha, root_ref_hash)
number_commits_ahead = raw_repository.
- count_commits_between(root_ref_hash, branch.target)
+ count_commits_between(root_ref_hash, branch.target.sha)
{ behind: number_commits_behind, ahead: number_commits_ahead }
end
@@ -364,7 +372,7 @@ class Repository
# We don't want to flush the cache if the commit didn't actually make any
# changes to any of the possible avatar files.
if revision && commit = self.commit(revision)
- return unless commit.diffs.
+ return unless commit.raw_diffs(deltas_only: true).
any? { |diff| AVATAR_FILES.include?(diff.new_path) }
end
@@ -593,7 +601,7 @@ class Repository
commit(sha)
end
- def next_branch(name, opts={})
+ def next_branch(name, opts = {})
branch_ids = self.branch_names.map do |n|
next 1 if n == name
result = n.match(/\A#{name}-([0-9]+)\z/)
@@ -612,11 +620,11 @@ class Repository
case value
when 'name'
branches.sort_by(&:name)
- when 'recently_updated'
+ when 'updated_desc'
branches.sort do |a, b|
commit(b.target).committed_date <=> commit(a.target).committed_date
end
- when 'last_updated'
+ when 'updated_asc'
branches.sort do |a, b|
commit(a.target).committed_date <=> commit(b.target).committed_date
end
@@ -628,9 +636,7 @@ class Repository
def tags_sorted_by(value)
case value
when 'name'
- # Would be better to use `sort_by` but `version_sorter` only exposes
- # `sort` and `rsort`
- VersionSorter.rsort(tag_names).map { |tag_name| find_tag(tag_name) }
+ VersionSorter.rsort(tags) { |tag| tag.name }
when 'updated_desc'
tags_sorted_by_committed_date.reverse
when 'updated_asc'
@@ -685,9 +691,7 @@ class Repository
end
def local_branches
- @local_branches ||= rugged.branches.each(:local).map do |branch|
- Gitlab::Git::Branch.new(branch.name, branch.target)
- end
+ @local_branches ||= raw_repository.local_branches
end
alias_method :branches, :local_branches
@@ -828,7 +832,7 @@ class Repository
end
def revert(user, commit, base_branch, revert_tree_id = nil)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
revert_tree_id ||= check_revert_content(commit, base_branch)
return false unless revert_tree_id
@@ -845,7 +849,7 @@ class Repository
end
def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
return false unless cherry_pick_tree_id
@@ -866,7 +870,7 @@ class Repository
end
def check_revert_content(commit, base_branch)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
args = [commit.id, source_sha]
args << { mainline: 1 } if commit.merge_commit?
@@ -880,7 +884,7 @@ class Repository
end
def check_cherry_pick_content(commit, base_branch)
- source_sha = find_branch(base_branch).target
+ source_sha = find_branch(base_branch).target.sha
args = [commit.id, source_sha]
args << 1 if commit.merge_commit?
@@ -971,7 +975,7 @@ class Repository
was_empty = empty?
if !was_empty && target_branch
- oldrev = target_branch.target
+ oldrev = target_branch.target.id
end
# Make commit
@@ -985,9 +989,13 @@ class Repository
if was_empty || !target_branch
# Create branch
rugged.references.create(ref, newrev)
+
+ # If repo was empty expire cache
+ after_create if was_empty
+ after_create_branch
else
# Update head
- current_head = find_branch(branch).target
+ current_head = find_branch(branch).target.id
# Make sure target branch was not changed during pre-receive hook
if current_head == oldrev
@@ -1045,7 +1053,7 @@ class Repository
end
def tags_sorted_by_committed_date
- tags.sort_by { |tag| commit(tag.target).committed_date }
+ tags.sort_by { |tag| tag.target.committed_date }
end
def keep_around_ref_name(sha)
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index 12df68ef83b..3b8b9833565 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -7,4 +7,8 @@ class SpamLog < ActiveRecord::Base
user.block
user.destroy
end
+
+ def text
+ [title, description].join("\n")
+ end
end
diff --git a/app/models/spam_report.rb b/app/models/spam_report.rb
deleted file mode 100644
index cdc7321b08e..00000000000
--- a/app/models/spam_report.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class SpamReport < ActiveRecord::Base
- belongs_to :user
-
- validates :user, presence: true
-end
diff --git a/app/models/user.rb b/app/models/user.rb
index db747434959..87a2d999843 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -23,13 +23,13 @@ class User < ActiveRecord::Base
default_value_for :theme_id, gitlab_config.default_theme
attr_encrypted :otp_secret,
- key: Gitlab::Application.config.secret_key_base,
+ key: Gitlab::Application.secrets.otp_key_base,
mode: :per_attribute_iv_and_salt,
insecure_mode: true,
algorithm: 'aes-256-cbc'
devise :two_factor_authenticatable,
- otp_secret_encryption_key: Gitlab::Application.config.secret_key_base
+ otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
@@ -809,13 +809,13 @@ class User < ActiveRecord::Base
def todos_done_count(force: false)
Rails.cache.fetch(['users', id, 'todos_done_count'], force: force) do
- todos.done.count
+ TodosFinder.new(self, state: :done).execute.count
end
end
def todos_pending_count(force: false)
Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force) do
- todos.pending.count
+ TodosFinder.new(self, state: :pending).execute.count
end
end
diff --git a/app/models/user_agent_detail.rb b/app/models/user_agent_detail.rb
new file mode 100644
index 00000000000..0949c6ef083
--- /dev/null
+++ b/app/models/user_agent_detail.rb
@@ -0,0 +1,9 @@
+class UserAgentDetail < ActiveRecord::Base
+ belongs_to :subject, polymorphic: true
+
+ validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
+
+ def submittable?
+ !submitted?
+ end
+end
diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb
new file mode 100644
index 00000000000..5c60addbe7c
--- /dev/null
+++ b/app/services/akismet_service.rb
@@ -0,0 +1,79 @@
+class AkismetService
+ attr_accessor :owner, :text, :options
+
+ def initialize(owner, text, options = {})
+ @owner = owner
+ @text = text
+ @options = options
+ end
+
+ def is_spam?
+ return false unless akismet_enabled?
+
+ params = {
+ type: 'comment',
+ text: text,
+ created_at: DateTime.now,
+ author: owner.name,
+ author_email: owner.email,
+ referrer: options[:referrer],
+ }
+
+ begin
+ is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
+ is_spam || is_blatant
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ false
+ end
+ end
+
+ def submit_ham
+ return false unless akismet_enabled?
+
+ params = {
+ type: 'comment',
+ text: text,
+ author: owner.name,
+ author_email: owner.email
+ }
+
+ begin
+ akismet_client.submit_ham(options[:ip_address], options[:user_agent], params)
+ true
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
+ false
+ end
+ end
+
+ def submit_spam
+ return false unless akismet_enabled?
+
+ params = {
+ type: 'comment',
+ text: text,
+ author: owner.name,
+ author_email: owner.email
+ }
+
+ begin
+ akismet_client.submit_spam(options[:ip_address], options[:user_agent], params)
+ true
+ rescue => e
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
+ false
+ end
+ end
+
+ private
+
+ def akismet_client
+ @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
+ Gitlab.config.gitlab.url)
+ end
+
+ def akismet_enabled?
+ current_application_settings.akismet_enabled
+ end
+end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index e294a962352..6072123b851 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -24,10 +24,14 @@ module Auth
token[:access] = names.map do |name|
{ type: 'repository', name: name, actions: %w(*) }
end
-
+
token.encoded
end
+ def self.token_expire_at
+ Time.now + current_application_settings.container_registry_token_expire_delay.minutes
+ end
+
private
def authorized_token(*accesses)
@@ -35,7 +39,7 @@ module Auth
token.issuer = registry.issuer
token.audience = params[:service]
token.subject = current_user.try(:username)
- token.expire_time = ContainerRegistryAuthenticationService.token_expire_at
+ token.expire_time = self.class.token_expire_at
token[:access] = accesses.compact
token
end
@@ -81,9 +85,5 @@ module Auth
def registry
Gitlab.config.registry
end
-
- def self.token_expire_at
- Time.now + current_application_settings.container_registry_token_expire_delay.minutes
- end
end
end
diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
deleted file mode 100644
index 4946f7076fd..00000000000
--- a/app/services/ci/create_builds_service.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-module Ci
- class CreateBuildsService
- def initialize(pipeline)
- @pipeline = pipeline
- @config = pipeline.config_processor
- end
-
- def execute(stage, user, status, trigger_request = nil)
- builds_attrs = @config.builds_for_stage_and_ref(stage, @pipeline.ref, @pipeline.tag, trigger_request)
-
- # check when to create next build
- builds_attrs = builds_attrs.select do |build_attrs|
- case build_attrs[:when]
- when 'on_success'
- status == 'success'
- when 'on_failure'
- status == 'failed'
- when 'always', 'manual'
- %w(success failed).include?(status)
- end
- end
-
- # don't create the same build twice
- builds_attrs.reject! do |build_attrs|
- @pipeline.builds.find_by(ref: @pipeline.ref,
- tag: @pipeline.tag,
- trigger_request: trigger_request,
- name: build_attrs[:name])
- end
-
- builds_attrs.map do |build_attrs|
- build_attrs.slice!(:name,
- :commands,
- :tag_list,
- :options,
- :allow_failure,
- :stage,
- :stage_idx,
- :environment,
- :when,
- :yaml_variables)
-
- build_attrs.merge!(pipeline: @pipeline,
- ref: @pipeline.ref,
- tag: @pipeline.tag,
- trigger_request: trigger_request,
- user: user,
- project: @pipeline.project)
-
- # TODO: The proper implementation for this is in
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
- build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
-
- ##
- # We do not persist new builds here.
- # Those will be persisted when @pipeline is saved.
- #
- @pipeline.builds.new(build_attrs)
- end
- end
- end
-end
diff --git a/app/services/ci/create_pipeline_builds_service.rb b/app/services/ci/create_pipeline_builds_service.rb
new file mode 100644
index 00000000000..005014fa1de
--- /dev/null
+++ b/app/services/ci/create_pipeline_builds_service.rb
@@ -0,0 +1,42 @@
+module Ci
+ class CreatePipelineBuildsService < BaseService
+ attr_reader :pipeline
+
+ def execute(pipeline)
+ @pipeline = pipeline
+
+ new_builds.map do |build_attributes|
+ create_build(build_attributes)
+ end
+ end
+
+ private
+
+ def create_build(build_attributes)
+ build_attributes = build_attributes.merge(
+ pipeline: pipeline,
+ project: pipeline.project,
+ ref: pipeline.ref,
+ tag: pipeline.tag,
+ user: current_user,
+ trigger_request: trigger_request
+ )
+ pipeline.builds.create(build_attributes)
+ end
+
+ def new_builds
+ @new_builds ||= pipeline.config_builds_attributes.
+ reject { |build| existing_build_names.include?(build[:name]) }
+ end
+
+ def existing_build_names
+ @existing_build_names ||= pipeline.builds.pluck(:name)
+ end
+
+ def trigger_request
+ return @trigger_request if defined?(@trigger_request)
+
+ @trigger_request ||= pipeline.trigger_requests.first
+ end
+ end
+end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index be91bf0db85..cde856b0186 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -1,49 +1,101 @@
module Ci
class CreatePipelineService < BaseService
- def execute
- pipeline = project.pipelines.new(params)
- pipeline.user = current_user
+ attr_reader :pipeline
- unless ref_names.include?(params[:ref])
- pipeline.errors.add(:base, 'Reference not found')
- return pipeline
+ def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil)
+ @pipeline = Ci::Pipeline.new(
+ project: project,
+ ref: ref,
+ sha: sha,
+ before_sha: before_sha,
+ tag: tag?,
+ trigger_requests: Array(trigger_request),
+ user: current_user
+ )
+
+ unless project.builds_enabled?
+ return error('Pipeline is disabled')
end
- if commit
- pipeline.sha = commit.id
- else
- pipeline.errors.add(:base, 'Commit not found')
- return pipeline
+ unless trigger_request || can?(current_user, :create_pipeline, project)
+ return error('Insufficient permissions to create a new pipeline')
end
- unless can?(current_user, :create_pipeline, project)
- pipeline.errors.add(:base, 'Insufficient permissions to create a new pipeline')
- return pipeline
+ unless branch? || tag?
+ return error('Reference not found')
+ end
+
+ unless commit
+ return error('Commit not found')
end
unless pipeline.config_processor
- pipeline.errors.add(:base, pipeline.yaml_errors || 'Missing .gitlab-ci.yml file')
- return pipeline
+ unless pipeline.ci_yaml_file
+ return error('Missing .gitlab-ci.yml file')
+ end
+ return error(pipeline.yaml_errors, save: save_on_errors)
end
- pipeline.save!
+ if !ignore_skip_ci && skip_ci?
+ pipeline.skip if save_on_errors
+ return pipeline
+ end
- unless pipeline.create_builds(current_user)
- pipeline.errors.add(:base, 'No builds for this pipeline.')
+ unless pipeline.config_builds_attributes.present?
+ return error('No builds for this pipeline.')
end
pipeline.save
+ pipeline.process!
pipeline
end
private
- def ref_names
- @ref_names ||= project.repository.ref_names
+ def skip_ci?
+ pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message
end
def commit
- @commit ||= project.commit(params[:ref])
+ @commit ||= project.commit(origin_sha || origin_ref)
+ end
+
+ def sha
+ commit.try(:id)
+ end
+
+ def before_sha
+ params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
+ end
+
+ def origin_sha
+ params[:checkout_sha] || params[:after]
+ end
+
+ def origin_ref
+ params[:ref]
+ end
+
+ def branch?
+ project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
+ end
+
+ def tag?
+ project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
+ end
+
+ def ref
+ Gitlab::Git.ref_name(origin_ref)
+ end
+
+ def valid_sha?
+ origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
+ end
+
+ def error(message, save: false)
+ pipeline.errors.add(:base, message)
+ pipeline.drop if save
+ pipeline
end
end
end
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 1e629cf119a..6af3c1ca5b1 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -1,20 +1,11 @@
module Ci
class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil)
- commit = project.commit(ref)
- return unless commit
+ trigger_request = trigger.trigger_requests.create(variables: variables)
- # check if ref is tag
- tag = project.repository.find_tag(ref).present?
-
- pipeline = project.pipelines.create(sha: commit.sha, ref: ref, tag: tag)
-
- trigger_request = trigger.trigger_requests.create!(
- variables: variables,
- pipeline: pipeline,
- )
-
- if pipeline.create_builds(nil, trigger_request)
+ pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref).
+ execute(ignore_skip_ci: true, trigger_request: trigger_request)
+ if pipeline.persisted?
trigger_request
end
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
new file mode 100644
index 00000000000..6f7610d42ba
--- /dev/null
+++ b/app/services/ci/process_pipeline_service.rb
@@ -0,0 +1,77 @@
+module Ci
+ class ProcessPipelineService < BaseService
+ attr_reader :pipeline
+
+ def execute(pipeline)
+ @pipeline = pipeline
+
+ # This method will ensure that our pipeline does have all builds for all stages created
+ if created_builds.empty?
+ create_builds!
+ end
+
+ new_builds =
+ stage_indexes_of_created_builds.map do |index|
+ process_stage(index)
+ end
+
+ # Return a flag if a when builds got enqueued
+ new_builds.flatten.any?
+ end
+
+ private
+
+ def create_builds!
+ Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline)
+ end
+
+ def process_stage(index)
+ current_status = status_for_prior_stages(index)
+
+ created_builds_in_stage(index).select do |build|
+ process_build(build, current_status)
+ end
+ end
+
+ def process_build(build, current_status)
+ return false unless Statuseable::COMPLETED_STATUSES.include?(current_status)
+
+ if valid_statuses_for_when(build.when).include?(current_status)
+ build.enqueue
+ true
+ else
+ build.skip
+ false
+ end
+ end
+
+ def valid_statuses_for_when(value)
+ case value
+ when 'on_success'
+ %w[success]
+ when 'on_failure'
+ %w[failed]
+ when 'always'
+ %w[success failed]
+ else
+ []
+ end
+ end
+
+ def status_for_prior_stages(index)
+ pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
+ end
+
+ def stage_indexes_of_created_builds
+ created_builds.order(:stage_idx).pluck('distinct stage_idx')
+ end
+
+ def created_builds_in_stage(index)
+ created_builds.where(stage_idx: index)
+ end
+
+ def created_builds
+ pipeline.builds.created
+ end
+ end
+end
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 149822aa647..6d6075628af 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -20,10 +20,12 @@ class CompareService
)
end
- Gitlab::Git::Compare.new(
+ raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
- source_sha,
+ source_sha
)
+
+ Compare.new(raw_compare, target_project)
end
end
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
deleted file mode 100644
index 0b66b854dea..00000000000
--- a/app/services/create_commit_builds_service.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-class CreateCommitBuildsService
- def execute(project, user, params)
- return unless project.builds_enabled?
-
- before_sha = params[:checkout_sha] || params[:before]
- sha = params[:checkout_sha] || params[:after]
- origin_ref = params[:ref]
-
- ref = Gitlab::Git.ref_name(origin_ref)
- tag = Gitlab::Git.tag_ref?(origin_ref)
-
- # Skip branch removal
- if sha == Gitlab::Git::BLANK_SHA
- return false
- end
-
- @pipeline = Ci::Pipeline.new(
- project: project,
- sha: sha,
- ref: ref,
- before_sha: before_sha,
- tag: tag,
- user: user)
-
- ##
- # Skip creating pipeline if no gitlab-ci.yml is found
- #
- unless @pipeline.ci_yaml_file
- return false
- end
-
- ##
- # Skip creating builds for commits that have [ci skip]
- # but save pipeline object
- #
- if @pipeline.skip_ci?
- return save_pipeline!
- end
-
- ##
- # Skip creating builds when CI config is invalid
- # but save pipeline object
- #
- unless @pipeline.config_processor
- return save_pipeline!
- end
-
- ##
- # Skip creating pipeline object if there are no builds for it.
- #
- unless @pipeline.create_builds(user)
- @pipeline.errors.add(:base, 'No builds created')
- return false
- end
-
- save_pipeline!
- end
-
- private
-
- ##
- # Create a new pipeline and touch object to calculate status
- #
- def save_pipeline!
- @pipeline.save!
- @pipeline.touch
- @pipeline
- end
-end
diff --git a/app/services/create_spam_log_service.rb b/app/services/create_spam_log_service.rb
deleted file mode 100644
index 59a66fde47a..00000000000
--- a/app/services/create_spam_log_service.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class CreateSpamLogService < BaseService
- def initialize(project, user, params)
- super(project, user, params)
- end
-
- def execute
- spam_params = params.merge({ user_id: @current_user.id,
- project_id: @project.id } )
- spam_log = SpamLog.new(spam_params)
- spam_log.save
- spam_log
- end
-end
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 332c55581a1..87f066edb6f 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -40,6 +40,6 @@ class DeleteBranchService < BaseService
def build_push_data(branch)
Gitlab::PushDataBuilder
- .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
+ .build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", [])
end
end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
index 1e41fbe34b6..32e0eed6b63 100644
--- a/app/services/delete_tag_service.rb
+++ b/app/services/delete_tag_service.rb
@@ -34,6 +34,6 @@ class DeleteTagService < BaseService
def build_push_data(tag)
Gitlab::PushDataBuilder
- .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
+ .build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", [])
end
end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index ce79287e35a..eaff88d6463 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -18,9 +18,14 @@ class DeleteUserService
user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace
# that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).async_execute
end
- user.destroy
+ # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing
+ namespace = user.namespace
+ user_data = user.destroy
+ namespace.really_destroy!
+
+ user_data
end
end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
index 3c42ac61be4..0081364b8aa 100644
--- a/app/services/destroy_group_service.rb
+++ b/app/services/destroy_group_service.rb
@@ -5,13 +5,23 @@ class DestroyGroupService
@group, @current_user = group, user
end
+ def async_execute
+ group.transaction do
+ # Soft delete via paranoia gem
+ group.destroy
+ job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
+ Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
+ end
+ end
+
def execute
group.projects.each do |project|
+ # Execute the destruction of the models immediately to ensure atomic cleanup.
# Skip repository removal because we remove directory with namespace
- # that contain all this repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
+ # that contain all these repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
end
- group.destroy
+ group.really_destroy!
end
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index c4a206f785e..ea94818713b 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -15,6 +15,7 @@ module Files
else
params[:file_content]
end
+ @last_commit_sha = params[:last_commit_sha]
# Validate parameters
validate
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 8d2b5083179..4fc3b640799 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -2,11 +2,34 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
+ class FileChangedError < StandardError; end
+
def commit
repository.update_file(current_user, @file_path, @file_content,
branch: @target_branch,
previous_path: @previous_path,
message: @commit_message)
end
+
+ private
+
+ def validate
+ super
+
+ if file_has_changed?
+ raise FileChangedError.new("You are attempting to update a file that has changed since you started editing it.")
+ end
+ end
+
+ def file_has_changed?
+ return false unless @last_commit_sha && last_commit
+
+ @last_commit_sha != last_commit.sha
+ end
+
+ def last_commit
+ @last_commit ||= Gitlab::Git::Commit.
+ last_for_path(@source_project.repository, @source_branch, @file_path)
+ end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index e02b50ff9a2..6f521462cf3 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -69,7 +69,7 @@ class GitPushService < BaseService
SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
- CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
+ Ci::CreatePipelineService.new(project, current_user, build_push_data).execute
ProjectCacheWorker.perform_async(@project.id)
end
@@ -88,9 +88,18 @@ class GitPushService < BaseService
# Set protection on the default branch if configured
if current_application_settings.default_branch_protection != PROTECTION_NONE
- developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
- developers_can_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? true : false
- @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push, developers_can_merge: developers_can_merge })
+
+ params = {
+ name: @project.default_branch,
+ push_access_level_attributes: {
+ access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ },
+ merge_access_level_attributes: {
+ access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ }
+ }
+
+ ProtectedBranches::CreateService.new(@project, current_user, params).execute
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 58573078048..d2b52f16fa8 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -11,7 +11,7 @@ class GitTagPushService < BaseService
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
- CreateCommitBuildsService.new.execute(project, current_user, @push_data)
+ Ci::CreatePipelineService.new(project, current_user, @push_data).execute
ProjectCacheWorker.perform_async(project.id)
true
@@ -26,8 +26,8 @@ class GitTagPushService < BaseService
unless Gitlab::Git.blank_ref?(params[:newrev])
tag_name = Gitlab::Git.ref_name(params[:ref])
tag = project.repository.find_tag(tag_name)
-
- if tag && tag.target == params[:newrev]
+
+ if tag && tag.object_sha == params[:newrev]
commit = project.commit(tag.target)
commits = [commit].compact
message = tag.message
diff --git a/app/services/ham_service.rb b/app/services/ham_service.rb
new file mode 100644
index 00000000000..b0e1799b489
--- /dev/null
+++ b/app/services/ham_service.rb
@@ -0,0 +1,26 @@
+class HamService
+ attr_accessor :spam_log
+
+ def initialize(spam_log)
+ @spam_log = spam_log
+ end
+
+ def mark_as_ham!
+ if akismet.submit_ham
+ spam_log.update_attribute(:submitted_as_ham, true)
+ else
+ false
+ end
+ end
+
+ private
+
+ def akismet
+ @akismet ||= AkismetService.new(
+ spam_log.user,
+ spam_log.text,
+ ip_address: spam_log.source_ip,
+ user_agent: spam_log.user_agent
+ )
+ end
+end
diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb
new file mode 100644
index 00000000000..6442406d77e
--- /dev/null
+++ b/app/services/import_export_clean_up_service.rb
@@ -0,0 +1,24 @@
+class ImportExportCleanUpService
+ LAST_MODIFIED_TIME_IN_MINUTES = 1440
+
+ attr_reader :mmin, :path
+
+ def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
+ @mmin = mmin
+ @path = Gitlab::ImportExport.storage_path
+ end
+
+ def execute
+ Gitlab::Metrics.measure(:import_export_clean_up) do
+ return unless File.directory?(path)
+
+ clean_up_export_files
+ end
+ end
+
+ private
+
+ def clean_up_export_files
+ Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete))
+ end
+end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e63e1af8766..65550ab8ec6 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -2,20 +2,35 @@ module Issues
class CreateService < Issues::BaseService
def execute
filter_params
- label_params = params[:label_ids]
- issue = project.issues.new(params.except(:label_ids))
- issue.author = params[:author] || current_user
+ label_params = params.delete(:label_ids)
+ @request = params.delete(:request)
+ @api = params.delete(:api)
+ @issue = project.issues.new(params)
+ @issue.author = params[:author] || current_user
- if issue.save
- issue.update_attributes(label_ids: label_params)
- notification_service.new_issue(issue, current_user)
- todo_service.new_issue(issue, current_user)
- event_service.open_issue(issue, current_user)
- issue.create_cross_references!(current_user)
- execute_hooks(issue, 'open')
+ @issue.spam = spam_service.check(@api)
+
+ if @issue.save
+ @issue.update_attributes(label_ids: label_params)
+ notification_service.new_issue(@issue, current_user)
+ todo_service.new_issue(@issue, current_user)
+ event_service.open_issue(@issue, current_user)
+ user_agent_detail_service.create
+ @issue.create_cross_references!(current_user)
+ execute_hooks(@issue, 'open')
end
- issue
+ @issue
+ end
+
+ private
+
+ def spam_service
+ SpamService.new(@issue, @request)
+ end
+
+ def user_agent_detail_service
+ UserAgentDetailService.new(@issue, @request)
end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 15358f80208..9e3f6af628d 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -2,8 +2,9 @@ module Members
class DestroyService < BaseService
attr_accessor :member, :current_user
- def initialize(member, user)
- @member, @current_user = member, user
+ def initialize(member, current_user)
+ @member = member
+ @current_user = current_user
end
def execute
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index bc3606a14c2..ba424b09463 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -17,16 +17,19 @@ module MergeRequests
end
end
- def hook_data(merge_request, action)
+ def hook_data(merge_request, action, oldrev = nil)
hook_data = merge_request.to_hook_data(current_user)
hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action
+ if oldrev && !Gitlab::Git.blank_ref?(oldrev)
+ hook_data[:object_attributes][:oldrev] = oldrev
+ end
hook_data
end
- def execute_hooks(merge_request, action = 'open')
+ def execute_hooks(merge_request, action = 'open', oldrev = nil)
if merge_request.project
- merge_data = hook_data(merge_request, action)
+ merge_data = hook_data(merge_request, action, oldrev)
merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
merge_request.project.execute_services(merge_data, :merge_request_hooks)
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 7fe57747265..290742f1506 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -34,7 +34,7 @@ module MergeRequests
# At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed
if commits.present?
- merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project)
+ merge_request.compare_commits = commits
merge_request.can_be_created = true
merge_request.compare = compare
else
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
new file mode 100644
index 00000000000..08c1f72d65a
--- /dev/null
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -0,0 +1,63 @@
+module MergeRequests
+ class GetUrlsService < BaseService
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute(changes)
+ branches = get_branches(changes)
+ merge_requests_map = opened_merge_requests_from_source_branches(branches)
+ branches.map do |branch|
+ existing_merge_request = merge_requests_map[branch]
+ if existing_merge_request
+ url_for_existing_merge_request(existing_merge_request)
+ else
+ url_for_new_merge_request(branch)
+ end
+ end
+ end
+
+ private
+
+ def opened_merge_requests_from_source_branches(branches)
+ merge_requests = MergeRequest.from_project(project).opened.from_source_branches(branches)
+ merge_requests.inject({}) do |hash, mr|
+ hash[mr.source_branch] = mr
+ hash
+ end
+ end
+
+ def get_branches(changes)
+ return [] if project.empty_repo?
+ return [] unless project.merge_requests_enabled
+
+ changes_list = Gitlab::ChangesList.new(changes)
+ changes_list.map do |change|
+ next unless Gitlab::Git.branch_ref?(change[:ref])
+
+ # Deleted branch
+ next if Gitlab::Git.blank_ref?(change[:newrev])
+
+ # Default branch
+ branch_name = Gitlab::Git.branch_name(change[:ref])
+ next if branch_name == project.default_branch
+
+ branch_name
+ end.compact
+ end
+
+ def url_for_new_merge_request(branch_name)
+ merge_request_params = { source_branch: branch_name }
+ url = Gitlab::Routing.url_helpers.new_namespace_project_merge_request_url(project.namespace, project, merge_request: merge_request_params)
+ { branch_name: branch_name, url: url, new_merge_request: true }
+ end
+
+ def url_for_existing_merge_request(merge_request)
+ target_project = merge_request.target_project
+ url = Gitlab::Routing.url_helpers.namespace_project_merge_request_url(target_project.namespace, target_project, merge_request)
+ { branch_name: merge_request.source_branch, url: url, new_merge_request: false }
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb
new file mode 100644
index 00000000000..2945a7fd4e4
--- /dev/null
+++ b/app/services/merge_requests/merge_request_diff_cache_service.rb
@@ -0,0 +1,8 @@
+module MergeRequests
+ class MergeRequestDiffCacheService
+ def execute(merge_request)
+ # Executing the iteration we cache all the highlighted diff information
+ merge_request.diffs.diff_files.to_a
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 0dac0614141..b037780c431 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -35,7 +35,13 @@ module MergeRequests
}
commit_id = repository.merge(current_user, merge_request, options)
- merge_request.update(merge_commit_sha: commit_id)
+
+ if commit_id
+ merge_request.update(merge_commit_sha: commit_id)
+ else
+ merge_request.update(merge_error: 'Conflicts detected during merge')
+ false
+ end
rescue GitHooksService::PreReceiveError => e
merge_request.update(merge_error: e.message)
false
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 1daf6bbf553..5cedd6f11d9 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -137,7 +137,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
merge_requests_for_source_branch.each do |merge_request|
- execute_hooks(merge_request, 'update')
+ execute_hooks(merge_request, 'update', @oldrev)
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 882606e38d0..8a53f65aec1 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -6,8 +6,12 @@ module Projects
DELETED_FLAG = '+deleted'
- def pending_delete!
- project.schedule_delete!(current_user.id, params)
+ def async_execute
+ project.transaction do
+ project.update_attribute(:pending_delete, true)
+ job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
+ Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.path_with_namespace} with job ID #{job_id}")
+ end
end
def execute
diff --git a/app/services/projects/enable_deploy_key_service.rb b/app/services/projects/enable_deploy_key_service.rb
new file mode 100644
index 00000000000..3cf4264ce9b
--- /dev/null
+++ b/app/services/projects/enable_deploy_key_service.rb
@@ -0,0 +1,17 @@
+module Projects
+ class EnableDeployKeyService < BaseService
+ def execute
+ key = accessible_keys.find_by(id: params[:key_id] || params[:id])
+ return unless key
+
+ project.deploy_keys << key
+ key
+ end
+
+ private
+
+ def accessible_keys
+ current_user.accessible_deploy_keys
+ end
+ end
+end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f06311511cc..921ca6748d3 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,7 +3,7 @@ module Projects
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
-
+
if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
new file mode 100644
index 00000000000..6150a2a83c9
--- /dev/null
+++ b/app/services/protected_branches/create_service.rb
@@ -0,0 +1,27 @@
+module ProtectedBranches
+ class CreateService < BaseService
+ attr_reader :protected_branch
+
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+
+ protected_branch = project.protected_branches.new(params)
+
+ ProtectedBranch.transaction do
+ protected_branch.save!
+
+ if protected_branch.push_access_level.blank?
+ protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER)
+ end
+
+ if protected_branch.merge_access_level.blank?
+ protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER)
+ end
+ end
+
+ protected_branch
+ rescue ActiveRecord::RecordInvalid
+ protected_branch
+ end
+ end
+end
diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb
new file mode 100644
index 00000000000..89d8ba60134
--- /dev/null
+++ b/app/services/protected_branches/update_service.rb
@@ -0,0 +1,13 @@
+module ProtectedBranches
+ class UpdateService < BaseService
+ attr_reader :protected_branch
+
+ def execute(protected_branch)
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
+
+ @protected_branch = protected_branch
+ @protected_branch.update(params)
+ @protected_branch
+ end
+ end
+end
diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb
index 0b56b09738d..aa84d36a206 100644
--- a/app/services/repository_archive_clean_up_service.rb
+++ b/app/services/repository_archive_clean_up_service.rb
@@ -1,6 +1,8 @@
class RepositoryArchiveCleanUpService
LAST_MODIFIED_TIME_IN_MINUTES = 120
+ attr_reader :mmin, :path
+
def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES)
@mmin = mmin
@path = Gitlab.config.gitlab.repository_downloads_path
@@ -17,8 +19,6 @@ class RepositoryArchiveCleanUpService
private
- attr_reader :mmin, :path
-
def clean_up_old_archives
run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete))
end
diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb
new file mode 100644
index 00000000000..48903291799
--- /dev/null
+++ b/app/services/spam_service.rb
@@ -0,0 +1,78 @@
+class SpamService
+ attr_accessor :spammable, :request, :options
+
+ def initialize(spammable, request = nil)
+ @spammable = spammable
+ @request = request
+ @options = {}
+
+ if @request
+ @options[:ip_address] = @request.env['action_dispatch.remote_ip'].to_s
+ @options[:user_agent] = @request.env['HTTP_USER_AGENT']
+ @options[:referrer] = @request.env['HTTP_REFERRER']
+ else
+ @options[:ip_address] = @spammable.ip_address
+ @options[:user_agent] = @spammable.user_agent
+ end
+ end
+
+ def check(api = false)
+ return false unless request && check_for_spam?
+
+ return false unless akismet.is_spam?
+
+ create_spam_log(api)
+ true
+ end
+
+ def mark_as_spam!
+ return false unless spammable.submittable_as_spam?
+
+ if akismet.submit_spam
+ spammable.user_agent_detail.update_attribute(:submitted, true)
+ else
+ false
+ end
+ end
+
+ private
+
+ def akismet
+ @akismet ||= AkismetService.new(
+ spammable_owner,
+ spammable.spammable_text,
+ options
+ )
+ end
+
+ def spammable_owner
+ @user ||= User.find(spammable_owner_id)
+ end
+
+ def spammable_owner_id
+ @owner_id ||=
+ if spammable.respond_to?(:author_id)
+ spammable.author_id
+ elsif spammable.respond_to?(:creator_id)
+ spammable.creator_id
+ end
+ end
+
+ def check_for_spam?
+ spammable.check_for_spam?
+ end
+
+ def create_spam_log(api)
+ SpamLog.create(
+ {
+ user_id: spammable_owner_id,
+ title: spammable.spam_title,
+ description: spammable.spam_description,
+ source_ip: options[:ip_address],
+ user_agent: options[:user_agent],
+ noteable_type: spammable.class.to_s,
+ via_api: api
+ }
+ )
+ end
+end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1ab3b5789bc..e13dc9265b8 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -2,7 +2,9 @@
#
# Used for creating system notes (e.g., when a user references a merge request
# from an issue, an issue's assignee changes, an issue is closed, etc.)
-class SystemNoteService
+module SystemNoteService
+ extend self
+
# Called when commits are added to a Merge Request
#
# noteable - Noteable object
@@ -15,7 +17,7 @@ class SystemNoteService
# See new_commit_summary and existing_commit_summary.
#
# Returns the created Note object
- def self.add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil)
+ def add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil)
total_count = new_commits.length + existing_commits.length
commits_text = "#{total_count} commit".pluralize(total_count)
@@ -40,7 +42,7 @@ class SystemNoteService
# "Reassigned to @rspeicher"
#
# Returns the created Note object
- def self.change_assignee(noteable, project, author, assignee)
+ def change_assignee(noteable, project, author, assignee)
body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}"
create_note(noteable: noteable, project: project, author: author, note: body)
@@ -63,7 +65,7 @@ class SystemNoteService
# "Removed ~5 label"
#
# Returns the created Note object
- def self.change_label(noteable, project, author, added_labels, removed_labels)
+ def change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count
references = ->(label) { label.to_reference(format: :id) }
@@ -101,7 +103,7 @@ class SystemNoteService
# "Miletone changed to 7.11"
#
# Returns the created Note object
- def self.change_milestone(noteable, project, author, milestone)
+ def change_milestone(noteable, project, author, milestone)
body = 'Milestone '
body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}"
@@ -123,7 +125,7 @@ class SystemNoteService
# "Status changed to closed by bc17db76"
#
# Returns the created Note object
- def self.change_status(noteable, project, author, status, source)
+ def change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
body << " by #{source.gfm_reference(project)}" if source
@@ -131,26 +133,26 @@ class SystemNoteService
end
# Called when 'merge when build succeeds' is executed
- def self.merge_when_build_succeeds(noteable, project, author, last_commit)
+ def merge_when_build_succeeds(noteable, project, author, last_commit)
body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when 'merge when build succeeds' is canceled
- def self.cancel_merge_when_build_succeeds(noteable, project, author)
+ def cancel_merge_when_build_succeeds(noteable, project, author)
body = 'Canceled the automatic merge'
create_note(noteable: noteable, project: project, author: author, note: body)
end
- def self.remove_merge_request_wip(noteable, project, author)
+ def remove_merge_request_wip(noteable, project, author)
body = 'Unmarked this merge request as a Work In Progress'
create_note(noteable: noteable, project: project, author: author, note: body)
end
- def self.add_merge_request_wip(noteable, project, author)
+ def add_merge_request_wip(noteable, project, author)
body = 'Marked this merge request as a **Work In Progress**'
create_note(noteable: noteable, project: project, author: author, note: body)
@@ -168,7 +170,7 @@ class SystemNoteService
# "Title changed from **Old** to **New**"
#
# Returns the created Note object
- def self.change_title(noteable, project, author, old_title)
+ def change_title(noteable, project, author, old_title)
new_title = noteable.title.dup
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs
@@ -191,7 +193,7 @@ class SystemNoteService
# "Made the issue confidential"
#
# Returns the created Note object
- def self.change_issue_confidentiality(issue, project, author)
+ def change_issue_confidentiality(issue, project, author)
body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible'
create_note(noteable: issue, project: project, author: author, note: body)
end
@@ -210,7 +212,7 @@ class SystemNoteService
# "Target branch changed from `Old` to `New`"
#
# Returns the created Note object
- def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch)
+ def change_branch(noteable, project, author, branch_type, old_branch, new_branch)
body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize
create_note(noteable: noteable, project: project, author: author, note: body)
end
@@ -229,7 +231,7 @@ class SystemNoteService
# "Restored target branch `feature`"
#
# Returns the created Note object
- def self.change_branch_presence(noteable, project, author, branch_type, branch, presence)
+ def change_branch_presence(noteable, project, author, branch_type, branch, presence)
verb =
if presence == :add
'restored'
@@ -245,7 +247,7 @@ class SystemNoteService
# Example note text:
#
# "Started branch `201-issue-branch-button`"
- def self.new_issue_branch(issue, project, author, branch)
+ def new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
@@ -270,7 +272,7 @@ class SystemNoteService
# See cross_reference_note_content.
#
# Returns the created Note object
- def self.cross_reference(noteable, mentioner, author)
+ def cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner)
gfm_reference = mentioner.gfm_reference(noteable.project)
@@ -294,7 +296,7 @@ class SystemNoteService
end
end
- def self.cross_reference?(note_text)
+ def cross_reference?(note_text)
note_text.start_with?(cross_reference_note_prefix)
end
@@ -308,7 +310,7 @@ class SystemNoteService
# mentioner - Mentionable object
#
# Returns Boolean
- def self.cross_reference_disallowed?(noteable, mentioner)
+ def cross_reference_disallowed?(noteable, mentioner)
return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
return false unless mentioner.is_a?(MergeRequest)
return false unless noteable.is_a?(Commit)
@@ -328,7 +330,7 @@ class SystemNoteService
#
# Returns Boolean
- def self.cross_reference_exists?(noteable, mentioner)
+ def cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
@@ -342,9 +344,60 @@ class SystemNoteService
notes_for_mentioner(mentioner, noteable, notes).count > 0
end
+ # Build an Array of lines detailing each commit added in a merge request
+ #
+ # new_commits - Array of new Commit objects
+ #
+ # Returns an Array of Strings
+ def new_commit_summary(new_commits)
+ new_commits.collect do |commit|
+ "* #{commit.short_id} - #{escape_html(commit.title)}"
+ end
+ end
+
+ # Called when the status of a Task has changed
+ #
+ # noteable - Noteable object.
+ # project - Project owning noteable
+ # author - User performing the change
+ # new_task - TaskList::Item object.
+ #
+ # Example Note text:
+ #
+ # "Soandso marked the task Whatever as completed."
+ #
+ # Returns the created Note object
+ def change_task_status(noteable, project, author, new_task)
+ status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
+ body = "Marked the task **#{new_task.source}** as #{status_label}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when noteable has been moved to another project
+ #
+ # direction - symbol, :to or :from
+ # noteable - Noteable object
+ # noteable_ref - Referenced noteable
+ # author - User performing the move
+ #
+ # Example Note text:
+ #
+ # "Moved to some_namespace/project_new#11"
+ #
+ # Returns the created Note object
+ def noteable_moved(noteable, project, noteable_ref, author, direction:)
+ unless [:to, :from].include?(direction)
+ raise ArgumentError, "Invalid direction `#{direction}`"
+ end
+
+ cross_reference = noteable_ref.to_reference(project)
+ body = "Moved #{direction} #{cross_reference}"
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
private
- def self.notes_for_mentioner(mentioner, noteable, notes)
+ def notes_for_mentioner(mentioner, noteable, notes)
if mentioner.is_a?(Commit)
notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}")
else
@@ -353,29 +406,18 @@ class SystemNoteService
end
end
- def self.create_note(args = {})
+ def create_note(args = {})
Note.create(args.merge(system: true))
end
- def self.cross_reference_note_prefix
+ def cross_reference_note_prefix
'mentioned in '
end
- def self.cross_reference_note_content(gfm_reference)
+ def cross_reference_note_content(gfm_reference)
"#{cross_reference_note_prefix}#{gfm_reference}"
end
- # Build an Array of lines detailing each commit added in a merge request
- #
- # new_commits - Array of new Commit objects
- #
- # Returns an Array of Strings
- def self.new_commit_summary(new_commits)
- new_commits.collect do |commit|
- "* #{commit.short_id} - #{escape_html(commit.title)}"
- end
- end
-
# Build a single line summarizing existing commits being added in a merge
# request
#
@@ -392,7 +434,7 @@ class SystemNoteService
# "* ea0f8418 - 1 commit from branch `feature`"
#
# Returns a newline-terminated String
- def self.existing_commit_summary(noteable, existing_commits, oldrev = nil)
+ def existing_commit_summary(noteable, existing_commits, oldrev = nil)
return '' if existing_commits.empty?
count = existing_commits.size
@@ -415,47 +457,7 @@ class SystemNoteService
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n"
end
- # Called when the status of a Task has changed
- #
- # noteable - Noteable object.
- # project - Project owning noteable
- # author - User performing the change
- # new_task - TaskList::Item object.
- #
- # Example Note text:
- #
- # "Soandso marked the task Whatever as completed."
- #
- # Returns the created Note object
- def self.change_task_status(noteable, project, author, new_task)
- status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE
- body = "Marked the task **#{new_task.source}** as #{status_label}"
- create_note(noteable: noteable, project: project, author: author, note: body)
- end
-
- # Called when noteable has been moved to another project
- #
- # direction - symbol, :to or :from
- # noteable - Noteable object
- # noteable_ref - Referenced noteable
- # author - User performing the move
- #
- # Example Note text:
- #
- # "Moved to some_namespace/project_new#11"
- #
- # Returns the created Note object
- def self.noteable_moved(noteable, project, noteable_ref, author, direction:)
- unless [:to, :from].include?(direction)
- raise ArgumentError, "Invalid direction `#{direction}`"
- end
-
- cross_reference = noteable_ref.to_reference(project)
- body = "Moved #{direction} #{cross_reference}"
- create_note(noteable: noteable, project: project, author: author, note: body)
- end
-
- def self.escape_html(text)
+ def escape_html(text)
Rack::Utils.escape_html(text)
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 6b48d68cccb..eb833dd82ac 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -144,8 +144,9 @@ class TodoService
def mark_todos_as_done(todos, current_user)
todos = current_user.todos.where(id: todos.map(&:id)) unless todos.respond_to?(:update_all)
- todos.update_all(state: :done)
+ marked_todos = todos.update_all(state: :done)
current_user.update_todos_count_cache
+ marked_todos
end
# When user marks an issue as todo
diff --git a/app/services/user_agent_detail_service.rb b/app/services/user_agent_detail_service.rb
new file mode 100644
index 00000000000..a1ee3df5fe1
--- /dev/null
+++ b/app/services/user_agent_detail_service.rb
@@ -0,0 +1,13 @@
+class UserAgentDetailService
+ attr_accessor :spammable, :request
+
+ def initialize(spammable, request)
+ @spammable, @request = spammable, request
+ end
+
+ def create
+ return unless request
+
+ spammable.create_user_agent_detail(user_agent: request.env['HTTP_USER_AGENT'], ip_address: request.env['action_dispatch.remote_ip'].to_s)
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 23b52d08df7..c7fd344eea2 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -228,6 +228,9 @@
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
+ .help-block
+ Set the maximum file size each build's artifacts can have
+ = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
- if Gitlab.config.registry.enabled
%fieldset
@@ -363,7 +366,9 @@
.col-sm-10
= f.select :repository_storage, repository_storage_options_for_select, {}, class: 'form-control'
.help-block
- You can manage the repository storage paths in your gitlab.yml configuration file
+ Manage repository storage paths. Learn more in the
+ = succeed "." do
+ = link_to "repository storages documentation", help_page_path("administration/repository_storages")
%fieldset
%legend Repository Checks
@@ -385,4 +390,4 @@
.form-actions
- = f.submit 'Save', class: 'btn btn-save' \ No newline at end of file
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/background_jobs/_head.html.haml b/app/views/admin/background_jobs/_head.html.haml
index 9d722bd7382..89d7a40d6b0 100644
--- a/app/views/admin/background_jobs/_head.html.haml
+++ b/app/views/admin/background_jobs/_head.html.haml
@@ -16,3 +16,7 @@
= 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
diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml
index ce818c30c30..352adbedee4 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -11,16 +11,18 @@
- else
%span.build-link ##{build.id}
- - if build.stuck?
- %i.fa.fa-warning.text-warning
-
- if build.ref
+ .icon-container
+ = build.tag? ? icon('tag') : icon('code-fork')
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
- = custom_icon("icon_commit")
+ .icon-container
+ = custom_icon("icon_commit")
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
+ - if build.stuck?
+ %i.fa.fa-warning.text-warning
.label-container
- if build.tags.any?
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 452fc25ab07..e6687f43816 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -112,7 +112,7 @@
%h4 Projects
.data
= link_to admin_namespaces_projects_path do
- %h1= number_with_delimiter(Project.count)
+ %h1= number_with_delimiter(Project.cached_count)
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 448aa953548..602cfa9b6fc 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -28,6 +28,3 @@
.form-actions
= f.submit 'Save', class: 'btn btn-save js-save-button'
= link_to "Cancel", admin_labels_path, class: 'btn btn-cancel'
-
-:javascript
- new Labels();
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
new file mode 100644
index 00000000000..ae918086a57
--- /dev/null
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -0,0 +1,26 @@
+- @no_container = true
+- page_title 'Requests Profiles'
+= render 'admin/background_jobs/head'
+
+%div{ class: container_class }
+ %h3.page-title
+ = page_title
+
+ .bs-callout.clearfix
+ Pass the header
+ %code X-Profile-Token: #{@profile_token}
+ to profile the request
+
+ - if @profiles.present?
+ .prepend-top-default
+ - @profiles.each do |path, profiles|
+ .panel.panel-default.panel-small
+ .panel-heading
+ %code= path
+ %ul.content-list
+ - profiles.each do |profile|
+ %li
+ = link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
+ - else
+ %p
+ No profiles found
diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml
index 8aea67f4497..4ce4eab8753 100644
--- a/app/views/admin/spam_logs/_spam_log.html.haml
+++ b/app/views/admin/spam_logs/_spam_log.html.haml
@@ -24,6 +24,11 @@
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
%td
+ - if spam_log.submitted_as_ham?
+ .btn.btn-xs.disabled
+ Submitted as ham
+ - else
+ = link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-xs btn-warning'
- if user && !user.blocked?
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index d37489bebea..76c9ed0ee8b 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -140,12 +140,10 @@
.panel-heading
This user is blocked
.panel-body
- %p Blocking user has the following effects:
+ %p A blocked user cannot:
%ul
- %li User will not be able to login
- %li User will not be able to access git repositories
- %li Personal projects will be left
- %li Owned groups will be left
+ %li Log in
+ %li Access Git repositories
%br
= link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
- else
diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml
index 8e81671b7e7..b7d3acac2b1 100644
--- a/app/views/devise/sessions/_new_crowd.html.haml
+++ b/app/views/devise/sessions/_new_crowd.html.haml
@@ -1,4 +1,4 @@
-= form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do
+= form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
- if devise_mapping.rememberable?
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index de18bc2d844..2e7da2747d0 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -5,4 +5,4 @@
- providers.each do |provider|
%span.light
- has_icon = provider_has_icon?(provider)
- = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
+ = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 431d312b4ca..85e188d6f8b 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -189,7 +189,7 @@
%li
%a Sort by date
- = link_to 'New issue', '#', class: 'btn btn-new'
+ = link_to 'New issue', '#', class: 'btn btn-new btn-inverted'
.lead
Only nav links without button and search
diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml
index 6e993e58f0d..15dd98077c8 100644
--- a/app/views/import/bitbucket/status.html.haml
+++ b/app/views/import/bitbucket/status.html.haml
@@ -74,6 +74,4 @@
= link_to "import flow", status_import_bitbucket_path, "data-no-turbolink" => "true"
again.
-
-:javascript
- new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index d3d3c595c17..c8a6fa1aa9e 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -56,5 +56,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_fogbugz_path}", "#{import_fogbugz_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml
index 7486b1423e2..54ff1d27c67 100644
--- a/app/views/import/github/status.html.haml
+++ b/app/views/import/github/status.html.haml
@@ -4,10 +4,6 @@
%i.fa.fa-github
Import projects from GitHub
-%p
- %i.fa.fa-warning
- To import GitHub pull requests, any pull request source branches that had been deleted are temporarily restored on GitHub. To prevent any connected CI services from being overloaded with dozens of irrelevant branches being created and deleted again, GitHub webhooks are temporarily disabled during the import process, but only if you have admin access to the GitHub repository.
-
%p.light
Select projects you want to import.
%hr
@@ -55,5 +51,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_github_path}", import_path: "#{import_github_path}" } }
diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml
index aedb8468eca..fcfc6fd37f4 100644
--- a/app/views/import/gitlab/status.html.haml
+++ b/app/views/import/gitlab/status.html.haml
@@ -51,5 +51,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml
index 267eee4f262..ed3afb0ce33 100644
--- a/app/views/import/gitorious/status.html.haml
+++ b/app/views/import/gitorious/status.html.haml
@@ -51,5 +51,4 @@
Import
= icon("spinner spin", class: "loading-icon")
-:javascript
- new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitorious_path}", import_path: "#{import_gitorious_path}" } }
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 5ada6b174eb..e79f122940a 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -77,5 +77,4 @@
= link_to "import flow", new_import_google_code_path
again.
-:javascript
- new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}");
+.js-importer-status{ data: { jobs_import_path: "#{jobs_import_google_code_path}", import_path: "#{import_google_code_path}" } }
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index 5ee8772882e..ac04f57e217 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -9,7 +9,7 @@
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
%span
Overview
- = nav_link(controller: %w(system_info background_jobs logs health_check)) do
+ = nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
= link_to admin_system_info_path, title: 'Monitoring' do
%span
Monitoring
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 9e65d94186b..1d3b8fc3683 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -66,7 +66,7 @@
- if project_nav_tab? :issues
= nav_link(controller: [:issues, :labels, :milestones]) do
- = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do
+ = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
- if @project.default_issues_tracker?
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index ee9c0366f2b..9fe94291db7 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -6,13 +6,13 @@
- content_for :scripts_body_top do
- project = @target_project || @project
- if @project_wiki && @page
- - markdown_preview_path = namespace_project_wiki_markdown_preview_path(project.namespace, project, @page.slug)
+ - preview_markdown_path = namespace_project_wiki_preview_markdown_path(project.namespace, project, @page.slug)
- else
- - markdown_preview_path = markdown_preview_namespace_project_path(project.namespace, project)
+ - preview_markdown_path = preview_markdown_namespace_project_path(project.namespace, project)
- if current_user
:javascript
window.project_uploads_path = "#{namespace_project_uploads_path project.namespace,project}";
- window.markdown_preview_path = "#{markdown_preview_path}";
+ window.preview_markdown_path = "#{preview_markdown_path}";
- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index fc64c98038b..ca5c2f2688c 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -3,3 +3,5 @@ New Issue was created.
Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_name %>
+
+<%= @issue.description %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index d4aad8d1862..3c8f178ac77 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -6,3 +6,5 @@ New Merge Request <%= @merge_request.to_reference %>
Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %>
+<%= @merge_request.description %>
+
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 57d16d29158..c80f22457b4 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -70,7 +70,7 @@
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
Disconnect
- else
- = link_to user_omniauth_authorize_path(provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do
Connect
%hr
- if current_user.can_change_username?
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 51f74f3b7ce..8ef31ca3bda 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -24,6 +24,3 @@
.project-clone-holder
= render "shared/clone_panel"
-
-:javascript
- new Star();
diff --git a/app/views/projects/badges/badge.svg.erb b/app/views/projects/badges/badge.svg.erb
new file mode 100644
index 00000000000..a5fef4fc56f
--- /dev/null
+++ b/app/views/projects/badges/badge.svg.erb
@@ -0,0 +1,36 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="<%= badge.width %>" height="20">
+ <linearGradient id="b" x2="0" y2="100%">
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
+ <stop offset="1" stop-opacity=".1"/>
+ </linearGradient>
+
+ <mask id="a">
+ <rect width="<%= badge.width %>" height="20" rx="3" fill="#fff"/>
+ </mask>
+
+ <g mask="url(#a)">
+ <path fill="<%= badge.key_color %>"
+ d="M0 0 h<%= badge.key_width %> v20 H0 z"/>
+ <path fill="<%= badge.value_color %>"
+ d="M<%= badge.key_width %> 0 h<%= badge.value_width %> v20 H<%= badge.key_width %> z"/>
+ <path fill="url(#b)"
+ d="M0 0 h<%= badge.width %> v20 H0 z"/>
+ </g>
+
+ <g fill="#fff" text-anchor="middle">
+ <g font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+ <text x="<%= badge.key_text_anchor %>" y="15" fill="#010101" fill-opacity=".3">
+ <%= badge.key_text %>
+ </text>
+ <text x="<%= badge.key_text_anchor %>" y="14">
+ <%= badge.key_text %>
+ </text>
+ <text x="<%= badge.value_text_anchor %>" y="15" fill="#010101" fill-opacity=".3">
+ <%= badge.value_text %>
+ </text>
+ <text x="<%= badge.value_text_anchor %>" y="14">
+ <%= badge.value_text %>
+ </text>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index cdac50f7a8d..ff893ea74e1 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -16,6 +16,7 @@
- if current_user
.btn-group{ role: "group" }
- = edit_blob_link
+ - if blob_text_viewable?(@blob)
+ = edit_blob_link
= replace_blob_link
= delete_blob_link
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index 18caddabd39..4c356d1f07f 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,9 +1,15 @@
.file-content.image_file
- if blob.svg?
- - # We need to scrub SVG but we cannot do so in the RawController: it would
- - # be wrong/strange if RawController modified the data.
- - blob.load_all_data!(@repository)
- - blob = sanitize_svg(blob)
- %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ - if blob.size_within_svg_limits?
+ - # We need to scrub SVG but we cannot do so in the RawController: it would
+ - # be wrong/strange if RawController modified the data.
+ - blob.load_all_data!(@repository)
+ - blob = sanitize_svg(blob)
+ %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"}
+ - else
+ .nothing-here-block
+ The SVG could not be displayed as it is too large, you can
+ #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')}
+ instead.
- else
%img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))}
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index 5926d181ba3..a79ae53c780 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,20 +1,30 @@
- if @lines.present?
+ - line_class = diff_view == :inline ? '' : diff_view
- if @form.unfold? && @form.since != 1 && !@form.bottom?
- %tr.line_holder
- = render "projects/diffs/match_line", { line: @match_line,
- line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
+ %tr.line_holder{ class: line_class }
+ = diff_match_line @form.since, @form.since, text: @match_line, view: diff_view
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
- %tr.line_holder{ id: line_old }
- %td.old_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_old), "##{line_old}"
- %td.new_line.diff-line-num{ data: { linenumber: line_old } }
- = link_to raw(line_new) , "##{line_old}"
- %td.line_content.noteable_line==#{' ' * @form.indent}#{line}
+ - line_content = capture do
+ %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line}
+ %tr.line_holder{ id: line_old, class: line_class }
+ - case diff_view
+ - when :inline
+ %td.old_line.diff-line-num{ data: { linenumber: line_old } }
+ %a{href: "##{line_old}", data: { linenumber: line_old }}
+ %td.new_line.diff-line-num{ data: { linenumber: line_new } }
+ %a{href: "##{line_new}", data: { linenumber: line_new }}
+ = line_content
+ - when :parallel
+ %td.old_line.diff-line-num{data: { linenumber: line_old }}
+ = link_to raw(line_old), "##{line_old}"
+ = line_content
+ %td.new_line.diff-line-num{data: { linenumber: line_new }}
+ = link_to raw(line_new), "##{line_new}"
+ = line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
- %tr.line_holder{ id: @form.to }
- = render "projects/diffs/match_line", { line: @match_line,
- line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
+ %tr.line_holder{ id: @form.to, class: line_class }
+ = diff_match_line @form.to, @form.to, text: @match_line, view: diff_view, bottom: true
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index b1c9895f43e..7b0621f9401 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,5 +1,11 @@
- page_title "Edit", @blob.path, @ref
+- if @conflict
+ .alert.alert-danger
+ Someone edited the file the same time you did. Please check out
+ = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank"
+ and make sure your changes will not unintentionally remove theirs.
+
.file-editor
%ul.nav-links.no-bottom.js-edit-mode
%li.active
@@ -13,8 +19,7 @@
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
-
- = hidden_field_tag 'last_commit', @last_commit
+ = hidden_field_tag 'last_commit_sha', @last_commit_sha
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml
index 9fe65cbb104..d54c76ff9c8 100644
--- a/app/views/projects/branches/_commit.html.haml
+++ b/app/views/projects/branches/_commit.html.haml
@@ -1,5 +1,5 @@
.branch-commit
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace"
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace"
&middot;
%span.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 6f806e3ce53..e889f29c816 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -7,28 +7,28 @@
.nav-text
Protected branches can be managed in project settings
- - if can? current_user, :push_code, @project
- .nav-controls
- = form_tag(filter_branches_path, method: :get) do
- = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
- .dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- - if params[:sort].present?
- = params[:sort].humanize
- - else
- Name
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- %li
- = link_to filter_branches_path(sort: nil) do
- = sort_title_name
- = link_to filter_branches_path(sort: 'recently_updated') do
- = sort_title_recently_updated
- = link_to filter_branches_path(sort: 'last_updated') do
- = sort_title_oldest_updated
+ .nav-controls
+ = form_tag(filter_branches_path, method: :get) do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
+
+ .dropdown.inline
+ %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
+ %span.light
+ = projects_sort_options_hash[@sort]
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li
+ = link_to filter_branches_path(sort: sort_value_name) do
+ = sort_title_name
+ = link_to filter_branches_path(sort: sort_value_recently_updated) do
+ = sort_title_recently_updated
+ = link_to filter_branches_path(sort: sort_value_oldest_updated) do
+ = sort_title_oldest_updated
+
+ - if can? current_user, :push_code, @project
= link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do
New branch
+
- if @branches.any?
%ul.content-list.all-branches
- @branches.each do |branch|
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index dc57b49f27a..a8bc53c2849 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -40,7 +40,7 @@
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
.title
Build details
- - if @build.retryable?
+ - if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right', method: :post
- if @build.merge_request
%p.build-detail-row
@@ -88,8 +88,9 @@
%p
%span.build-light-text Variables:
- %code
- - @build.trigger_request.variables.each do |key, value|
+
+ - @build.trigger_request.variables.each do |key, value|
+ %code
#{key}=#{value}
.block
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 16b8e1cca91..ca907077c2b 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -9,7 +9,7 @@
- if can_create_issue
%li
- = link_to url_for_new_issue(@project, only_path: true) do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project) do
= icon('exclamation-circle fw')
New issue
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index a9fb3c58431..91081435220 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -13,15 +13,10 @@
- else
%span ##{build.id}
- - if build.stuck?
- .icon-container
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
- - if defined?(retried) && retried
- .icon-container
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
-
- if defined?(ref) && ref
- if build.ref
+ .icon-container
+ = build.tag? ? icon('tag') : icon('code-fork')
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
- else
.light none
@@ -31,6 +26,11 @@
- if defined?(commit_sha) && commit_sha
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
+ - if build.stuck?
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+ - if defined?(retried) && retried
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+
.label-container
- if build.tags.any?
- build.tags.each do |tag|
@@ -45,7 +45,6 @@
- if build.manual?
%span.label.label-info manual
-
- if defined?(runner) && runner
%td
- if build.try(:runner)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 2f7d54f0bdd..78709a92aed 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -33,7 +33,7 @@
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.latest.stages_status
+ - stages_status = pipeline.statuses.relevant.latest.stages_status
- stages.each do |stage|
%td.stage-cell
- status = stages_status[stage]
@@ -53,11 +53,11 @@
- if pipeline.finished_at
%p.finished-at
= icon("calendar")
- #{time_ago_with_tooltip(pipeline.finished_at)}
+ #{time_ago_with_tooltip(pipeline.finished_at, short_format: true, skip_js: true)}
%td.pipeline-actions
.controls.hidden-xs.pull-right
- - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
+ - artifacts = pipeline.builds.latest.with_artifacts_not_expired
- actions = pipeline.manual_actions
- if artifacts.present? || actions.any?
.btn-group.inline
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index ea33aa472a6..935433306ea 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -2,7 +2,7 @@
= nav_link(path: 'commit#show') do
= link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Changes
- %span.badge= @diffs.count
+ %span.badge= @diffs.size
= nav_link(path: 'commit#builds') do
= link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Builds
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 540689f4a61..640abdb993f 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -46,5 +46,5 @@
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
- - pipeline.statuses.stages.each do |stage|
- = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.where(stage: stage)
+ - pipeline.statuses.relevant.stages.each do |stage|
+ = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage)
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index d0da2606587..ed44d86a687 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -7,7 +7,7 @@
= render "ci_menu"
- else
%div.block-connector
-= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs
+= render "projects/diffs/diffs", diffs: @diffs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type|
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index af09b3418ea..d79336f5a60 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,7 +1,7 @@
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix
- if params[:to] && params[:from]
- = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
+ = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
.form-group.dropdown.compare-form-group.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 28a50e7031a..819e9bc15ae 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -8,7 +8,7 @@
- if @commits.present?
= render "projects/commits/commit_list"
- = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
+ = render "projects/diffs/diffs", diffs: @diffs
- else
.light-well
.center
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index f70dba224fa..f7bf3b834ef 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -2,9 +2,9 @@
.pull-right
- actions = deployment.manual_actions
- if actions.present?
- .btn-group.inline
- .btn-group
- %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+ .inline
+ .dropdown
+ %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index 0f9d9512d88..28813babd7b 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -1,12 +1,16 @@
%div.branch-commit
- if deployment.ref
- = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace"
- &middot;
+ .icon-container
+ = deployment.tag? ? icon('tag') : icon('code-fork')
+ = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace branch-name"
+ .icon-container
+ = custom_icon("icon_commit")
= link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace"
%p.commit-title
%span
- if commit_title = deployment.commit_title
+ = author_avatar(deployment.commit, size: 20)
= link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index baf02f1e6a0..cd95841ca5a 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -8,6 +8,7 @@
%td
- if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
+ = user_avatar(user: deployment.user, size: 20)
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index a1b071f130c..d37961c4e40 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -13,7 +13,7 @@
.nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
This diff is collapsed. Click to expand it.
- elsif diff_file.diff_lines.length > 0
- - if diff_view == 'parallel'
+ - if diff_view == :parallel
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
- else
= render "projects/diffs/text_file", diff_file: diff_file
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 8ae433b4823..62aff36aadd 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,20 +1,19 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
-- if diff_view == 'parallel'
+- diff_files = diffs.diff_files
+- if diff_view == :parallel
- fluid_layout true
-- diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository)
-
.content-block.oneline-block.files-changed
.inline-parallel-buttons
- if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
- = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
+ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
- = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
+ = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs')
- elsif current_controller?(:merge_requests)
- = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs')
+ = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs')
- elsif current_controller?(:compare)
- = diff_compare_whitespace_link(@project, params[:from], params[:to], class: 'hidden-xs')
+ = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs')
.btn-group
= inline_diff_btn
= parallel_diff_btn
@@ -23,12 +22,12 @@
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
-.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}}
+.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, diffs.project))}}
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file)
- blob = diff_file.blob(diff_commit)
- next unless blob
- - blob.load_all_data!(project.repository) unless blob.only_display_raw?
+ - blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw?
- = render 'projects/diffs/file', i: index, project: project,
- diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs
+ = render 'projects/diffs/file', index: index, project: diffs.project,
+ diff_file: diff_file, diff_commit: diff_commit, blob: blob
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c306909fb1a..8fbd89100ca 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,6 +1,6 @@
-.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)}
+.diff-file.file-holder{id: "diff-#{index}", data: diff_file_html_data(project, diff_file.file_path, diff_commit.id)}
.file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}"
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{index}"
- unless diff_file.submodule?
.file-actions.hidden-xs
@@ -9,11 +9,12 @@
= icon('comment')
\
- - if editable_diff?(diff_file)
- = edit_blob_link(@merge_request.source_project,
- @merge_request.source_branch, diff_file.new_path,
- from_merge_request_id: @merge_request.id)
+ - if editable_diff?(diff_file)
+ = edit_blob_link(@merge_request.source_project,
+ @merge_request.source_branch, diff_file.new_path,
+ from_merge_request_id: @merge_request.id,
+ skip_visible_check: true)
- = view_file_btn(diff_commit.id, diff_file, project)
+ = view_file_btn(diff_commit.id, diff_file.new_path, project)
- = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
+ = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 5a8a131d10c..2d6a370b848 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,12 +1,10 @@
- plain = local_assigns.fetch(:plain, false)
-- line_code = diff_file.line_code(line)
-- position = diff_file.position(line)
- type = line.type
-%tr.line_holder{ id: line_code, class: type }
+- line_code = diff_file.line_code(line) unless plain
+%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } }
- case type
- when 'match'
- = render "projects/diffs/match_line", { line: line.text,
- line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
+ = diff_match_line line.old_pos, line.new_pos, text: line.text
- when 'nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
@@ -24,4 +22,4 @@
= link_text
- else
%a{href: "##{line_code}", data: { linenumber: link_text }}
- %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type)
+ %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml
deleted file mode 100644
index d6dddd97879..00000000000
--- a/app/views/projects/diffs/_match_line.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-%td.old_line.diff-line-num{data: {linenumber: line_old},
- class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
- \...
-%td.new_line.diff-line-num{data: {linenumber: line_new},
- class: [unfold_bottom_class(bottom), unfold_class(!new_file)]}
- \...
-%td.line_content.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 7f30faa20d8..28aad3f4725 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,14 +1,15 @@
/ Side-by-side diff view
%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data }
%table
+ - last_line = 0
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
+ - last_line = right.new_pos if right
%tr.line_holder.parallel
- if left
- if left.meta?
- %td.old_line.diff-line-num.empty-cell
- %td.line_content.parallel.match= left.text
+ = diff_match_line left.old_pos, nil, text: left.text, view: :parallel
- else
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
@@ -21,8 +22,7 @@
- if right
- if right.meta?
- %td.old_line.diff-line-num.empty-cell
- %td.line_content.parallel.match= left.text
+ = diff_match_line nil, right.new_pos, text: left.text, view: :parallel
- else
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
@@ -37,3 +37,5 @@
- discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file)
- if discussion_left || discussion_right
= render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right
+ - if !diff_file.new_file && last_line > 0
+ = diff_match_line last_line, last_line, bottom: true, view: :parallel
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index ea2a3e01277..e751dabdf99 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,7 +2,7 @@
.commit-stat-summary
Showing
= link_to '#', class: 'js-toggle-button' do
- %strong #{pluralize(diff_files.count, "changed file")}
+ %strong #{pluralize(diff_files.size, "changed file")}
with
%strong.cgreen #{diff_files.sum(&:added_lines)} additions
and
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 5970b9abf2b..ab5463ba89d 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -15,6 +15,5 @@
- if discussion
= render "discussions/diff_discussion", discussion: discussion
- - if last_line > 0
- = render "projects/diffs/match_line", { line: "",
- line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
+ - if !diff_file.new_file && last_line > 0
+ = diff_match_line last_line, last_line, bottom: true
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 10fa1ddf2e5..295a1b62535 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -11,5 +11,5 @@
= link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm"
%p
To preserve performance only
- %strong #{diff_files.count} of #{diff_files.real_size}
+ %strong #{diff_files.size} of #{diff_files.real_size}
files are displayed.
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 921155e970b..b282aa52b25 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -4,6 +4,7 @@
%h4.prepend-top-0
Project settings
.col-lg-9
+ .project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset.append-bottom-0
.form-group
@@ -190,6 +191,7 @@
%h4.prepend-top-0.warning-title
Rename repository
.col-lg-9
+ = render 'projects/errors'
= form_for([@project.namespace.becomes(Namespace), @project]) do |f|
.form-group.project_name_holder
= f.label :name, class: 'label-light' do
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index e2453395602..36a6162a5a8 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -2,8 +2,12 @@
%tr.environment
%td
- %strong
- = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
+ = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment)
+
+ %td
+ - if last_deployment
+ = user_avatar(user: last_deployment.user, size: 20)
+ %strong ##{last_deployment.id}
%td
- if last_deployment
diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml
index c07f4bd510c..6d040f5cfe6 100644
--- a/app/views/projects/environments/_form.html.haml
+++ b/app/views/projects/environments/_form.html.haml
@@ -1,7 +1,22 @@
-= form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: 'col-lg-9' } do |f|
- = form_errors(@environment)
- .form-group
- = f.label :name, 'Name', class: 'label-light'
- = f.text_field :name, required: true, class: 'form-control'
- = f.submit 'Create environment', class: 'btn btn-create'
- = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Environments
+ %p
+ Environments allow you to track deployments of your application
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci/environments")
+
+ = form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f|
+ = form_errors(@environment)
+
+ .form-group
+ = f.label :name, 'Name', class: 'label-light'
+ = f.text_field :name, required: true, class: 'form-control'
+ .form-group
+ = f.label :external_url, 'External URL', class: 'label-light'
+ = f.url_field :external_url, class: 'form-control'
+
+ .form-actions
+ = f.submit 'Save', class: 'btn btn-save'
+ = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
new file mode 100644
index 00000000000..6d1bdb9320f
--- /dev/null
+++ b/app/views/projects/environments/edit.html.haml
@@ -0,0 +1,6 @@
+- page_title "Edit", @environment.name, "Environments"
+
+%h3.page-title
+ Edit environment
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index a6dd34653ab..b3eb5b0011a 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -23,10 +23,11 @@
New environment
- else
.table-holder
- %table.table.environments
+ %table.table.builds.environments
%tbody
%th Environment
- %th Last deployment
- %th Date
+ %th Last Deployment
+ %th Commit
+ %th
%th
= render @environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 89e06567196..e51667ade2d 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,12 +1,6 @@
- page_title 'New Environment'
-.row.prepend-top-default.append-bottom-default
- .col-lg-3
- %h4.prepend-top-0
- New Environment
- %p
- Environments allow you to track deployments of your application
- = succeed "." do
- = link_to "Read more about environments", help_page_path("ci/environments")
-
- = render 'form'
+%h3.page-title
+ New environment
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index b8b1ce52a91..8f8c1c4ce22 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -6,10 +6,10 @@
.top-area
.col-md-9
%h3.page-title= @environment.name.capitalize
-
.col-md-3
.nav-controls
- if can?(current_user, :update_environment, @environment)
+ = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
= link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
- if @deployments.blank?
@@ -23,13 +23,13 @@
= link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
- %table.table.environments
+ %table.table.builds.environments
%thead
%tr
%th ID
%th Commit
%th Build
- %th Date
+ %th
%th
= render @deployments
diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml
index c58223fd39e..195f18afc76 100644
--- a/app/views/projects/graphs/ci/_build_times.haml
+++ b/app/views/projects/graphs/ci/_build_times.haml
@@ -19,4 +19,9 @@
]
}
var ctx = $("#build_timesChart").get(0).getContext("2d");
- new Chart(ctx).Bar(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+ var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
+ new Chart(ctx).Bar(data, options);
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
index 8fca07114fa..1fbf6ca2c1c 100644
--- a/app/views/projects/graphs/ci/_builds.haml
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -48,4 +48,9 @@
]
}
var ctx = $("##{scope}Chart").get(0).getContext("2d");
- new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, maintainAspectRatio: false});
+ var options = { scaleOverlay: true, responsive: true, maintainAspectRatio: false };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
+ new Chart(ctx).Line(data, options);
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 65db8af494d..7e34a89f9ae 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -59,6 +59,10 @@
var container = $(selector).parent();
var generateChart = function() {
selector.attr('width', $(container).width());
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8
+ }
return new Chart(ctx).Bar(data, options);
};
// enabling auto-resizing
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index a985b442b2d..ac5f792d140 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -32,7 +32,7 @@
:javascript
$.ajax({
type: "GET",
- url: location.href,
+ url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}",
dataType: "json",
success: function (data) {
var graph = new ContributorsStatGraph();
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
index 403adb7426b..60b45115b73 100644
--- a/app/views/projects/issues/_head.html.haml
+++ b/app/views/projects/issues/_head.html.haml
@@ -2,7 +2,7 @@
%ul{ class: (container_class) }
- if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
= nav_link(controller: :issues) do
- = link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do
+ = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do
%span
Issues
diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml
new file mode 100644
index 00000000000..72669372497
--- /dev/null
+++ b/app/views/projects/issues/_issue_by_email.html.haml
@@ -0,0 +1,27 @@
+.issues-footer.text-center
+ %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } }
+ Email a new issue to this project
+
+#issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" }
+ .modal-dialog{ role: "document" }
+ .modal-content
+ .modal-header
+ %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } }
+ %span{ aria: { hidden: "true" } }= icon("times")
+ %h4.modal-title
+ Create new issue by email
+ .modal-body
+ %p
+ Write an email to the below email address. (This is a private email address, so keep it secret.)
+ .email-modal-input-group.input-group
+ = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#issue_email')
+ %p
+ Send an email to this address to create an issue.
+ %p
+ Use the subject line as the title of your issue.
+ %p
+ Use the message as the body of your issue (feel free to include some nice
+ = succeed ")." do
+ = link_to "Markdown", help_page_path('markdown', 'markdown')
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index c6fc499a7b8..6ea9f612d13 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -4,8 +4,8 @@
%ul.unstyled-list
- @related_branches.each do |branch|
%li
- - sha = @project.repository.find_branch(branch).target
- - pipeline = @project.pipeline(sha, branch) if sha
+ - target = @project.repository.find_branch(branch).target
+ - pipeline = @project.pipeline(target.sha, branch) if target
- if pipeline
%span.related-branch-ci-status
= render_pipeline_status(pipeline)
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 7612fe3719a..1a87045aa60 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,5 +1,6 @@
- @no_container = true
- page_title "Issues"
+- new_issue_email = @project.new_issue_address(current_user)
= render "projects/issues/head"
= content_for :meta_tags do
@@ -18,12 +19,20 @@
Subscribe
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = link_to new_namespace_project_issue_path(@project.namespace,
+ @project,
+ issue: { assignee_id: issues_finder.assignee.try(:id),
+ milestone_id: issues_finder.milestones.first.try(:id) }),
+ class: "btn btn-new",
+ title: "New Issue",
+ id: "new_issue_link" do
New Issue
= render 'shared/issuable/filter', type: :issues
.issues-holder
- = render "issues"
+ = render 'issues'
+ - if new_issue_email
+ = render 'issue_by_email', email: new_issue_email
- else
.blank-state.blank-state-welcome
%h2.blank-state-title.blank-state-welcome-title
@@ -40,3 +49,5 @@
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue
+ - if new_issue_email
+ = render 'issue_by_email', email: new_issue_email
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 9b6a97c0959..9f1a046ea74 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -37,14 +37,19 @@
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
= link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ - if @issue.submittable_as_spam? && current_user.admin?
+ %li
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
+
- if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- if can?(current_user, :update_issue, @issue)
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' do
- Edit
+ - if @issue.submittable_as_spam? && current_user.admin?
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index a5e67b95727..598bd743676 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -42,7 +42,7 @@
%h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits.
%p To preserve performance the line changes are not shown.
- else
- = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false
+ = render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false
- if @pipeline
#builds.builds.tab-pane
= render "projects/merge_requests/show/builds"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 13cacf45fb5..d97c36cf63b 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -2,7 +2,7 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
-- if diff_view == 'parallel'
+- if diff_view == :parallel
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 1b0bae86ad4..013b05628fa 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,6 +1,5 @@
- if @merge_request_diff.collected?
- = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options),
- project: @merge_request.project, diff_refs: @merge_request.diff_refs
+ = render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
- else
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 6ef640bb654..494695a03a5 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -42,3 +42,16 @@
.ci_widget.ci-error{style: "display:none"}
= icon("times-circle")
Could not connect to the CI server. Please check your settings and try again.
+
+- @merge_request.environments.each do |environment|
+ .mr-widget-heading
+ .ci_widget.ci-success
+ = ci_icon_for_status("success")
+ %span.hidden-sm
+ Deployed to
+ = succeed '.' do
+ = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: 'environment'
+ - external_url = environment.external_url
+ - if external_url
+ = link_to external_url, target: '_blank' do
+ = icon('external-link', text: "View on #{external_url.gsub(/\A.*?:\/\//, '')}", right: true)
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index facdfcc9447..adcc984f506 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -46,28 +46,18 @@
%div
- if github_import_enabled?
= link_to new_import_github_path, class: 'btn import_github' do
- = icon 'github', text: 'GitHub'
+ = icon('github', text: 'GitHub')
%div
- if bitbucket_import_enabled?
- - if bitbucket_import_configured?
- = link_to status_import_bitbucket_path, class: 'btn import_bitbucket', "data-no-turbolink" => "true" do
- %i.fa.fa-bitbucket
- Bitbucket
- - else
- = link_to status_import_bitbucket_path, class: 'how_to_import_link btn import_bitbucket', "data-no-turbolink" => "true" do
- %i.fa.fa-bitbucket
- Bitbucket
+ = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", "data-no-turbolink" => "true" do
+ = icon('bitbucket', text: 'Bitbucket')
+ - unless bitbucket_import_configured?
= render 'bitbucket_import_modal'
%div
- if gitlab_import_enabled?
- - if gitlab_import_configured?
- = link_to status_import_gitlab_path, class: 'btn import_gitlab' do
- %i.fa.fa-heart
- GitLab.com
- - else
- = link_to status_import_gitlab_path, class: 'how_to_import_link btn import_gitlab' do
- %i.fa.fa-heart
- GitLab.com
+ = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless bitbucket_import_configured?}" do
+ = icon('gitlab', text: 'GitLab.com')
+ - unless gitlab_import_configured?
= render 'gitlab_import_modal'
%div
- if gitorious_import_enabled?
@@ -77,23 +67,19 @@
%div
- if google_code_import_enabled?
= link_to new_import_google_code_path, class: 'btn import_google_code' do
- %i.fa.fa-google
- Google Code
+ = icon('google', text: 'Google Code')
%div
- if fogbugz_import_enabled?
= link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
- %i.fa.fa-bug
- Fogbugz
+ = icon('bug', text: 'Fogbugz')
%div
- if git_import_enabled?
= link_to "#", class: 'btn js-toggle-button import_git' do
- %i.fa.fa-git
- %span Repo by URL
+ = icon('git', text: 'Repo by URL')
%div{ class: 'import_gitlab_project' }
- if gitlab_project_import_enabled?
= link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do
- %i.fa.fa-gitlab
- %span GitLab export
+ = icon('gitlab', text: 'GitLab export')
.js-toggle-content.hide
= render "shared/import_form", f: f
@@ -159,4 +145,4 @@
$('.import_git').click(function( event ) {
$projectImportUrl = $('#project_import_url')
$projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'))
- }); \ No newline at end of file
+ });
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index 5f4ec2e40c8..55202725b9e 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -9,7 +9,7 @@
.form-group
= f.label :ref, 'Create for', class: 'control-label'
.col-sm-10
- = f.text_field :ref, required: true, tabindex: 2, class: 'form-control'
+ = f.text_field :ref, required: true, tabindex: 2, class: 'form-control js-branch-name ui-autocomplete-input', autocomplete: :false, id: :ref
.help-block Existing branch name, tag
.form-actions
= f.submit 'Create pipeline', class: 'btn btn-create', tabindex: 3
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml
new file mode 100644
index 00000000000..7b7fa56d993
--- /dev/null
+++ b/app/views/projects/pipelines_settings/_badge.html.haml
@@ -0,0 +1,27 @@
+.row{ class: badge.title.gsub(' ', '-') }
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ = badge.title.capitalize
+ .col-lg-9
+ .prepend-top-10
+ .panel.panel-default
+ .panel-heading
+ %b
+ = badge.title.capitalize
+ &middot;
+ = badge.to_html
+ .pull-right
+ = render 'shared/ref_switcher', destination: 'badges', align_right: true
+ .panel-body
+ .row
+ .col-md-2.text-center
+ Markdown
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.md', badge.to_markdown)
+ .row
+ %hr
+ .row
+ .col-md-2.text-center
+ HTML
+ .col-md-10.code.js-syntax-highlight
+ = highlight('.html', badge.to_html)
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
index 228bad36ebd..8c7222bfe3d 100644
--- a/app/views/projects/pipelines_settings/show.html.haml
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -77,27 +77,4 @@
%hr
.row.prepend-top-default
- .col-lg-3.profile-settings-sidebar
- %h4.prepend-top-0
- Builds Badge
- .col-lg-9
- .prepend-top-10
- .panel.panel-default
- .panel-heading
- %b Builds badge &middot;
- = @build_badge.to_html
- .pull-right
- = render 'shared/ref_switcher', destination: 'badges', align_right: true
- .panel-body
- .row
- .col-md-2.text-center
- Markdown
- .col-md-10.code.js-syntax-highlight
- = highlight('.md', @build_badge.to_markdown)
- .row
- %hr
- .row
- .col-md-2.text-center
- HTML
- .col-md-10.code.js-syntax-highlight
- = highlight('.html', @build_badge.to_html)
+ = render partial: 'badge', collection: @badges
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 720d67dff7c..04b19a8c5a7 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,28 +1,28 @@
-%h5.prepend-top-0
- Already Protected (#{@protected_branches.size})
-- if @protected_branches.empty?
- %p.settings-message.text-center
- No branches are protected, protect a branch with the form above.
-- else
- - can_admin_project = can?(current_user, :admin_project, @project)
- .table-responsive
- %table.table.protected-branches-list
+.panel.panel-default.protected-branches-list
+ - if @protected_branches.empty?
+ .panel-heading
+ %h3.panel-title
+ Protected branch (#{@protected_branches.size})
+ %p.settings-message.text-center
+ There are currently no protected branches, protect a branch with the form above.
+ - else
+ - can_admin_project = can?(current_user, :admin_project, @project)
+
+ %table.table.table-bordered
%colgroup
- %col{ width: "20%" }
- %col{ width: "30%" }
%col{ width: "25%" }
+ %col{ width: "30%" }
%col{ width: "25%" }
- - if can_admin_project
- %col
+ %col{ width: "20%" }
%thead
%tr
- %th Protected Branch
- %th Commit
- %th Developers Can Push
- %th Developers Can Merge
+ %th Protected branch (#{@protected_branches.size})
+ %th Last commit
+ %th Allowed to merge
+ %th Allowed to push
- if can_admin_project
%th
%tbody
= render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
- = paginate @protected_branches, theme: 'gitlab'
+ = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
new file mode 100644
index 00000000000..85d0c494ba8
--- /dev/null
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -0,0 +1,36 @@
+= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
+ .panel.panel-default
+ .panel-heading
+ %h3.panel-title
+ Protect a branch
+ .panel-body
+ .form-horizontal
+ .form-group
+ = f.label :name, class: 'col-md-2 text-right' do
+ Branch:
+ .col-md-10
+ = render partial: "dropdown", locals: { f: f }
+ .help-block
+ = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
+ such as
+ %code *-stable
+ or
+ %code production/*
+ are supported
+ .form-group
+ %label.col-md-2.text-right{ for: 'merge_access_level_attributes' }
+ Allowed to merge:
+ .col-md-10
+ = dropdown_tag('Select',
+ options: { toggle_class: 'js-allowed-to-merge wide',
+ data: { field_name: 'protected_branch[merge_access_level_attributes][access_level]', input_id: 'merge_access_level_attributes' }})
+ .form-group
+ %label.col-md-2.text-right{ for: 'push_access_level_attributes' }
+ Allowed to push:
+ .col-md-10
+ = dropdown_tag('Select',
+ options: { toggle_class: 'js-allowed-to-push wide',
+ data: { field_name: 'protected_branch[push_access_level_attributes][access_level]', input_id: 'push_access_level_attributes' }})
+
+ .panel-footer
+ = f.submit 'Protect', class: 'btn-create btn', disabled: true
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml
index b803d932e67..a9e27df5a87 100644
--- a/app/views/projects/protected_branches/_dropdown.html.haml
+++ b/app/views/projects/protected_branches/_dropdown.html.haml
@@ -1,17 +1,15 @@
= f.hidden_field(:name)
-= dropdown_tag("Protected Branch",
- options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit',
+= dropdown_tag('Select branch or create wildcard',
+ options: { toggle_class: 'js-protected-branch-select js-filter-submit wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_branch_name],
project_id: @project.try(:id) } }) do
- %ul.dropdown-footer-list.hidden.protected-branch-select-footer-list
+ %ul.dropdown-footer-list
%li
= link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do
- Create new
-
-:javascript
- new ProtectedBranchSelect();
+ Create wildcard
+ %code
diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml
index 7fda7f96047..e2e01ee78f8 100644
--- a/app/views/projects/protected_branches/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -1,5 +1,4 @@
-- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
-%tr
+%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } }
%td
= protected_branch.name
- if @project.root_ref?(protected_branch.name)
@@ -15,9 +14,15 @@
- else
(branch was removed from repository)
%td
- = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
+ = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
+ = dropdown_tag( (protected_branch.merge_access_level.humanize || 'Select') ,
+ options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container',
+ data: { field_name: "allowed_to_merge_#{protected_branch.id}" }})
%td
- = check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url })
+ = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level
+ = dropdown_tag( (protected_branch.push_access_level.humanize || 'Select') ,
+ options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container',
+ data: { field_name: "allowed_to_push_#{protected_branch.id}" }})
- if can_admin_project
%td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index 950df740bbc..49dcc9a6ba4 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -14,37 +14,7 @@
%li prevent <strong>anyone</strong> from deleting the branch
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
- %h5.prepend-top-0
- Protect a branch
- if can? current_user, :admin_project, @project
- = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
- = form_errors(@protected_branch)
+ = render 'create_protected_branch'
- .form-group
- = f.label :name, "Branch", class: "label-light"
- = render partial: "dropdown", locals: { f: f }
- %p.help-block
- = link_to "Wildcards", help_page_path('user/project/protected_branches', anchor: "wildcard-protected-branches")
- such as
- %code *-stable
- or
- %code production/*
- are supported.
-
- .form-group
- = f.check_box :developers_can_push, class: "pull-left"
- .prepend-left-20
- = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
- %p.light.append-bottom-0
- Allow developers to push to this branch
-
- .form-group
- = f.check_box :developers_can_merge, class: "pull-left"
- .prepend-left-20
- = f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0"
- %p.light.append-bottom-0
- Allow developers to accept merge requests to this branch
- = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
-
- %hr
= render "branches_list"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index dd1cf680cfa..a666d07e9eb 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -43,6 +43,10 @@
%li
= link_to 'Contribution guide', contribution_guide_path(@project)
+ - if @repository.gitlab_ci_yml
+ %li
+ = link_to 'CI configuration', ci_configuration_path(@project)
+
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index a3a4bd4f752..84da16b6bb1 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
%span.str-truncated
- = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
+ = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link"
diff --git a/app/views/projects/tree/_tree_row.html.haml b/app/views/projects/tree/_tree_row.html.haml
new file mode 100644
index 00000000000..0a5c6f048f7
--- /dev/null
+++ b/app/views/projects/tree/_tree_row.html.haml
@@ -0,0 +1,6 @@
+- if tree_row.type == :tree
+ = render partial: 'projects/tree/tree_item', object: tree_row, as: 'tree_item', locals: { type: 'folder' }
+- elsif tree_row.type == :blob
+ = render partial: 'projects/tree/blob_item', object: tree_row, as: 'blob_item', locals: { type: 'file' }
+- elsif tree_row.type == :commit
+ = render partial: 'projects/tree/submodule_item', object: tree_row, as: 'submodule_item'
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 7d9bd08385a..dcf1f767bf7 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.project-edit-content .btn-save').enable();
+ $('.edit-project .btn-save').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 797a1a59e9f..643f7c589e6 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -18,9 +18,14 @@
.error-alert
.help-block
- To link to a (new) page, simply type
- %code [Link Title](page-slug)
- \.
+ = succeed '.' do
+ To link to a (new) page, simply type
+ %code [Link Title](page-slug)
+
+ = succeed '.' do
+ More examples are in the
+ = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown")
+
.form-group
= f.label :commit_message, class: 'control-label'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 8163aff43b6..e0400083870 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,6 +1,7 @@
- project = note.project
- note_url = Gitlab::UrlBuilder.build(note)
-- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
+- noteable_identifier = note.noteable.try(:iid) || note.noteable.try(:id)
+
.search-result-row
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
@@ -10,7 +11,10 @@
&middot;
- if note.for_commit?
- = link_to "Commit #{truncate_sha(note.commit_id)}", note_url
+ = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do
+ = truncate_sha(note.commit_id)
+ %span.light Commit deleted
+
- else
%span #{note.noteable_type.titleize} ##{noteable_identifier}
&middot;
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
index dce492352ac..e324d0e5203 100644
--- a/app/views/shared/_labels_row.html.haml
+++ b/app/views/shared/_labels_row.html.haml
@@ -1,9 +1,5 @@
- labels.each do |label|
%span.label-row.btn-group{ role: "group", aria: { label: label.name }, style: "color: #{text_color_for_bg(label.color)}" }
- = link_to label.name, label_filter_path(@project, label, type: controller.controller_name),
- class: "btn btn-transparent has-tooltip",
- style: "background-color: #{label.color};",
- title: escape_once(label.description),
- data: { container: "body" }
+ = link_to_label(label, css_class: 'btn btn-transparent')
%button.btn.btn-transparent.label-remove.js-label-filter-remove{ type: "button", style: "background-color: #{label.color};", data: { label: label.title } }
= icon("times")
diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_cancel.svg
index 6a0bc1490c4..fd1ebbcbabd 100644
--- a/app/views/shared/icons/_icon_status_cancel.svg
+++ b/app/views/shared/icons/_icon_status_cancel.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#5C5C5C" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <rect width="10" height="1" x="2" y="6.5" fill="#5C5C5C" transform="rotate(45 7 7)" rx=".3"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#5C5C5C" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <rect width="8" height="2" x="3" y="6" transform="rotate(45 7 7)" rx=".5"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg
index c41ca18cae7..e56e0887416 100644
--- a/app/views/shared/icons/_icon_status_failed.svg
+++ b/app/views/shared/icons/_icon_status_failed.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#D22852" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <path fill="#D22852" d="M7.5,6.5 L7.5,4.30578971 C7.5,4.12531853 7.36809219,4 7.20537567,4 L6.79462433,4 C6.63904572,4 6.5,4.13690672 6.5,4.30578971 L6.5,6.5 L4.30578971,6.5 C4.12531853,6.5 4,6.63190781 4,6.79462433 L4,7.20537567 C4,7.36095428 4.13690672,7.5 4.30578971,7.5 L6.5,7.5 L6.5,9.69421029 C6.5,9.87468147 6.63190781,10 6.79462433,10 L7.20537567,10 C7.36095428,10 7.5,9.86309328 7.5,9.69421029 L7.5,7.5 L9.69421029,7.5 C9.87468147,7.5 10,7.36809219 10,7.20537567 L10,6.79462433 C10,6.63904572 9.86309328,6.5 9.69421029,6.5 L7.5,6.5 Z" transform="rotate(45 7 7)"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#D22852" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7.72916667,6.27083333 L7.72916667,4.28939247 C7.72916667,4.12531853 7.59703895,4 7.43405116,4 L6.56594884,4 C6.40541585,4 6.27083333,4.12956542 6.27083333,4.28939247 L6.27083333,6.27083333 L4.28939247,6.27083333 C4.12531853,6.27083333 4,6.40296105 4,6.56594884 L4,7.43405116 C4,7.59458415 4.12956542,7.72916667 4.28939247,7.72916667 L6.27083333,7.72916667 L6.27083333,9.71060753 C6.27083333,9.87468147 6.40296105,10 6.56594884,10 L7.43405116,10 C7.59458415,10 7.72916667,9.87043458 7.72916667,9.71060753 L7.72916667,7.72916667 L9.71060753,7.72916667 C9.87468147,7.72916667 10,7.59703895 10,7.43405116 L10,6.56594884 C10,6.40541585 9.87043458,6.27083333 9.71060753,6.27083333 L7.72916667,6.27083333 Z" transform="rotate(-45 7 7)"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg
index 035cd8b4ccc..117f0367161 100644
--- a/app/views/shared/icons/_icon_status_pending.svg
+++ b/app/views/shared/icons/_icon_status_pending.svg
@@ -1,13 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#E75E40" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <rect width="1" height="4" x="5" y="5" fill="#E75E40" rx=".3"/>
- <rect width="1" height="4" x="8" y="5" fill="#E75E40" rx=".3"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#E75E40" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M4.69999981,5.30065012 C4.69999981,5.13460564 4.83842754,5 5.00354719,5 L5.89645243,5 C6.06409702,5 6.19999981,5.13308716 6.19999981,5.30065012 L6.19999981,8.69934988 C6.19999981,8.86539436 6.06157207,9 5.89645243,9 L5.00354719,9 C4.8359026,9 4.69999981,8.86691284 4.69999981,8.69934988 L4.69999981,5.30065012 Z M7.69999981,5.30065012 C7.69999981,5.13460564 7.83842754,5 8.00354719,5 L8.89645243,5 C9.06409702,5 9.19999981,5.13308716 9.19999981,5.30065012 L9.19999981,8.69934988 C9.19999981,8.86539436 9.06157207,9 8.89645243,9 L8.00354719,9 C7.8359026,9 7.69999981,8.86691284 7.69999981,8.69934988 L7.69999981,5.30065012 Z"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg
index a48b3a25099..920d7952eb5 100644
--- a/app/views/shared/icons/_icon_status_running.svg
+++ b/app/views/shared/icons/_icon_status_running.svg
@@ -1,12 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#2D9FD8" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <path fill="#2D9FD8" d="M7,3.00800862 C9.09023405,3.13960661 10.7448145,4.87657932 10.7448145,7 C10.7448145,9.209139 8.95395346,11 6.74481446,11 C5.4560962,11 4.30972054,10.3905589 3.57817301,9.44416214 L7,7 L7,3.00800862 Z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#2D9FD8" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7,3 C9.209139,3 11,4.790861 11,7 C11,9.209139 9.209139,11 7,11 C5.65802855,11 4.47040669,10.3391508 3.74481446,9.32513253 L7,7 L7,3 L7,3 Z"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
index 260eab013a3..67b378b3571 100644
--- a/app/views/shared/icons/_icon_status_success.svg
+++ b/app/views/shared/icons/_icon_status_success.svg
@@ -1,15 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <use stroke="#31AF64" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
- <g fill="#31AF64" transform="rotate(45 -.13 10.953)">
- <rect width="1" height="5" x="2" rx=".3"/>
- <rect width="3" height="1" y="4" rx=".3"/>
- </g>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#31AF64" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M7.29166667,7.875 L5.54840803,7.875 C5.38293028,7.875 5.25,8.00712771 5.25,8.17011551 L5.25,9.03821782 C5.25,9.19875081 5.38360183,9.33333333 5.54840803,9.33333333 L8.24853534,9.33333333 C8.52035522,9.33333333 8.75,9.11228506 8.75,8.83960819 L8.75,8.46475969 L8.75,4.07392947 C8.75,3.92144267 8.61787229,3.79166667 8.45488449,3.79166667 L7.58678218,3.79166667 C7.42624919,3.79166667 7.29166667,3.91804003 7.29166667,4.07392947 L7.29166667,7.875 Z" transform="rotate(45 7 6.563)"/>
</g>
</svg>
diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg
index d47e7a1c93f..d0ad4bd65b1 100644
--- a/app/views/shared/icons/_icon_status_warning.svg
+++ b/app/views/shared/icons/_icon_status_warning.svg
@@ -1,15 +1,6 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- <circle id="a" cx="7" cy="7" r="7"/>
- <mask id="b" width="14" height="14" x="0" y="0" fill="white">
- <use xlink:href="#a"/>
- </mask>
- </defs>
- <g fill="none" fill-rule="evenodd">
- <g fill="#FF8A24" transform="translate(6 3)">
- <rect width="2" height="5" rx=".5"/>
- <rect width="2" height="2" y="6" rx=".5"/>
- </g>
- <use stroke="#FF8A24" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
+ <g fill="#FF8A24" fill-rule="evenodd">
+ <path d="M12.5,7 C12.5,3.96243388 10.0375661,1.5 7,1.5 C3.96243388,1.5 1.5,3.96243388 1.5,7 C1.5,10.0375661 3.96243388,12.5 7,12.5 C10.0375661,12.5 12.5,10.0375661 12.5,7 Z M0,7 C0,3.13400675 3.13400675,0 7,0 C10.8659932,0 14,3.13400675 14,7 C14,10.8659932 10.8659932,14 7,14 C3.13400675,14 0,10.8659932 0,7 Z"/>
+ <path d="M6,3.49769878 C6,3.22282734 6.21403503,3 6.50468445,3 L7.49531555,3 C7.77404508,3 8,3.21484375 8,3.49769878 L8,7.50230122 C8,7.77717266 7.78596497,8 7.49531555,8 L6.50468445,8 C6.22595492,8 6,7.78515625 6,7.50230122 L6,3.49769878 Z M6,9.50468445 C6,9.22595492 6.21403503,9 6.50468445,9 L7.49531555,9 C7.77404508,9 8,9.21403503 8,9.50468445 L8,10.4953156 C8,10.7740451 7.78596497,11 7.49531555,11 L6.50468445,11 C6.22595492,11 6,10.785965 6,10.4953156 L6,9.50468445 Z"/>
</g>
</svg>
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index 0acb8253139..4e280c371ac 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -4,7 +4,7 @@
- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels')
.dropdown-page-one
= dropdown_title(title)
- = dropdown_filter(filter_placeholder, search_id: "label-name")
+ = dropdown_filter(filter_placeholder)
= dropdown_content
- if @project && show_footer
= dropdown_footer do
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 5ae485f36ba..fc6e206d082 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -1,4 +1,4 @@
-- show_roles = local_assigns.fetch(:show_roles, default_show_roles(member))
+- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
- user = member.user
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index b8b66d08db8..281ec728e41 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,6 +12,8 @@
%li.project-row{ class: css_class }
= cache(cache_key) do
.controls
+ - if project.archived
+ %span.label.label-warning archived
- if project.commit.try(:status)
%span
= render_commit_status(project.commit)
@@ -24,7 +26,7 @@
= icon('star')
= project.star_count
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)}
- = visibility_level_icon(project.visibility_level, fw: false)
+ = visibility_level_icon(project.visibility_level, fw: true)
.title
= link_to project_path(project), class: dom_class(project) do
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 2585ed9360b..470dac6d75b 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -19,7 +19,7 @@
= f.label :token, "Secret Token", class: 'label-light'
= f.text_field :token, class: "form-control", placeholder: ''
%p.help-block
- Use this token to validate received payloads
+ Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header.
.form-group
= f.label :url, "Trigger", class: 'label-light'
%ul.list-unstyled
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index f2649e38eb3..842eebdea9e 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -21,31 +21,35 @@ class EmailReceiverWorker
return unless raw.present?
can_retry = false
- reason = nil
-
- case e
- when Gitlab::Email::Receiver::SentNotificationNotFoundError
- reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::EmptyEmailError
- can_retry = true
- reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
- when Gitlab::Email::Receiver::AutoGeneratedEmailError
- reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::UserNotFoundError
- reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
- when Gitlab::Email::Receiver::UserBlockedError
- reason = "Your account has been blocked. If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::UserNotAuthorizedError
- reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::NoteableNotFoundError
- reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
- when Gitlab::Email::Receiver::InvalidNoteError
- can_retry = true
- reason = e.message
- else
- return
+ reason =
+ case e
+ when Gitlab::Email::UnknownIncomingEmail
+ "We couldn't figure out what the email is for. Please create your issue or comment through the web interface."
+ when Gitlab::Email::SentNotificationNotFoundError
+ "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
+ when Gitlab::Email::ProjectNotFound
+ "We couldn't find the project. Please check if there's any typo."
+ when Gitlab::Email::EmptyEmailError
+ can_retry = true
+ "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
+ when Gitlab::Email::AutoGeneratedEmailError
+ "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
+ when Gitlab::Email::UserNotFoundError
+ "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
+ when Gitlab::Email::UserBlockedError
+ "Your account has been blocked. If you believe this is in error, contact a staff member."
+ when Gitlab::Email::UserNotAuthorizedError
+ "You are not allowed to perform this action. If you believe this is in error, contact a staff member."
+ when Gitlab::Email::NoteableNotFoundError
+ "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
+ when Gitlab::Email::InvalidNoteError,
+ Gitlab::Email::InvalidIssueError
+ can_retry = true
+ e.message
+ end
+
+ if reason
+ EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
-
- EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later
end
end
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 0b6a01a3200..c6a5af2809a 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -33,25 +33,14 @@ class EmailsOnPushWorker
reverse_compare = false
if action == :push
- merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
- compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
-
- diff_refs = Gitlab::Diff::DiffRefs.new(
- base_sha: merge_base_sha,
- start_sha: before_sha,
- head_sha: after_sha
- )
+ compare = CompareService.new.execute(project, before_sha, project, after_sha)
+ diff_refs = compare.diff_refs
return false if compare.same
if compare.commits.empty?
- compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
-
- diff_refs = Gitlab::Diff::DiffRefs.new(
- base_sha: merge_base_sha,
- start_sha: after_sha,
- head_sha: before_sha
- )
+ compare = CompareService.new.execute(project, after_sha, project, before_sha)
+ diff_refs = compare.diff_refs
reverse_compare = true
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
new file mode 100644
index 00000000000..5048746f09b
--- /dev/null
+++ b/app/workers/group_destroy_worker.rb
@@ -0,0 +1,17 @@
+class GroupDestroyWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(group_id, user_id)
+ begin
+ group = Group.with_deleted.find(group_id)
+ rescue ActiveRecord::RecordNotFound
+ return
+ end
+
+ user = User.find(user_id)
+
+ DestroyGroupService.new(group, user).execute
+ end
+end
diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb
new file mode 100644
index 00000000000..72e3a9ae734
--- /dev/null
+++ b/app/workers/import_export_project_cleanup_worker.rb
@@ -0,0 +1,9 @@
+class ImportExportProjectCleanupWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform
+ ImportExportCleanUpService.new.execute
+ end
+end
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 605ec4f04e5..19f38358eb5 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -141,8 +141,10 @@ class IrkerWorker
end
def files_count(commit)
- files = "#{commit.diffs.real_size} file"
- files += 's' if commit.diffs.count > 1
+ diffs = commit.raw_diffs(deltas_only: true)
+
+ files = "#{diffs.real_size} file"
+ files += 's' if diffs.size > 1
files
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 09035a7cf2d..a9a2b716005 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -10,6 +10,10 @@ class PostReceive
log("Check gitlab.yml config for correct repositories.storages values. No repository storage path matches \"#{repo_path}\"")
end
+ changes = Base64.decode64(changes) unless changes.include?(' ')
+ # Use Sidekiq.logger so arguments can be correlated with execution
+ # time and thread ID's.
+ Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS']
post_received = Gitlab::GitPostReceive.new(repo_path, identifier, changes)
if post_received.project.nil?
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index b51c6a266c9..3062301a9b1 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -12,6 +12,6 @@ class ProjectDestroyWorker
user = User.find(user_id)
- ::Projects::DestroyService.new(project, user, params).execute
+ ::Projects::DestroyService.new(project, user, params.symbolize_keys).execute
end
end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index f7604e48f83..d69d6037053 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -4,7 +4,7 @@ class RepositoryForkWorker
sidekiq_options queue: :gitlab_shell
- def perform(project_id, source_path, target_path)
+ def perform(project_id, forked_from_repository_storage_path, source_path, target_path)
project = Project.find_by_id(project_id)
unless project.present?
@@ -12,7 +12,8 @@ class RepositoryForkWorker
return
end
- result = gitlab_shell.fork_repository(project.repository_storage_path, source_path, target_path)
+ result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path,
+ project.repository_storage_path, target_path)
unless result
logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}")
project.mark_import_as_failed('The project could not be forked.')
diff --git a/app/workers/gitlab_remove_project_export_worker.rb b/app/workers/requests_profiles_worker.rb
index 1d91897d520..9dd228a2483 100644
--- a/app/workers/gitlab_remove_project_export_worker.rb
+++ b/app/workers/requests_profiles_worker.rb
@@ -1,9 +1,9 @@
-class GitlabRemoveProjectExportWorker
+class RequestsProfilesWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform
- Project.remove_gitlab_exports!
+ Gitlab::RequestProfiler.remove_all_profiles
end
end
diff --git a/config/application.rb b/config/application.rb
index 06ebb14a5fe..4a9ed41cbf8 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -107,7 +107,8 @@ module Gitlab
end
end
- redis_config_hash = Gitlab::Redis.redis_store_options
+ # Use Redis caching across all environments
+ redis_config_hash = Gitlab::Redis.params
redis_config_hash[:namespace] = Gitlab::Redis::CACHE_NAMESPACE
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 293f2b71d65..74325872b09 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -68,6 +68,25 @@
:why: https://opensource.org/licenses/BSD-2-Clause
:versions: []
:when: 2016-05-02 05:55:09.796363000 Z
+- - :whitelist
+ - LGPLv2+
+ - :who: Stan Hu
+ :why: Equivalent to LGPLv2
+ :versions: []
+ :when: 2016-06-07 17:14:10.907682000 Z
+- - :whitelist
+ - Artistic 2.0
+ - :who: Josh Frye
+ :why: Disk/mount information display on Admin pages
+ :versions: []
+ :when: 2016-06-29 16:32:45.432113000 Z
+- - :whitelist
+ - Simplified BSD
+ - :who: Douwe Maan
+ :why: https://opensource.org/licenses/BSD-2-Clause
+ :versions: []
+ :when: 2016-07-26 21:24:07.248480000 Z
+
# LICENSE BLACKLIST
- - :blacklist
@@ -175,15 +194,3 @@
:why: https://github.com/jmcnevin/rubypants/blob/master/LICENSE.rdoc
:versions: []
:when: 2016-05-02 05:56:50.696858000 Z
-- - :whitelist
- - LGPLv2+
- - :who: Stan Hu
- :why: Equivalent to LGPLv2
- :versions: []
- :when: 2016-06-07 17:14:10.907682000 Z
-- - :whitelist
- - Artistic 2.0
- - :who: Josh Frye
- :why: Disk/mount information display on Admin pages
- :versions: []
- :when: 2016-06-29 16:32:45.432113000 Z
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 86f55210487..deac3b0f0f9 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -287,9 +287,12 @@ Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker'
Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *'
Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker'
-Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
-Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker'
+Settings.cron_jobs['import_export_project_cleanup_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['import_export_project_cleanup_worker']['cron'] ||= '0 * * * *'
+Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'ImportExportProjectCleanupWorker'
+Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
#
# GitLab Shell
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index e026151a032..ed88c8ee1b8 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,6 +1,3 @@
-# GIT over HTTP
-require_dependency Rails.root.join('lib/gitlab/backend/grack_auth')
-
# GIT over SSH
require_dependency Rails.root.join('lib/gitlab/backend/shell')
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index 37746968675..d92f64e1647 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -26,4 +26,4 @@ def validate_storages
end
end
-validate_storages unless Rails.env.test?
+validate_storages unless Rails.env.test? || ENV['SKIP_STORAGE_VALIDATION'] == 'true'
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 73977341b73..a0a8f88584c 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -100,6 +100,9 @@ Devise.setup do |config|
# secure: true in order to force SSL only cookies.
# config.cookie_options = {}
+ # Send a notification email when the user's password is changed
+ config.send_password_change_notification = true
+
# ==> Configuration for :validatable
# Range for password length. Default is 6..128.
config.password_length = 8..128
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index f3cddac5b36..52522e099e7 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -144,6 +144,13 @@ if Gitlab::Metrics.enabled?
end
config.instrument_methods(Rinku)
+ config.instrument_instance_methods(Repository)
+
+ config.instrument_methods(Gitlab::Highlight)
+ config.instrument_instance_methods(Gitlab::Highlight)
+
+ # This is a Rails scope so we have to instrument it manually.
+ config.instrument_method(Project, :visible_to_user)
end
GC::Profiler.enable
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index 3e553120205..f498732feca 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -12,3 +12,10 @@ Mime::Type.register_alias "text/html", :md
Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov]
Mime::Type.register "video/webm", :webm
Mime::Type.register "video/ogg", :ogv
+
+middlewares = Gitlab::Application.config.middleware
+middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, {
+ Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body|
+ ActiveSupport::JSON.decode(body)
+ end
+})
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
new file mode 100644
index 00000000000..a9aa802681a
--- /dev/null
+++ b/config/initializers/request_profiler.rb
@@ -0,0 +1,5 @@
+require 'gitlab/request_profiler/middleware'
+
+Rails.application.configure do |config|
+ config.middleware.use(Gitlab::RequestProfiler::Middleware)
+end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index dae3a4a9a93..291fa6c0abc 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -2,49 +2,86 @@
require 'securerandom'
-# Your secret key for verifying the integrity of signed cookies.
-# If you change this key, all old signed cookies will become invalid!
-# Make sure the secret is at least 30 characters and all random,
-# no regular words or you'll be exposed to dictionary attacks.
-
-def find_secure_token
- token_file = Rails.root.join('.secret')
- if ENV.key?('SECRET_KEY_BASE')
- ENV['SECRET_KEY_BASE']
- elsif File.exist? token_file
- # Use the existing token.
- File.read(token_file).chomp
- else
- # Generate a new token of 64 random hexadecimal characters and store it in token_file.
- token = SecureRandom.hex(64)
- File.write(token_file, token)
- token
+# Transition material in .secret to the secret_key_base key in config/secrets.yml.
+# Historically, ENV['SECRET_KEY_BASE'] takes precedence over .secret, so we maintain that
+# behavior.
+#
+# It also used to be the case that the key material in ENV['SECRET_KEY_BASE'] or .secret
+# was used to encrypt OTP (two-factor authentication) data so if present, we copy that key
+# material into config/secrets.yml under otp_key_base.
+#
+# Finally, if we have successfully migrated all secrets to config/secrets.yml, delete the
+# .secret file to avoid confusion.
+#
+def create_tokens
+ secret_file = Rails.root.join('.secret')
+ file_secret_key = File.read(secret_file).chomp if File.exist?(secret_file)
+ env_secret_key = ENV['SECRET_KEY_BASE']
+
+ # Ensure environment variable always overrides secrets.yml.
+ Rails.application.secrets.secret_key_base = env_secret_key if env_secret_key.present?
+
+ defaults = {
+ secret_key_base: file_secret_key || generate_new_secure_token,
+ otp_key_base: env_secret_key || file_secret_key || generate_new_secure_token,
+ db_key_base: generate_new_secure_token
+ }
+
+ missing_secrets = set_missing_keys(defaults)
+ write_secrets_yml(missing_secrets) unless missing_secrets.empty?
+
+ begin
+ File.delete(secret_file) if file_secret_key
+ rescue => e
+ warn "Error deleting useless .secret file: #{e}"
end
end
-Rails.application.config.secret_token = find_secure_token
-Rails.application.config.secret_key_base = find_secure_token
-
-# CI
def generate_new_secure_token
SecureRandom.hex(64)
end
-if Rails.application.secrets.db_key_base.blank?
- warn "Missing `db_key_base` for '#{Rails.env}' environment. The secrets will be generated and stored in `config/secrets.yml`"
+def warn_missing_secret(secret)
+ warn "Missing Rails.application.secrets.#{secret} for #{Rails.env} environment. The secret will be generated and stored in config/secrets.yml."
+end
- all_secrets = YAML.load_file('config/secrets.yml') if File.exist?('config/secrets.yml')
- all_secrets ||= {}
+def set_missing_keys(defaults)
+ defaults.stringify_keys.each_with_object({}) do |(key, default), missing|
+ if Rails.application.secrets[key].blank?
+ warn_missing_secret(key)
- # generate secrets
- env_secrets = all_secrets[Rails.env.to_s] || {}
- env_secrets['db_key_base'] ||= generate_new_secure_token
- all_secrets[Rails.env.to_s] = env_secrets
+ missing[key] = Rails.application.secrets[key] = default
+ end
+ end
+end
+
+def write_secrets_yml(missing_secrets)
+ secrets_yml = Rails.root.join('config/secrets.yml')
+ rails_env = Rails.env.to_s
+ secrets = YAML.load_file(secrets_yml) if File.exist?(secrets_yml)
+ secrets ||= {}
+ secrets[rails_env] ||= {}
+
+ secrets[rails_env].merge!(missing_secrets) do |key, old, new|
+ # Previously, it was possible this was set to the literal contents of an Erb
+ # expression that evaluated to an empty value. We don't want to support that
+ # specifically, just ensure we don't break things further.
+ #
+ if old.present?
+ warn <<EOM
+Rails.application.secrets.#{key} was blank, but the literal value in config/secrets.yml was:
+ #{old}
- # save secrets
- File.open('config/secrets.yml', 'w', 0600) do |file|
- file.write(YAML.dump(all_secrets))
+This probably isn't the expected value for this secret. To keep using a literal Erb string in config/secrets.yml, replace `<%` with `<%%`.
+EOM
+
+ exit 1 # rubocop:disable Rails/Exit
+ end
+
+ new
end
- Rails.application.secrets.db_key_base = env_secrets['db_key_base']
+ File.write(secrets_yml, YAML.dump(secrets), mode: 'w', perm: 0600)
end
+
+create_tokens
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index 0d9d87bac00..70be2617cab 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -13,9 +13,9 @@ end
if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else
- redis_config = Gitlab::Redis.redis_store_options
+ redis_config = Gitlab::Redis.params
redis_config[:namespace] = Gitlab::Redis::SESSION_NAMESPACE
-
+
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: redis_config,
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 5e839327e7a..f7e714cd6bc 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,12 +1,14 @@
+# Custom Redis configuration
+redis_config_hash = Gitlab::Redis.params
+redis_config_hash[:namespace] = Gitlab::Redis::SIDEKIQ_NAMESPACE
+
Sidekiq.configure_server do |config|
- config.redis = {
- url: Gitlab::Redis.url,
- namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE
- }
+ config.redis = redis_config_hash
config.server_middleware do |chain|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
+ chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
end
# Sidekiq-cron: load recurring jobs from gitlab.yml
@@ -38,8 +40,5 @@ Sidekiq.configure_server do |config|
end
Sidekiq.configure_client do |config|
- config.redis = {
- url: Gitlab::Redis.url,
- namespace: Gitlab::Redis::SIDEKIQ_NAMESPACE
- }
+ config.redis = redis_config_hash
end
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index 30770b71e24..cd869657c53 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -7,6 +7,8 @@ module Rack
class Request
def trusted_proxy?(ip)
Rails.application.config.action_dispatch.trusted_proxies.any? { |proxy| proxy === ip }
+ rescue IPAddr::InvalidAddressError
+ false
end
end
end
diff --git a/config/mail_room.yml b/config/mail_room.yml
index 7cab24b295e..c639f8260aa 100644
--- a/config/mail_room.yml
+++ b/config/mail_room.yml
@@ -1,47 +1,36 @@
+# If you change this file in a Merge Request, please also create
+# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
+#
:mailboxes:
-<%
-require "yaml"
-require "json"
-require_relative "lib/gitlab/redis" unless defined?(Gitlab::Redis)
+ <%
+ require_relative "lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
+ config = Gitlab::MailRoom.config
-rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
-
-config_file = ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] || "config/gitlab.yml"
-if File.exists?(config_file)
- all_config = YAML.load_file(config_file)[rails_env]
-
- config = all_config["incoming_email"] || {}
- config['enabled'] = false if config['enabled'].nil?
- config['port'] = 143 if config['port'].nil?
- config['ssl'] = false if config['ssl'].nil?
- config['start_tls'] = false if config['start_tls'].nil?
- config['mailbox'] = "inbox" if config['mailbox'].nil?
-
- if config['enabled'] && config['address']
- redis_url = Gitlab::Redis.new(rails_env).url
- %>
+ if Gitlab::MailRoom.enabled?
+ %>
-
- :host: <%= config['host'].to_json %>
- :port: <%= config['port'].to_json %>
- :ssl: <%= config['ssl'].to_json %>
- :start_tls: <%= config['start_tls'].to_json %>
- :email: <%= config['user'].to_json %>
- :password: <%= config['password'].to_json %>
+ :host: <%= config[:host].to_json %>
+ :port: <%= config[:port].to_json %>
+ :ssl: <%= config[:ssl].to_json %>
+ :start_tls: <%= config[:start_tls].to_json %>
+ :email: <%= config[:user].to_json %>
+ :password: <%= config[:password].to_json %>
+ :idle_timeout: 60
- :name: <%= config['mailbox'].to_json %>
+ :name: <%= config[:mailbox].to_json %>
:delete_after_delivery: true
:delivery_method: sidekiq
:delivery_options:
- :redis_url: <%= redis_url.to_json %>
- :namespace: resque:gitlab
+ :redis_url: <%= config[:redis_url].to_json %>
+ :namespace: <%= Gitlab::Redis::SIDEKIQ_NAMESPACE %>
:queue: incoming_email
:worker: EmailReceiverWorker
:arbitration_method: redis
:arbitration_options:
- :redis_url: <%= redis_url.to_json %>
- :namespace: mail_room:gitlab
+ :redis_url: <%= config[:redis_url].to_json %>
+ :namespace: <%= Gitlab::Redis::MAILROOM_NAMESPACE %>
+
<% end %>
-<% end %>
diff --git a/config/resque.yml.example b/config/resque.yml.example
index d98f43f71b2..0c19d8bc1d3 100644
--- a/config/resque.yml.example
+++ b/config/resque.yml.example
@@ -1,6 +1,34 @@
# If you change this file in a Merge Request, please also create
# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
#
-development: redis://localhost:6379
-test: redis://localhost:6379
-production: unix:/var/run/redis/redis.sock
+development:
+ url: redis://localhost:6379
+ # sentinels:
+ # -
+ # host: localhost
+ # port: 26380 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26381 # point to sentinel, not to redis port
+test:
+ url: redis://localhost:6379
+production:
+ # Redis (single instance)
+ url: unix:/var/run/redis/redis.sock
+ ##
+ # Redis + Sentinel (for HA)
+ #
+ # Please read instructions carefully before using it as you may lose data:
+ # http://redis.io/topics/sentinel
+ #
+ # You must specify a list of a few sentinels that will handle client connection
+ # please read here for more information: https://docs.gitlab.com/ce/administration/high_availability/redis.html
+ ##
+ # url: redis://master:6379
+ # sentinels:
+ # -
+ # host: slave1
+ # port: 26379 # point to sentinel, not to redis port
+ # -
+ # host: slave2
+ # port: 26379 # point to sentinel, not to redis port
diff --git a/config/routes.rb b/config/routes.rb
index 84a89200111..634ca35b5b4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -42,10 +42,9 @@ Rails.application.routes.draw do
resource :lint, only: [:show, :create]
- resources :projects do
+ resources :projects, only: [:index, :show] do
member do
get :status, to: 'projects#badge'
- get :integration
end
end
@@ -85,9 +84,6 @@ Rails.application.routes.draw do
# Health check
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
- # Enable Grack support (for LFS only)
- mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
-
# Help
get 'help' => 'help#index'
get 'help/shortcuts' => 'help#shortcuts'
@@ -144,13 +140,13 @@ Rails.application.routes.draw do
get :jobs
end
- resource :gitlab, only: [:create, :new], controller: :gitlab do
+ resource :gitlab, only: [:create], controller: :gitlab do
get :status
get :callback
get :jobs
end
- resource :bitbucket, only: [:create, :new], controller: :bitbucket do
+ resource :bitbucket, only: [:create], controller: :bitbucket do
get :status
get :callback
get :jobs
@@ -243,7 +239,6 @@ Rails.application.routes.draw do
get :projects
get :keys
get :groups
- put :team_update
put :block
put :unblock
put :unlock
@@ -257,7 +252,11 @@ Rails.application.routes.draw do
resource :impersonation, only: :destroy
resources :abuse_reports, only: [:index, :destroy]
- resources :spam_logs, only: [:index, :destroy]
+ resources :spam_logs, only: [:index, :destroy] do
+ member do
+ post :mark_as_ham
+ end
+ end
resources :applications
@@ -281,6 +280,7 @@ Rails.application.routes.draw do
resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
+ resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
root to: 'projects#index', as: :projects
@@ -300,7 +300,7 @@ Rails.application.routes.draw do
end
end
- resource :appearances, path: 'appearance' do
+ resource :appearances, only: [:show, :create, :update], path: 'appearance' do
member do
get :preview
delete :logo
@@ -309,7 +309,7 @@ Rails.application.routes.draw do
end
resource :application_settings, only: [:show, :update] do
- resources :services
+ resources :services, only: [:index, :edit, :update]
put :reset_runners_token
put :reset_health_check_token
put :clear_repository_check_states
@@ -346,7 +346,7 @@ Rails.application.routes.draw do
end
scope module: :profiles do
- resource :account, only: [:show, :update] do
+ resource :account, only: [:show] do
member do
delete :unlink
end
@@ -358,7 +358,7 @@ Rails.application.routes.draw do
end
end
resource :preferences, only: [:show, :update]
- resources :keys
+ resources :keys, only: [:index, :show, :new, :create, :destroy]
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
@@ -472,7 +472,7 @@ Rails.application.routes.draw do
post :unarchive
post :housekeeping
post :toggle_star
- post :markdown_preview
+ post :preview_markdown
post :export
post :remove_export
post :generate_new_export
@@ -483,11 +483,26 @@ Rails.application.routes.draw do
end
scope module: :projects do
- # Git HTTP clients ('git clone' etc.)
scope constraints: { id: /.+\.git/, format: nil } do
+ # Git HTTP clients ('git clone' etc.)
get '/info/refs', to: 'git_http#info_refs'
post '/git-upload-pack', to: 'git_http#git_upload_pack'
post '/git-receive-pack', to: 'git_http#git_receive_pack'
+
+ # Git LFS API (metadata)
+ post '/info/lfs/objects/batch', to: 'lfs_api#batch'
+ post '/info/lfs/objects', to: 'lfs_api#deprecated'
+ get '/info/lfs/objects/*oid', to: 'lfs_api#deprecated'
+
+ # GitLab LFS object storage
+ scope constraints: { oid: /[a-f0-9]{64}/ } do
+ get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#download'
+
+ scope constraints: { size: /[0-9]+/ } do
+ put '/gitlab-lfs/objects/*oid/*size/authorize', to: 'lfs_storage#upload_authorize'
+ put '/gitlab-lfs/objects/*oid/*size', to: 'lfs_storage#upload_finalize'
+ end
+ end
end
# Allow /info/refs, /info/refs?service=git-upload-pack, and
@@ -627,13 +642,17 @@ Rails.application.routes.draw do
get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
- resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
+ # Don't use format parameter as file extension (old 3.0.x behavior)
+ # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
+ scope format: false do
+ resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex }
- resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
- member do
- get :commits
- get :ci
- get :languages
+ resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do
+ member do
+ get :commits
+ get :ci
+ get :languages
+ end
end
end
@@ -657,10 +676,10 @@ Rails.application.routes.draw do
get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID
delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID
put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID
- post '/wikis/*id/markdown_preview', to: 'wikis#markdown_preview', constraints: WIKI_SLUG_ID, as: 'wiki_markdown_preview'
+ post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown'
end
- resource :repository, only: [:show, :create] do
+ resource :repository, only: [:create] do
member do
get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
end
@@ -743,7 +762,7 @@ Rails.application.routes.draw do
end
end
- resources :environments, only: [:index, :show, :new, :create, :destroy]
+ resources :environments
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
@@ -783,7 +802,7 @@ Rails.application.routes.draw do
end
end
- resources :labels, constraints: { id: /\d+/ } do
+ resources :labels, except: [:show], constraints: { id: /\d+/ } do
collection do
post :generate
post :set_priorities
@@ -799,6 +818,7 @@ Rails.application.routes.draw do
member do
post :toggle_subscription
post :toggle_award_emoji
+ post :mark_as_spam
get :referenced_merge_requests
get :related_branches
get :can_create_branch
@@ -808,7 +828,7 @@ Rails.application.routes.draw do
end
end
- resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
+ resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
collection do
delete :leave
@@ -855,7 +875,10 @@ Rails.application.routes.draw do
resources :badges, only: [:index] do
collection do
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
- get :build, constraints: { format: /svg/ }
+ constraints format: /svg/ do
+ get :build
+ get :coverage
+ end
end
end
end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index b4639999967..e3316ecdb6c 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do
'https://github.com/airbnb/javascript.git',
'https://github.com/tessalt/echo-chamber-js.git',
'https://github.com/atom/atom.git',
- 'https://github.com/ipselon/react-ui-builder.git',
'https://github.com/mattermost/platform.git',
'https://github.com/purifycss/purifycss.git',
'https://github.com/facebook/nuclide.git',
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index 124704cb451..6441a036e75 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,6 +1,22 @@
class Gitlab::Seeder::Builds
STAGES = %w[build notify_build test notify_test deploy notify_deploy]
-
+ BUILDS = [
+ { name: 'build:linux', stage: 'build', status: :success },
+ { name: 'build:osx', stage: 'build', status: :success },
+ { name: 'slack post build', stage: 'notify_build', status: :success },
+ { name: 'rspec:linux', stage: 'test', status: :success },
+ { name: 'rspec:windows', stage: 'test', status: :success },
+ { name: 'rspec:windows', stage: 'test', status: :success },
+ { name: 'rspec:osx', stage: 'test', status_event: :success },
+ { name: 'spinach:linux', stage: 'test', status: :pending },
+ { name: 'spinach:osx', stage: 'test', status: :canceled },
+ { name: 'cucumber:linux', stage: 'test', status: :running },
+ { name: 'cucumber:osx', stage: 'test', status: :failed },
+ { name: 'slack post test', stage: 'notify_test', status: :success },
+ { name: 'staging', stage: 'deploy', environment: 'staging', status: :success },
+ { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success },
+ ]
+
def initialize(project)
@project = project
end
@@ -8,26 +24,8 @@ class Gitlab::Seeder::Builds
def seed!
pipelines.each do |pipeline|
begin
- build_create!(pipeline, name: 'build:linux', stage: 'build')
- build_create!(pipeline, name: 'build:osx', stage: 'build')
-
- build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
-
- build_create!(pipeline, name: 'rspec:linux', stage: 'test')
- build_create!(pipeline, name: 'rspec:windows', stage: 'test')
- build_create!(pipeline, name: 'rspec:windows', stage: 'test')
- build_create!(pipeline, name: 'rspec:osx', stage: 'test')
- build_create!(pipeline, name: 'spinach:linux', stage: 'test')
- build_create!(pipeline, name: 'spinach:osx', stage: 'test')
- build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
- build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
-
- build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
-
- build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
- build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
-
- commit_status_create!(pipeline, name: 'jenkins')
+ BUILDS.each { |opts| build_create!(pipeline, opts) }
+ commit_status_create!(pipeline, name: 'jenkins', status: :success)
print '.'
rescue ActiveRecord::RecordInvalid
@@ -48,35 +46,33 @@ class Gitlab::Seeder::Builds
def build_create!(pipeline, opts = {})
attributes = build_attributes_for(pipeline, opts)
- build = Ci::Build.new(attributes)
- if opts[:name].start_with?('build')
- artifacts_cache_file(artifacts_archive_path) do |file|
- build.artifacts_file = file
- end
+ Ci::Build.create!(attributes) do |build|
+ if opts[:name].start_with?('build')
+ artifacts_cache_file(artifacts_archive_path) do |file|
+ build.artifacts_file = file
+ end
- artifacts_cache_file(artifacts_metadata_path) do |file|
- build.artifacts_metadata = file
+ artifacts_cache_file(artifacts_metadata_path) do |file|
+ build.artifacts_metadata = file
+ end
end
- end
-
- build.save!
- build.update(status: build_status)
- if %w(running success failed).include?(build.status)
- # We need to set build trace after saving a build (id required)
- build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+ if %w(running success failed).include?(build.status)
+ # We need to set build trace after saving a build (id required)
+ build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
+ end
end
end
-
+
def commit_status_create!(pipeline, opts = {})
attributes = commit_status_attributes_for(pipeline, opts)
- GenericCommitStatus.create(attributes)
+ GenericCommitStatus.create!(attributes)
end
-
+
def commit_status_attributes_for(pipeline, opts)
{ name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
- ref: 'master', user: build_user, project: @project, pipeline: pipeline,
+ ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline,
created_at: Time.now, updated_at: Time.now
}.merge(opts)
end
diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb
new file mode 100644
index 00000000000..103c7f9445c
--- /dev/null
+++ b/db/fixtures/development/16_protected_branches.rb
@@ -0,0 +1,12 @@
+Gitlab::Seeder.quiet do
+ admin_user = User.find(1)
+
+ Project.all.each do |project|
+ params = {
+ name: 'master'
+ }
+
+ ProtectedBranches::CreateService.new(project, admin_user, params).execute
+ print '.'
+ end
+end
diff --git a/db/migrate/20140407135544_fix_namespaces.rb b/db/migrate/20140407135544_fix_namespaces.rb
index 91374966698..0026ce645a6 100644
--- a/db/migrate/20140407135544_fix_namespaces.rb
+++ b/db/migrate/20140407135544_fix_namespaces.rb
@@ -1,8 +1,14 @@
# rubocop:disable all
class FixNamespaces < ActiveRecord::Migration
+ DOWNTIME = false
+
def up
- Namespace.where('name <> path and type is null').each do |namespace|
- namespace.update_attribute(:name, namespace.path)
+ namespaces = exec_query('SELECT id, path FROM namespaces WHERE name <> path and type is null')
+
+ namespaces.each do |row|
+ id = row['id']
+ path = row['path']
+ exec_query("UPDATE namespaces SET name = '#{path}' WHERE id = #{id}")
end
end
diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb
new file mode 100644
index 00000000000..f27295524e1
--- /dev/null
+++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddProtectedBranchesPushAccess < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :protected_branch_push_access_levels do |t|
+ t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false
+
+ # Gitlab::Access::MASTER == 40
+ t.integer :access_level, default: 40, null: false
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb
new file mode 100644
index 00000000000..32adfa266cd
--- /dev/null
+++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb
@@ -0,0 +1,17 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddProtectedBranchesMergeAccess < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :protected_branch_merge_access_levels do |t|
+ t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false
+
+ # Gitlab::Access::MASTER == 40
+ t.integer :access_level, default: 40, null: false
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb
new file mode 100644
index 00000000000..1db0df92bec
--- /dev/null
+++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::Migration
+ DOWNTIME = true
+ DOWNTIME_REASON = <<-HEREDOC
+ We're creating a `merge_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this
+ is running, we might be left with a `protected_branch` _without_ an associated `merge_access_level`. The `protected_branches`
+ table must not change while this is running, so downtime is required.
+
+ https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410
+ HEREDOC
+
+ def up
+ execute <<-HEREDOC
+ INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at)
+ SELECT id, (CASE WHEN developers_can_merge THEN 30 ELSE 40 END), now(), now()
+ FROM protected_branches
+ HEREDOC
+ end
+
+ def down
+ execute <<-HEREDOC
+ UPDATE protected_branches SET developers_can_merge = TRUE
+ WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels
+ WHERE access_level = 30);
+ HEREDOC
+ end
+end
diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb
new file mode 100644
index 00000000000..5c3e189bb5b
--- /dev/null
+++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Migration
+ DOWNTIME = true
+ DOWNTIME_REASON = <<-HEREDOC
+ We're creating a `push_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this
+ is running, we might be left with a `protected_branch` _without_ an associated `push_access_level`. The `protected_branches`
+ table must not change while this is running, so downtime is required.
+
+ https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410
+ HEREDOC
+
+ def up
+ execute <<-HEREDOC
+ INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at)
+ SELECT id, (CASE WHEN developers_can_push THEN 30 ELSE 40 END), now(), now()
+ FROM protected_branches
+ HEREDOC
+ end
+
+ def down
+ execute <<-HEREDOC
+ UPDATE protected_branches SET developers_can_push = TRUE
+ WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels
+ WHERE access_level = 30);
+ HEREDOC
+ end
+end
diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb
new file mode 100644
index 00000000000..52a9819c628
--- /dev/null
+++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # This is only required for `#down`
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ remove_column :protected_branches, :developers_can_push, :boolean
+ end
+
+ def down
+ add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, allow_null: false)
+ end
+end
diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb
new file mode 100644
index 00000000000..4a7bde7f9f3
--- /dev/null
+++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb
@@ -0,0 +1,19 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # This is only required for `#down`
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ remove_column :protected_branches, :developers_can_merge, :boolean
+ end
+
+ def down
+ add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false)
+ end
+end
diff --git a/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb b/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb
new file mode 100644
index 00000000000..756910a1fa0
--- /dev/null
+++ b/db/migrate/20160716115711_add_queued_at_to_ci_builds.rb
@@ -0,0 +1,9 @@
+class AddQueuedAtToCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :queued_at, :timestamp
+ end
+end
diff --git a/db/migrate/20160725083350_add_external_url_to_enviroments.rb b/db/migrate/20160725083350_add_external_url_to_enviroments.rb
new file mode 100644
index 00000000000..21a8abd310b
--- /dev/null
+++ b/db/migrate/20160725083350_add_external_url_to_enviroments.rb
@@ -0,0 +1,9 @@
+class AddExternalUrlToEnviroments < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column(:environments, :external_url, :string)
+ end
+end
diff --git a/db/migrate/20160727163552_create_user_agent_details.rb b/db/migrate/20160727163552_create_user_agent_details.rb
new file mode 100644
index 00000000000..ed4ccfedc0a
--- /dev/null
+++ b/db/migrate/20160727163552_create_user_agent_details.rb
@@ -0,0 +1,18 @@
+class CreateUserAgentDetails < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :user_agent_details do |t|
+ t.string :user_agent, null: false
+ t.string :ip_address, null: false
+ t.integer :subject_id, null: false
+ t.string :subject_type, null: false
+ t.boolean :submitted, default: false, null: false
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb b/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb
new file mode 100644
index 00000000000..e28ab31d629
--- /dev/null
+++ b/db/migrate/20160729173930_remove_project_id_from_spam_logs.rb
@@ -0,0 +1,29 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveProjectIdFromSpamLogs < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = true
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ DOWNTIME_REASON = 'Removing a column that contains data that is not used anywhere.'
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def change
+ remove_column :spam_logs, :project_id, :integer
+ end
+end
diff --git a/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
new file mode 100644
index 00000000000..296f1dfac7b
--- /dev/null
+++ b/db/migrate/20160801163709_add_submitted_as_ham_to_spam_logs.rb
@@ -0,0 +1,20 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddSubmittedAsHamToSpamLogs < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # When a migration requires downtime you **must** uncomment the following
+ # constant and define a short and easy to understand explanation as to why the
+ # migration requires downtime.
+ # DOWNTIME_REASON = ''
+
+ disable_ddl_transaction!
+
+ def change
+ add_column_with_default :spam_logs, :submitted_as_ham, :boolean, default: false
+ end
+end
diff --git a/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb
new file mode 100644
index 00000000000..5fd51cb65f1
--- /dev/null
+++ b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb
@@ -0,0 +1,9 @@
+class RemoveBuildsEnableIndexOnProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ remove_index :projects, column: :builds_enabled if index_exists?(:projects, :builds_enabled)
+ end
+end
diff --git a/db/migrate/20160804150737_add_timestamps_to_members_again.rb b/db/migrate/20160804150737_add_timestamps_to_members_again.rb
new file mode 100644
index 00000000000..6691ba57fbb
--- /dev/null
+++ b/db/migrate/20160804150737_add_timestamps_to_members_again.rb
@@ -0,0 +1,21 @@
+# rubocop:disable all
+# 20141121133009_add_timestamps_to_members.rb was meant to ensure that all
+# rows in the members table had created_at and updated_at set, following an
+# error in a previous migration. This failed to set all rows in at least one
+# case: https://gitlab.com/gitlab-org/gitlab-ce/issues/20568
+#
+# Why this happened is lost in the mists of time, so repeat the SQL query
+# without speculation, just in case more than one person was affected.
+class AddTimestampsToMembersAgain < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def up
+ execute "UPDATE members SET created_at = NOW() WHERE created_at IS NULL"
+ execute "UPDATE members SET updated_at = NOW() WHERE updated_at IS NULL"
+ end
+
+ def down
+ # no change
+ end
+
+end
diff --git a/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb
new file mode 100644
index 00000000000..a853de3abfb
--- /dev/null
+++ b/db/migrate/20160805041956_add_deleted_at_to_namespaces.rb
@@ -0,0 +1,12 @@
+class AddDeletedAtToNamespaces < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_column :namespaces, :deleted_at, :datetime
+ add_concurrent_index :namespaces, :deleted_at
+ end
+end
diff --git a/db/migrate/20160810102349_remove_ci_runner_trigram_indexes.rb b/db/migrate/20160810102349_remove_ci_runner_trigram_indexes.rb
new file mode 100644
index 00000000000..0cfb637804b
--- /dev/null
+++ b/db/migrate/20160810102349_remove_ci_runner_trigram_indexes.rb
@@ -0,0 +1,27 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveCiRunnerTrigramIndexes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # Disabled for the "down" method so the indexes can be re-created concurrently.
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab::Database.postgresql?
+
+ transaction do
+ execute 'DROP INDEX IF EXISTS index_ci_runners_on_token_trigram;'
+ execute 'DROP INDEX IF EXISTS index_ci_runners_on_description_trigram;'
+ end
+ end
+
+ def down
+ return unless Gitlab::Database.postgresql?
+
+ execute 'CREATE INDEX CONCURRENTLY index_ci_runners_on_token_trigram ON ci_runners USING gin(token gin_trgm_ops);'
+ execute 'CREATE INDEX CONCURRENTLY index_ci_runners_on_description_trigram ON ci_runners USING gin(description gin_trgm_ops);'
+ end
+end
diff --git a/db/migrate/20160810142633_remove_redundant_indexes.rb b/db/migrate/20160810142633_remove_redundant_indexes.rb
new file mode 100644
index 00000000000..8641c6ffa8f
--- /dev/null
+++ b/db/migrate/20160810142633_remove_redundant_indexes.rb
@@ -0,0 +1,112 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveRedundantIndexes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ indexes = [
+ [:ci_taggings, 'ci_taggings_idx'],
+ [:audit_events, 'index_audit_events_on_author_id'],
+ [:audit_events, 'index_audit_events_on_type'],
+ [:ci_builds, 'index_ci_builds_on_erased_by_id'],
+ [:ci_builds, 'index_ci_builds_on_project_id_and_commit_id'],
+ [:ci_builds, 'index_ci_builds_on_type'],
+ [:ci_commits, 'index_ci_commits_on_project_id'],
+ [:ci_commits, 'index_ci_commits_on_project_id_and_committed_at'],
+ [:ci_commits, 'index_ci_commits_on_project_id_and_committed_at_and_id'],
+ [:ci_commits, 'index_ci_commits_on_project_id_and_sha'],
+ [:ci_commits, 'index_ci_commits_on_sha'],
+ [:ci_events, 'index_ci_events_on_created_at'],
+ [:ci_events, 'index_ci_events_on_is_admin'],
+ [:ci_events, 'index_ci_events_on_project_id'],
+ [:ci_jobs, 'index_ci_jobs_on_deleted_at'],
+ [:ci_jobs, 'index_ci_jobs_on_project_id'],
+ [:ci_projects, 'index_ci_projects_on_gitlab_id'],
+ [:ci_projects, 'index_ci_projects_on_shared_runners_enabled'],
+ [:ci_services, 'index_ci_services_on_project_id'],
+ [:ci_sessions, 'index_ci_sessions_on_session_id'],
+ [:ci_sessions, 'index_ci_sessions_on_updated_at'],
+ [:ci_tags, 'index_ci_tags_on_name'],
+ [:ci_triggers, 'index_ci_triggers_on_deleted_at'],
+ [:identities, 'index_identities_on_created_at_and_id'],
+ [:issues, 'index_issues_on_title'],
+ [:keys, 'index_keys_on_created_at_and_id'],
+ [:members, 'index_members_on_created_at_and_id'],
+ [:members, 'index_members_on_type'],
+ [:milestones, 'index_milestones_on_created_at_and_id'],
+ [:namespaces, 'index_namespaces_on_visibility_level'],
+ [:projects, 'index_projects_on_builds_enabled_and_shared_runners_enabled'],
+ [:services, 'index_services_on_category'],
+ [:services, 'index_services_on_created_at_and_id'],
+ [:services, 'index_services_on_default'],
+ [:snippets, 'index_snippets_on_created_at'],
+ [:snippets, 'index_snippets_on_created_at_and_id'],
+ [:todos, 'index_todos_on_state'],
+ [:web_hooks, 'index_web_hooks_on_created_at_and_id'],
+
+ # These indexes _may_ be used but they can be replaced by other existing
+ # indexes.
+
+ # There's already a composite index on (project_id, iid) which means that
+ # a separate index for _just_ project_id is not needed.
+ [:issues, 'index_issues_on_project_id'],
+
+ # These are all composite indexes for the columns (created_at, id). In all
+ # these cases there's already a standalone index for "created_at" which
+ # can be used instead.
+ #
+ # Because the "id" column of these composite indexes is never needed (due
+ # to "id" already being indexed as its a primary key) these composite
+ # indexes are useless.
+ [:issues, 'index_issues_on_created_at_and_id'],
+ [:merge_requests, 'index_merge_requests_on_created_at_and_id'],
+ [:namespaces, 'index_namespaces_on_created_at_and_id'],
+ [:notes, 'index_notes_on_created_at_and_id'],
+ [:projects, 'index_projects_on_created_at_and_id'],
+ [:users, 'index_users_on_created_at_and_id'],
+ ]
+
+ transaction do
+ indexes.each do |(table, index)|
+ remove_index(table, name: index) if index_exists_by_name?(table, index)
+ end
+ end
+
+ add_concurrent_index(:users, :created_at)
+ add_concurrent_index(:projects, :created_at)
+ add_concurrent_index(:namespaces, :created_at)
+ end
+
+ def down
+ # We're only restoring the composite indexes that could be replaced with
+ # individual ones, just in case somebody would ever want to revert.
+ transaction do
+ remove_index(:users, :created_at)
+ remove_index(:projects, :created_at)
+ remove_index(:namespaces, :created_at)
+ end
+
+ [:issues, :merge_requests, :namespaces, :notes, :projects, :users].each do |table|
+ add_concurrent_index(table, [:created_at, :id],
+ name: "index_#{table}_on_created_at_and_id")
+ end
+ end
+
+ # Rails' index_exists? doesn't work when you only give it a table and index
+ # name. As such we have to use some extra code to check if an index exists for
+ # a given name.
+ def index_exists_by_name?(table, index)
+ indexes_for_table[table].include?(index)
+ end
+
+ def indexes_for_table
+ @indexes_for_table ||= Hash.new do |hash, table_name|
+ hash[table_name] = indexes(table_name).map(&:name)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b87f8108bb2..52ba60ace11 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: 20160721081015) do
+ActiveRecord::Schema.define(version: 20160810142633) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -102,9 +102,7 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "updated_at"
end
- add_index "audit_events", ["author_id"], name: "index_audit_events_on_author_id", using: :btree
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
- add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "award_emoji", force: :cascade do |t|
t.string "name"
@@ -172,6 +170,7 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.integer "artifacts_size"
t.string "when"
t.text "yaml_variables"
+ t.datetime "queued_at"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
@@ -179,13 +178,10 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree
- add_index "ci_builds", ["erased_by_id"], name: "index_ci_builds_on_erased_by_id", using: :btree
add_index "ci_builds", ["gl_project_id"], name: "index_ci_builds_on_gl_project_id", using: :btree
- add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
- add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
create_table "ci_commits", force: :cascade do |t|
t.integer "project_id"
@@ -209,11 +205,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
- add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
- add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
- add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
- add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
- add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree
@@ -226,10 +217,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "updated_at"
end
- add_index "ci_events", ["created_at"], name: "index_ci_events_on_created_at", using: :btree
- add_index "ci_events", ["is_admin"], name: "index_ci_events_on_is_admin", using: :btree
- add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree
-
create_table "ci_jobs", force: :cascade do |t|
t.integer "project_id", null: false
t.text "commands"
@@ -244,9 +231,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "deleted_at"
end
- add_index "ci_jobs", ["deleted_at"], name: "index_ci_jobs_on_deleted_at", using: :btree
- add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
-
create_table "ci_projects", force: :cascade do |t|
t.string "name"
t.integer "timeout", default: 3600, null: false
@@ -270,9 +254,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.text "generated_yaml_config"
end
- add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
- add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
-
create_table "ci_runner_projects", force: :cascade do |t|
t.integer "runner_id", null: false
t.integer "project_id"
@@ -301,10 +282,8 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.boolean "locked", default: false, null: false
end
- add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
- add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
create_table "ci_services", force: :cascade do |t|
t.string "type"
@@ -316,8 +295,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.text "properties"
end
- add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree
-
create_table "ci_sessions", force: :cascade do |t|
t.string "session_id", null: false
t.text "data"
@@ -325,9 +302,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "updated_at"
end
- add_index "ci_sessions", ["session_id"], name: "index_ci_sessions_on_session_id", using: :btree
- add_index "ci_sessions", ["updated_at"], name: "index_ci_sessions_on_updated_at", using: :btree
-
create_table "ci_taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
@@ -338,7 +312,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "created_at"
end
- add_index "ci_taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "ci_taggings_idx", unique: true, using: :btree
add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
create_table "ci_tags", force: :cascade do |t|
@@ -346,8 +319,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.integer "taggings_count", default: 0
end
- add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree
-
create_table "ci_trigger_requests", force: :cascade do |t|
t.integer "trigger_id", null: false
t.text "variables"
@@ -365,7 +336,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.integer "gl_project_id"
end
- add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree
add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
create_table "ci_variables", force: :cascade do |t|
@@ -427,9 +397,10 @@ ActiveRecord::Schema.define(version: 20160721081015) do
create_table "environments", force: :cascade do |t|
t.integer "project_id"
- t.string "name", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "external_url"
end
add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
@@ -470,7 +441,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "updated_at"
end
- add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "issues", force: :cascade do |t|
@@ -496,16 +466,13 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
- add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "issues", ["due_date"], name: "index_issues_on_due_date", using: :btree
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
- add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
- add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
create_table "keys", force: :cascade do |t|
@@ -519,7 +486,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.boolean "public", default: false, null: false
end
- add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
@@ -584,11 +550,9 @@ ActiveRecord::Schema.define(version: 20160721081015) do
end
add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree
- add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree
add_index "members", ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree
add_index "members", ["requested_at"], name: "index_members_on_requested_at", using: :btree
add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
- add_index "members", ["type"], name: "index_members_on_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
create_table "merge_request_diffs", force: :cascade do |t|
@@ -625,17 +589,16 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.datetime "locked_at"
t.integer "updated_by_id"
t.string "merge_error"
- t.text "merge_params"
t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
t.string "merge_commit_sha"
t.datetime "deleted_at"
t.string "in_progress_merge_commit_sha"
+ t.text "merge_params"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
- add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
@@ -658,7 +621,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.integer "iid"
end
- add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree
add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree
add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
@@ -678,16 +640,17 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false
+ t.datetime "deleted_at"
end
- add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
+ add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
+ add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
- add_index "namespaces", ["visibility_level"], name: "index_namespaces_on_visibility_level", using: :btree
create_table "notes", force: :cascade do |t|
t.text "note"
@@ -710,7 +673,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
- add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
@@ -850,10 +812,8 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.boolean "request_access_enabled", default: true, null: false
end
- add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
- add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
- add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
+ add_index "projects", ["created_at"], name: "index_projects_on_created_at", using: :btree
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
@@ -867,13 +827,29 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
+ create_table "protected_branch_merge_access_levels", force: :cascade do |t|
+ t.integer "protected_branch_id", null: false
+ t.integer "access_level", default: 40, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree
+
+ create_table "protected_branch_push_access_levels", force: :cascade do |t|
+ t.integer "protected_branch_id", null: false
+ t.integer "access_level", default: 40, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree
+
create_table "protected_branches", force: :cascade do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "developers_can_push", default: false, null: false
- t.boolean "developers_can_merge", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -923,9 +899,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.boolean "wiki_page_events", default: true
end
- add_index "services", ["category"], name: "index_services_on_category", using: :btree
- add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
- add_index "services", ["default"], name: "index_services_on_default", using: :btree
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
add_index "services", ["template"], name: "index_services_on_template", using: :btree
@@ -942,8 +915,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
- add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree
- add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree
add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"}
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
@@ -955,12 +926,12 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.string "source_ip"
t.string "user_agent"
t.boolean "via_api"
- t.integer "project_id"
t.string "noteable_type"
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.boolean "submitted_as_ham", default: false, null: false
end
create_table "subscriptions", force: :cascade do |t|
@@ -1012,7 +983,6 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree
add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree
add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree
- add_index "todos", ["state"], name: "index_todos_on_state", using: :btree
add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree
@@ -1029,6 +999,16 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
+ create_table "user_agent_details", force: :cascade do |t|
+ t.string "user_agent", null: false
+ t.string "ip_address", null: false
+ t.integer "subject_id", null: false
+ t.string "subject_type", null: false
+ t.boolean "submitted", default: false, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
@@ -1092,7 +1072,7 @@ ActiveRecord::Schema.define(version: 20160721081015) do
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
- add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree
+ add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
@@ -1132,9 +1112,10 @@ ActiveRecord::Schema.define(version: 20160721081015) do
t.string "token"
end
- add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_foreign_key "personal_access_tokens", "users"
+ add_foreign_key "protected_branch_merge_access_levels", "protected_branches"
+ add_foreign_key "protected_branch_push_access_levels", "protected_branches"
add_foreign_key "u2f_registrations", "users"
end
diff --git a/doc/README.md b/doc/README.md
index b5b377822e6..fc51ea911b9 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -9,7 +9,7 @@
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [Importing and exporting projects between instances](user/project/settings/import_export.md).
-- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Markdown](user/markdown.md) GitLab's advanced formatting system.
- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
- [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md)
@@ -54,7 +54,5 @@
## Contributor documentation
-- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are
- contributing to documentation.
-- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+- [Development](development/README.md) All styleguides and explanations how to contribute.
- [Legal](legal/README.md) Contributor license agreements.
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
new file mode 100644
index 00000000000..64353f7282b
--- /dev/null
+++ b/doc/administration/build_artifacts.md
@@ -0,0 +1,90 @@
+# Build artifacts administration
+
+>**Notes:**
+>- Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
+>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
+ changed to `ZIP`.
+>- This is the administration documentation. For the user guide see
+ [user/project/builds/artifacts.md](../user/project/builds/artifacts.md).
+
+Artifacts is a list of files and directories which are attached to a build
+after it completes successfully. This feature is enabled by default in all
+GitLab installations. Keep reading if you want to know how to disable it.
+
+## Disabling build artifacts
+
+To disable artifacts site-wide, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['artifacts_enabled'] = false
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+ ```yaml
+ artifacts:
+ enabled: false
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Storing build artifacts
+
+After a successful build, GitLab Runner uploads an archive containing the build
+artifacts to GitLab.
+
+To change the location where the artifacts are stored, follow the steps below.
+
+---
+
+**In Omnibus installations:**
+
+_The artifacts are stored by default in
+`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+ `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**In installations from source:**
+
+_The artifacts are stored by default in
+`/home/git/gitlab/shared/artifacts`._
+
+1. To change the storage path for example to `/mnt/storage/artifacts`, edit
+ `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
+
+ ```yaml
+ artifacts:
+ enabled: true
+ path: /mnt/storage/artifacts
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
+## Set the maximum file size of the artifacts
+
+Provided the artifacts are enabled, you can change the maximum file size of the
+artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration#maximum-artifacts-size).
+
+[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab"
+[restart gitlab]: restart_gitlab.md "How to restart GitLab"
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index d5d43303454..28c4c7c86ca 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -1,7 +1,6 @@
# GitLab Container Registry Administration
-> **Note:**
-This feature was [introduced][ce-4040] in GitLab 8.8.
+> [Introduced][ce-4040] in GitLab 8.8.
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
@@ -122,6 +121,10 @@ Registry is exposed to the outside world is `4567`, here is what you need to set
in `gitlab.rb` or `gitlab.yml` if you are using Omnibus GitLab or installed
GitLab from source respectively.
+>**Note:**
+Be careful to choose a port different than the one that Registry listens to (`5000` by default),
+otherwise you will run into conflicts .
+
---
**Omnibus GitLab installations**
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index e3306c22d3f..0387d730489 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -44,8 +44,7 @@ as appropriate.
## Custom error messages
->**Note:**
-This feature was [introduced][5073] in GitLab 8.10.
+> [Introduced][5073] in GitLab 8.10.
If the commit is declined or an error occurs during the Git hook check,
the STDERR or STDOUT message of the hook will be present in GitLab's UI.
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index f6153216f33..bc424330656 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -1,7 +1,12 @@
# Configuring Redis for GitLab HA
-You can choose to install and manage Redis yourself, or you can use GitLab
-Omnibus packages to help.
+You can choose to install and manage Redis yourself, or you can use the one
+that comes bundled with GitLab Omnibus packages.
+
+> **Note:** Redis does not require authentication by default. See
+ [Redis Security](http://redis.io/topics/security) documentation for more
+ information. We recommend using a combination of a Redis password and tight
+ firewall rules to secure your Redis service.
## Configure your own Redis server
@@ -9,49 +14,293 @@ If you're hosting GitLab on a cloud provider, you can optionally use a
managed service for Redis. For example, AWS offers a managed ElastiCache service
that runs Redis.
-> **Note:** Redis does not require authentication by default. See
- [Redis Security](http://redis.io/topics/security) documentation for more
- information. We recommend using a combination of a Redis password and tight
- firewall rules to secure your Redis service.
+## Configure Redis using Omnibus
-## Configure using Omnibus
+If you don't want to bother setting up your own Redis server, you can use the
+one bundled with Omnibus. In this case, you should disable all services except
+Redis.
1. Download/install GitLab Omnibus using **steps 1 and 2** from
[GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
steps on the download page.
1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration.
Be sure to change the `external_url` to match your eventual GitLab front-end
- URL.
+ URL:
```ruby
- external_url 'https://gitlab.example.com'
+ external_url 'https://gitlab.example.com'
- # Disable all components except Redis
- redis['enable'] = true
- bootstrap['enable'] = false
- nginx['enable'] = false
- unicorn['enable'] = false
- sidekiq['enable'] = false
- postgresql['enable'] = false
- gitlab_workhorse['enable'] = false
- mailroom['enable'] = false
+ # Disable all services except Redis
+ redis['enable'] = true
+ bootstrap['enable'] = false
+ nginx['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ postgresql['enable'] = false
+ gitlab_workhorse['enable'] = false
+ mailroom['enable'] = false
- # Redis configuration
- redis['port'] = 6379
- redis['bind'] = '0.0.0.0'
+ # Redis configuration
+ redis['port'] = 6379
+ redis['bind'] = '0.0.0.0'
- # If you wish to use Redis authentication (recommended)
- redis['password'] = 'Redis Password'
+ # If you wish to use Redis authentication (recommended)
+ redis['password'] = 'Redis Password'
```
1. Run `sudo gitlab-ctl reconfigure` to install and configure PostgreSQL.
> **Note**: This `reconfigure` step will result in some errors.
That's OK - don't be alarmed.
+
1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations
from running on upgrade. Only the primary GitLab application server should
handle migrations.
+## Experimental Redis Sentinel support
+
+> [Introduced][ce-1877] in GitLab 8.11.
+
+Since GitLab 8.11, you can configure a list of Redis Sentinel servers that
+will monitor a group of Redis servers to provide you with a standard failover
+support.
+
+There is currently one exception to the Sentinel support: `mail_room`, the
+component that processes incoming emails. It doesn't support Sentinel yet, but
+we hope to integrate a future release that does support it.
+
+To get a better understanding on how to correctly setup Sentinel, please read
+the [Redis Sentinel documentation](http://redis.io/topics/sentinel) first, as
+failing to configure it correctly can lead to data loss.
+
+The configuration consists of three parts:
+
+- Redis setup
+- Sentinel setup
+- GitLab setup
+
+Read carefully how to configure those components below.
+
+### Redis setup
+
+You must have at least 2 Redis servers: 1 Master, 1 or more Slaves.
+They should be configured the same way and with similar server specs, as
+in a failover situation, any Slave can be elected as the new Master by
+the Sentinel servers.
+
+In a minimal setup, the only required change for the slaves in `redis.conf`
+is the addition of a `slaveof` line pointing to the initial master.
+You can increase the security by defining a `requirepass` configuration in
+the master, and `masterauth` in slaves.
+
+---
+
+**Configuring your own Redis server**
+
+1. Add to the slaves' `redis.conf`:
+
+ ```conf
+ # IP and port of the master Redis server
+ slaveof 10.10.10.10 6379
+ ```
+
+1. Optionally, set up password authentication for increased security.
+ Add the following to master's `redis.conf`:
+
+ ```conf
+ # Optional password authentication for increased security
+ requirepass "<password>"
+ ```
+
+1. Then add this line to all the slave servers' `redis.conf`:
+
+ ```conf
+ masterauth "<password>"
+ ```
+
+1. Restart the Redis services for the changes to take effect.
+
+---
+
+**Using Redis via Omnibus**
+
+1. Edit `/etc/gitlab/gitlab.rb` of a master Redis machine (usualy a single machine):
+
+ ```ruby
+ ## Redis TCP support (will disable UNIX socket transport)
+ redis['bind'] = '0.0.0.0' # or specify an IP to bind to a single one
+ redis['port'] = 6379
+
+ ## Master redis instance
+ redis['password'] = '<huge password string here>'
+ ```
+
+1. Edit `/etc/gitlab/gitlab.rb` of a slave Redis machine (should be one or more machines):
+
+ ```ruby
+ ## Redis TCP support (will disable UNIX socket transport)
+ redis['bind'] = '0.0.0.0' # or specify an IP to bind to a single one
+ redis['port'] = 6379
+
+ ## Slave redis instance
+ redis['master_ip'] = '10.10.10.10' # IP of master Redis server
+ redis['master_port'] = 6379 # Port of master Redis server
+ redis['master_password'] = "<huge password string here>"
+ ```
+
+1. Reconfigure the GitLab for the changes to take effect: `sudo gitlab-ctl reconfigure`
+
+---
+
+Now that the Redis servers are all set up, let's configure the Sentinel
+servers.
+
+### Sentinel setup
+
+We don't provide yet an automated way to setup and run the Sentinel daemon
+from Omnibus installation method. You must follow the instructions below and
+run it by yourself.
+
+The support for Sentinel in Ruby has some [caveats](https://github.com/redis/redis-rb/issues/531).
+While you can give any name for the `master-group-name` part of the
+configuration, as in this example:
+
+```conf
+sentinel monitor <master-group-name> <ip> <port> <quorum>
+```
+
+,for it to work in Ruby, you have to use the "hostname" of the master Redis
+server, otherwise you will get an error message like:
+`Redis::CannotConnectError: No sentinels available.`. Read
+[Sentinel troubleshooting](#sentinel-troubleshooting) for more information.
+
+Here is an example configuration file (`sentinel.conf`) for a Sentinel node:
+
+```conf
+port 26379
+sentinel monitor master-redis.example.com 10.10.10.10 6379 1
+sentinel down-after-milliseconds master-redis.example.com 10000
+sentinel config-epoch master-redis.example.com 0
+sentinel leader-epoch master-redis.example.com 0
+```
+
+---
+
+The final part is to inform the main GitLab application server of the Redis
+master and the new sentinels servers.
+
+### GitLab setup
+
+You can enable or disable sentinel support at any time in new or existing
+installations. From the GitLab application perspective, all it requires is
+the correct credentials for the master Redis and for a few Sentinel nodes.
+
+It doesn't require a list of all Sentinel nodes, as in case of a failure,
+the application will need to query only one of them.
+
+>**Note:**
+The following steps should be performed in the [GitLab application server](gitlab.md).
+
+**For source based installations**
+
+1. Edit `/home/git/gitlab/config/resque.yml` following the example in
+ `/home/git/gitlab/config/resque.yml.example`, and uncomment the sentinels
+ line, changing to the correct server credentials.
+1. Restart GitLab for the changes to take effect.
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add/change the following lines:
+
+ ```ruby
+ gitlab-rails['redis_host'] = "master-redis.example.com"
+ gitlab-rails['redis_port'] = 6379
+ gitlab-rails['redis_password'] = '<huge password string here>'
+ gitlab-rails['redis_sentinels'] = [
+ {'host' => '10.10.10.1', 'port' => 26379},
+ {'host' => '10.10.10.2', 'port' => 26379},
+ {'host' => '10.10.10.3', 'port' => 26379}
+ ]
+ ```
+
+1. [Reconfigure] the GitLab for the changes to take effect.
+
+### Sentinel troubleshooting
+
+If you get an error like: `Redis::CannotConnectError: No sentinels available.`,
+there may be something wrong with your configuration files or it can be related
+to [this issue][gh-531] ([pull request][gh-534] that should make things better).
+
+It's a bit rigid the way you have to config `resque.yml` and `sentinel.conf`,
+otherwise `redis-rb` will not work properly.
+
+The hostname ('my-primary-redis') of the primary Redis server (`sentinel.conf`)
+**must** match the one configured in GitLab (`resque.yml` for source installations
+or `gitlab-rails['redis_*']` in Omnibus) and it must be valid ex:
+
+```conf
+# sentinel.conf:
+sentinel monitor my-primary-redis 10.10.10.10 6379 1
+sentinel down-after-milliseconds my-primary-redis 10000
+sentinel config-epoch my-primary-redis 0
+sentinel leader-epoch my-primary-redis 0
+```
+
+```yaml
+# resque.yaml
+production:
+ url: redis://my-primary-redis:6378
+ sentinels:
+ -
+ host: slave1
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
+```
+
+When in doubt, please read [Redis Sentinel documentation](http://redis.io/topics/sentinel)
+
+---
+
+To make sure your configuration is correct:
+
+1. SSH into your GitLab application server
+1. Enter the Rails console:
+
+ ```
+ # For Omnibus installations
+ sudo gitlab-rails console
+
+ # For source installations
+ sudo -u git rails console RAILS_ENV=production
+ ```
+
+1. Run in the console:
+
+ ```ruby
+ redis = Redis.new(Gitlab::Redis.params)
+ redis.info
+ ```
+
+ Keep this screen open and try to simulate a failover below.
+
+1. To simulate a failover on master Redis, SSH into the Redis server and run:
+
+ ```bash
+ # port must match your master redis port
+ redis-cli -h localhost -p 6379 DEBUG sleep 60
+ ```
+
+1. Then back in the Rails console from the first step, run:
+
+ ```
+ redis.info
+ ```
+
+ You should see a different port after a few seconds delay
+ (the failover/reconnect time).
+
---
Read more on high-availability configuration:
@@ -60,3 +309,9 @@ Read more on high-availability configuration:
1. [Configure NFS](nfs.md)
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
+
+[ce-1877]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1877
+[restart]: ../restart_gitlab.md#installations-from-source
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[gh-531]: https://github.com/redis/redis-rb/issues/531
+[gh-534]: https://github.com/redis/redis-rb/issues/534
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index a5fa7d358a2..34b4f1faa94 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -1,6 +1,6 @@
# Housekeeping
-_**Note:** This feature was [introduced][ce-2371] in GitLab 8.4_
+> [Introduced][ce-2371] in GitLab 8.4.
---
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index c212059b9d5..39b1883375e 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -1,13 +1,14 @@
# Project import/export
>**Note:**
- - This feature was [introduced][ce-3050] in GitLab 8.9
- - Importing will not be possible if the import instance version is lower
- than that of the exporter.
- - For existing installations, the project import option has to be enabled in
- application settings (`/admin/application_settings`) under 'Import sources'.
- - The exports are stored in a temporary [shared directory][tmp] and are deleted
- every 24 hours by a specific worker.
+>
+> - [Introduced][ce-3050] in GitLab 8.9.
+> - Importing will not be possible if the import instance version is lower
+> than that of the exporter.
+> - For existing installations, the project import option has to be enabled in
+> application settings (`/admin/application_settings`) under 'Import sources'.
+> - The exports are stored in a temporary [shared directory][tmp] and are deleted
+> every 24 hours by a specific worker.
The GitLab Import/Export version can be checked by using:
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 4172b604cec..bc2b1f20ed3 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -1,8 +1,7 @@
# Repository checks
->**Note:**
-This feature was [introduced][ce-3232] in GitLab 8.7. It is OFF by
-default because it still causes too many false alarms.
+> [Introduced][ce-3232] in GitLab 8.7. It is OFF by default because it still
+causes too many false alarms.
Git has a built-in mechanism, [git fsck][git-fsck], to verify the
integrity of all data committed to a repository. GitLab administrators
diff --git a/doc/api/README.md b/doc/api/README.md
index d1e6c54c521..f3117815c7c 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -16,6 +16,8 @@ following locations:
- [Commits](commits.md)
- [Deploy Keys](deploy_keys.md)
- [Groups](groups.md)
+- [Group Access Requests](access_requests.md)
+- [Group Members](members.md)
- [Issues](issues.md)
- [Keys](keys.md)
- [Labels](labels.md)
@@ -25,6 +27,8 @@ following locations:
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
- [Projects](projects.md) including setting Webhooks
+- [Project Access Requests](access_requests.md)
+- [Project Members](members.md)
- [Project Snippets](project_snippets.md)
- [Repositories](repositories.md)
- [Repository Files](repository_files.md)
@@ -74,14 +78,14 @@ You can use an OAuth 2 token to authenticate with the API by passing it either i
Example of using the OAuth2 token in the header:
```shell
-curl -H "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/projects
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v3/projects
```
Read more about [GitLab as an OAuth2 client](oauth2.md).
### Personal Access Tokens
-> **Note:** This feature was [introduced][ce-3749] in GitLab 8.8
+> [Introduced][ce-3749] in GitLab 8.8.
You can create as many personal access tokens as you like from your GitLab
profile (`/profile/personal_access_tokens`); perhaps one for each application
@@ -154,7 +158,7 @@ be returned with status code `403`:
```json
{
- "message": "403 Forbidden: Must be admin to use sudo"
+ "message": "403 Forbidden - Must be admin to use sudo"
}
```
@@ -204,7 +208,7 @@ resources you can pass the following parameters:
In the example below, we list 50 [namespaces](namespaces.md) per page.
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/namespaces?per_page=50
```
### Pagination Link header
@@ -218,7 +222,7 @@ and we request the second page (`page=2`) of [comments](notes.md) of the issue
with ID `8` which belongs to the project with ID `8`:
```bash
-curl -I -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/issues/8/notes?per_page=3&page=2
+curl --head --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/issues/8/notes?per_page=3&page=2
```
The response will then be:
diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md
new file mode 100644
index 00000000000..ea308b54d62
--- /dev/null
+++ b/doc/api/access_requests.md
@@ -0,0 +1,147 @@
+# Group and project access requests
+
+ >**Note:** This feature was introduced in GitLab 8.11
+
+ **Valid access levels**
+
+ The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
+
+```
+10 => Guest access
+20 => Reporter access
+30 => Developer access
+40 => Master access
+50 => Owner access # Only valid for groups
+```
+
+## List access requests for a group or project
+
+Gets a list of access requests viewable by the authenticated user.
+
+Returns `200` if the request succeeds.
+
+```
+GET /groups/:id/access_requests
+GET /projects/:id/access_requests
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "requested_at": "2012-10-22T14:13:35Z"
+ },
+ {
+ "id": 2,
+ "username": "john_doe",
+ "name": "John Doe",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "requested_at": "2012-10-22T14:13:35Z"
+ }
+]
+```
+
+## Request access to a group or project
+
+Requests access for the authenticated user to a group or project.
+
+Returns `201` if the request succeeds.
+
+```
+POST /groups/:id/access_requests
+POST /projects/:id/access_requests
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "requested_at": "2012-10-22T14:13:35Z"
+}
+```
+
+## Approve an access request
+
+Approves an access request for the given user.
+
+Returns `201` if the request succeeds.
+
+```
+PUT /groups/:id/access_requests/:user_id/approve
+PUT /projects/:id/access_requests/:user_id/approve
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the access requester |
+| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests/:user_id/approve?access_level=20
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests/:user_id/approve?access_level=20
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 20
+}
+```
+
+## Deny an access request
+
+Denies an access request for the given user.
+
+Returns `200` if the request succeeds.
+
+```
+DELETE /groups/:id/access_requests/:user_id
+DELETE /projects/:id/access_requests/:user_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the access requester |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/access_requests/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/access_requests/:user_id
+```
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 796b3680a75..72ec99b7c56 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,6 +1,6 @@
# Award Emoji
- >**Note:** This feature was introduced in GitLab 8.9
+> [Introduced][ce-4575] in GitLab 8.9.
An awarded emoji tells a thousand words, and can be awarded on issues, merge
requests and notes/comments. Issues, merge requests and notes are further called
@@ -25,7 +25,7 @@ Parameters:
| `awardable_id` | integer | yes | The ID of an awardable |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji
```
Example Response:
@@ -85,7 +85,7 @@ Parameters:
| `award_id` | integer | yes | The ID of the award emoji |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/1
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/1
```
Example Response:
@@ -127,7 +127,7 @@ Parameters:
| `name` | string | yes | The name of the emoji, without colons |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji?name=blowfish
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji?name=blowfish
```
Example Response:
@@ -170,7 +170,7 @@ Parameters:
| `award_id` | integer | yes | The ID of a award_emoji |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/344
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/344
```
Example Response:
@@ -217,7 +217,7 @@ Parameters:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji
```
Example Response:
@@ -259,7 +259,7 @@ Parameters:
| `award_id` | integer | yes | The ID of the award emoji |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji/2
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji/2
```
Example Response:
@@ -299,7 +299,7 @@ Parameters:
| `name` | string | yes | The name of the emoji, without colons |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji?name=rocket
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/notes/1/award_emoji?name=rocket
```
Example Response:
@@ -342,7 +342,7 @@ Parameters:
| `award_id` | integer | yes | The ID of a award_emoji |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/345
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" http://gitlab.example.com/api/v3/projects/1/issues/80/award_emoji/345
```
Example Response:
@@ -365,3 +365,5 @@ Example Response:
"awardable_type": "Note"
}
```
+
+[ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575
diff --git a/doc/api/branches.md b/doc/api/branches.md
index dbe8306c66f..0b5f7778fc7 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -13,7 +13,7 @@ GET /projects/:id/repository/branches
| `id` | integer | yes | The ID of a project |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches
```
Example response:
@@ -57,7 +57,7 @@ GET /projects/:id/repository/branches/:branch
| `branch` | string | yes | The name of the branch |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master
```
Example response:
@@ -95,7 +95,7 @@ PUT /projects/:id/repository/branches/:branch/protect
```
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
```
| Attribute | Type | Required | Description |
@@ -140,7 +140,7 @@ PUT /projects/:id/repository/branches/:branch/unprotect
```
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/unprotect
```
| Attribute | Type | Required | Description |
@@ -185,7 +185,7 @@ POST /projects/:id/repository/branches
| `ref` | string | yes | The branch name or commit SHA to create branch from |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches?branch_name=newbranch&ref=master"
```
Example response:
@@ -230,7 +230,7 @@ It returns `200` if it succeeds, `404` if the branch to be deleted does not exis
or `400` for other reasons. In case of an error, an explaining message is provided.
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/branches/newbranch"
```
Example response:
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 0881a7d7a90..1b7a1840138 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -15,7 +15,7 @@ GET /projects/:id/triggers
| `id` | integer | yes | The ID of a project |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
+curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
```
```json
@@ -51,7 +51,7 @@ GET /projects/:id/triggers/:token
| `token` | string | yes | The `token` of a trigger |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
+curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
```json
@@ -77,7 +77,7 @@ POST /projects/:id/triggers
| `id` | integer | yes | The ID of a project |
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
+curl --request POST --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers"
```
```json
@@ -104,7 +104,7 @@ DELETE /projects/:id/triggers/:token
| `token` | string | yes | The `token` of a trigger |
```
-curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
+curl --request DELETE --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
```json
diff --git a/doc/api/build_variables.md b/doc/api/build_variables.md
index b96f1bdac8a..a21751a49ea 100644
--- a/doc/api/build_variables.md
+++ b/doc/api/build_variables.md
@@ -13,7 +13,7 @@ GET /projects/:id/variables
| `id` | integer | yes | The ID of a project |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables"
+curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables"
```
```json
@@ -43,7 +43,7 @@ GET /projects/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1"
+curl --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/TEST_VARIABLE_1"
```
```json
@@ -68,7 +68,7 @@ POST /projects/:id/variables
| `value` | string | yes | The `value` of a variable |
```
-curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" -F "key=NEW_VARIABLE" -F "value=new value"
+curl --request POST --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
```
```json
@@ -93,7 +93,7 @@ PUT /projects/:id/variables/:key
| `value` | string | yes | The `value` of a variable |
```
-curl -X PUT -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" -F "value=updated value"
+curl --request PUT --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
```
```json
@@ -117,7 +117,7 @@ DELETE /projects/:id/variables/:key
| `key` | string | yes | The `key` of a variable |
```
-curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1"
+curl --request DELETE --header "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/variables/VARIABLE_1"
```
```json
diff --git a/doc/api/builds.md b/doc/api/builds.md
index 24d90e22a9b..8864df03c98 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -14,7 +14,7 @@ GET /projects/:id/builds
| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
```
Example of response
@@ -123,7 +123,7 @@ GET /projects/:id/repository/commits/:sha/builds
| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `pending`, `running`, `failed`, `success`, `canceled`; showing all builds if none provided |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
```
Example of response
@@ -209,7 +209,7 @@ GET /projects/:id/builds/:build_id
| `build_id` | integer | yes | The ID of a build |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
```
Example of response
@@ -271,7 +271,7 @@ GET /projects/:id/builds/:build_id/artifacts
| `build_id` | integer | yes | The ID of a build |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/artifacts"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/artifacts"
```
Response:
@@ -305,7 +305,7 @@ Parameters
Example request:
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/artifacts/master/download?job=test"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/artifacts/master/download?job=test"
```
Example response:
@@ -331,7 +331,7 @@ GET /projects/:id/builds/:build_id/trace
| build_id | integer | yes | The ID of a build |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/trace"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8/trace"
```
Response:
@@ -355,7 +355,7 @@ POST /projects/:id/builds/:build_id/cancel
| `build_id` | integer | yes | The ID of a build |
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
```
Example of response
@@ -401,7 +401,7 @@ POST /projects/:id/builds/:build_id/retry
| `build_id` | integer | yes | The ID of a build |
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
```
Example of response
@@ -451,7 +451,7 @@ Parameters
Example of request
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/erase"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/erase"
```
Example of response
@@ -501,7 +501,7 @@ Parameters
Example request:
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/artifacts/keep"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/artifacts/keep"
```
Example response:
diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md
index d779463fd8c..2a71b087f19 100644
--- a/doc/api/ci/builds.md
+++ b/doc/api/ci/builds.md
@@ -35,7 +35,7 @@ POST /ci/api/v1/builds/register
```
-curl -X POST "https://gitlab.example.com/ci/api/v1/builds/register" -F "token=t0k3n"
+curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n"
```
### Update details of an existing build
@@ -52,7 +52,7 @@ PUT /ci/api/v1/builds/:id
| `trace` | string | no | The trace of a build |
```
-curl -X PUT "https://gitlab.example.com/ci/api/v1/builds/1234" -F "token=t0k3n" -F "state=running" -F "trace=Running git clone...\n"
+curl --request PUT "https://gitlab.example.com/ci/api/v1/builds/1234" --form "token=t0k3n" --form "state=running" --form "trace=Running git clone...\n"
```
### Incremental build trace update
@@ -87,7 +87,7 @@ Headers:
| `Content-Range` | string | yes | Bytes range of trace that is sent |
```
-curl -X PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" -H "BUILD-TOKEN=build_t0k3n" -H "Content-Range=0-21" -d "Running git clone...\n"
+curl --request PATCH "https://gitlab.example.com/ci/api/v1/builds/1234/trace.txt" --header "BUILD-TOKEN=build_t0k3n" --header "Content-Range=0-21" --data "Running git clone...\n"
```
@@ -104,7 +104,7 @@ POST /ci/api/v1/builds/:id/artifacts
| `file` | mixed | yes | Artifacts file |
```
-curl -X POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n" -F "file=@/path/to/file"
+curl --request POST "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n" --form "file=@/path/to/file"
```
### Download the artifacts file from build
@@ -119,7 +119,7 @@ GET /ci/api/v1/builds/:id/artifacts
| `token` | string | yes | The build authorization token |
```
-curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n"
+curl "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n"
```
### Remove the artifacts file from build
@@ -134,5 +134,5 @@ DELETE /ci/api/v1/builds/:id/artifacts
| `token` | string | yes | The build authorization token |
```
-curl -X DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" -F "token=build_t0k3n"
+curl --request DELETE "https://gitlab.example.com/ci/api/v1/builds/1234/artifacts" --form "token=build_t0k3n"
```
diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md
index 96b3c42f773..ecec53fde03 100644
--- a/doc/api/ci/runners.md
+++ b/doc/api/ci/runners.md
@@ -35,7 +35,7 @@ POST /ci/api/v1/runners/register
Example request:
```sh
-curl -X POST "https://gitlab.example.com/ci/api/v1/runners/register" -F "token=t0k3n"
+curl --request POST "https://gitlab.example.com/ci/api/v1/runners/register" --form "token=t0k3n"
```
## Delete a Runner
@@ -53,5 +53,5 @@ DELETE /ci/api/v1/runners/delete
Example request:
```sh
-curl -X DELETE "https://gitlab.example.com/ci/api/v1/runners/delete" -F "token=t0k3n"
+curl --request DELETE "https://gitlab.example.com/ci/api/v1/runners/delete" --form "token=t0k3n"
```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 57c2e1d9b87..5c98c5d7565 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -16,7 +16,7 @@ GET /projects/:id/repository/commits
| `until` | string | no | Only commits before or in this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits"
```
Example response:
@@ -62,7 +62,7 @@ Parameters:
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master
```
Example response:
@@ -81,6 +81,11 @@ Example response:
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
],
+ "stats": {
+ "additions": 15,
+ "deletions": 10,
+ "total": 25
+ },
"status": "running"
}
```
@@ -101,7 +106,7 @@ Parameters:
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/diff"
```
Example response:
@@ -137,7 +142,7 @@ Parameters:
| `sha` | string | yes | The commit hash or name of a repository branch or tag |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/comments"
```
Example response:
@@ -190,7 +195,7 @@ POST /projects/:id/repository/commits/:sha/comments
| `line_type` | string | no | The line type. Takes `new` or `old` as arguments |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -F "note=Nice picture man\!" -F "path=dudeism.md" -F "line=11" -F "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "note=Nice picture man\!" --form "path=dudeism.md" --form "line=11" --form "line_type=new" https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/comments
```
Example response:
@@ -235,7 +240,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `all` | boolean | no | Return all statuses, not only the latest ones
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/repository/commits/18f3e63d05582537db6d183d9d557be09e1f90c8/statuses
```
Example response:
@@ -310,7 +315,7 @@ POST /projects/:id/statuses/:sha
| `description` | string | no | The short description of the status
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
```
Example response:
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index 9280f0d68b6..73cb4b7ea8c 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -7,23 +7,23 @@ First, find the ID of the projects you're interested in, by either listing all
projects:
```
-curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
+curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
```
Or finding the ID of a group and then listing all projects in that group:
```
-curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
+curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234:
-curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
+curl --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
```
With those IDs, add the same deploy key to all:
```
for project_id in 321 456 987; do
- curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
+ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/deploy_keys
done
```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 4e620ccc81a..ca44afbf355 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -9,7 +9,7 @@ GET /deploy_keys
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/deploy_keys"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/deploy_keys"
```
Example response:
@@ -44,7 +44,7 @@ GET /projects/:id/deploy_keys
| `id` | integer | yes | The ID of the project |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys"
```
Example response:
@@ -82,7 +82,7 @@ Parameters:
| `key_id` | integer | yes | The ID of the deploy key |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/11"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/11"
```
Example response:
@@ -114,7 +114,7 @@ POST /projects/:id/deploy_keys
| `key` | string | yes | New deploy key |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/"
```
Example response:
@@ -142,7 +142,7 @@ DELETE /projects/:id/deploy_keys/:key_id
| `key_id` | integer | yes | The ID of the deploy key |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/13"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/deploy_keys/13"
```
Example response:
@@ -159,3 +159,51 @@ Example response:
"id" : 13
}
```
+
+## Enable a deploy key
+
+Enables a deploy key for a project so this can be used. Returns the enabled key, with a status code 201 when successful.
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/enable
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
+
+Example response:
+
+```json
+{
+ "key" : "ssh-rsa AAAA...",
+ "id" : 12,
+ "title" : "My deploy key",
+ "created_at" : "2015-08-29T12:44:31.550Z"
+}
+```
+
+## Disable a deploy key
+
+Disable a deploy key for a project. Returns the disabled key.
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/deploy_keys/13/disable
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the project |
+| `key_id` | integer | yes | The ID of the deploy key |
+
+Example response:
+
+```json
+{
+ "key" : "ssh-rsa AAAA...",
+ "id" : 12,
+ "title" : "My deploy key",
+ "created_at" : "2015-08-29T12:44:31.550Z"
+}
+```
diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md
new file mode 100644
index 00000000000..87a5fa67124
--- /dev/null
+++ b/doc/api/enviroments.md
@@ -0,0 +1,117 @@
+# Environments
+
+## List environments
+
+Get all environments for a given project.
+
+```
+GET /projects/:id/environments
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/environments
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Env1",
+ "external_url": "https://env1.example.gitlab.com"
+ }
+]
+```
+
+## Create a new environment
+
+Creates a new environment with the given name and external_url.
+
+It returns 201 if the environment was successfully created, 400 for wrong parameters.
+
+```
+POST /projects/:id/environment
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer | yes | The ID of the project |
+| `name` | string | yes | The name of the environment |
+| `external_url` | string | no | Place to link to for this environment |
+
+```bash
+curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "deploy",
+ "external_url": "https://deploy.example.gitlab.com"
+}
+```
+
+## Edit an existing environment
+
+Updates an existing environment's name and/or external_url.
+
+It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned.
+
+```
+PUT /projects/:id/environments/:environments_id
+```
+
+| Attribute | Type | Required | Description |
+| --------------- | ------- | --------------------------------- | ------------------------------- |
+| `id` | integer | yes | The ID of the project |
+| `environment_id` | integer | yes | The ID of the environment | The ID of the environment |
+| `name` | string | no | The new name of the environment |
+| `external_url` | string | no | The new external_url |
+
+```bash
+curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "staging",
+ "external_url": "https://staging.example.gitlab.com"
+}
+```
+
+## Delete an environment
+
+It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist.
+
+```
+DELETE /projects/:id/environments/:environment_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer | yes | The ID of the project |
+| `environment_id` | integer | yes | The ID of the environment |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "deploy",
+ "external_url": "https://deploy.example.gitlab.com"
+}
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 87480bebfc4..a898387eaa2 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -1,514 +1,434 @@
-# Groups
-
-## List groups
-
-Get a list of groups. (As user: my groups, as admin: all groups)
-
-```
-GET /groups
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-You can search for groups by name or path, see below.
-
-
-## List a group's projects
-
-Get a list of projects in this group.
-
-```
-GET /groups/:id/projects
-```
-
-Parameters:
-
-- `archived` (optional) - if passed, limit by archived status
-- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
-- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
-- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
-- `search` (optional) - Return list of authorized projects according to a search criteria
-- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
-
-```json
-[
- {
- "id": 9,
- "description": "foo",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 10,
- "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
- "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
- "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
- "name": "Html5 Boilerplate",
- "name_with_namespace": "Experimental / Html5 Boilerplate",
- "path": "html5-boilerplate",
- "path_with_namespace": "h5bp/html5-boilerplate",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": true,
- "created_at": "2016-04-05T21:40:50.169Z",
- "last_activity_at": "2016-04-06T16:52:08.432Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 5,
- "name": "Experimental",
- "path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
- },
- "avatar_url": null,
- "star_count": 1,
- "forks_count": 0,
- "open_issues_count": 3,
- "public_builds": true,
- "shared_with_groups": []
- }
-]
-```
-
-## Details of a group
-
-Get all details of a group.
-
-```
-GET /groups/:id
-```
-
-Parameters:
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or path of a group |
-
-```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
-```
-
-Example response:
-
-```json
-{
- "id": 4,
- "name": "Twitter",
- "path": "twitter",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "visibility_level": 20,
- "avatar_url": null,
- "web_url": "https://gitlab.example.com/groups/twitter",
- "projects": [
- {
- "id": 7,
- "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
- "default_branch": "master",
- "tag_list": [],
- "public": true,
- "archived": false,
- "visibility_level": 20,
- "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
- "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
- "web_url": "https://gitlab.example.com/twitter/typeahead-js",
- "name": "Typeahead.Js",
- "name_with_namespace": "Twitter / Typeahead.Js",
- "path": "typeahead-js",
- "path_with_namespace": "twitter/typeahead-js",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "container_registry_enabled": true,
- "created_at": "2016-06-17T07:47:25.578Z",
- "last_activity_at": "2016-06-17T07:47:25.881Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 4,
- "name": "Twitter",
- "path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "open_issues_count": 3,
- "public_builds": true,
- "shared_with_groups": []
- },
- {
- "id": 6,
- "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 10,
- "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
- "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
- "web_url": "https://gitlab.example.com/twitter/flight",
- "name": "Flight",
- "name_with_namespace": "Twitter / Flight",
- "path": "flight",
- "path_with_namespace": "twitter/flight",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "container_registry_enabled": true,
- "created_at": "2016-06-17T07:47:24.661Z",
- "last_activity_at": "2016-06-17T07:47:24.838Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 4,
- "name": "Twitter",
- "path": "twitter",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:24.216Z",
- "updated_at": "2016-06-17T07:47:24.216Z",
- "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "open_issues_count": 8,
- "public_builds": true,
- "shared_with_groups": []
- }
- ],
- "shared_projects": [
- {
- "id": 8,
- "description": "Velit eveniet provident fugiat saepe eligendi autem.",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 0,
- "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
- "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
- "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
- "name": "Html5 Boilerplate",
- "name_with_namespace": "H5bp / Html5 Boilerplate",
- "path": "html5-boilerplate",
- "path_with_namespace": "h5bp/html5-boilerplate",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": false,
- "container_registry_enabled": true,
- "created_at": "2016-06-17T07:47:27.089Z",
- "last_activity_at": "2016-06-17T07:47:27.310Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 5,
- "name": "H5bp",
- "path": "h5bp",
- "owner_id": null,
- "created_at": "2016-06-17T07:47:26.621Z",
- "updated_at": "2016-06-17T07:47:26.621Z",
- "description": "Id consequatur rem vel qui doloremque saepe.",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 20
- },
- "avatar_url": null,
- "star_count": 0,
- "forks_count": 0,
- "open_issues_count": 4,
- "public_builds": true,
- "shared_with_groups": [
- {
- "group_id": 4,
- "group_name": "Twitter",
- "group_access_level": 30
- },
- {
- "group_id": 3,
- "group_name": "Gitlab Org",
- "group_access_level": 10
- }
- ]
- }
- ]
-}
-```
-
-## New group
-
-Creates a new project group. Available only for users who can create groups.
-
-```
-POST /groups
-```
-
-Parameters:
-
-- `name` (required) - The name of the group
-- `path` (required) - The path of the group
-- `description` (optional) - The group's description
-- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
-
-## Transfer project to group
-
-Transfer a project to the Group namespace. Available only for admin
-
-```
-POST /groups/:id/projects/:project_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `project_id` (required) - The ID of a project
-
-## Update group
-
-Updates the project group. Only available to group owners and administrators.
-
-```
-PUT /groups/:id
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of the group |
-| `name` | string | no | The name of the group |
-| `path` | string | no | The path of the group |
-| `description` | string | no | The description of the group |
-| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
-
-```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
-
-```
-
-Example response:
-
-```json
-{
- "id": 5,
- "name": "Experimental",
- "path": "h5bp",
- "description": "foo",
- "visibility_level": 10,
- "avatar_url": null,
- "web_url": "http://gitlab.example.com/groups/h5bp",
- "projects": [
- {
- "id": 9,
- "description": "foo",
- "default_branch": "master",
- "tag_list": [],
- "public": false,
- "archived": false,
- "visibility_level": 10,
- "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
- "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
- "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
- "name": "Html5 Boilerplate",
- "name_with_namespace": "Experimental / Html5 Boilerplate",
- "path": "html5-boilerplate",
- "path_with_namespace": "h5bp/html5-boilerplate",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "builds_enabled": true,
- "snippets_enabled": true,
- "created_at": "2016-04-05T21:40:50.169Z",
- "last_activity_at": "2016-04-06T16:52:08.432Z",
- "shared_runners_enabled": true,
- "creator_id": 1,
- "namespace": {
- "id": 5,
- "name": "Experimental",
- "path": "h5bp",
- "owner_id": null,
- "created_at": "2016-04-05T21:40:49.152Z",
- "updated_at": "2016-04-07T08:07:48.466Z",
- "description": "foo",
- "avatar": {
- "url": null
- },
- "share_with_group_lock": false,
- "visibility_level": 10
- },
- "avatar_url": null,
- "star_count": 1,
- "forks_count": 0,
- "open_issues_count": 3,
- "public_builds": true,
- "shared_with_groups": []
- }
- ]
-}
-```
-
-## Remove group
-
-Removes group with all projects inside.
-
-```
-DELETE /groups/:id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-
-## Search for group
-
-Get all groups that match your string in their name or path.
-
-```
-GET /groups?search=foobar
-```
-
-```json
-[
- {
- "id": 1,
- "name": "Foobar Group",
- "path": "foo-bar",
- "description": "An interesting group"
- }
-]
-```
-
-## Group members
-
-**Group access levels**
-
-The group access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
-
-```
-GUEST = 10
-REPORTER = 20
-DEVELOPER = 30
-MASTER = 40
-OWNER = 50
-```
-
-### List group members
-
-Get a list of group members viewable by the authenticated user.
-
-```
-GET /groups/:id/members
-```
-
-```json
-[
- {
- "id": 1,
- "username": "raymond_smith",
- "name": "Raymond Smith",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- },
- {
- "id": 2,
- "username": "john_doe",
- "name": "John Doe",
- "state": "active",
- "created_at": "2012-10-22T14:13:35Z",
- "access_level": 30
- }
-]
-```
-
-### Add group member
-
-Adds a user to the list of group members.
-
-```
-POST /groups/:id/members
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a group
-- `user_id` (required) - The ID of a user to add
-- `access_level` (required) - Project access level
-
-### Edit group team member
-
-Updates a group team member to a specified access level.
-
-```
-PUT /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID of a group
-- `user_id` (required) - The ID of a group member
-- `access_level` (required) - Project access level
-
-### Remove user team member
-
-Removes user from user team.
-
-```
-DELETE /groups/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or path of a user group
-- `user_id` (required) - The ID of a group member
-
-## Namespaces in groups
-
-By default, groups only get 20 namespaces at a time because the API results are paginated.
-
-To get more (up to 100), pass the following as an argument to the API call:
-```
-/groups?per_page=100
-```
-
-And to switch pages add:
-```
-/groups?per_page=100&page=2
-```
+# Groups
+
+## List groups
+
+Get a list of groups. (As user: my groups, as admin: all groups)
+
+```
+GET /groups
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
+
+You can search for groups by name or path, see below.
+
+
+## List a group's projects
+
+Get a list of projects in this group.
+
+```
+GET /groups/:id/projects
+```
+
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first
+
+```json
+[
+ {
+ "id": 9,
+ "description": "foo",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
+ },
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+]
+```
+
+## Details of a group
+
+Get all details of a group.
+
+```
+GET /groups/:id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or path of a group |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4
+```
+
+Example response:
+
+```json
+{
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "visibility_level": 20,
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "projects": [
+ {
+ "id": 7,
+ "description": "Voluptas veniam qui et beatae voluptas doloremque explicabo facilis.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/typeahead-js.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/typeahead-js.git",
+ "web_url": "https://gitlab.example.com/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:25.578Z",
+ "last_activity_at": "2016-06-17T07:47:25.881Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ },
+ {
+ "id": 6,
+ "description": "Aspernatur omnis repudiandae qui voluptatibus eaque.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com:twitter/flight.git",
+ "http_url_to_repo": "https://gitlab.example.com/twitter/flight.git",
+ "web_url": "https://gitlab.example.com/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:24.661Z",
+ "last_activity_at": "2016-06-17T07:47:24.838Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:24.216Z",
+ "updated_at": "2016-06-17T07:47:24.216Z",
+ "description": "Aliquid qui quis dignissimos distinctio ut commodi voluptas est.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 8,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ],
+ "shared_projects": [
+ {
+ "id": 8,
+ "description": "Velit eveniet provident fugiat saepe eligendi autem.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "git@gitlab.example.com:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "https://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "https://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "container_registry_enabled": true,
+ "created_at": "2016-06-17T07:47:27.089Z",
+ "last_activity_at": "2016-06-17T07:47:27.310Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-06-17T07:47:26.621Z",
+ "updated_at": "2016-06-17T07:47:26.621Z",
+ "description": "Id consequatur rem vel qui doloremque saepe.",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 20
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "open_issues_count": 4,
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
+ }
+ ]
+}
+```
+
+## New group
+
+Creates a new project group. Available only for users who can create groups.
+
+```
+POST /groups
+```
+
+Parameters:
+
+- `name` (required) - The name of the group
+- `path` (required) - The path of the group
+- `description` (optional) - The group's description
+- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
+
+## Transfer project to group
+
+Transfer a project to the Group namespace. Available only for admin
+
+```
+POST /groups/:id/projects/:project_id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a group
+- `project_id` (required) - The ID of a project
+
+## Update group
+
+Updates the project group. Only available to group owners and administrators.
+
+```
+PUT /groups/:id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of the group |
+| `name` | string | no | The name of the group |
+| `path` | string | no | The path of the group |
+| `description` | string | no | The description of the group |
+| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
+
+```
+
+Example response:
+
+```json
+{
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "description": "foo",
+ "visibility_level": 10,
+ "avatar_url": null,
+ "web_url": "http://gitlab.example.com/groups/h5bp",
+ "projects": [
+ {
+ "id": 9,
+ "description": "foo",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "git@gitlab.example.com/html5-boilerplate.git",
+ "http_url_to_repo": "http://gitlab.example.com/h5bp/html5-boilerplate.git",
+ "web_url": "http://gitlab.example.com/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "Experimental / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": true,
+ "created_at": "2016-04-05T21:40:50.169Z",
+ "last_activity_at": "2016-04-06T16:52:08.432Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "Experimental",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-04-05T21:40:49.152Z",
+ "updated_at": "2016-04-07T08:07:48.466Z",
+ "description": "foo",
+ "avatar": {
+ "url": null
+ },
+ "share_with_group_lock": false,
+ "visibility_level": 10
+ },
+ "avatar_url": null,
+ "star_count": 1,
+ "forks_count": 0,
+ "open_issues_count": 3,
+ "public_builds": true,
+ "shared_with_groups": []
+ }
+ ]
+}
+```
+
+## Remove group
+
+Removes group with all projects inside.
+
+```
+DELETE /groups/:id
+```
+
+Parameters:
+
+- `id` (required) - The ID or path of a user group
+
+## Search for group
+
+Get all groups that match your string in their name or path.
+
+```
+GET /groups?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "name": "Foobar Group",
+ "path": "foo-bar",
+ "description": "An interesting group"
+ }
+]
+```
+
+## Group members
+
+Please consult the [Group Members](members.md) documentation.
+
+## Namespaces in groups
+
+By default, groups only get 20 namespaces at a time because the API results are paginated.
+
+To get more (up to 100), pass the following as an argument to the API call:
+```
+/groups?per_page=100
+```
+
+And to switch pages add:
+```
+/groups?per_page=100&page=2
+```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 419fb8f85d8..a665645ad0e 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -33,7 +33,7 @@ GET /issues?labels=foo,bar&state=opened
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/issues
```
Example response:
@@ -110,7 +110,7 @@ GET /groups/:id/issues?milestone=1.0.0&state=opened
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues
```
Example response:
@@ -189,7 +189,7 @@ GET /projects/:id/issues?iid=42
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues
```
Example response:
@@ -254,7 +254,7 @@ GET /projects/:id/issues/:issue_id
| `issue_id`| integer | yes | The ID of a project's issue |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/41
```
Example response:
@@ -327,7 +327,7 @@ POST /projects/:id/issues
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
```
Example response:
@@ -388,7 +388,7 @@ PUT /projects/:id/issues/:issue_id
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
```
Example response:
@@ -438,7 +438,7 @@ DELETE /projects/:id/issues/:issue_id
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85
```
## Move an issue
@@ -463,7 +463,7 @@ POST /projects/:id/issues/:issue_id/move
| `to_project_id` | integer | yes | The ID of the new project |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85/move
```
Example response:
@@ -518,7 +518,7 @@ POST /projects/:id/issues/:issue_id/subscription
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
```
Example response:
@@ -573,7 +573,7 @@ DELETE /projects/:id/issues/:issue_id/subscription
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/subscription
```
Example response:
@@ -628,7 +628,7 @@ POST /projects/:id/issues/:issue_id/todo
| `issue_id` | integer | yes | The ID of a project's issue |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/todo
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/93/todo
```
Example response:
diff --git a/doc/api/labels.md b/doc/api/labels.md
index a181c0f57a2..3653ccf304a 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -13,7 +13,7 @@ GET /projects/:id/labels
| `id` | integer | yes | The ID of the project |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/labels
```
Example response:
@@ -82,7 +82,7 @@ POST /projects/:id/labels
| `description` | string | no | The description of the label |
```bash
-curl --data "name=feature&color=#5843AD" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+curl --data "name=feature&color=#5843AD" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
```
Example response:
@@ -113,7 +113,7 @@ DELETE /projects/:id/labels
| `name` | string | yes | The name of the label |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels?name=bug"
```
Example response:
@@ -153,7 +153,7 @@ PUT /projects/:id/labels
| `description` | string | no | The new description of the label |
```bash
-curl -X PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
+curl --request PUT --data "name=documentation&new_name=docs&color=#8E44AD&description=Documentation" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/labels"
```
Example response:
@@ -184,7 +184,7 @@ POST /projects/:id/labels/:label_id/subscription
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
```
Example response:
@@ -219,7 +219,7 @@ DELETE /projects/:id/labels/:label_id/subscription
| `label_id` | integer or string | yes | The ID or title of a project's label |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription
```
Example response:
diff --git a/doc/api/licenses.md b/doc/api/licenses.md
index 855b0eab56f..ed26d1fb7fb 100644
--- a/doc/api/licenses.md
+++ b/doc/api/licenses.md
@@ -116,7 +116,7 @@ If you omit the `fullname` parameter but authenticate your request, the name of
the authenticated user will be used to replace the copyright holder placeholder.
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project
```
Example response:
diff --git a/doc/api/members.md b/doc/api/members.md
new file mode 100644
index 00000000000..d002e6eaf89
--- /dev/null
+++ b/doc/api/members.md
@@ -0,0 +1,182 @@
+# Group and project members
+
+**Valid access levels**
+
+The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
+
+```
+10 => Guest access
+20 => Reporter access
+30 => Developer access
+40 => Master access
+50 => Owner access # Only valid for groups
+```
+
+## List all members of a group or project
+
+Gets a list of group or project members viewable by the authenticated user.
+
+Returns `200` if the request succeeds.
+
+```
+GET /groups/:id/members
+GET /projects/:id/members
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `query` | string | no | A query string to search for members |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+ },
+ {
+ "id": 2,
+ "username": "john_doe",
+ "name": "John Doe",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+ }
+]
+```
+
+## Get a member of a group or project
+
+Gets a member of a group or project.
+
+Returns `200` if the request succeeds.
+
+```
+GET /groups/:id/members/:user_id
+GET /projects/:id/members/:user_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the member |
+
+```bash
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+}
+```
+
+## Add a member to a group or project
+
+Adds a member to a group or project.
+
+Returns `201` if the request succeeds.
+
+```
+POST /groups/:id/members
+POST /projects/:id/members
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the new member |
+| `access_level` | integer | yes | A valid access level |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=30
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=30
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 30
+}
+```
+
+## Edit a member of a group or project
+
+Updates a member of a group or project.
+
+Returns `200` if the request succeeds.
+
+```
+PUT /groups/:id/members/:user_id
+PUT /projects/:id/members/:user_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the member |
+| `access_level` | integer | yes | A valid access level |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=40
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=40
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "username": "raymond_smith",
+ "name": "Raymond Smith",
+ "state": "active",
+ "created_at": "2012-10-22T14:13:35Z",
+ "access_level": 40
+}
+```
+
+## Remove a member from a group or project
+
+Removes a user from a group or project.
+
+Returns `200` if the request succeeds.
+
+```
+DELETE /groups/:id/members/:user_id
+DELETE /projects/:id/members/:user_id
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The group/project ID or path |
+| `user_id` | integer | yes | The user ID of the member |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index e00882e6d5d..3e88a758936 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -418,7 +418,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id
| `merge_request_id` | integer | yes | The ID of a project's merge request |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85
```
## Accept MR
@@ -587,7 +587,7 @@ GET /projects/:id/merge_requests/:merge_request_id/closes_issues
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/76/merge_requests/1/closes_issues
```
Example response when the GitLab issue tracker is used:
@@ -665,7 +665,7 @@ POST /projects/:id/merge_requests/:merge_request_id/subscription
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
```
Example response:
@@ -739,7 +739,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id/subscription
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/17/subscription
```
Example response:
@@ -812,7 +812,7 @@ POST /projects/:id/merge_requests/:merge_request_id/todo
| `merge_request_id` | integer | yes | The ID of the merge request |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/27/todo
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/27/todo
```
Example response:
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index e4202025f80..ae7d22a4be5 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -20,7 +20,7 @@ Parameters:
| `state` | string | optional | Return only `active` or `closed` milestones` |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/milestones
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/milestones
```
Example Response:
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index 42d9ce3d391..88cd407d792 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -19,7 +19,7 @@ GET /namespaces
Example request:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces
```
Example response:
@@ -54,7 +54,7 @@ GET /namespaces?search=foobar
Example request:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/namespaces?search=twitter
```
Example response:
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 7aa1c2155bf..85d140d06ac 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -124,7 +124,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/11/notes/636
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/issues/11/notes/636
```
Example Response:
@@ -248,7 +248,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/snippets/52/notes/1659
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/snippets/52/notes/1659
```
Example Response:
@@ -376,7 +376,7 @@ Parameters:
| `note_id` | integer | yes | The ID of a note |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/7/notes/1602
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/merge_requests/7/notes/1602
```
Example Response:
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 31902e145f6..16ef79617c0 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,41 +1,62 @@
# GitLab as an OAuth2 client
-This document is about using other OAuth authentication service providers to sign into GitLab.
-If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
+This document covers using the OAuth2 protocol to access GitLab.
-OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password.
+If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md).
-Before using the OAuth2 you should create an application in user's account. Each application gets a unique App ID and App Secret parameters. You should not share these.
+OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password to a third-party.
This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper)
## Web Application Flow
-This flow is using for authentication from third-party web sites and is probably used the most.
-It basically consists of an exchange of an authorization token for an access token. For more detailed info, check out the [RFC spec here](http://tools.ietf.org/html/rfc6749#section-4.1)
+This is the most common type of flow and is used by server-side clients that wish to access GitLab on a user's behalf.
+
+>**Note:**
+This flow **should not** be used for client-side clients as you would leak your `client_secret`. Client-side clients should use the Implicit Grant (which is currently unsupported).
-This flow consists from 3 steps.
+For more detailed information, check out the [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1)
+
+In the following sections you will be introduced to the three steps needed for this flow.
### 1. Registering the client
-Create an application in user's account profile.
+First, you should create an application (`/profile/applications`) in your user's account.
+Each application gets a unique App ID and App Secret parameters.
+
+>**Note:**
+**You should not share/leak your App ID or App Secret.**
### 2. Requesting authorization
-To request the authorization token, you should visit the `/oauth/authorize` endpoint. You can do that by visiting manually the URL:
+To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint:
+
+```
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=your_unique_state_hash
+```
+
+This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided.
+
+The redirect will include the GET `code` parameter, for example:
```
-http://localhost:3000/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code
+http://myapp.com/oauth/redirect?code=1234567890&state=your_unique_state_hash
```
-Where REDIRECT_URI is the URL in your app where users will be sent after authorization.
+You should then use the `code` to request an access token.
+
+>**Important:**
+It is highly recommended that you send a `state` value with the request to `/oauth/authorize` and
+validate that value is returned and matches in the redirect request.
+This is important to prevent [CSFR attacks](http://www.oauthsecurity.com/#user-content-authorization-code-flow),
+`state` really should have been a requirement in the standard!
### 3. Requesting the access token
-To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client:
+Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example, we are using Ruby's `rest-client`:
```
-parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI'
+parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
RestClient.post 'http://localhost:3000/oauth/token', parameters
# The response will be
@@ -46,6 +67,8 @@ RestClient.post 'http://localhost:3000/oauth/token', parameters
"refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
}
```
+>**Note:**
+The `redirect_uri` must match the `redirect_uri` used in the original authorization request.
You can now make requests to the API with the access token returned.
@@ -60,7 +83,7 @@ GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN
Or you can put the token to the Authorization header:
```
-curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
+curl --header "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user
```
## Resource Owner Password Credentials
@@ -77,6 +100,9 @@ The credentials should only be used when there is a high degree of trust between
client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
available (such as an authorization code).
+>**Important:**
+Never store the users credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens] are a better choice.
+
Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0ba0bffb4ac..37d97b2db44 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -529,7 +529,7 @@ POST /projects/:id/star
| `id` | integer | yes | The ID of the project |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
```
Example response:
@@ -595,7 +595,7 @@ DELETE /projects/:id/star
| `id` | integer | yes | The ID of the project |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/5/star"
```
Example response:
@@ -665,7 +665,7 @@ POST /projects/:id/archive
| `id` | integer | yes | The ID of the project |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/archive"
```
Example response:
@@ -751,7 +751,7 @@ POST /projects/:id/unarchive
| `id` | integer | yes | The ID of the project |
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/unarchive"
```
Example response:
@@ -858,95 +858,9 @@ Parameters:
In Markdown contexts, the link is automatically expanded when the format in `markdown` is used.
-## Team members
+## Project members
-### List project team members
-
-Get a list of a project's team members.
-
-```
-GET /projects/:id/members
-```
-
-Parameters:
-
-- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
-- `query` (optional) - Query string to search for members
-
-### Get project team member
-
-Gets a project team member.
-
-```
-GET /projects/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
-- `user_id` (required) - The ID of a user
-
-```json
-{
- "id": 1,
- "username": "john_smith",
- "email": "john@example.com",
- "name": "John Smith",
- "state": "active",
- "created_at": "2012-05-23T08:00:58Z",
- "access_level": 40
-}
-```
-
-### Add project team member
-
-Adds a user to a project team. This is an idempotent method and can be called multiple times
-with the same parameters. Adding team membership to a user that is already a member does not
-affect the existing membership.
-
-```
-POST /projects/:id/members
-```
-
-Parameters:
-
-- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
-- `user_id` (required) - The ID of a user to add
-- `access_level` (required) - Project access level
-
-### Edit project team member
-
-Updates a project team member to a specified access level.
-
-```
-PUT /projects/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
-- `user_id` (required) - The ID of a team member
-- `access_level` (required) - Project access level
-
-### Remove project team member
-
-Removes a user from a project team.
-
-```
-DELETE /projects/:id/members/:user_id
-```
-
-Parameters:
-
-- `id` (required) - The ID or NAMESPACE/PROJECT_NAME of a project
-- `user_id` (required) - The ID of a team member
-
-This method removes the project member if the user has the proper access rights to do so.
-It returns a status code 403 if the member does not have the proper rights to perform this action.
-In all other cases this method is idempotent and revoking team membership for a user who is not
-currently a team member is considered success.
-Please note that the returned JSON currently differs slightly. Thus you should not
-rely on the returned JSON structure.
+Please consult the [Project Members](members.md) documentation.
### Share project with group
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 623063f357b..fc3af5544de 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -12,6 +12,10 @@ Allows you to receive information about file in repository like name, size, cont
GET /projects/:id/repository/files
```
+```bash
+curl --request GET --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/models/key.rb&ref=master'
+```
+
Example response:
```json
@@ -39,6 +43,10 @@ Parameters:
POST /projects/:id/repository/files
```
+```bash
+curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20content&commit_message=create%20a%20new%20file'
+```
+
Example response:
```json
@@ -62,6 +70,10 @@ Parameters:
PUT /projects/:id/repository/files
```
+```bash
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20other%20content&commit_message=update%20file'
+```
+
Example response:
```json
@@ -94,6 +106,10 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify
DELETE /projects/:id/repository/files
```
+```bash
+curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&commit_message=delete%20file'
+```
+
Example response:
```json
diff --git a/doc/api/runners.md b/doc/api/runners.md
index ddfa298f79d..28610762dca 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -18,7 +18,7 @@ GET /runners?scope=active
| `scope` | string | no | The scope of specific runners to show, one of: `active`, `paused`, `online`; showing all runners if none provided |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners"
```
Example response:
@@ -57,7 +57,7 @@ GET /runners/all?scope=online
| `scope` | string | no | The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`; showing all runners if none provided |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/all"
```
Example response:
@@ -108,7 +108,7 @@ GET /runners/:id
| `id` | integer | yes | The ID of a runner |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
```
Example response:
@@ -158,7 +158,7 @@ PUT /runners/:id
| `tag_list` | array | no | The list of tags for a runner; put array of tags, that should be finally assigned to a runner |
```
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" -F "description=test-1-20150125-test" -F "tag_list=ruby,mysql,tag1,tag2"
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
Example response:
@@ -207,7 +207,7 @@ DELETE /runners/:id
| `id` | integer | yes | The ID of a runner |
```
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/runners/6"
```
Example response:
@@ -237,7 +237,7 @@ GET /projects/:id/runners
| `id` | integer | yes | The ID of a project |
```
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners"
```
Example response:
@@ -275,7 +275,7 @@ POST /projects/:id/runners
| `runner_id` | integer | yes | The ID of a runner |
```
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" -F "runner_id=9"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners" --form "runner_id=9"
```
Example response:
@@ -306,7 +306,7 @@ DELETE /projects/:id/runners/:runner_id
| `runner_id` | integer | yes | The ID of a runner |
```
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners/9"
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/9/runners/9"
```
Example response:
diff --git a/doc/api/services.md b/doc/api/services.md
index f821a614047..579fdc0c8c9 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -355,7 +355,7 @@ PUT /projects/:id/services/gemnasium
Parameters:
-- `api_key` (**required**) - Your personal API KEY on gemnasium.com
+- `api_key` (**required**) - Your personal API KEY on gemnasium.com
- `token` (**required**) - The project's slug on gemnasium.com
### Delete Gemnasium service
@@ -503,6 +503,7 @@ PUT /projects/:id/services/pivotaltracker
Parameters:
- `token` (**required**)
+- `restrict_to_branch` (optional) - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.
### Delete PivotalTracker service
@@ -661,4 +662,3 @@ Get JetBrains TeamCity CI service settings for a project.
```
GET /projects/:id/services/teamcity
```
-
diff --git a/doc/api/session.md b/doc/api/session.md
index 066a055702d..9076c48b899 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -21,7 +21,7 @@ POST /session
| `password` | string | yes | The password of the user |
```bash
-curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
+curl --request POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
```
Example response:
diff --git a/doc/api/settings.md b/doc/api/settings.md
index ea39b32561c..a76dad0ebd4 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -13,7 +13,7 @@ GET /application/settings
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings
```
Example response:
@@ -75,7 +75,7 @@ PUT /application/settings
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols.
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1
```
Example response:
diff --git a/doc/api/sidekiq_metrics.md b/doc/api/sidekiq_metrics.md
index ebd131c94ca..1ae732d40d6 100644
--- a/doc/api/sidekiq_metrics.md
+++ b/doc/api/sidekiq_metrics.md
@@ -15,7 +15,7 @@ GET /sidekiq/queue_metrics
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/queue_metrics
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/queue_metrics
```
Example response:
@@ -40,7 +40,7 @@ GET /sidekiq/process_metrics
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/process_metrics
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/process_metrics
```
Example response:
@@ -82,7 +82,7 @@ GET /sidekiq/job_stats
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/job_stats
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/job_stats
```
Example response:
@@ -106,7 +106,7 @@ GET /sidekiq/compound_metrics
```
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/compound_metrics
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/sidekiq/compound_metrics
```
Example response:
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index dc036d7e27f..1802fae14fe 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -20,7 +20,7 @@ GET /hooks
Example request:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks
```
Example response:
@@ -52,7 +52,7 @@ POST /hooks
Example request:
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/hooks?url=https://gitlab.example.com/hook"
```
Example response:
@@ -80,7 +80,7 @@ GET /hooks/:id
Example request:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
```
Example response:
@@ -117,7 +117,7 @@ DELETE /hooks/:id
Example request:
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/hooks/2
```
Example response:
diff --git a/doc/api/tags.md b/doc/api/tags.md
index ac9fac92f4c..54059117456 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -56,7 +56,7 @@ Parameters:
| `tag_name` | string | yes | The name of the tag |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/tags/v1.0.0
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/tags/v1.0.0
```
Example Response:
diff --git a/doc/api/todos.md b/doc/api/todos.md
index 937c71de386..0cd644dfd2f 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -1,6 +1,6 @@
# Todos
-**Note:** This feature was [introduced][ce-3188] in GitLab 8.10
+> [Introduced][ce-3188] in GitLab 8.10.
## Get a list of todos
@@ -22,7 +22,7 @@ Parameters:
| `type` | string | no | The type of a todo. Can be either `Issue` or `MergeRequest` |
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
```
Example Response:
@@ -194,7 +194,7 @@ Parameters:
| `id` | integer | yes | The ID of a todo |
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130
```
Example Response:
@@ -284,7 +284,7 @@ DELETE /todos
```
```bash
-curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
```
Example Response:
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 0833027f91d..10ce4ac8940 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -14,7 +14,7 @@
- [Use variables in your `.gitlab-ci.yml`](variables/README.md)
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger builds through the API](triggers/README.md)
-- [Build artifacts](build_artifacts/README.md)
+- [Build artifacts](../user/project/builds/artifacts.md)
- [User permissions](../user/permissions.md#gitlab-ci)
- [API](../api/ci/README.md)
- [CI services (linked docker containers)](services/README.md)
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 9553bb11e9d..05605f10fb4 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -1,175 +1,4 @@
-# Introduction to build artifacts
+This document was moved to:
-Artifacts is a list of files and directories which are attached to a build
-after it completes successfully. This feature is enabled by default in all GitLab installations.
-
-_If you are searching for ways to use artifacts, jump to
-[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._
-
-Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by
-GitLab Runner are uploaded to GitLab and are downloadable as a single archive
-(`tar.gz`) using the GitLab UI.
-
-Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
-changed to `ZIP`, and it is now possible to browse its contents, with the added
-ability of downloading the files separately.
-
-**Note:**
-The artifacts browser will be available only for new artifacts that are sent
-to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
-browse old artifacts already uploaded to GitLab.
-
-## Disabling build artifacts
-
-To disable artifacts site-wide, follow the steps below.
-
----
-
-**In Omnibus installations:**
-
-1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
-
- ```ruby
- gitlab_rails['artifacts_enabled'] = false
- ```
-
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
-
-**In installations from source:**
-
-1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
-
- ```yaml
- artifacts:
- enabled: false
- ```
-
-1. Save the file and [restart GitLab][] for the changes to take effect.
-
-## Defining artifacts in `.gitlab-ci.yml`
-
-A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
-the following:
-
-```yaml
-pdf:
- script: xelatex mycv.tex
- artifacts:
- paths:
- - mycv.pdf
-```
-
-A job named `pdf` calls the `xelatex` command in order to build a pdf file from
-the latex source file `mycv.tex`. We then define the `artifacts` paths which in
-turn are defined with the `paths` keyword. All paths to files and directories
-are relative to the repository that was cloned during the build.
-
-For more examples on artifacts, follow the
-[separate artifacts yaml documentation](../yaml/README.md#artifacts).
-
-## Storing build artifacts
-
-After a successful build, GitLab Runner uploads an archive containing the build
-artifacts to GitLab.
-
-To change the location where the artifacts are stored, follow the steps below.
-
----
-
-**In Omnibus installations:**
-
-_The artifacts are stored by default in
-`/var/opt/gitlab/gitlab-rails/shared/artifacts`._
-
-1. To change the storage path for example to `/mnt/storage/artifacts`, edit
- `/etc/gitlab/gitlab.rb` and add the following line:
-
- ```ruby
- gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
- ```
-
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-
----
-
-**In installations from source:**
-
-_The artifacts are stored by default in
-`/home/git/gitlab/shared/artifacts`._
-
-1. To change the storage path for example to `/mnt/storage/artifacts`, edit
- `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
-
- ```yaml
- artifacts:
- enabled: true
- path: /mnt/storage/artifacts
- ```
-
-1. Save the file and [restart GitLab][] for the changes to take effect.
-
-## Browsing build artifacts
-
-When GitLab receives an artifacts archive, an archive metadata file is also
-generated. This metadata file describes all the entries that are located in the
-artifacts archive itself. The metadata file is in a binary format, with
-additional GZIP compression.
-
-GitLab does not extract the artifacts archive in order to save space, memory
-and disk I/O. It instead inspects the metadata file which contains all the
-relevant information. This is especially important when there is a lot of
-artifacts, or an archive is a very large file.
-
----
-
-After a successful build, if you visit the build's specific page, you can see
-that there are two buttons.
-
-One is for downloading the artifacts archive and the other for browsing its
-contents.
-
-![Build artifacts browser button](img/build_artifacts_browser_button.png)
-
----
-
-The archive browser shows the name and the actual file size of each file in the
-archive. If your artifacts contained directories, then you are also able to
-browse inside them.
-
-Below you can see an image of three different file formats, as well as two
-directories.
-
-![Build artifacts browser](img/build_artifacts_browser.png)
-
----
-
-## Downloading build artifacts
-
-If you need to download the whole archive, there are buttons in various places
-inside GitLab that make that possible.
-
-1. While on the builds page, you can see the download icon for each build's
- artifacts archive in the right corner
-
-1. While inside a specific build, you are presented with a download button
- along with the one that browses the archive
-
-1. And finally, when browsing an archive you can see the download button at
- the top right corner
-
----
-
-Note that GitLab does not extract the entire artifacts archive to send just a
-single file to the user.
-
-When clicking on a specific file, [GitLab Workhorse] extracts it from the
-archive and the download begins.
-
-This implementation saves space, memory and disk I/O.
-
-[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository"
-[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
-[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation"
-[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
+- [user/project/builds/artifacts.md](../../user/project/builds/artifacts.md) - user guide
+- [administration/build_artifacts.md](../../administration/build_artifacts.md) - administrator guide
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png
deleted file mode 100644
index 59cf2b8746b..00000000000
--- a/doc/ci/build_artifacts/img/build_artifacts_browser.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png
deleted file mode 100644
index 7801c2e6fa6..00000000000
--- a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 7f83f846454..0f64137a8a9 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -38,7 +38,7 @@ GitLab Runner then executes build scripts as the `gitlab-runner` user.
$ sudo gitlab-ci-multi-runner register -n \
--url https://gitlab.com/ci \
--registration-token REGISTRATION_TOKEN \
- --executor shell
+ --executor shell \
--description "My Runner"
```
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index bfafcc44d66..175e9d79904 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -49,7 +49,7 @@ apt-get update -yqq
apt-get install git -yqq
# Install phpunit, the tool that we will use for testing
-curl -Lo /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
+curl --location --output /usr/local/bin/phpunit https://phar.phpunit.de/phpunit.phar
chmod +x /usr/local/bin/phpunit
# Install mysql driver
@@ -235,7 +235,7 @@ cache:
before_script:
# Install composer dependencies
-- curl -sS https://getcomposer.org/installer | php
+- curl --silent --show-error https://getcomposer.org/installer | php
- php composer.phar install
...
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 48a9f994759..d90d7aca4fd 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -32,6 +32,41 @@ project.
Clicking on a pipeline will show the builds that were run for that pipeline.
+## Badges
+
+There are build status and test coverage report badges available.
+
+Go to pipeline settings to see available badges and code you can use to embed
+badges in the `README.md` or your website.
+
+### Build status badge
+
+You can access a build status badge image using following link:
+
+```
+http://example.gitlab.com/namespace/project/badges/branch/build.svg
+```
+
+### Test coverage report badge
+
+GitLab makes it possible to define the regular expression for coverage report,
+that each build log will be matched against. This means that each build in the
+pipeline can have the test coverage percentage value defined.
+
+You can access test coverage badge using following link:
+
+```
+http://example.gitlab.com/namespace/project/badges/branch/coverage.svg
+```
+
+If you would like to get the coverage report from the specific job, you can add
+a `job=coverage_job_name` parameter to the URL. For example, it is possible to
+use following Markdown code to embed the est coverage report into `README.md`:
+
+```markdown
+![coverage](http://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)
+```
+
[builds]: #builds
[jobs]: yaml/README.md#jobs
[stages]: yaml/README.md#stages
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 7fa1a478f34..c835ebc2d44 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -218,22 +218,14 @@ project's settings.
For more information read the
[Builds emails service documentation](../../project_services/builds_emails.md).
-## Builds badge
-
-You can access a builds badge image using following link:
-
-```
-http://example.gitlab.com/namespace/project/badges/branch/build.svg
-```
-
-Awesome! You started using CI in GitLab!
-
## Examples
Visit the [examples README][examples] to see a list of examples using GitLab
CI with various languages.
-[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation
+Awesome! You started using CI in GitLab!
+
+[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner
[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/
[examples]: ../examples/README.md
[ci]: https://about.gitlab.com/gitlab-ci/
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 5c316510d0e..6c6767fea0b 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -1,6 +1,6 @@
# Triggering Builds through the API
-_**Note:** This feature was [introduced][ci-229] in GitLab CE 7.14_
+> [Introduced][ci-229] in GitLab CE 7.14.
Triggers can be used to force a rebuild of a specific branch, tag or commit,
with an API call.
@@ -77,9 +77,9 @@ See the [Examples](#examples) section below for more details.
Using cURL you can trigger a rebuild with minimal effort, for example:
```bash
-curl -X POST \
- -F token=TOKEN \
- -F ref=master \
+curl --request POST \
+ --form token=TOKEN \
+ --form ref=master \
https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
@@ -88,7 +88,7 @@ In this case, the project with ID `9` will get rebuilt on `master` branch.
Alternatively, you can pass the `token` and `ref` arguments in the query string:
```bash
-curl -X POST \
+curl --request POST \
"https://gitlab.example.com/api/v3/projects/9/trigger/builds?token=TOKEN&ref=master"
```
@@ -103,7 +103,7 @@ need to add in project's A `.gitlab-ci.yml`:
build_docs:
stage: deploy
script:
- - "curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds"
+ - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds"
only:
- tags
```
@@ -158,10 +158,10 @@ You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
and the script of the `upload_package` job will run:
```bash
-curl -X POST \
- -F token=TOKEN \
- -F ref=master \
- -F "variables[UPLOAD_TO_S3]=true" \
+curl --request POST \
+ --form token=TOKEN \
+ --form ref=master \
+ --form "variables[UPLOAD_TO_S3]=true" \
https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
@@ -172,7 +172,7 @@ in conjunction with cron. The example below triggers a build on the `master`
branch of project with ID `9` every night at `00:30`:
```bash
-30 0 * * * curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds
+30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds
```
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index ea3fff1596e..01d71088543 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -379,6 +379,8 @@ job:
- bundle exec rspec
```
+Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``).
+
### stage
`stage` allows to group build into different stages. Builds of the same `stage`
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 55077197ff9..047a0b08406 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -1,8 +1,7 @@
# GitLab Container Registry
-> **Note:**
-This feature was [introduced][ce-4040] in GitLab 8.8. Docker Registry manifest
-v1 support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
+> [Introduced][ce-4040] in GitLab 8.8. Docker Registry manifest
+`v1` support was added in GitLab 8.9 to support Docker versions earlier than 1.10.
> **Note:**
This document is about the user guide. To learn how to enable GitLab Container
@@ -90,6 +89,10 @@ your `.gitlab-ci.yml`, you have to follow the
[Using a private Docker Registry][private-docker]
documentation. This workflow will be simplified in the future.
+## Troubleshooting
+
+See [the GitLab Docker registry troubleshooting guide](troubleshooting.md).
+
[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
[docker-docs]: https://docs.docker.com/engine/userguide/intro/
[private-docker]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry
diff --git a/doc/container_registry/img/mitmproxy-docker.png b/doc/container_registry/img/mitmproxy-docker.png
new file mode 100644
index 00000000000..4e3e37b413d
--- /dev/null
+++ b/doc/container_registry/img/mitmproxy-docker.png
Binary files differ
diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md
new file mode 100644
index 00000000000..14c4a7d9a63
--- /dev/null
+++ b/doc/container_registry/troubleshooting.md
@@ -0,0 +1,141 @@
+# Troubleshooting the GitLab Container Registry
+
+## Basic Troubleshooting
+
+1. Check to make sure that the system clock on your Docker client and GitLab server have
+ been synchronized (e.g. via NTP).
+
+2. If you are using an S3-backed Registry, double check that the IAM
+ permissions and the S3 credentials (including region) are correct. See [the
+ sample IAM policy](https://docs.docker.com/registry/storage-drivers/s3/)
+ for more details.
+
+3. Check the Registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs
+ for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues
+ there.
+
+## Advanced Troubleshooting
+
+>**NOTE:** The following section is only recommended for experts.
+
+Sometimes it's not obvious what is wrong, and you may need to dive deeper into
+the communication between the Docker client and the Registry to find out
+what's wrong. We will use a concrete example in the past to illustrate how to
+diagnose a problem with the S3 setup.
+
+### Unexpected 403 error during push
+
+A user attempted to enable an S3-backed Registry. The `docker login` step went
+fine. However, when pushing an image, the output showed:
+
+```
+The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test]
+dc5e59c14160: Pushing [==================================================>] 14.85 kB
+03c20c1a019a: Pushing [==================================================>] 2.048 kB
+a08f14ef632e: Pushing [==================================================>] 2.048 kB
+228950524c88: Pushing 2.048 kB
+6a8ecde4cc03: Pushing [==> ] 9.901 MB/205.7 MB
+5f70bf18a086: Pushing 1.024 kB
+737f40e80b7f: Waiting
+82b57dbc5385: Waiting
+19429b698a22: Waiting
+9436069b92a3: Waiting
+error parsing HTTP 403 response body: unexpected end of JSON input: ""
+```
+
+This error is ambiguous, as it's not clear whether the 403 is coming from the
+GitLab Rails application, the Docker Registry, or something else. In this
+case, since we know that since the login succeeded, we probably need to look
+at the communication between the client and the Registry.
+
+The REST API between the Docker client and Registry is [described
+here](https://docs.docker.com/registry/spec/api/). Normally, one would just
+use Wireshark or tcpdump to capture the traffic and see where things went
+wrong. However, since all communication between Docker clients and servers
+are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even
+if you know the private key. What can we do instead?
+
+One way would be to disable HTTPS by setting up an [insecure
+Registry](https://docs.docker.com/registry/insecure/). This could introduce a
+security hole and is only recommended for local testing. If you have a
+production system and can't or don't want to do this, there is another way:
+use mitmproxy, which stands for Man-in-the-Middle Proxy.
+
+### mitmproxy
+
+[mitmproxy](https://mitmproxy.org/) allows you to place a proxy between your
+client and server to inspect all traffic. One wrinkle is that your system
+needs to trust the mitmproxy SSL certificates for this to work.
+
+The following installation instructions assume you are running Ubuntu:
+
+1. Install mitmproxy (see http://docs.mitmproxy.org/en/stable/install.html)
+1. Run `mitmproxy --port 9000` to generate its certificates.
+ Enter <kbd>CTRL</kbd>-<kbd>C</kbd> to quit.
+1. Install the certificate from `~/.mitmproxy` to your system:
+
+ ```sh
+ sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
+ sudo update-ca-certificates
+ ```
+
+If successful, the output should indicate that a certificate was added:
+
+```sh
+Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
+Running hooks in /etc/ca-certificates/update.d....done.
+```
+
+To verify that the certificates are properly installed, run:
+
+```sh
+mitmproxy --port 9000
+```
+
+This will run mitmproxy on port `9000`. In another window, run:
+
+```sh
+curl --proxy http://localhost:9000 https://httpbin.org/status/200
+```
+
+If everything is setup correctly, you will see information on the mitmproxy window and
+no errors from the curl commands.
+
+### Running the Docker daemon with a proxy
+
+For Docker to connect through a proxy, you must start the Docker daemon with the
+proper environment variables. The easiest way is to shutdown Docker (e.g. `sudo initctl stop docker`)
+and then run Docker by hand. As root, run:
+
+```sh
+export HTTP_PROXY="http://localhost:9000"
+export HTTPS_PROXY="https://localhost:9000"
+docker daemon --debug
+```
+
+This will launch the Docker daemon and proxy all connections through mitmproxy.
+
+### Running the Docker client
+
+Now that we have mitmproxy and Docker running, we can attempt to login and push
+a container image. You may need to run as root to do this. For example:
+
+```sh
+docker login s3-testing.myregistry.com:4567
+docker push s3-testing.myregistry.com:4567/root/docker-test
+```
+
+In the example above, we see the following trace on the mitmproxy window:
+
+![mitmproxy output from Docker](img/mitmproxy-docker.png)
+
+The above image shows:
+
+* The initial PUT requests went through fine with a 201 status code.
+* The 201 redirected the client to the S3 bucket.
+* The HEAD request to the AWS bucket reported a 403 Unauthorized.
+
+What does this mean? This strongly suggests that the S3 user does not have the right
+[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html).
+The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/).
+Once the right permissions were set, the error will go away.
diff --git a/doc/development/README.md b/doc/development/README.md
index c5d5af43864..bf67b5d8dff 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -1,18 +1,37 @@
# Development
+## Outside of docs
+
+- [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) main contributing guide
+- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) contributing process
+- [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md) to install a development version
+
+## Styleguides
+
+- [Documentation styleguide](doc_styleguide.md) Use this styleguide if you are
+ contributing to documentation.
+- [SQL Migration Style Guide](migration_style_guide.md) for creating safe SQL migrations
+- [Testing standards and style guidelines](testing.md)
+- [UI guide](ui_guide.md) for building GitLab with existing CSS styles and elements
+- [SQL guidelines](sql.md) for SQL guidelines
+
+## Process
+
+- [Code review guidelines](code_review.md) for reviewing code and having code reviewed.
+
+## Backend howtos
+
- [Architecture](architecture.md) of GitLab
- [CI setup](ci_setup.md) for testing GitLab
-- [Code review guidelines](code_review.md) for reviewing code and having code
- reviewed.
- [Gotchas](gotchas.md) to avoid
- [How to dump production data to staging](db_dump.md)
- [Instrumentation](instrumentation.md)
-- [Licensing](licensing.md) for ensuring license compliance
-- [Migration Style Guide](migration_style_guide.md) for creating safe migrations
- [Performance guidelines](performance.md)
- [Rake tasks](rake_tasks.md) for development
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Sidekiq debugging](sidekiq_debugging.md)
-- [SQL guidelines](sql.md) for SQL guidelines
-- [Testing standards and style guidelines](testing.md)
-- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements
+- [What requires downtime?](what_requires_downtime.md)
+
+## Compliance
+
+- [Licensing](licensing.md) for ensuring license compliance
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 6ee7b3cfeeb..927a1872413 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -3,12 +3,64 @@
This styleguide recommends best practices to improve documentation and to keep
it organized and easy to find.
-## Naming
+## Location and naming of documents
-- When creating a new document and it has more than one word in its name,
- make sure to use underscores instead of spaces or dashes (`-`). For example,
- a proper naming would be `import_projects_from_github.md`. The same rule
- applies to images.
+>**Note:**
+These guidelines derive from the discussion taken place in issue [#3349](ce-3349).
+
+The documentation hierarchy can be vastly improved by providing a better layout
+and organization of directories.
+
+Having a structured document layout, we will be able to have meaningful URLs
+like `docs.gitlab.com/user/project/merge_requests.html`. With this pattern,
+you can immediately tell that you are navigating a user related documentation
+and is about the project and its merge requests.
+
+The table below shows what kind of documentation goes where.
+
+| Directory | What belongs here |
+| --------- | -------------- |
+| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. |
+| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. |
+| `doc/api/` | API related documentation. |
+| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. |
+| `doc/legal/` | Legal documents about contributing to GitLab. |
+| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). |
+| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. |
+
+---
+
+**General rules:**
+
+1. The correct naming and location of a new document, is a combination
+ of the relative URL of the document in question and the GitLab Map design
+ that is used for UX purposes ([source][graffle], [image][gitlab-map]).
+1. When creating a new document and it has more than one word in its name,
+ make sure to use underscores instead of spaces or dashes (`-`). For example,
+ a proper naming would be `import_projects_from_github.md`. The same rule
+ applies to images.
+1. There are four main directories, `user`, `administration`, `api` and `development`.
+1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`,
+ `profile/`, `dashboard/` and `admin_area/`.
+ 1. `doc/user/project/` should contain all project related documentation.
+ 1. `doc/user/group/` should contain all group related documentation.
+ 1. `doc/user/profile/` should contain all profile related documentation.
+ Every page you would navigate under `/profile` should have its own document,
+ i.e. `account.md`, `applications.md`, `emails.md`, etc.
+ 1. `doc/user/dashboard/` should contain all dashboard related documentation.
+ 1. `doc/user/admin_area/` should contain all admin related documentation
+ describing what can be achieved by accessing GitLab's admin interface
+ (_not to be confused with `doc/administration` where server access is
+ required_).
+ 1. Every category under `/admin/application_settings` should have its
+ own document located at `doc/user/admin_area/settings/`. For example,
+ the **Visibility and Access Controls** category should have a document
+ located at `doc/user/admin_area/settings/visibility_and_access_controls.md`.
+
+---
+
+If you are unsure where a document should live, you can ping `@axil` in your
+merge request.
## Text
@@ -103,15 +155,15 @@ Inside the document:
- Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a
- note: `>**Note:** This feature was introduced in GitLab 8.3`
+ note: `> Introduced in GitLab 8.3.`.
- If possible every feature should have a link to the MR that introduced it.
The above note would be then transformed to:
- `>**Note:** This feature was [introduced][ce-1242] in GitLab 8.3`, where
+ `> [Introduced][ce-1242] in GitLab 8.3.`, where
the [link identifier](#links) is named after the repository (CE) and the MR
- number
+ number.
- If the feature is only in GitLab EE, don't forget to mention it, like:
- `>**Note:** This feature was introduced in GitLab EE 8.3`. Otherwise, leave
- this mention out
+ `> Introduced in GitLab EE 8.3.`. Otherwise, leave
+ this mention out.
## References
@@ -244,6 +296,12 @@ In this case:
Here is a list of must-have items. Use them in the exact order that appears
on this document. Further explanation is given below.
+- Every method must be described using [Grape's DSL](https://github.com/ruby-grape/grape/tree/v0.13.0#describing-methods)
+ (see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb
+ for a good example):
+ - `desc` for the method summary (you can pass it a block for additional details)
+ - `params` for the method params (this acts as description **and** validation
+ of the params)
- Every method must have the REST API request. For example:
```
@@ -297,7 +355,7 @@ Below is a set of [cURL][] examples that you can use in the API documentation.
Get the details of a group:
```bash
-curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/gitlab-org
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/gitlab-org
```
#### cURL example with parameters passed in the URL
@@ -305,7 +363,7 @@ curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/
Create a new project under the authenticated user's namespace:
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects?name=foo"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects?name=foo"
```
#### Post data using cURL's --data
@@ -315,7 +373,7 @@ cURL's `--data` option. The example below will create a new project `foo` under
the authenticated user's namespace.
```bash
-curl --data "name=foo" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects"
+curl --data "name=foo" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects"
```
#### Post data using JSON content
@@ -324,7 +382,7 @@ curl --data "name=foo" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.
and double quotes.
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"path": "my-group", "name": "My group"}' https://gitlab.example.com/api/v3/groups
```
#### Post data using form-data
@@ -333,7 +391,7 @@ Instead of using JSON or urlencode you can use multipart/form-data which
properly handles data encoding:
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -F "title=ssh-key" -F "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v3/users/25/keys
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "title=ssh-key" --form "key=ssh-rsa AAAAB3NzaC1yc2EA..." https://gitlab.example.com/api/v3/users/25/keys
```
The above example is run by and administrator and will add an SSH public key
@@ -347,7 +405,7 @@ contains spaces in its title. Observe how spaces are escaped using the `%20`
ASCII code.
```bash
-curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/42/issues?title=Hello%20Dude"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/42/issues?title=Hello%20Dude"
```
Use `%2F` for slashes (`/`).
@@ -359,10 +417,13 @@ restrict the sign-up e-mail domains of a GitLab instance to `*.example.com` and
`example.net`, you would do something like this:
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.example.com" -d "domain_whitelist[]=example.net" https://gitlab.example.com/api/v3/application/settings
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "domain_whitelist[]=*.example.com" --data "domain_whitelist[]=example.net" https://gitlab.example.com/api/v3/application/settings
```
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
[gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation"
[doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation"
+[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure"
+[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
+[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 9d7fe7440d2..159d5ce286d 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -41,10 +41,10 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9
[Exception]: http://stackoverflow.com/q/10048173/223897
-## Don't use inline CoffeeScript in views
+## Don't use inline CoffeeScript/JavaScript in views
Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
-performance overhead.
+performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided.
_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb)
in an initializer._
@@ -52,6 +52,7 @@ in an initializer._
### Further reading
- Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu)
+- Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting)
- Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897)
## ID-based CSS selectors need to be a bit more specific
diff --git a/doc/development/newlines_styleguide.md b/doc/development/newlines_styleguide.md
new file mode 100644
index 00000000000..e03adcaadea
--- /dev/null
+++ b/doc/development/newlines_styleguide.md
@@ -0,0 +1,102 @@
+# Newlines styleguide
+
+This style guide recommends best practices for newlines in Ruby code.
+
+## Rule: separate code with newlines only when it makes sense from logic perspectice
+
+```ruby
+# bad
+def method
+ issue = Issue.new
+
+ issue.save
+
+ render json: issue
+end
+```
+
+```ruby
+# good
+def method
+ issue = Issue.new
+ issue.save
+
+ render json: issue
+end
+```
+
+## Rule: separate code and block with newlines
+
+### Newline before block
+
+```ruby
+# bad
+def method
+ issue = Issue.new
+ if issue.save
+ render json: issue
+ end
+end
+```
+
+```ruby
+# good
+def method
+ issue = Issue.new
+
+ if issue.save
+ render json: issue
+ end
+end
+```
+
+## Newline after block
+
+```ruby
+# bad
+def method
+ if issue.save
+ issue.send_email
+ end
+ render json: issue
+end
+```
+
+```ruby
+# good
+def method
+ if issue.save
+ issue.send_email
+ end
+
+ render json: issue
+end
+```
+
+### Exception: no need for newline when code block starts or ends right inside another code block
+
+```ruby
+# bad
+def method
+
+ if issue
+
+ if issue.valid?
+ issue.save
+ end
+
+ end
+
+end
+```
+
+```ruby
+# good
+def method
+ if issue
+ if issue.valid?
+ issue.save
+ end
+ end
+end
+```
diff --git a/doc/development/performance.md b/doc/development/performance.md
index fb37b3a889c..7ff603e2c4a 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -15,8 +15,8 @@ The process of solving performance problems is roughly as follows:
3. Add your findings based on the measurement period (screenshots of graphs,
timings, etc) to the issue mentioned in step 1.
4. Solve the problem.
-5. Create a merge request, assign the "performance" label and ping the right
- people (e.g. [@yorickpeterse][yorickpeterse] and [@joshfng][joshfng]).
+5. Create a merge request, assign the "Performance" label and assign it to
+ [@yorickpeterse][yorickpeterse] for reviewing.
6. Once a change has been deployed make sure to _again_ measure for at least 24
hours to see if your changes have any impact on the production environment.
7. Repeat until you're done.
@@ -36,8 +36,8 @@ graphs/dashboards.
GitLab provides two built-in tools to aid the process of improving performance:
-* [Sherlock](doc/development/profiling.md#sherlock)
-* [GitLab Performance Monitoring](doc/monitoring/performance/monitoring.md)
+* [Sherlock](profiling.md#sherlock)
+* [GitLab Performance Monitoring](../monitoring/performance/monitoring.md)
GitLab employees can use GitLab.com's performance monitoring systems located at
<http://performance.gitlab.net>, this requires you to log in using your
@@ -254,5 +254,4 @@ referencing an object directly may even slow code down.
[#15607]: https://gitlab.com/gitlab-org/gitlab-ce/issues/15607
[yorickpeterse]: https://gitlab.com/u/yorickpeterse
-[joshfng]: https://gitlab.com/u/joshfng
[anti-pattern]: https://en.wikipedia.org/wiki/Anti-pattern
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 8852dbcb19e..a7175f3f87e 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -14,11 +14,33 @@ Note: `db:setup` calls `db:seed` but this does nothing.
## Run tests
-This runs all test suites present in GitLab.
+In order to run the test you can use the following commands:
+- `rake spinach` to run the spinach suite
+- `rake spec` to run the rspec suite
+- `rake teaspoon` to run the teaspoon test suite
+- `rake gitlab:test` to run all the tests
-```
-bundle exec rake test
-```
+Note: Both `rake spinach` and `rake spec` takes significant time to pass.
+Instead of running full test suite locally you can save a lot of time by running
+a single test or directory related to your changes. After you submit merge request
+CI will run full test suite for you. Green CI status in the merge request means
+full test suite is passed.
+
+Note: You can't run `rspec .` since this will try to run all the `_spec.rb`
+files it can find, also the ones in `/tmp`
+
+To run a single test file you can use:
+
+- `bundle exec rspec spec/controllers/commit_controller_spec.rb` for a rspec test
+- `bundle exec spinach features/project/issues/milestones.feature` for a spinach test
+
+To run several tests inside one directory:
+
+- `bundle exec rspec spec/requests/api/` for the rspec tests if you want to test API only
+- `bundle exec spinach features/profile/` for the spinach tests if you want to test only profile pages
+
+If you want to use [Spring](https://github.com/rails/spring) set
+`ENABLE_SPRING=1` in your environment.
## Generate searchable docs for source code
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index 65252288019..3a8c823e026 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -47,6 +47,42 @@ information from database or file system
* `rss` for rss/atom feed
* `plus` for link or dropdown that lead to page where you create new object (For example new issue page)
+### SVGs
+
+When exporting SVGs, be sure to follow the following guidelines:
+
+1. Convert all strokes to outlines.
+2. Use pathfinder tools to combine overlapping paths and create compound paths.
+3. SVGs that are limited to one color should be exported without a fill color so the color can be set using CSS.
+4. Ensure that exported SVGs have been run through an [SVG cleaner](https://github.com/RazrFalcon/SVGCleaner) to remove unused elements and attributes.
+
+You can open your svg in a text editor to ensure that it is clean.
+Incorrect files will look like this:
+
+```xml
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="16px" height="17px" viewBox="0 0 16 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
+ <title>Group</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+ <g id="Group" fill="#7E7C7C">
+ <path d="M15.1111,1 L0.8891,1 C0.3981,1 0.0001,1.446 0.0001,1.996 L0.0001,15.945 C0.0001,16.495 0.3981,16.941 0.8891,16.941 L15.1111,16.941 C15.6021,16.941 16.0001,16.495 16.0001,15.945 L16.0001,1.996 C16.0001,1.446 15.6021,1 15.1111,1 L15.1111,1 L15.1111,1 Z M14.0001,6.0002 L14.0001,14.949 L2.0001,14.949 L2.0001,6.0002 L14.0001,6.0002 Z M14.0001,4.0002 L14.0001,2.993 L2.0001,2.993 L2.0001,4.0002 L14.0001,4.0002 Z" id="Combined-Shape"></path>
+ <polygon id="Fill-11" points="3 2.0002 5 2.0002 5 0.0002 3 0.0002"></polygon>
+ <polygon id="Fill-16" points="11 2.0002 13 2.0002 13 0.0002 11 0.0002"></polygon>
+ <path d="M5.37709616,11.5511984 L6.92309616,12.7821984 C7.35112915,13.123019 7.97359761,13.0565604 8.32002627,12.6330535 L10.7740263,9.63305349 C11.1237073,9.20557058 11.0606364,8.57555475 10.6331535,8.22587373 C10.2056706,7.87619272 9.57565475,7.93926361 9.22597373,8.36674651 L6.77197373,11.3667465 L8.16890384,11.2176016 L6.62290384,9.98660159 C6.19085236,9.6425813 5.56172188,9.71394467 5.21770159,10.1459962 C4.8736813,10.5780476 4.94504467,11.2071781 5.37709616,11.5511984 L5.37709616,11.5511984 Z" id="Stroke-21"></path>
+ </g>
+ </g>
+</svg>
+```
+
+Correct file will look like this:
+
+```xml
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17" enable-background="new 0 0 16 17"><path d="m15.1 1h-2.1v-1h-2v1h-6v-1h-2v1h-2.1c-.5 0-.9.5-.9 1v14c0 .6.4 1 .9 1h14.2c.5 0 .9-.4.9-1v-14c0-.5-.4-1-.9-1m-1.1 14h-12v-9h12v9m0-11h-12v-1h12v1"/><path d="m5.4 11.6l1.5 1.2c.4.3 1.1.3 1.4-.1l2.5-3c.3-.4.3-1.1-.1-1.4-.5-.4-1.1-.3-1.5.1l-1.8 2.2-.8-.6c-.4-.3-1.1-.3-1.4.2-.3.4-.3 1 .2 1.4"/></svg>
+```
+
## Buttons
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
new file mode 100644
index 00000000000..2574c2c0472
--- /dev/null
+++ b/doc/development/what_requires_downtime.md
@@ -0,0 +1,161 @@
+# What requires downtime?
+
+When working with a database certain operations can be performed without taking
+GitLab offline, others do require a downtime period. This guide describes
+various operations and their impact.
+
+## Adding Columns
+
+On PostgreSQL you can safely add a new column to an existing table as long as it
+does **not** have a default value. For example, this query would not require
+downtime:
+
+```sql
+ALTER TABLE projects ADD COLUMN random_value int;
+```
+
+Add a column _with_ a default however does require downtime. For example,
+consider this query:
+
+```sql
+ALTER TABLE projects ADD COLUMN random_value int DEFAULT 42;
+```
+
+This requires updating every single row in the `projects` table so that
+`random_value` is set to `42` by default. This requires updating all rows and
+indexes in a table. This in turn acquires enough locks on the table for it to
+effectively block any other queries.
+
+As of MySQL 5.6 adding a column to a table is still quite an expensive
+operation, even when using `ALGORITHM=INPLACE` and `LOCK=NONE`. This means
+downtime _may_ be required when modifying large tables as otherwise the
+operation could potentially take hours to complete.
+
+Adding a column with a default value _can_ be done without requiring downtime
+when using the migration helper method
+`Gitlab::Database::MigrationHelpers#add_column_with_default`. This method works
+similar to `add_column` except it updates existing rows in batches without
+blocking access to the table being modified. See ["Adding Columns With Default
+Values"](migration_style_guide.html#adding-columns-with-default-values) for more
+information on how to use this method.
+
+## Dropping Columns
+
+On PostgreSQL you can safely remove an existing column without the need for
+downtime. When you drop a column in PostgreSQL it's not immediately removed,
+instead it is simply disabled. The data is removed on the next vacuum run.
+
+On MySQL this operation requires downtime.
+
+While database wise dropping a column may be fine on PostgreSQL this operation
+still requires downtime because the application code may still be using the
+column that was removed. For example, consider the following migration:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ def change
+ remove_column :projects, :dummy
+ end
+end
+```
+
+Now imagine that the GitLab instance is running and actively uses the `dummy`
+column. If we were to run the migration this would result in the GitLab instance
+producing errors whenever it tries to use the `dummy` column.
+
+As a result of the above downtime _is_ required when removing a column, even
+when using PostgreSQL.
+
+## Changing Column Constraints
+
+Generally changing column constraints requires checking all rows in the table to
+see if they meet the new constraint, unless a constraint is _removed_. For
+example, changing a column that previously allowed NULL values to not allow NULL
+values requires the database to verify all existing rows.
+
+The specific behaviour varies a bit between databases but in general the safest
+approach is to assume changing constraints requires downtime.
+
+## Changing Column Types
+
+This operation requires downtime.
+
+## Adding Indexes
+
+Adding indexes is an expensive process that blocks INSERT and UPDATE queries for
+the duration. When using PostgreSQL one can work arounds this by using the
+`CONCURRENTLY` option:
+
+```sql
+CREATE INDEX CONCURRENTLY index_name ON projects (column_name);
+```
+
+Migrations can take advantage of this by using the method
+`add_concurrent_index`. For example:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ def change
+ add_concurrent_index :projects, :column_name
+ end
+end
+```
+
+When running this on PostgreSQL the `CONCURRENTLY` option mentioned above is
+used. On MySQL this method produces a regular `CREATE INDEX` query.
+
+MySQL doesn't really have a workaround for this. Supposedly it _can_ create
+indexes without the need for downtime but only for variable width columns. The
+details on this are a bit sketchy. Since it's better to be safe than sorry one
+should assume that adding indexes requires downtime on MySQL.
+
+## Dropping Indexes
+
+Dropping an index does not require downtime on both PostgreSQL and MySQL.
+
+## Adding Tables
+
+This operation is safe as there's no code using the table just yet.
+
+## Dropping Tables
+
+This operation requires downtime as application code may still be using the
+table.
+
+## Adding Foreign Keys
+
+Adding foreign keys acquires an exclusive lock on both the source and target
+tables in PostgreSQL. This requires downtime as otherwise the entire application
+grinds to a halt for the duration of the operation.
+
+On MySQL this operation also requires downtime _unless_ foreign key checks are
+disabled. Because this means checks aren't enforced this is not ideal, as such
+one should assume MySQL also requires downtime.
+
+## Removing Foreign Keys
+
+This operation should not require downtime on both PostgreSQL and MySQL.
+
+## Updating Data
+
+Updating data should generally be safe. The exception to this is data that's
+being migrated from one version to another while the application still produces
+data in the old version.
+
+For example, imagine the application writes the string `'dog'` to a column but
+it really is meant to write `'cat'` instead. One might think that the following
+migration is all that is needed to solve this problem:
+
+```ruby
+class MyMigration < ActiveRecord::Migration
+ def up
+ execute("UPDATE some_table SET column = 'cat' WHERE column = 'dog';")
+ end
+end
+```
+
+Unfortunately this is not enough. Because the application is still running and
+using the old value this may result in the table still containing rows where
+`column` is set to `dog`, even after the migration finished.
+
+In these cases downtime _is_ required, even for rarely updated tables.
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 89ce8bcc3e8..b61f436c1a4 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -120,3 +120,11 @@ You need to be in the created branch.
git checkout NAME-OF-BRANCH
git merge master
```
+
+### Merge master branch with created branch
+You need to be in the master branch.
+```
+git checkout master
+git merge NAME-OF-BRANCH
+```
+
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 9bc0dbb5e2a..eb9606934cd 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -89,7 +89,7 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -O --progress https://www.kernel.org/pub/software/scm/git/git-2.7.4.tar.gz
+ curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.7.4.tar.gz
echo '7104c4f5d948a75b499a954524cb281fe30c6649d8abe20982936f75ec1f275b git-2.7.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.7.4.tar.gz
cd git-2.7.4/
./configure
@@ -108,8 +108,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-_**Note:** The current supported Ruby version is 2.1.x. Ruby 2.2 and 2.3 are
-currently not supported._
+_**Note:** The current supported Ruby versions are 2.1.x and 2.3.x. 2.3.x is preferred, and support for 2.1.x will be dropped in the future.
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
in production, frequently leads to hard to diagnose problems. For example,
@@ -124,9 +123,9 @@ Remove the old Ruby 1.8 if present:
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.8.tar.gz
- echo 'c7e50159357afd87b13dc5eaf4ac486a70011149 ruby-2.1.8.tar.gz' | shasum -c - && tar xzf ruby-2.1.8.tar.gz
- cd ruby-2.1.8
+ curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
+ echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum -c - && tar xzf ruby-2.3.1.tar.gz
+ cd ruby-2.3.1
./configure --disable-install-rdoc
make
sudo make install
@@ -143,7 +142,7 @@ gitlab-workhorse we need a Go compiler. The instructions below assume you
use 64-bit Linux. You can find downloads for other platforms at the [Go download
page](https://golang.org/dl).
- curl -O --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
+ curl --remote-name --progress https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz
echo '43afe0c5017e502630b1aea4d44b8a7f059bf60d7f29dfd58db454d4e4e0ae53 go1.5.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.5.3.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
@@ -269,9 +268,9 @@ sudo usermod -aG redis git
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-10-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab
-**Note:** You can change `8-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -588,15 +587,17 @@ for the changes to take effect.
### Custom Redis Connection
-If you'd like Resque to connect to a Redis server on a non-standard port or on a different host, you can configure its connection string via the `config/resque.yml` file.
+If you'd like to connect to a Redis server on a non-standard port or on a different host, you can configure its connection string via the `config/resque.yml` file.
# example
- production: redis://redis.example.tld:6379
+ production:
+ url: redis://redis.example.tld:6379
If you want to connect the Redis server via socket, then use the "unix:" URL scheme and the path to the Redis socket file in the `config/resque.yml` file.
# example
- production: unix:/path/to/redis/socket
+ production:
+ url: unix:/path/to/redis/socket
### Custom SSH Connection
diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md
index 5cc09bd536d..06c787cfcc7 100644
--- a/doc/integration/akismet.md
+++ b/doc/integration/akismet.md
@@ -1,9 +1,14 @@
# Akismet
+> *Note:* Before 8.11 only issues submitted via the API and for non-project
+members were submitted to Akismet.
+
GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
-GitLab uses Akismet to prevent users who are not members of a project from
-creating spam via the GitLab API. Detected spam will be rejected, and
-an entry in the "Spam Log" section in the Admin page will be created.
+GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues
+created via the WebUI or the API can be submitted to Akismet for review.
+
+Detected spam will be rejected, and an entry in the "Spam Log" section in the
+Admin page will be created.
Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
adding a user to a project will disable the Akismet check and prevent this
@@ -28,3 +33,26 @@ To use Akismet:
7. Save the configuration.
![Screenshot of Akismet settings](img/akismet_settings.png)
+
+
+## Training
+
+> *Note:* Training the Akismet filter is only available in 8.11 and above.
+
+As a way to better recognize between spam and ham, you can train the Akismet
+filter whenever there is a false positive or false negative.
+
+When an entry is recognized as spam, it is rejected and added to the Spam Logs.
+From here you can review if they are really spam. If one of them is not really
+spam, you can use the `Submit as ham` button to tell Akismet that it falsely
+recognized an entry as spam.
+
+![Screenshot of Spam Logs](img/spam_log.png)
+
+If an entry that is actually spam was not recognized as such, you will be able
+to also submit this to Akismet. The `Submit as spam` button will only appear
+to admin users.
+
+![Screenshot of Issue](img/submit_issue.png)
+
+Training Akismet will help it to recognize spam more accurately in the future.
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 63432b04432..2eb6266ebe7 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -14,7 +14,7 @@ Bitbucket will generate an application ID and secret key for you to use.
1. Select "Add consumer".
1. Provide the required details.
- - Name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
+ - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Application description: Fill this in if you wish.
- URL: The URL to your GitLab installation. 'https://gitlab.company.com'
1. Select "Save".
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 340c8a55fb3..8a01afd1177 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -16,7 +16,7 @@ GitHub will generate an application ID and secret key for you to use.
1. Select "Register new application".
1. Provide the required details.
- - Application name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
+ - Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com'
- Application description: Fill this in if you wish.
- Authorization callback URL is 'http(s)://${YOUR_DOMAIN}'
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index b215cc7c609..6d8f3912ede 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -14,7 +14,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Select "New application".
1. Provide the required details.
- - Name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or something else descriptive.
+ - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- Redirect URI:
```
diff --git a/doc/integration/img/spam_log.png b/doc/integration/img/spam_log.png
new file mode 100644
index 00000000000..8d574448690
--- /dev/null
+++ b/doc/integration/img/spam_log.png
Binary files differ
diff --git a/doc/integration/img/submit_issue.png b/doc/integration/img/submit_issue.png
new file mode 100644
index 00000000000..9fd9c20f6b3
--- /dev/null
+++ b/doc/integration/img/submit_issue.png
Binary files differ
diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md
index 4769f26b259..abbea09f22f 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -7,7 +7,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Select "Create new app"
1. Fill in the application details.
- - Name: This can be anything. Consider something like "\<Organization\>'s GitLab" or "\<Your Name\>'s GitLab" or
+ - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or
something else descriptive.
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index 7b94506c297..edd6c59138f 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -6,13 +6,17 @@ You accept and agree to the following terms and conditions for Your present and
"You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
- "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+ "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
-2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+2. Grant of Copyright License.
-3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
-4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation designated on Schedule A below (or in a subsequent written modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation.
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+
+4. You represent that You are legally entitled to grant the above license. You represent further that each employee of the Corporation is authorized to submit Contributions on behalf of the Corporation, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of corporation here]."
5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
@@ -20,6 +24,6 @@ You accept and agree to the following terms and conditions for Your present and
7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
-8. It is your responsibility to notify GitLab B.V. when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V..
+8. It is your responsibility to notify GitLab B.V. when any change is required to the designation of employees not authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V..
This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index c6c7ac81c0d..4ac81ab3ee7 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -1,704 +1 @@
-# Markdown
-
-## Table of Contents
-
-**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
-
-* [Newlines](#newlines)
-* [Multiple underscores in words](#multiple-underscores-in-words)
-* [URL auto-linking](#url-auto-linking)
-* [Multiline Blockquote](#multiline-blockquote)
-* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
-* [Inline Diff](#inline-diff)
-* [Emoji](#emoji)
-* [Special GitLab references](#special-gitlab-references)
-* [Task Lists](#task-lists)
-* [Videos](#videos)
-
-**[Standard Markdown](#standard-markdown)**
-
-* [Headers](#headers)
-* [Emphasis](#emphasis)
-* [Lists](#lists)
-* [Links](#links)
-* [Images](#images)
-* [Blockquotes](#blockquotes)
-* [Inline HTML](#inline-html)
-* [Horizontal Rule](#horizontal-rule)
-* [Line Breaks](#line-breaks)
-* [Tables](#tables)
-
-**[References](#references)**
-
-## GitLab Flavored Markdown (GFM)
-
-> **Note:**
-Not all of the GitLab-specific extensions to Markdown that are described in
-this document currently work on our documentation website.
->
-For the best result, we encourage you to check this document out as rendered
-by GitLab: [markdown.md]
-
-_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._
-
-GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
-
-You can use GFM in the following areas:
-
-- comments
-- issues
-- merge requests
-- milestones
-- snippets (the snippet must be named with a `.md` extension)
-- wiki pages
-- markdown documents inside the repository
-
-You can also use other rich text files in GitLab. You might have to install a
-dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
-
-## Newlines
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines
-
-GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
-
-A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
-Line-breaks, or softreturns, are rendered if you end a line with two or more spaces:
-
- Roses are red [followed by two or more spaces]
- Violets are blue
-
- Sugar is sweet
-
-Roses are red
-Violets are blue
-
-Sugar is sweet
-
-## Multiple underscores in words
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words
-
-It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words:
-
- perform_complicated_task
-
- do_this_and_do_that_and_another_thing
-
-perform_complicated_task
-
-do_this_and_do_that_and_another_thing
-
-## URL auto-linking
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking
-
-GFM will autolink almost any URL you copy and paste into your text:
-
- * https://www.google.com
- * https://google.com/
- * ftp://ftp.us.debian.org/debian/
- * smb://foo/bar/baz
- * irc://irc.freenode.net/gitlab
- * http://localhost:3000
-
-* https://www.google.com
-* https://google.com/
-* ftp://ftp.us.debian.org/debian/
-* smb://foo/bar/baz
-* irc://irc.freenode.net/gitlab
-* http://localhost:3000
-
-## Multiline Blockquote
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote
-
-On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
-GFM supports multiline blockquotes fenced by <code>>>></code>:
-
-```no-highlight
->>>
-If you paste a message from somewhere else
-
-that
-
-spans
-
-multiple lines,
-
-you can quote that without having to manually prepend `>` to every line!
->>>
-```
-
->>>
-If you paste a message from somewhere else
-
-that
-
-spans
-
-multiple lines,
-
-you can quote that without having to manually prepend `>` to every line!
->>>
-
-## Code and Syntax Highlighting
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting
-
-_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
-list of supported languages visit the Rouge website._
-
-Blocks of code are either fenced by lines with three back-ticks <code>```</code>,
-or are indented with four spaces. Only the fenced code blocks support syntax
-highlighting:
-
-```no-highlight
-Inline `code` has `back-ticks around` it.
-```
-
-Inline `code` has `back-ticks around` it.
-
-Example:
-
- ```javascript
- var s = "JavaScript syntax highlighting";
- alert(s);
- ```
-
- ```python
- def function():
- #indenting works just fine in the fenced code block
- s = "Python syntax highlighting"
- print s
- ```
-
- ```ruby
- require 'redcarpet'
- markdown = Redcarpet.new("Hello World!")
- puts markdown.to_html
- ```
-
- ```
- No language indicated, so no syntax highlighting.
- s = "There is no highlighting for this."
- But let's throw in a <b>tag</b>.
- ```
-
-becomes:
-
-```javascript
-var s = "JavaScript syntax highlighting";
-alert(s);
-```
-
-```python
-def function():
- #indenting works just fine in the fenced code block
- s = "Python syntax highlighting"
- print s
-```
-
-```ruby
-require 'redcarpet'
-markdown = Redcarpet.new("Hello World!")
-puts markdown.to_html
-```
-
-```
-No language indicated, so no syntax highlighting.
-s = "There is no highlighting for this."
-But let's throw in a <b>tag</b>.
-```
-
-## Inline Diff
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff
-
-With inline diffs tags you can display {+ additions +} or [- deletions -].
-
-The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
-
-However the wrapping tags cannot be mixed as such:
-
-- {+ additions +]
-- [+ additions +}
-- {- deletions -]
-- [- deletions -}
-
-## Emoji
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji
-
- Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-
- :zap: You can use emoji anywhere GFM is supported. :v:
-
- You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-
- If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-
- Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-
-Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
-
-:zap: You can use emoji anywhere GFM is supported. :v:
-
-You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
-
-If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
-
-Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
-
-## Special GitLab References
-
-GFM recognizes special references.
-
-You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
-
-GFM will turn that reference into a link so you can navigate between them easily.
-
-GFM will recognize the following:
-
-| input | references |
-|:-----------------------|:--------------------------- |
-| `@user_name` | specific user |
-| `@group_name` | specific group |
-| `@all` | entire team |
-| `#123` | issue |
-| `!123` | merge request |
-| `$123` | snippet |
-| `~123` | label by ID |
-| `~bug` | one-word label by name |
-| `~"feature request"` | multi-word label by name |
-| `%123` | milestone by ID |
-| `%v1.23` | one-word milestone by name |
-| `%"release candidate"` | multi-word milestone by name |
-| `9ba12248` | specific commit |
-| `9ba12248...b19a04f5` | commit range comparison |
-| `[README](doc/README)` | repository file references |
-
-GFM also recognizes certain cross-project references:
-
-| input | references |
-|:----------------------------------------|:------------------------|
-| `namespace/project#123` | issue |
-| `namespace/project!123` | merge request |
-| `namespace/project%123` | milestone |
-| `namespace/project$123` | snippet |
-| `namespace/project@9ba12248` | specific commit |
-| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
-| `namespace/project~"Some label"` | issues with given label |
-
-## Task Lists
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists
-
-You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
-
-```no-highlight
-- [x] Completed task
-- [ ] Incomplete task
- - [ ] Sub-task 1
- - [x] Sub-task 2
- - [ ] Sub-task 3
-```
-
-- [x] Completed task
-- [ ] Incomplete task
- - [ ] Sub-task 1
- - [x] Sub-task 2
- - [ ] Sub-task 3
-
-Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
-
-## Videos
-
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos
-
-Image tags with a video extension are automatically converted to a video player.
-
-The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
-
- Here's a sample video:
-
- ![Sample Video](img/video.mp4)
-
-Here's a sample video:
-
-![Sample Video](img/video.mp4)
-
-# Standard Markdown
-
-## Headers
-
-```no-highlight
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-Alternatively, for H1 and H2, an underline-ish style:
-
-Alt-H1
-======
-
-Alt-H2
-------
-```
-
-# H1
-## H2
-### H3
-#### H4
-##### H5
-###### H6
-
-Alternatively, for H1 and H2, an underline-ish style:
-
-Alt-H1
-======
-
-Alt-H2
-------
-
-### Header IDs and links
-
-All Markdown-rendered headers automatically get IDs, except in comments.
-
-On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
-
-The IDs are generated from the content of the header according to the following rules:
-
-1. All text is converted to lowercase
-1. All non-word text (e.g., punctuation, HTML) is removed
-1. All spaces are converted to hyphens
-1. Two or more hyphens in a row are converted to one
-1. If a header with the same ID has already been generated, a unique
- incrementing number is appended, starting at 1.
-
-For example:
-
-```
-# This header has spaces in it
-## This header has a :thumbsup: in it
-# This header has Unicode in it: 한글
-## This header has spaces in it
-### This header has spaces in it
-```
-
-Would generate the following link IDs:
-
-1. `this-header-has-spaces-in-it`
-1. `this-header-has-a-in-it`
-1. `this-header-has-unicode-in-it-한글`
-1. `this-header-has-spaces-in-it`
-1. `this-header-has-spaces-in-it-1`
-
-Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
-
-## Emphasis
-
-```no-highlight
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
-
-Strikethrough uses two tildes. ~~Scratch this.~~
-```
-
-Emphasis, aka italics, with *asterisks* or _underscores_.
-
-Strong emphasis, aka bold, with **asterisks** or __underscores__.
-
-Combined emphasis with **asterisks and _underscores_**.
-
-Strikethrough uses two tildes. ~~Scratch this.~~
-
-## Lists
-
-```no-highlight
-1. First ordered list item
-2. Another item
- * Unordered sub-list.
-1. Actual numbers don't matter, just that it's a number
- 1. Ordered sub-list
-4. And another item.
-
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
-```
-
-1. First ordered list item
-2. Another item
- * Unordered sub-list.
-1. Actual numbers don't matter, just that it's a number
- 1. Ordered sub-list
-4. And another item.
-
-* Unordered list can use asterisks
-- Or minuses
-+ Or pluses
-
-If a list item contains multiple paragraphs,
-each subsequent paragraph should be indented with four spaces.
-
-```no-highlight
-1. First ordered list item
-
- Second paragraph of first item.
-2. Another item
-```
-
-1. First ordered list item
-
- Second paragraph of first item.
-2. Another item
-
-If the second paragraph isn't indented with four spaces,
-the second list item will be incorrectly labeled as `1`.
-
-```no-highlight
-1. First ordered list item
-
- Second paragraph of first item.
-2. Another item
-```
-
-1. First ordered list item
-
- Second paragraph of first item.
-2. Another item
-
-## Links
-
-There are two ways to create links, inline-style and reference-style.
-
- [I'm an inline-style link](https://www.google.com)
-
- [I'm a reference-style link][Arbitrary case-insensitive reference text]
-
- [I'm a relative reference to a repository file](LICENSE)
-
- [You can use numbers for reference-style link definitions][1]
-
- Or leave it empty and use the [link text itself][]
-
- Some text to show that the reference links can follow later.
-
- [arbitrary case-insensitive reference text]: https://www.mozilla.org
- [1]: http://slashdot.org
- [link text itself]: https://www.reddit.com
-
-[I'm an inline-style link](https://www.google.com)
-
-[I'm a reference-style link][Arbitrary case-insensitive reference text]
-
-[I'm a relative reference to a repository file](LICENSE)[^1]
-
-[You can use numbers for reference-style link definitions][1]
-
-Or leave it empty and use the [link text itself][]
-
-Some text to show that the reference links can follow later.
-
-[arbitrary case-insensitive reference text]: https://www.mozilla.org
-[1]: http://slashdot.org
-[link text itself]: https://www.reddit.com
-
-**Note**
-
-Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
-
-`[I'm a reference-style link](style)`
-
-will point the link to `wikis/style` when the link is inside of a wiki markdown file.
-
-## Images
-
- Here's our logo (hover to see the title text):
-
- Inline-style:
- ![alt text](img/logo.png)
-
- Reference-style:
- ![alt text1][logo]
-
- [logo]: img/logo.png
-
-Here's our logo:
-
-Inline-style:
-
-![alt text](img/logo.png)
-
-Reference-style:
-
-![alt text][logo]
-
-[logo]: img/logo.png
-
-## Blockquotes
-
-```no-highlight
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
-
-Quote break.
-
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
-```
-
-> Blockquotes are very handy in email to emulate reply text.
-> This line is part of the same quote.
-
-Quote break.
-
-> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
-
-## Inline HTML
-
-You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
-
-See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements.
-
-```no-highlight
-<dl>
- <dt>Definition list</dt>
- <dd>Is something people use sometimes.</dd>
-
- <dt>Markdown in HTML</dt>
- <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
-```
-
-<dl>
- <dt>Definition list</dt>
- <dd>Is something people use sometimes.</dd>
-
- <dt>Markdown in HTML</dt>
- <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
-</dl>
-
-## Horizontal Rule
-
-```
-Three or more...
-
----
-
-Hyphens
-
-***
-
-Asterisks
-
-___
-
-Underscores
-```
-
-Three or more...
-
----
-
-Hyphens
-
-***
-
-Asterisks
-
-___
-
-Underscores
-
-## Line Breaks
-
-My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
-
-Here are some things to try out:
-
-```
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also a separate paragraph, but...
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-This line is also a separate paragraph, and...
-This line is on its own line, because the previous line ends with two
-spaces.
-```
-
-Here's a line for us to start with.
-
-This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
-
-This line is also begins a separate paragraph, but...
-This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
-
-This line is also a separate paragraph, and...
-This line is on its own line, because the previous line ends with two
-spaces.
-
-## Tables
-
-Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
-
-```
-| header 1 | header 2 |
-| -------- | -------- |
-| cell 1 | cell 2 |
-| cell 3 | cell 4 |
-```
-
-Code above produces next output:
-
-| header 1 | header 2 |
-| -------- | -------- |
-| cell 1 | cell 2 |
-| cell 3 | cell 4 |
-
-**Note**
-
-The row of dashes between the table header and body must have at least three dashes in each column.
-
-By including colons in the header row, you can align the text within that column:
-
-```
-| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
-| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
-| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
-| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
-```
-
-| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
-| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
-| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
-| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
-
-## References
-
-- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
-- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
-- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
-
-[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md
-[rouge]: http://rouge.jneen.net/ "Rouge website"
-[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
-[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
+This document was moved to [user/markdown.md](../user/markdown.md).
diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md
index 0d17799372f..eac57bc3de4 100644
--- a/doc/monitoring/health_check.md
+++ b/doc/monitoring/health_check.md
@@ -1,6 +1,6 @@
# Health Check
->**Note:** This feature was [introduced][ce-3888] in GitLab 8.8.
+> [Introduced][ce-3888] in GitLab 8.8.
GitLab provides a health check endpoint for uptime monitoring on the `health_check` web
endpoint. The health check reports on the overall system status based on the status of
@@ -24,7 +24,7 @@ https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN
or as an HTTP header:
```bash
-curl -H "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
+curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
```
## Using the Endpoint
@@ -45,7 +45,7 @@ You can also ask for the status of specific services:
For example, the JSON output of the following health check:
```bash
-curl -H "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
+curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json
```
would be like:
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index fa976134341..835af5443a3 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -11,12 +11,13 @@ You can only restore a backup to exactly the same version of GitLab that you cre
on, for example 7.2.1. The best way to migrate your repositories from one server to
another is through backup restore.
-You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
-(for omnibus packages) or `/home/git/gitlab/.secret` (for installations
-from source). This file contains the database encryption key used
-for two-factor authentication. If you restore a GitLab backup without
-restoring the database encryption key, users who have two-factor
-authentication enabled will lose access to your GitLab server.
+You need to keep separate copies of `/etc/gitlab/gitlab-secrets.json` and
+`/etc/gitlab/gitlab.rb` (for omnibus packages) or
+`/home/git/gitlab/config/secrets.yml` (for installations from source). This file
+contains the database encryption keys used for two-factor authentication and CI
+secret variables, among other things. If you restore a GitLab backup without
+restoring the database encryption key, users who have two-factor authentication
+enabled will lose access to your GitLab server.
```
# use this command if you've installed GitLab with the Omnibus package
@@ -221,11 +222,12 @@ of using encryption in the first place!
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have an installation from source, please consider backing up your `.secret` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+If you have an installation from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
-At the very **minimum** you should backup `/etc/gitlab/gitlab-secrets.json`
-(Omnibus) or `/home/git/gitlab/.secret` (source) to preserve your
-database encryption key.
+At the very **minimum** you should backup `/etc/gitlab/gitlab.rb` and
+`/etc/gitlab/gitlab-secrets.json` (Omnibus), or
+`/home/git/gitlab/config/secrets.yml` (source) to preserve your database
+encryption key.
## Restore a previously created backup
@@ -240,11 +242,11 @@ the SQL database it needs to import data into ('gitlabhq_production').
All existing data will be either erased (SQL) or moved to a separate
directory (repositories, uploads).
-If some or all of your GitLab users are using two-factor authentication
-(2FA) then you must also make sure to restore
-`/etc/gitlab/gitlab-secrets.json` (Omnibus) or `/home/git/gitlab/.secret`
-(installations from source). Note that you need to run `gitlab-ctl
-reconfigure` after changing `gitlab-secrets.json`.
+If some or all of your GitLab users are using two-factor authentication (2FA)
+then you must also make sure to restore `/etc/gitlab/gitlab.rb` and
+`/etc/gitlab/gitlab-secrets.json` (Omnibus), or
+`/home/git/gitlab/config/secrets.yml` (installations from source). Note that you
+need to run `gitlab-ctl reconfigure` after changing `gitlab-secrets.json`.
### Installation from source
@@ -382,6 +384,13 @@ backups using all your disk space. To do this add the following lines to
gitlab_rails['backup_keep_time'] = 604800
```
+Note that the `backup_keep_time` configuration option only manages local
+files. GitLab does not automatically prune old files stored in a third-party
+object storage (e.g. AWS S3) because the user may not have permission to list
+and delete files. We recommend that you configure the appropriate retention
+policy for your object storage. For example, you can configure [the S3 backup
+policy here as described here](http://stackoverflow.com/questions/37553070/gitlab-omnibus-delete-backup-from-amazon-s3).
+
NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
## Alternative backup strategies
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 629d38efc53..8a5e2d6e16b 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -60,8 +60,8 @@ block_auto_created_users: false
## Disable Two-factor Authentication (2FA) for all users
This task will disable 2FA for all users that have it enabled. This can be
-useful if GitLab's `.secret` file has been lost and users are unable to login,
-for example.
+useful if GitLab's `config/secrets.yml` file has been lost and users are unable
+to login, for example.
```bash
# omnibus-gitlab
diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md
index c163bfd348d..c66c6dd0fd8 100644
--- a/doc/update/4.0-to-4.1.md
+++ b/doc/update/4.0-to-4.1.md
@@ -42,7 +42,7 @@ sudo -u gitlab -H bundle exec rake db:migrate RAILS_ENV=production
sudo mv /etc/init.d/gitlab /etc/init.d/gitlab.old
# get new one using sidekiq
-sudo curl -L --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/4-1-stable/init.d/gitlab
+sudo curl --location --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/4-1-stable/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index ee6de51c923..7654f4a0131 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -126,7 +126,7 @@ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids
```bash
# init.d
sudo rm /etc/init.d/gitlab
-sudo curl -L --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-0-stable/init.d/gitlab
+sudo curl --location --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-0-stable/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
# unicorn
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index f0fddcf83af..c19a819ab5a 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -63,7 +63,7 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```bash
# init.d
sudo rm /etc/init.d/gitlab
-sudo curl -L --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-1-stable/init.d/gitlab
+sudo curl --location --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlab-recipes/5-1-stable/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index c5254f6fb0c..fe8990b6843 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -67,7 +67,7 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```bash
sudo rm /etc/init.d/gitlab
-sudo curl -L --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-3-stable/lib/support/init.d/gitlab
+sudo curl --location --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-3-stable/lib/support/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index c4a6146dcda..5f82ad7d444 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -71,7 +71,7 @@ sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```bash
sudo rm /etc/init.d/gitlab
-sudo curl -L --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-4-stable/lib/support/init.d/gitlab
+sudo curl --location --output /etc/init.d/gitlab https://raw.github.com/gitlabhq/gitlabhq/5-4-stable/lib/support/init.d/gitlab
sudo chmod +x /etc/init.d/gitlab
```
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index 236430b5951..5352fd52f93 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -33,7 +33,7 @@ Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
-curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
+curl --location --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
cd ruby-2.1.2
./configure --disable-install-rdoc
make
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index a4e9be9946e..71f39c44077 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -33,7 +33,7 @@ Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
-curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
+curl --location --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
cd ruby-2.1.2
./configure --disable-install-rdoc
make
diff --git a/doc/update/7.14-to-8.0.md b/doc/update/7.14-to-8.0.md
index 305017b7048..117e2afaaa0 100644
--- a/doc/update/7.14-to-8.0.md
+++ b/doc/update/7.14-to-8.0.md
@@ -71,7 +71,7 @@ sudo -u git -H git checkout v2.6.5
First we download Go 1.5 and install it into `/usr/local/go`:
```bash
-curl -O --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz
+curl --remote-name --progress https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz
echo '5817fa4b2252afdb02e11e8b9dc1d9173ef3bd5a go1.5.linux-amd64.tar.gz' | shasum -c - && \
sudo tar -C /usr/local -xzf go1.5.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
new file mode 100644
index 00000000000..84c624cbcb7
--- /dev/null
+++ b/doc/update/8.10-to-8.11.md
@@ -0,0 +1,167 @@
+# From 8.10 to 8.11
+
+Make sure you view this update guide from the tag (version) of GitLab you would
+like to install. In most cases this should be the highest numbered production
+tag (without rc in it). You can select the tag in the version dropdown at the
+top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 8-11-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch --all --tags
+sudo -u git -H git checkout v3.3.3
+```
+
+### 5. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. This requires
+[Go 1.5](https://golang.org/dl) which should already be on your system from
+GitLab 8.1.
+
+```bash
+cd /home/git/gitlab-workhorse
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout v0.7.8
+sudo -u git -H make
+```
+
+### 6. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+```
+
+### 7. Update configuration files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+# For HTTPS configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab-ssl origin/8-11-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/8-10-stable:lib/support/nginx/gitlab origin/8-11-stable:lib/support/nginx/gitlab
+```
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/config/initializers/smtp_settings.rb.sample#L13?
+
+#### Init script
+
+Ensure you're still up-to-date with the latest init script changes:
+
+ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+
+### 8. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 9. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (8.10)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 8.9 to 8.10](8.9-to-8.10.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/8.9-to-8.10.md b/doc/update/8.9-to-8.10.md
index 71cbe5c8ac6..a057a423e61 100644
--- a/doc/update/8.9-to-8.10.md
+++ b/doc/update/8.9-to-8.10.md
@@ -46,7 +46,7 @@ sudo -u git -H git checkout 8-10-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
-sudo -u git -H git checkout v3.2.0
+sudo -u git -H git checkout v3.2.1
```
### 5. Update gitlab-workhorse
diff --git a/doc/user/admin_area/img/admin_labels.png b/doc/user/admin_area/img/admin_labels.png
new file mode 100644
index 00000000000..1ee33a534ab
--- /dev/null
+++ b/doc/user/admin_area/img/admin_labels.png
Binary files differ
diff --git a/doc/user/admin_area/labels.md b/doc/user/admin_area/labels.md
new file mode 100644
index 00000000000..9e2a89ebdf6
--- /dev/null
+++ b/doc/user/admin_area/labels.md
@@ -0,0 +1,9 @@
+# Labels
+
+## Default Labels
+
+### Define your own default Label Set
+
+Labels that are created within the Labels view on the Admin Dashboard will be automatically added to each new project.
+
+![Default label set](img/admin_labels.png)
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
new file mode 100644
index 00000000000..34e2e557f89
--- /dev/null
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -0,0 +1,20 @@
+# Continuous integration Admin settings
+
+## Maximum artifacts size
+
+The maximum size of the [build artifacts][art-yml] can be set in the Admin area
+of your GitLab instance. The value is in MB and the default is 100MB. Note that
+this setting is set for each build.
+
+1. Go to **Admin area > Settings** (`/admin/application_settings`).
+
+ ![Admin area settings button](img/admin_area_settings_button.png)
+
+1. Change the value of the maximum artifacts size (in MB):
+
+ ![Admin area maximum artifacts size](img/admin_area_maximum_artifacts_size.png)
+
+1. Hit **Save** for the changes to take effect.
+
+
+[art-yml]: ../../../administration/build_artifacts.md
diff --git a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
new file mode 100644
index 00000000000..53f7e76033e
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_area_settings_button.png b/doc/user/admin_area/settings/img/admin_area_settings_button.png
new file mode 100644
index 00000000000..509708b627f
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_area_settings_button.png
Binary files differ
diff --git a/doc/markdown/img/logo.png b/doc/user/img/markdown_logo.png
index 05c8b0d0ccf..05c8b0d0ccf 100644
--- a/doc/markdown/img/logo.png
+++ b/doc/user/img/markdown_logo.png
Binary files differ
diff --git a/doc/markdown/img/video.mp4 b/doc/user/img/markdown_video.mp4
index 1fc478842f5..1fc478842f5 100644
--- a/doc/markdown/img/video.mp4
+++ b/doc/user/img/markdown_video.mp4
Binary files differ
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
new file mode 100644
index 00000000000..7fe96e67dbb
--- /dev/null
+++ b/doc/user/markdown.md
@@ -0,0 +1,786 @@
+# Markdown
+
+## Table of Contents
+
+**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)**
+
+* [Newlines](#newlines)
+* [Multiple underscores in words](#multiple-underscores-in-words)
+* [URL auto-linking](#url-auto-linking)
+* [Multiline Blockquote](#multiline-blockquote)
+* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
+* [Inline Diff](#inline-diff)
+* [Emoji](#emoji)
+* [Special GitLab references](#special-gitlab-references)
+* [Task Lists](#task-lists)
+* [Videos](#videos)
+
+**[Standard Markdown](#standard-markdown)**
+
+* [Headers](#headers)
+* [Emphasis](#emphasis)
+* [Lists](#lists)
+* [Links](#links)
+* [Images](#images)
+* [Blockquotes](#blockquotes)
+* [Inline HTML](#inline-html)
+* [Horizontal Rule](#horizontal-rule)
+* [Line Breaks](#line-breaks)
+* [Tables](#tables)
+
+**[Wiki-Specific Markdown](#wiki-specific-markdown)**
+
+* [Wiki - Direct page link](#wiki-direct-page-link)
+* [Wiki - Direct file link](#wiki-direct-file-link)
+* [Wiki - Hierarchical link](#wiki-hierarchical-link)
+* [Wiki - Root link](#wiki-root-link)
+
+**[References](#references)**
+
+## GitLab Flavored Markdown (GFM)
+
+> **Note:**
+Not all of the GitLab-specific extensions to Markdown that are described in
+this document currently work on our documentation website.
+>
+For the best result, we encourage you to check this document out as rendered
+by GitLab: [markdown.md]
+
+_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._
+
+GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
+
+You can use GFM in the following areas:
+
+- comments
+- issues
+- merge requests
+- milestones
+- snippets (the snippet must be named with a `.md` extension)
+- wiki pages
+- markdown documents inside the repository
+
+You can also use other rich text files in GitLab. You might have to install a
+dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information.
+
+## Newlines
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines
+
+GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p).
+
+A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.
+Line-breaks, or softreturns, are rendered if you end a line with two or more spaces:
+
+ Roses are red [followed by two or more spaces]
+ Violets are blue
+
+ Sugar is sweet
+
+Roses are red
+Violets are blue
+
+Sugar is sweet
+
+## Multiple underscores in words
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words
+
+It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words:
+
+ perform_complicated_task
+
+ do_this_and_do_that_and_another_thing
+
+perform_complicated_task
+
+do_this_and_do_that_and_another_thing
+
+## URL auto-linking
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking
+
+GFM will autolink almost any URL you copy and paste into your text:
+
+ * https://www.google.com
+ * https://google.com/
+ * ftp://ftp.us.debian.org/debian/
+ * smb://foo/bar/baz
+ * irc://irc.freenode.net/gitlab
+ * http://localhost:3000
+
+* https://www.google.com
+* https://google.com/
+* ftp://ftp.us.debian.org/debian/
+* smb://foo/bar/baz
+* irc://irc.freenode.net/gitlab
+* http://localhost:3000
+
+## Multiline Blockquote
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote
+
+On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
+GFM supports multiline blockquotes fenced by <code>>>></code>:
+
+```no-highlight
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+```
+
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+
+## Code and Syntax Highlighting
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting
+
+_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
+list of supported languages visit the Rouge website._
+
+Blocks of code are either fenced by lines with three back-ticks <code>```</code>,
+or are indented with four spaces. Only the fenced code blocks support syntax
+highlighting:
+
+```no-highlight
+Inline `code` has `back-ticks around` it.
+```
+
+Inline `code` has `back-ticks around` it.
+
+Example:
+
+ ```javascript
+ var s = "JavaScript syntax highlighting";
+ alert(s);
+ ```
+
+ ```python
+ def function():
+ #indenting works just fine in the fenced code block
+ s = "Python syntax highlighting"
+ print s
+ ```
+
+ ```ruby
+ require 'redcarpet'
+ markdown = Redcarpet.new("Hello World!")
+ puts markdown.to_html
+ ```
+
+ ```
+ No language indicated, so no syntax highlighting.
+ s = "There is no highlighting for this."
+ But let's throw in a <b>tag</b>.
+ ```
+
+becomes:
+
+```javascript
+var s = "JavaScript syntax highlighting";
+alert(s);
+```
+
+```python
+def function():
+ #indenting works just fine in the fenced code block
+ s = "Python syntax highlighting"
+ print s
+```
+
+```ruby
+require 'redcarpet'
+markdown = Redcarpet.new("Hello World!")
+puts markdown.to_html
+```
+
+```
+No language indicated, so no syntax highlighting.
+s = "There is no highlighting for this."
+But let's throw in a <b>tag</b>.
+```
+
+## Inline Diff
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff
+
+With inline diffs tags you can display {+ additions +} or [- deletions -].
+
+The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}.
+
+However the wrapping tags cannot be mixed as such:
+
+- {+ additions +]
+- [+ additions +}
+- {- deletions -]
+- [- deletions -}
+
+## Emoji
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji
+
+ Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
+
+ :zap: You can use emoji anywhere GFM is supported. :v:
+
+ You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
+
+ If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+
+ Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
+
+Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
+
+:zap: You can use emoji anywhere GFM is supported. :v:
+
+You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that.
+
+If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes.
+
+Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup:
+
+## Special GitLab References
+
+GFM recognizes special references.
+
+You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project.
+
+GFM will turn that reference into a link so you can navigate between them easily.
+
+GFM will recognize the following:
+
+| input | references |
+|:-----------------------|:--------------------------- |
+| `@user_name` | specific user |
+| `@group_name` | specific group |
+| `@all` | entire team |
+| `#123` | issue |
+| `!123` | merge request |
+| `$123` | snippet |
+| `~123` | label by ID |
+| `~bug` | one-word label by name |
+| `~"feature request"` | multi-word label by name |
+| `%123` | milestone by ID |
+| `%v1.23` | one-word milestone by name |
+| `%"release candidate"` | multi-word milestone by name |
+| `9ba12248` | specific commit |
+| `9ba12248...b19a04f5` | commit range comparison |
+| `[README](doc/README)` | repository file references |
+
+GFM also recognizes certain cross-project references:
+
+| input | references |
+|:----------------------------------------|:------------------------|
+| `namespace/project#123` | issue |
+| `namespace/project!123` | merge request |
+| `namespace/project%123` | milestone |
+| `namespace/project$123` | snippet |
+| `namespace/project@9ba12248` | specific commit |
+| `namespace/project@9ba12248...b19a04f5` | commit range comparison |
+| `namespace/project~"Some label"` | issues with given label |
+
+## Task Lists
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists
+
+You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so:
+
+```no-highlight
+- [x] Completed task
+- [ ] Incomplete task
+ - [ ] Sub-task 1
+ - [x] Sub-task 2
+ - [ ] Sub-task 3
+```
+
+- [x] Completed task
+- [ ] Incomplete task
+ - [ ] Sub-task 1
+ - [x] Sub-task 2
+ - [ ] Sub-task 3
+
+Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes.
+
+## Videos
+
+> If this is not rendered correctly, see
+https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos
+
+Image tags with a video extension are automatically converted to a video player.
+
+The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`.
+
+ Here's a sample video:
+
+ ![Sample Video](img/markdown_video.mp4)
+
+Here's a sample video:
+
+![Sample Video](img/markdown_video.mp4)
+
+# Standard Markdown
+
+## Headers
+
+```no-highlight
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+```
+
+# H1
+## H2
+### H3
+#### H4
+##### H5
+###### H6
+
+Alternatively, for H1 and H2, an underline-ish style:
+
+Alt-H1
+======
+
+Alt-H2
+------
+
+### Header IDs and links
+
+All Markdown-rendered headers automatically get IDs, except in comments.
+
+On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else.
+
+The IDs are generated from the content of the header according to the following rules:
+
+1. All text is converted to lowercase
+1. All non-word text (e.g., punctuation, HTML) is removed
+1. All spaces are converted to hyphens
+1. Two or more hyphens in a row are converted to one
+1. If a header with the same ID has already been generated, a unique
+ incrementing number is appended, starting at 1.
+
+For example:
+
+```
+# This header has spaces in it
+## This header has a :thumbsup: in it
+# This header has Unicode in it: 한글
+## This header has spaces in it
+### This header has spaces in it
+```
+
+Would generate the following link IDs:
+
+1. `this-header-has-spaces-in-it`
+1. `this-header-has-a-in-it`
+1. `this-header-has-unicode-in-it-한글`
+1. `this-header-has-spaces-in-it`
+1. `this-header-has-spaces-in-it-1`
+
+Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID.
+
+## Emphasis
+
+```no-highlight
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+```
+
+Emphasis, aka italics, with *asterisks* or _underscores_.
+
+Strong emphasis, aka bold, with **asterisks** or __underscores__.
+
+Combined emphasis with **asterisks and _underscores_**.
+
+Strikethrough uses two tildes. ~~Scratch this.~~
+
+## Lists
+
+```no-highlight
+1. First ordered list item
+2. Another item
+ * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+ 1. Ordered sub-list
+4. And another item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+```
+
+1. First ordered list item
+2. Another item
+ * Unordered sub-list.
+1. Actual numbers don't matter, just that it's a number
+ 1. Ordered sub-list
+4. And another item.
+
+* Unordered list can use asterisks
+- Or minuses
++ Or pluses
+
+If a list item contains multiple paragraphs,
+each subsequent paragraph should be indented with four spaces.
+
+```no-highlight
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+```
+
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+
+If the second paragraph isn't indented with four spaces,
+the second list item will be incorrectly labeled as `1`.
+
+```no-highlight
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+```
+
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+
+## Links
+
+There are two ways to create links, inline-style and reference-style.
+
+ [I'm an inline-style link](https://www.google.com)
+
+ [I'm a reference-style link][Arbitrary case-insensitive reference text]
+
+ [I'm a relative reference to a repository file](LICENSE)
+
+ [You can use numbers for reference-style link definitions][1]
+
+ Or leave it empty and use the [link text itself][]
+
+ Some text to show that the reference links can follow later.
+
+ [arbitrary case-insensitive reference text]: https://www.mozilla.org
+ [1]: http://slashdot.org
+ [link text itself]: https://www.reddit.com
+
+[I'm an inline-style link](https://www.google.com)
+
+[I'm a reference-style link][Arbitrary case-insensitive reference text]
+
+[I'm a relative reference to a repository file](LICENSE)[^1]
+
+[You can use numbers for reference-style link definitions][1]
+
+Or leave it empty and use the [link text itself][]
+
+Some text to show that the reference links can follow later.
+
+[arbitrary case-insensitive reference text]: https://www.mozilla.org
+[1]: http://slashdot.org
+[link text itself]: https://www.reddit.com
+
+**Note**
+
+Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example:
+
+`[I'm a reference-style link](style)`
+
+will point the link to `wikis/style` when the link is inside of a wiki markdown file.
+
+## Images
+
+ Here's our logo (hover to see the title text):
+
+ Inline-style:
+ ![alt text](img/markdown_logo.png)
+
+ Reference-style:
+ ![alt text1][logo]
+
+ [logo]: img/markdown_logo.png
+
+Here's our logo:
+
+Inline-style:
+
+![alt text](img/markdown_logo.png)
+
+Reference-style:
+
+![alt text][logo]
+
+[logo]: img/markdown_logo.png
+
+## Blockquotes
+
+```no-highlight
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+```
+
+> Blockquotes are very handy in email to emulate reply text.
+> This line is part of the same quote.
+
+Quote break.
+
+> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote.
+
+## Inline HTML
+
+You can also use raw HTML in your Markdown, and it'll mostly work pretty well.
+
+See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements.
+
+```no-highlight
+<dl>
+ <dt>Definition list</dt>
+ <dd>Is something people use sometimes.</dd>
+
+ <dt>Markdown in HTML</dt>
+ <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+```
+
+<dl>
+ <dt>Definition list</dt>
+ <dd>Is something people use sometimes.</dd>
+
+ <dt>Markdown in HTML</dt>
+ <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
+</dl>
+
+## Horizontal Rule
+
+```
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+```
+
+Three or more...
+
+---
+
+Hyphens
+
+***
+
+Asterisks
+
+___
+
+Underscores
+
+## Line Breaks
+
+My basic recommendation for learning how line breaks work is to experiment and discover -- hit &lt;Enter&gt; once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend.
+
+Here are some things to try out:
+
+```
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+```
+
+Here's a line for us to start with.
+
+This line is separated from the one above by two newlines, so it will be a *separate paragraph*.
+
+This line is also begins a separate paragraph, but...
+This line is only separated by a single newline, so it's a separate line in the *same paragraph*.
+
+This line is also a separate paragraph, and...
+This line is on its own line, because the previous line ends with two
+spaces.
+
+## Tables
+
+Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them.
+
+```
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1 | cell 2 |
+| cell 3 | cell 4 |
+```
+
+Code above produces next output:
+
+| header 1 | header 2 |
+| -------- | -------- |
+| cell 1 | cell 2 |
+| cell 3 | cell 4 |
+
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
+By including colons in the header row, you can align the text within that column:
+
+```
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
+| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
+```
+
+| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
+| :----------- | :------: | ------------: | :----------- | :------: | ------------: |
+| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
+| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
+
+
+## Wiki-specific Markdown
+
+The following examples show how links inside wikis behave.
+
+### Wiki - Direct page link
+
+A link which just includes the slug for a page will point to that page,
+_at the base level of the wiki_.
+
+This snippet would link to a `documentation` page at the root of your wiki:
+
+```markdown
+[Link to Documentation](documentation)
+```
+
+### Wiki - Direct file link
+
+Links with a file extension point to that file, _relative to the current page_.
+
+If this snippet was placed on a page at `<your_wiki>/documentation/related`,
+it would link to `<your_wiki>/documentation/file.md`:
+
+```markdown
+[Link to File](file.md)
+```
+
+### Wiki - Hierarchical link
+
+A link can be constructed relative to the current wiki page using `./<page>`,
+`../<page>`, etc.
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/main`,
+ it would link to `<your_wiki>/documentation/related`:
+
+ ```markdown
+ [Link to Related Page](./related)
+ ```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
+ it would link to `<your_wiki>/documentation/main`:
+
+ ```markdown
+ [Link to Related Page](../main)
+ ```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/main`,
+ it would link to `<your_wiki>/documentation/related.md`:
+
+ ```markdown
+ [Link to Related Page](./related.md)
+ ```
+
+- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`,
+ it would link to `<your_wiki>/documentation/main.md`:
+
+ ```markdown
+ [Link to Related Page](../main.md)
+ ```
+
+### Wiki - Root link
+
+A link starting with a `/` is relative to the wiki root.
+
+- This snippet links to `<wiki_root>/documentation`:
+
+ ```markdown
+ [Link to Related Page](/documentation)
+ ```
+
+- This snippet links to `<wiki_root>/miscellaneous.md`:
+
+ ```markdown
+ [Link to Related Page](/miscellaneous.md)
+ ```
+## References
+
+- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
+- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown.
+- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown.
+
+[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md
+[rouge]: http://rouge.jneen.net/ "Rouge website"
+[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website"
+[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com
diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md
new file mode 100644
index 00000000000..c93ae1c369c
--- /dev/null
+++ b/doc/user/project/builds/artifacts.md
@@ -0,0 +1,104 @@
+# Introduction to build artifacts
+
+>**Notes:**
+>- Since GitLab 8.2 and GitLab Runner 0.7.0, build artifacts that are created by
+ GitLab Runner are uploaded to GitLab and are downloadable as a single archive
+ (`tar.gz`) using the GitLab UI.
+>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
+ changed to `ZIP`, and it is now possible to browse its contents, with the added
+ ability of downloading the files separately.
+>- The artifacts browser will be available only for new artifacts that are sent
+ to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
+ browse old artifacts already uploaded to GitLab.
+>- This is the user documentation. For the administration guide see
+ [administration/build_artifacts.md](../../../administration/build_artifacts.md).
+
+Artifacts is a list of files and directories which are attached to a build
+after it completes successfully. This feature is enabled by default in all GitLab installations.
+
+## Defining artifacts in `.gitlab-ci.yml`
+
+A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
+the following:
+
+```yaml
+pdf:
+ script: xelatex mycv.tex
+ artifacts:
+ paths:
+ - mycv.pdf
+```
+
+A job named `pdf` calls the `xelatex` command in order to build a pdf file from
+the latex source file `mycv.tex`. We then define the `artifacts` paths which in
+turn are defined with the `paths` keyword. All paths to files and directories
+are relative to the repository that was cloned during the build.
+
+For more examples on artifacts, follow the artifacts reference in
+[`.gitlab-ci.yml` documentation](../../../ci/yaml/README.md#artifacts).
+
+## Browsing build artifacts
+
+When GitLab receives an artifacts archive, an archive metadata file is also
+generated. This metadata file describes all the entries that are located in the
+artifacts archive itself. The metadata file is in a binary format, with
+additional GZIP compression.
+
+GitLab does not extract the artifacts archive in order to save space, memory
+and disk I/O. It instead inspects the metadata file which contains all the
+relevant information. This is especially important when there is a lot of
+artifacts, or an archive is a very large file.
+
+---
+
+After a build finishes, if you visit the build's specific page, you can see
+that there are two buttons. One is for downloading the artifacts archive and
+the other for browsing its contents.
+
+![Build artifacts browser button](img/build_artifacts_browser_button.png)
+
+---
+
+The archive browser shows the name and the actual file size of each file in the
+archive. If your artifacts contained directories, then you are also able to
+browse inside them.
+
+Below you can see how browsing looks like. In this case we have browsed inside
+the archive and at this point there is one directory and one HTML file.
+
+![Build artifacts browser](img/build_artifacts_browser.png)
+
+---
+
+## Downloading build artifacts
+
+>**Note:**
+GitLab does not extract the entire artifacts archive to send just a single file
+to the user. When clicking on a specific file, [GitLab Workhorse] extracts it
+from the archive and the download begins. This implementation saves space,
+memory and disk I/O.
+
+If you need to download the whole archive, there are buttons in various places
+inside GitLab that make that possible.
+
+1. While on the pipelines page, you can see the download icon for each build's
+ artifacts archive in the right corner:
+
+ ![Build artifacts in Pipelines page](img/build_artifacts_pipelines_page.png)
+
+1. While on the builds page, you can see the download icon for each build's
+ artifacts archive in the right corner:
+
+ ![Build artifacts in Builds page](img/build_artifacts_builds_page.png)
+
+1. While inside a specific build, you are presented with a download button
+ along with the one that browses the archive:
+
+ ![Build artifacts browser button](img/build_artifacts_browser_button.png)
+
+1. And finally, when browsing an archive you can see the download button at
+ the top right corner:
+
+ ![Build artifacts browser](img/build_artifacts_browser.png)
+
+[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
diff --git a/doc/user/project/builds/img/build_artifacts_browser.png b/doc/user/project/builds/img/build_artifacts_browser.png
new file mode 100644
index 00000000000..d95e2800c0f
--- /dev/null
+++ b/doc/user/project/builds/img/build_artifacts_browser.png
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_browser_button.png b/doc/user/project/builds/img/build_artifacts_browser_button.png
new file mode 100644
index 00000000000..463540634e3
--- /dev/null
+++ b/doc/user/project/builds/img/build_artifacts_browser_button.png
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_builds_page.png b/doc/user/project/builds/img/build_artifacts_builds_page.png
new file mode 100644
index 00000000000..db78386ba7b
--- /dev/null
+++ b/doc/user/project/builds/img/build_artifacts_builds_page.png
Binary files differ
diff --git a/doc/user/project/builds/img/build_artifacts_pipelines_page.png b/doc/user/project/builds/img/build_artifacts_pipelines_page.png
new file mode 100644
index 00000000000..6c2d1a4bdc7
--- /dev/null
+++ b/doc/user/project/builds/img/build_artifacts_pipelines_page.png
Binary files differ
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index 4258185b7d0..0f7e9eede19 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -22,34 +22,47 @@ created yet.
![Generate new labels](img/labels_generate.png)
+Creating a new label from scratch is as easy as pressing the **New label**
+button. From there on you can choose the name, give it an optional description,
+a color and you are set.
+
+When you are ready press the **Create label** button to create the new label.
+
+![New label](img/labels_new_label.png)
+
---
-You can skip that and create a new label or click that link and GitLab will
-generate a set of predefined labels for you. There 8 default generated labels
+## Default Labels
+
+It's possible to populate the labels for your project from a set of predefined labels.
+
+### Generate GitLab's predefined label set
+
+![Generate new labels](img/labels_generate.png)
+
+Click the link to 'Generate a default set of labels' and GitLab will
+generate a set of predefined labels for you. There are 8 default generated labels
in total and you can see them in the screenshot below.
![Default generated labels](img/labels_default.png)
---
-You can see that from the labels page you can have an overview of the number of
-issues and merge requests assigned to each label.
-
-Creating a new label from scratch is as easy as pressing the **New label**
-button. From there on you can choose the name, give it an optional description,
-a color and you are set.
+## Labels Overview
-When you are ready press the **Create label** button to create the new label.
+![Default generated labels](img/labels_default.png)
-![New label](img/labels_new_label.png)
+You can see that from the labels page you can have an overview of the number of
+issues and merge requests assigned to each label.
## Prioritize labels
>**Notes:**
- - This feature was introduced in GitLab 8.9.
- - Priority sorting is based on the highest priority label only. This might
- change in the future, follow the discussion in
- https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
+>
+> - Introduced in GitLab 8.9.
+> - Priority sorting is based on the highest priority label only. This might
+> change in the future, follow the discussion in
+> https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
Prioritized labels are like any other label, but sorted by priority. This allows
you to sort issues and merge requests by priority.
@@ -87,8 +100,7 @@ important.
## Create a new label right from the issue tracker
->**Note:**
-This feature was introduced in GitLab 8.6.
+> Introduced in GitLab 8.6.
There are times when you are already in the issue tracker searching for a
label, only to realize it doesn't exist. Instead of going to the **Labels**
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 6a8170b5ecb..96d9bdc1b29 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -47,8 +47,7 @@ creation.
## Wildcard protected branches
->**Note:**
-This feature was [introduced][ce-4665] in GitLab 8.10.
+> [Introduced][ce-4665] in GitLab 8.10.
You can specify a wildcard protected branch, which will protect all branches
matching the wildcard. For example:
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 38e9786123d..2513def49a4 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -1,18 +1,19 @@
# Project import/export
>**Notes:**
- - This feature was [introduced][ce-3050] in GitLab 8.9
- - Importing will not be possible if the import instance version is lower
- than that of the exporter.
- - For existing installations, the project import option has to be enabled in
- application settings (`/admin/application_settings`) under 'Import sources'.
- Ask your administrator if you don't see the **GitLab export** button when
- creating a new project.
- - You can find some useful raketasks if you are an administrator in the
- [import_export](../../../administration/raketasks/project_import_export.md)
- raketask.
- - The exports are stored in a temporary [shared directory][tmp] and are deleted
- every 24 hours by a specific worker.
+>
+> - [Introduced][ce-3050] in GitLab 8.9.
+> - Importing will not be possible if the import instance version is lower
+> than that of the exporter.
+> - For existing installations, the project import option has to be enabled in
+> application settings (`/admin/application_settings`) under 'Import sources'.
+> Ask your administrator if you don't see the **GitLab export** button when
+> creating a new project.
+> - You can find some useful raketasks if you are an administrator in the
+> [import_export](../../../administration/raketasks/project_import_export.md)
+> raketask.
+> - The exports are stored in a temporary [shared directory][tmp] and are deleted
+> every 24 hours by a specific worker.
Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance.
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 8559b67af04..d4b28d875cd 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -26,6 +26,10 @@ GitLab webhooks keep in mind the following things:
you are writing a low-level hook this is important to remember.
- GitLab ignores the HTTP status code returned by your endpoint.
+## Secret Token
+
+If you specify a secret token, it will be sent with the hook request in the `X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify that the request is legitimate.
+
## SSL Verification
By default, the SSL certificate of the webhook endpoint is verified based on
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
index e6f8b792707..1df0698afd0 100644
--- a/doc/workflow/award_emoji.md
+++ b/doc/workflow/award_emoji.md
@@ -1,7 +1,7 @@
# Award emoji
>**Note:**
-This feature was [introduced][1825] in GitLab 8.2.
+[Introduced][1825] in GitLab 8.2.
When you're collaborating online, you get fewer opportunities for high-fives
and thumbs-ups. Emoji can be awarded to issues and merge requests, making
@@ -16,7 +16,7 @@ award emoji.
## Sort issues and merge requests on vote count
>**Note:**
-This feature was [introduced][2871] in GitLab 8.5.
+[Introduced][2871] in GitLab 8.5.
You can quickly sort issues and merge requests by the number of votes they
have received. The sort options can be found in the dropdown menu as "Most
@@ -45,7 +45,7 @@ downvotes.
## Award emoji for comments
>**Note:**
-This feature was [introduced][4291] in GitLab 8.9.
+[Introduced][4291] in GitLab 8.9.
Award emoji can also be applied to individual comments when you want to
celebrate an accomplishment or agree with an opinion.
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
index 4a499009842..64b94d81024 100644
--- a/doc/workflow/cherry_pick_changes.md
+++ b/doc/workflow/cherry_pick_changes.md
@@ -1,7 +1,6 @@
# Cherry-pick changes
->**Note:**
-This feature was [introduced][ce-3514] in GitLab 8.7.
+> [Introduced][ce-3514] in GitLab 8.7.
---
diff --git a/doc/workflow/file_finder.md b/doc/workflow/file_finder.md
index b69ae663272..8d87b030c83 100644
--- a/doc/workflow/file_finder.md
+++ b/doc/workflow/file_finder.md
@@ -1,6 +1,6 @@
# File finder
-_**Note:** This feature was [introduced][gh-9889] in GitLab 8.4._
+> [Introduced][gh-9889] in GitLab 8.4.
---
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index a2b2a4b88f9..306caabf6e6 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -18,9 +18,6 @@ At its current state, GitHub importer can import:
With GitLab 8.7+, references to pull requests and issues are preserved.
-It is not yet possible to import your cross-repository pull requests (those from
-forks). We are working on improving this in the near future.
-
The importer page is visible when you [create a new project][new-project].
Click on the **GitHub** link and, if you are logged in via the GitHub
integration, you will be redirected to GitHub for permission to access your
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
index 399366b0cdc..5ead9f4177f 100644
--- a/doc/workflow/revert_changes.md
+++ b/doc/workflow/revert_changes.md
@@ -1,6 +1,6 @@
# Reverting changes
-_**Note:** This feature was [introduced][ce-1990] in GitLab 8.5._
+> [Introduced][ce-1990] in GitLab 8.5.
---
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
index ffcb832cdd7..36516883ef6 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -2,4 +2,75 @@
You can see GitLab's keyboard shortcuts by using 'shift + ?'
-![Shortcuts](shortcuts.png) \ No newline at end of file
+## Global Shortcuts
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>s</kbd> | Focus search |
+| <kbd>?</kbd> | Show/hide this dialog |
+| <kbd>⌘</kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview |
+| <kbd>↑</kbd> | Edit last comment (when focused on an empty textarea) |
+
+## Project Files Browsing
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>↑</kbd> | Move selection up |
+| <kbd>↓</kbd> | Move selection down |
+| <kbd>enter</kbd> | Open selection |
+
+## Finding Project File
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>↑</kbd> | Move selection up |
+| <kbd>↓</kbd> | Move selection down |
+| <kbd>enter</kbd> | Open selection |
+| <kbd>esc</kbd> | Go back |
+
+## Global Dashboard
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>g</kbd> + <kbd>a</kbd> | Go to the activity feed |
+| <kbd>g</kbd> + <kbd>p</kbd> | Go to projects |
+| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
+| <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests |
+
+## Project
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>g</kbd> + <kbd>p</kbd> | Go to the project's home page |
+| <kbd>g</kbd> + <kbd>e</kbd> | Go to the project's activity feed |
+| <kbd>g</kbd> + <kbd>f</kbd> | Go to files |
+| <kbd>g</kbd> + <kbd>c</kbd> | Go to commits |
+| <kbd>g</kbd> + <kbd>b</kbd> | Go to builds |
+| <kbd>g</kbd> + <kbd>n</kbd> | Go to network graph |
+| <kbd>g</kbd> + <kbd>g</kbd> | Go to graphs |
+| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
+| <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests |
+| <kbd>g</kbd> + <kbd>s</kbd> | Go to snippets |
+| <kbd>t</kbd> | Go to finding file |
+| <kbd>i</kbd> | New issue |
+
+## Network Graph
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>←</kbd> or <kbd>h</kbd> | Scroll left |
+| <kbd>→</kbd> or <kbd>l</kbd> | Scroll right |
+| <kbd>↑</kbd> or <kbd>k</kbd> | Scroll up |
+| <kbd>↓</kbd> or <kbd>j</kbd> | Scroll down |
+| <kbd>shift</kbd> + <kbd>↑</kbd> or <kbd>shift</kbd> + <kbd>k</kbd> | Scroll to top |
+| <kbd>shift</kbd> + <kbd>↓</kbd> or <kbd>shift</kbd> + <kbd>j</kbd> | Scroll to bottom |
+
+## Issues and Merge Requests
+
+| Keyboard Shortcut | Description |
+| ----------------- | ----------- |
+| <kbd>a</kbd> | Change assignee |
+| <kbd>m</kbd> | Change milestone |
+| <kbd>r</kbd> | Reply (quoting selected text) |
+| <kbd>e</kbd> | Edit issue/merge request |
+| <kbd>l</kbd> | Change label | \ No newline at end of file
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
deleted file mode 100644
index a9b1c4b4dcc..00000000000
--- a/doc/workflow/shortcuts.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 9524ffd5420..a50ba305deb 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -1,6 +1,6 @@
# GitLab Todos
->**Note:** This feature was [introduced][ce-2817] in GitLab 8.5.
+> [Introduced][ce-2817] in GitLab 8.5.
When you log into GitLab, you normally want to see where you should spend your
time and take some action, or what you need to keep an eye on. All without the
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
index 1832567a34c..ee8e7862572 100644
--- a/doc/workflow/web_editor.md
+++ b/doc/workflow/web_editor.md
@@ -70,8 +70,7 @@ There are multiple ways to create a branch from GitLab's web interface.
### Create a new branch from an issue
->**Note:**
-This feature was [introduced][ce-2808] in GitLab 8.6.
+> [Introduced][ce-2808] in GitLab 8.6.
In case your development workflow dictates to have an issue for every merge
request, you can quickly create a branch right on the issue page which will be
diff --git a/features/explore/groups.feature b/features/explore/groups.feature
index 5fc9b135601..9eacbe0b25e 100644
--- a/features/explore/groups.feature
+++ b/features/explore/groups.feature
@@ -24,14 +24,6 @@ Feature: Explore Groups
Then I should see project "Internal" items
And I should not see project "Enterprise" items
- Scenario: I should see group's members as user
- Given group "TestGroup" has internal project "Internal"
- And "John Doe" is owner of group "TestGroup"
- When I sign in as a user
- And I visit group "TestGroup" members page
- Then I should see group member "John Doe"
- And I should not see member roles
-
Scenario: I should see group with private, internal and public projects as visitor
Given group "TestGroup" has internal project "Internal"
Given group "TestGroup" has public project "Community"
@@ -56,14 +48,6 @@ Feature: Explore Groups
And I should not see project "Internal" items
And I should not see project "Enterprise" items
- Scenario: I should see group's members as visitor
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- And "John Doe" is owner of group "TestGroup"
- When I visit group "TestGroup" members page
- Then I should see group member "John Doe"
- And I should not see member roles
-
Scenario: I should see group with private, internal and public projects as user
Given group "TestGroup" has internal project "Internal"
Given group "TestGroup" has public project "Community"
@@ -91,15 +75,6 @@ Feature: Explore Groups
And I should see project "Internal" items
And I should not see project "Enterprise" items
- Scenario: I should see group's members as user
- Given group "TestGroup" has internal project "Internal"
- Given group "TestGroup" has public project "Community"
- And "John Doe" is owner of group "TestGroup"
- When I sign in as a user
- And I visit group "TestGroup" members page
- Then I should see group member "John Doe"
- And I should not see member roles
-
Scenario: I should see group with public project in public groups area
Given group "TestGroup" has public project "Community"
When I visit the public groups area
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 21768c15c17..1b8e4262e40 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -107,16 +107,6 @@ Feature: Project Merge Requests
Then The list should be sorted by "Least popular"
@javascript
- Scenario: I comment on a merge request diff
- Given project "Shop" have "Bug NS-05" open merge request with diffs inside
- And I visit merge request page "Bug NS-05"
- And I click on the Changes tab
- And I leave a comment like "Line is wrong" on diff
- And I switch to the merge request's comments tab
- Then I should see a discussion has started on diff
- And I should see a badge of "1" next to the discussion link
-
- @javascript
Scenario: I see a new comment on merge request diff from another user in the discussion tab
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
@@ -237,6 +227,15 @@ Feature: Project Merge Requests
Then I should see additional file lines
@javascript
+ Scenario: I unfold diff in Side-by-Side view
+ Given project "Shop" have "Bug NS-05" open merge request with diffs inside
+ And I visit merge request page "Bug NS-05"
+ And I click on the Changes tab
+ And I click Side-by-side Diff tab
+ And I unfold diff
+ Then I should see additional file lines
+
+ @javascript
Scenario: I show comments on a merge request side-by-side diff with comments in multiple files
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And I visit merge request page "Bug NS-05"
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 80ed4c6d64c..a7d61bc28e0 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -26,6 +26,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I see prefilled new Merge Request page' do
+ expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq new_namespace_project_merge_request_path(@project.namespace, @project)
expect(find("#merge_request_target_project_id").value).to eq @project.id.to_s
expect(find("input#merge_request_source_branch").value).to eq "fix"
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 726b37cfde5..ca3cd0ecc4e 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -1,4 +1,5 @@
class Spinach::Features::EventFilters < Spinach::FeatureSteps
+ include WaitForAjax
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -72,14 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end
When 'I click "push" event filter' do
- click_link("push_event_filter")
+ wait_for_ajax
+ click_link("Push events")
+ wait_for_ajax
end
When 'I click "team" event filter' do
- click_link("team_event_filter")
+ wait_for_ajax
+ click_link("Team")
+ wait_for_ajax
end
When 'I click "merge" event filter' do
- click_link("merged_event_filter")
+ wait_for_ajax
+ click_link("Merge events")
+ wait_for_ajax
end
end
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 8706f0e8e78..39c65bb6cde 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -43,9 +43,14 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
+ expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-author li a")
+
find(".js-assignee-search").click
+ expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(issue)
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 06db36c7014..6777101fb15 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -47,9 +47,14 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I click "All" link' do
find(".js-author-search").click
+ expect(page).to have_selector(".dropdown-menu-author li a")
find(".dropdown-menu-author li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-author li a")
+
find(".js-assignee-search").click
+ expect(page).to have_selector(".dropdown-menu-assignee li a")
find(".dropdown-menu-assignee li a", match: :first).click
+ expect(page).not_to have_selector(".dropdown-menu-assignee li a")
end
def should_see(merge_request)
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index 727a6a71373..dcfa88f69fc 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -29,6 +29,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
step 'I am redirected to the GitHub import page' do
+ expect(page).to have_content('Import Projects from GitHub')
expect(current_path).to eq new_import_github_path
end
@@ -47,6 +48,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
step 'I redirected to Google Code import page' do
+ expect(page).to have_content('Import projects from Google Code')
expect(current_path).to eq new_import_google_code_path
end
end
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
index 87f32e70d59..409bf0cb416 100644
--- a/features/steps/explore/groups.rb
+++ b/features/steps/explore/groups.rb
@@ -62,10 +62,6 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
expect(page).to have_content "John Doe"
end
- step 'I should not see member roles' do
- expect(body).not_to match(%r{owner|developer|reporter|guest}i)
- end
-
protected
def group_has_project(groupname, projectname, visibility_level)
diff --git a/features/steps/project/badges/build.rb b/features/steps/project/badges/build.rb
index 66a48a176e5..96c59322f9b 100644
--- a/features/steps/project/badges/build.rb
+++ b/features/steps/project/badges/build.rb
@@ -26,7 +26,7 @@ class Spinach::Features::ProjectBadgesBuild < Spinach::FeatureSteps
def expect_badge(status)
svg = Nokogiri::XML.parse(page.body)
- expect(page.response_headers).to include('Content-Type' => 'image/svg+xml')
+ expect(page.response_headers['Content-Type']).to include('image/svg+xml')
expect(svg.at(%Q{text:contains("#{status}")})).to be_truthy
end
end
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index b4a32ed2e38..055fca036d3 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -10,6 +10,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I click artifacts browse button' do
click_link 'Browse'
+ expect(page).not_to have_selector('.build-sidebar')
end
step 'I should see content of artifacts archive' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 0a42931147d..4bfb7e92e99 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -25,7 +25,7 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step 'project "Shop" has protected branches' do
project = Project.find_by(name: "Shop")
- project.protected_branches.create(name: "stable")
+ create(:protected_branch, project: project, name: "stable")
end
step 'I click new branch link' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 6b56a77b832..dacab6c7977 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -34,6 +34,9 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
+ expect(page).to have_content('Source branch')
+ expect(page).to have_content('Target branch')
+
first('.js-source-project').click
first('.dropdown-source-project a', text: @forked_project.path_with_namespace)
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 35f166c7c08..daee90b3767 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -354,6 +354,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
def filter_issue(text)
+ sleep 1
fill_in 'issue_search', with: text
+ sleep 1
end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index da848afd48e..53d1aedf27f 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -477,6 +477,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click Side-by-side Diff tab' do
find('a', text: 'Side-by-side').trigger('click')
+
+ # Waits for load
+ expect(page).to have_css('.parallel')
end
step 'I should see comments on the side-by-side diff page' do
@@ -486,10 +489,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I fill in merge request search with "Fe"' do
+ sleep 1
fill_in 'issue_search', with: "Fe"
end
step 'I click the "Target branch" dropdown' do
+ expect(page).to have_content('Target branch')
first('.target_branch').click
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 0fe046dcbf6..841d191d55b 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -69,6 +69,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I edit code' do
+ expect(page).to have_selector('.file-editor')
set_new_content
end
@@ -131,6 +132,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do
find('.add-to-tree').click
click_link 'New file'
+ expect(page).to have_selector('.file-editor')
end
step 'I click on "Upload file" link in repo' do
@@ -293,7 +295,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
first('.js-project-refs-dropdown').click
page.within '.project-refs-form' do
- click_link 'test'
+ click_link "'test'"
end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 732dc5d0b93..07a955b1a14 100644
--- a/features/steps/project/wiki.rb
+++ b/features/steps/project/wiki.rb
@@ -142,7 +142,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps
end
step 'I edit the Wiki page with a path' do
+ expect(page).to have_content('three')
click_on 'three'
+ expect(find('.nav-text')).to have_content('Three')
click_on 'Edit'
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 4d6b258f577..70e6d4836b2 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -10,20 +10,20 @@ module SharedBuilds
end
step 'project has a recent build' do
- @pipeline = create(:ci_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
+ @pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master')
@build = create(:ci_build_with_coverage, pipeline: @pipeline)
end
step 'recent build is successful' do
- @build.update(status: 'success')
+ @build.success
end
step 'recent build failed' do
- @build.update(status: 'failed')
+ @build.drop
end
step 'project has another build that is running' do
- create(:ci_build, pipeline: @pipeline, name: 'second build', status: 'running')
+ create(:ci_build, pipeline: @pipeline, name: 'second build', status_event: 'run')
end
step 'I visit recent build details page' do
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index b5fd24d246f..aa666a954bc 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -133,9 +133,7 @@ module SharedIssuable
end
step 'The list should be sorted by "Oldest updated"' do
- page.within('.content div.dropdown.inline.prepend-left-10') do
- expect(page.find('button.dropdown-toggle.btn')).to have_content('Oldest updated')
- end
+ expect(find('.issues-filters')).to have_content('Oldest updated')
end
step 'I click link "Next" in the sidebar' do
diff --git a/features/support/env.rb b/features/support/env.rb
index f0a3dd8d2d0..569fd444e86 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,6 +1,5 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
ENV['RAILS_ENV'] = 'test'
require './config/environment'
diff --git a/features/support/wait_for_ajax.rb b/features/support/wait_for_ajax.rb
new file mode 100644
index 00000000000..b90fc112671
--- /dev/null
+++ b/features/support/wait_for_ajax.rb
@@ -0,0 +1,11 @@
+module WaitForAjax
+ def wait_for_ajax
+ Timeout.timeout(Capybara.default_max_wait_time) do
+ loop until finished_all_ajax_requests?
+ end
+ end
+
+ def finished_all_ajax_requests?
+ page.evaluate_script('jQuery.active').zero?
+ end
+end
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
new file mode 100644
index 00000000000..d02b469dac8
--- /dev/null
+++ b/lib/api/access_requests.rb
@@ -0,0 +1,90 @@
+module API
+ class AccessRequests < Grape::API
+ before { authenticate! }
+
+ helpers ::API::Helpers::MembersHelpers
+
+ %w[group project].each do |source_type|
+ resource source_type.pluralize do
+ # Get a list of group/project access requests viewable by the authenticated user.
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ #
+ # Example Request:
+ # GET /groups/:id/access_requests
+ # GET /projects/:id/access_requests
+ get ":id/access_requests" do
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+
+ access_requesters = paginate(source.requesters.includes(:user))
+
+ present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters
+ end
+
+ # Request access to the group/project
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ #
+ # Example Request:
+ # POST /groups/:id/access_requests
+ # POST /projects/:id/access_requests
+ post ":id/access_requests" do
+ source = find_source(source_type, params[:id])
+ access_requester = source.request_access(current_user)
+
+ if access_requester.persisted?
+ present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester
+ else
+ render_validation_error!(access_requester)
+ end
+ end
+
+ # Approve a group/project access request
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the access requester
+ # access_level (optional) - Access level
+ #
+ # Example Request:
+ # PUT /groups/:id/access_requests/:user_id/approve
+ # PUT /projects/:id/access_requests/:user_id/approve
+ put ':id/access_requests/:user_id/approve' do
+ required_attributes! [:user_id]
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+
+ member = source.requesters.find_by!(user_id: params[:user_id])
+ if params[:access_level]
+ member.update(access_level: params[:access_level])
+ end
+ member.accept_request
+
+ status :created
+ present member.user, with: Entities::Member, member: member
+ end
+
+ # Deny a group/project access request
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the access requester
+ #
+ # Example Request:
+ # DELETE /groups/:id/access_requests/:user_id
+ # DELETE /projects/:id/access_requests/:user_id
+ delete ":id/access_requests/:user_id" do
+ required_attributes! [:user_id]
+ source = find_source(source_type, params[:id])
+
+ access_requester = source.requesters.find_by!(user_id: params[:user_id])
+
+ ::Members::DestroyService.new(access_requester, current_user).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 3d7d67510a8..d43af3f24e9 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -3,10 +3,20 @@ module API
include APIGuard
version 'v3', using: :path
+ rescue_from Gitlab::Access::AccessDeniedError do
+ rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
+ end
+
rescue_from ActiveRecord::RecordNotFound do
rack_response({ 'message' => '404 Not found' }.to_json, 404)
end
+ # Retain 405 error rather than a 500 error for Grape 0.15.0+.
+ # See: https://github.com/ruby-grape/grape/commit/252bfd27c320466ec3c0751812cf44245e97e5de
+ rescue_from Grape::Exceptions::Base do |e|
+ error! e.message, e.status, e.headers
+ end
+
rescue_from :all do |exception|
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
# why is this not wrapped in something reusable?
@@ -26,26 +36,27 @@ module API
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
helpers ::API::Helpers
+ mount ::API::AccessRequests
mount ::API::AwardEmoji
mount ::API::Branches
mount ::API::Builds
mount ::API::CommitStatuses
mount ::API::Commits
mount ::API::DeployKeys
+ mount ::API::Environments
mount ::API::Files
- mount ::API::GroupMembers
mount ::API::Groups
mount ::API::Internal
mount ::API::Issues
mount ::API::Keys
mount ::API::Labels
mount ::API::LicenseTemplates
+ mount ::API::Members
mount ::API::MergeRequests
mount ::API::Milestones
mount ::API::Namespaces
mount ::API::Notes
mount ::API::ProjectHooks
- mount ::API::ProjectMembers
mount ::API::ProjectSnippets
mount ::API::Projects
mount ::API::Repositories
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 66b853eb342..a77afe634f6 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -35,6 +35,10 @@ module API
# Protect a single branch
#
+ # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}`
+ # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility),
+ # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`.
+ #
# Parameters:
# id (required) - The ID of a project
# branch (required) - The name of the branch
@@ -49,17 +53,36 @@ module API
@branch = user_project.repository.find_branch(params[:branch])
not_found!('Branch') unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
- developers_can_push = to_boolean(params[:developers_can_push])
+
developers_can_merge = to_boolean(params[:developers_can_merge])
+ developers_can_push = to_boolean(params[:developers_can_push])
+
+ protected_branch_params = {
+ name: @branch.name
+ }
+
+ unless developers_can_merge.nil?
+ protected_branch_params.merge!({
+ merge_access_level_attributes: {
+ access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ }
+ })
+ end
+
+ unless developers_can_push.nil?
+ protected_branch_params.merge!({
+ push_access_level_attributes: {
+ access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER
+ }
+ })
+ end
if protected_branch
- protected_branch.developers_can_push = developers_can_push unless developers_can_push.nil?
- protected_branch.developers_can_merge = developers_can_merge unless developers_can_merge.nil?
- protected_branch.save
+ service = ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch_params)
+ service.execute(protected_branch)
else
- user_project.protected_branches.create(name: @branch.name,
- developers_can_push: developers_can_push || false,
- developers_can_merge: developers_can_merge || false)
+ service = ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params)
+ service.execute
end
present @branch, with: Entities::RepoBranch, project: user_project
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4a11c8e3620..b4eaf1813d4 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -54,7 +54,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! "Commit" unless commit
- commit.diffs.to_a
+ commit.raw_diffs.to_a
end
# Get a commit's comments
@@ -96,7 +96,7 @@ module API
}
if params[:path] && params[:line] && params[:line_type]
- commit.diffs(all_diffs: true).each do |diff|
+ commit.raw_diffs(all_diffs: true).each do |diff|
next unless diff.new_path == params[:path]
lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 5c570b5e5ca..825e05fbae3 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -10,6 +10,9 @@ module API
present keys, with: Entities::SSHKey
end
+ params do
+ requires :id, type: String, desc: 'The ID of the project'
+ end
resource :projects do
before { authorize_admin_project }
@@ -17,52 +20,43 @@ module API
# Use "projects/:id/deploy_keys/..." instead.
#
%w(keys deploy_keys).each do |path|
- # Get a specific project's deploy keys
- #
- # Example Request:
- # GET /projects/:id/deploy_keys
+ desc "Get a specific project's deploy keys" do
+ success Entities::SSHKey
+ end
get ":id/#{path}" do
present user_project.deploy_keys, with: Entities::SSHKey
end
- # Get single deploy key owned by currently authenticated user
- #
- # Example Request:
- # GET /projects/:id/deploy_keys/:key_id
+ desc 'Get single deploy key' do
+ success Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the deploy key'
+ end
get ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find params[:key_id]
present key, with: Entities::SSHKey
end
- # Add new deploy key to currently authenticated user
- # If deploy key already exists - it will be joined to project
- # but only if original one was accessible by same user
- #
- # Parameters:
- # key (required) - New deploy Key
- # title (required) - New deploy Key's title
- # Example Request:
- # POST /projects/:id/deploy_keys
+ # TODO: for 9.0 we should check if params are there with the params block
+ # grape provides, at this point we'd change behaviour so we can't
+ # Behaviour now if you don't provide all required params: it renders a
+ # validation error or two.
+ desc 'Add new deploy key to currently authenticated user' do
+ success Entities::SSHKey
+ end
post ":id/#{path}" do
attrs = attributes_for_keys [:title, :key]
+ attrs[:key].strip! if attrs[:key]
- if attrs[:key].present?
- attrs[:key].strip!
-
- # check if key already exist in project
- key = user_project.deploy_keys.find_by(key: attrs[:key])
- if key
- present key, with: Entities::SSHKey
- next
- end
+ key = user_project.deploy_keys.find_by(key: attrs[:key])
+ present key, with: Entities::SSHKey if key
- # Check for available deploy keys in other projects
- key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
- if key
- user_project.deploy_keys << key
- present key, with: Entities::SSHKey
- next
- end
+ # Check for available deploy keys in other projects
+ key = current_user.accessible_deploy_keys.find_by(key: attrs[:key])
+ if key
+ user_project.deploy_keys << key
+ present key, with: Entities::SSHKey
end
key = DeployKey.new attrs
@@ -74,12 +68,46 @@ module API
end
end
- # Delete existing deploy key of currently authenticated user
- #
- # Example Request:
- # DELETE /projects/:id/deploy_keys/:key_id
+ desc 'Enable a deploy key for a project' do
+ detail 'This feature was added in GitLab 8.11'
+ success Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the deploy key'
+ end
+ post ":id/#{path}/:key_id/enable" do
+ key = ::Projects::EnableDeployKeyService.new(user_project,
+ current_user, declared(params)).execute
+
+ if key
+ present key, with: Entities::SSHKey
+ else
+ not_found!('Deploy Key')
+ end
+ end
+
+ desc 'Disable a deploy key for a project' do
+ detail 'This feature was added in GitLab 8.11'
+ success Entities::SSHKey
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the deploy key'
+ end
+ delete ":id/#{path}/:key_id/disable" do
+ key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
+ key.destroy
+
+ present key.deploy_key, with: Entities::SSHKey
+ end
+
+ desc 'Delete existing deploy key of currently authenticated user' do
+ success Key
+ end
+ params do
+ requires :key_id, type: Integer, desc: 'The ID of the deploy key'
+ end
delete ":id/#{path}/:key_id" do
- key = user_project.deploy_keys.find params[:key_id]
+ key = user_project.deploy_keys.find(params[:key_id])
key.destroy
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index fbf0d74663f..ae74d14a4bb 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -91,9 +91,17 @@ module API
end
end
- class ProjectMember < UserBasic
+ class Member < UserBasic
expose :access_level do |user, options|
- options[:project].project_members.find_by(user_id: user.id).access_level
+ member = options[:member] || options[:members].find { |m| m.user_id == user.id }
+ member.access_level
+ end
+ end
+
+ class AccessRequester < UserBasic
+ expose :requested_at do |user, options|
+ access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id }
+ access_requester.requested_at
end
end
@@ -108,12 +116,6 @@ module API
expose :shared_projects, using: Entities::Project
end
- class GroupMember < UserBasic
- expose :access_level do |user, options|
- options[:group].group_members.find_by(user_id: user.id).access_level
- end
- end
-
class RepoBranch < Grape::Entity
expose :name
@@ -126,11 +128,13 @@ module API
end
expose :developers_can_push do |repo_branch, options|
- options[:project].developers_can_push_to_protected_branch? repo_branch.name
+ project = options[:project]
+ project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_level.access_level == Gitlab::Access::DEVELOPER }
end
expose :developers_can_merge do |repo_branch, options|
- options[:project].developers_can_merge_to_protected_branch? repo_branch.name
+ project = options[:project]
+ project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_level.access_level == Gitlab::Access::DEVELOPER }
end
end
@@ -149,8 +153,13 @@ module API
expose :safe_message, as: :message
end
+ class RepoCommitStats < Grape::Entity
+ expose :additions, :deletions, :total
+ end
+
class RepoCommitDetail < RepoCommit
expose :parent_ids, :committed_date, :authored_date
+ expose :stats, using: Entities::RepoCommitStats
expose :status
end
@@ -217,7 +226,7 @@ module API
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
- compare.diffs(all_diffs: true).to_a
+ compare.raw_diffs(all_diffs: true).to_a
end
end
@@ -318,7 +327,7 @@ module API
expose :id, :path, :kind
end
- class Member < Grape::Entity
+ class MemberAccess < Grape::Entity
expose :access_level
expose :notification_level do |member, options|
if member.notification_setting
@@ -327,10 +336,10 @@ module API
end
end
- class ProjectAccess < Member
+ class ProjectAccess < MemberAccess
end
- class GroupAccess < Member
+ class GroupAccess < MemberAccess
end
class ProjectService < Grape::Entity
@@ -489,6 +498,10 @@ module API
expose :key, :value
end
+ class Environment < Grape::Entity
+ expose :id, :name, :external_url
+ end
+
class RepoLicense < Grape::Entity
expose :key, :name, :nickname
expose :featured, as: :popular
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
new file mode 100644
index 00000000000..819f80d8365
--- /dev/null
+++ b/lib/api/environments.rb
@@ -0,0 +1,83 @@
+module API
+ # Environments RESTfull API endpoints
+ class Environments < Grape::API
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get all environments of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ optional :page, type: Integer, desc: 'Page number of the current request'
+ optional :per_page, type: Integer, desc: 'Number of items per page'
+ end
+ get ':id/environments' do
+ authorize! :read_environment, user_project
+
+ present paginate(user_project.environments), with: Entities::Environment
+ end
+
+ desc 'Creates a new environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the environment to be created'
+ optional :external_url, type: String, desc: 'URL on which this deployment is viewable'
+ end
+ post ':id/environments' do
+ authorize! :create_environment, user_project
+
+ create_params = declared(params, include_parent_namespaces: false).to_h
+ environment = user_project.environments.create(create_params)
+
+ if environment.persisted?
+ present environment, with: Entities::Environment
+ else
+ render_validation_error!(environment)
+ end
+ end
+
+ desc 'Updates an existing environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ optional :name, type: String, desc: 'The new environment name'
+ optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable'
+ end
+ put ':id/environments/:environment_id' do
+ authorize! :update_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h
+ if environment.update(update_params)
+ present environment, with: Entities::Environment
+ else
+ render_validation_error!(environment)
+ end
+ end
+
+ desc 'Deletes an existing environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ end
+ delete ':id/environments/:environment_id' do
+ authorize! :update_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ present environment.destroy, with: Entities::Environment
+ end
+ end
+ end
+end
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
deleted file mode 100644
index dbe5bb08d3f..00000000000
--- a/lib/api/group_members.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-module API
- class GroupMembers < Grape::API
- before { authenticate! }
-
- resource :groups do
- # Get a list of group members viewable by the authenticated user.
- #
- # Example Request:
- # GET /groups/:id/members
- get ":id/members" do
- group = find_group(params[:id])
- users = group.users
- present users, with: Entities::GroupMember, group: group
- end
-
- # Add a user to the list of group members
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- # access_level (required) - Project access level
- # Example Request:
- # POST /groups/:id/members
- post ":id/members" do
- group = find_group(params[:id])
- authorize! :admin_group, group
- required_attributes! [:user_id, :access_level]
-
- unless validate_access_level?(params[:access_level])
- render_api_error!("Wrong access level", 422)
- end
-
- if group.group_members.find_by(user_id: params[:user_id])
- render_api_error!("Already exists", 409)
- end
-
- group.add_users([params[:user_id]], params[:access_level], current_user)
- member = group.group_members.find_by(user_id: params[:user_id])
- present member.user, with: Entities::GroupMember, group: group
- end
-
- # Update group member
- #
- # Parameters:
- # id (required) - The ID of a group
- # user_id (required) - The ID of a group member
- # access_level (required) - Project access level
- # Example Request:
- # PUT /groups/:id/members/:user_id
- put ':id/members/:user_id' do
- group = find_group(params[:id])
- authorize! :admin_group, group
- required_attributes! [:access_level]
-
- group_member = group.group_members.find_by(user_id: params[:user_id])
- not_found!('User can not be found') if group_member.nil?
-
- if group_member.update_attributes(access_level: params[:access_level])
- @member = group_member.user
- present @member, with: Entities::GroupMember, group: group
- else
- handle_member_errors group_member.errors
- end
- end
-
- # Remove member.
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- delete ":id/members/:user_id" do
- group = find_group(params[:id])
- authorize! :admin_group, group
- member = group.group_members.find_by(user_id: params[:user_id])
-
- if member.nil?
- render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}", 404)
- else
- member.destroy
- end
- end
- end
- end
-end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 130509cdad6..d0469d6602d 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -28,7 +28,7 @@ module API
# If the sudo is the current user do nothing
if identifier && !(@current_user.id == identifier || @current_user.username == identifier)
- render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin?
+ forbidden!('Must be admin to use sudo') unless @current_user.is_admin?
@current_user = User.by_username_or_id(identifier)
not_found!("No user id or username for: #{identifier}") if @current_user.nil?
end
@@ -49,16 +49,15 @@ module API
def user_project
@project ||= find_project(params[:id])
- @project || not_found!("Project")
end
def find_project(id)
project = Project.find_with_namespace(id) || Project.find_by(id: id)
- if project && can?(current_user, :read_project, project)
+ if can?(current_user, :read_project, project)
project
else
- nil
+ not_found!('Project')
end
end
@@ -89,11 +88,7 @@ module API
end
def find_group(id)
- begin
- group = Group.find(id)
- rescue ActiveRecord::RecordNotFound
- group = Group.find_by!(path: id)
- end
+ group = Group.find_by(path: id) || Group.find_by(id: id)
if can?(current_user, :read_group, group)
group
@@ -135,7 +130,7 @@ module API
end
def authorize!(action, subject)
- forbidden! unless abilities.allowed?(current_user, action, subject)
+ forbidden! unless can?(current_user, action, subject)
end
def authorize_push_project
@@ -197,10 +192,6 @@ module API
errors
end
- def validate_access_level?(level)
- Gitlab::Access.options_with_owner.values.include? level.to_i
- end
-
# Checks the occurrences of datetime attributes, each attribute if present in the params hash must be in ISO 8601
# format (YYYY-MM-DDTHH:MM:SSZ) or a Bad Request error is invoked.
#
@@ -411,11 +402,6 @@ module API
File.read(Gitlab.config.gitlab_shell.secret_file).chomp
end
- def handle_member_errors(errors)
- error!(errors[:access_level], 422) if errors[:access_level].any?
- not_found!(errors)
- end
-
def send_git_blob(repository, blob)
env['api.format'] = :txt
content_type 'text/plain'
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
new file mode 100644
index 00000000000..90114f6f667
--- /dev/null
+++ b/lib/api/helpers/members_helpers.rb
@@ -0,0 +1,13 @@
+module API
+ module Helpers
+ module MembersHelpers
+ def find_source(source_type, id)
+ public_send("find_#{source_type}", id)
+ end
+
+ def authorize_admin_source!(source_type, source)
+ authorize! :"admin_#{source_type}", source
+ end
+ end
+ end
+end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 959b700de78..d8e9ac406c4 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -74,6 +74,10 @@ module API
response
end
+ get "/merge_request_urls" do
+ ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
+ end
+
#
# Discover user by ssh key
#
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c588103e517..077258faee1 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -3,8 +3,6 @@ module API
class Issues < Grape::API
before { authenticate! }
- helpers ::Gitlab::AkismetHelper
-
helpers do
def filter_issues_state(issues, state)
case state
@@ -21,17 +19,6 @@ module API
def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone)
end
-
- def create_spam_log(project, current_user, attrs)
- params = attrs.merge({
- source_ip: client_ip(env),
- user_agent: user_agent(env),
- noteable_type: 'Issue',
- via_api: true
- })
-
- ::CreateSpamLogService.new(project, current_user, params).execute
- end
end
resource :issues do
@@ -168,15 +155,13 @@ module API
end
project = user_project
- text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
- if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
- create_spam_log(project, current_user, attrs)
+ issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute
+
+ if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
- issue = ::Issues::CreateService.new(project, current_user, attrs).execute
-
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
diff --git a/lib/api/members.rb b/lib/api/members.rb
new file mode 100644
index 00000000000..2fae83f60b2
--- /dev/null
+++ b/lib/api/members.rb
@@ -0,0 +1,155 @@
+module API
+ class Members < Grape::API
+ before { authenticate! }
+
+ helpers ::API::Helpers::MembersHelpers
+
+ %w[group project].each do |source_type|
+ resource source_type.pluralize do
+ # Get a list of group/project members viewable by the authenticated user.
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # query - Query string
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ # GET /projects/:id/members
+ get ":id/members" do
+ source = find_source(source_type, params[:id])
+
+ members = source.members.includes(:user)
+ members = members.joins(:user).merge(User.search(params[:query])) if params[:query]
+ members = paginate(members)
+
+ present members.map(&:user), with: Entities::Member, members: members
+ end
+
+ # Get a group/project member
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the member
+ #
+ # Example Request:
+ # GET /groups/:id/members/:user_id
+ # GET /projects/:id/members/:user_id
+ get ":id/members/:user_id" do
+ source = find_source(source_type, params[:id])
+
+ members = source.members
+ member = members.find_by!(user_id: params[:user_id])
+
+ present member.user, with: Entities::Member, member: member
+ end
+
+ # Add a new group/project member
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the new member
+ # access_level (required) - A valid access level
+ #
+ # Example Request:
+ # POST /groups/:id/members
+ # POST /projects/:id/members
+ post ":id/members" do
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+ required_attributes! [:user_id, :access_level]
+
+ access_requester = source.requesters.find_by(user_id: params[:user_id])
+ if access_requester
+ # We pass current_user = access_requester so that the requester doesn't
+ # receive a "access denied" email
+ ::Members::DestroyService.new(access_requester, access_requester.user).execute
+ end
+
+ member = source.members.find_by(user_id: params[:user_id])
+
+ # This is to ensure back-compatibility but 409 behavior should be used
+ # for both project and group members in 9.0!
+ conflict!('Member already exists') if source_type == 'group' && member
+
+ unless member
+ source.add_user(params[:user_id], params[:access_level], current_user)
+ member = source.members.find_by(user_id: params[:user_id])
+ end
+
+ if member
+ present member.user, with: Entities::Member, member: member
+ else
+ # Since `source.add_user` doesn't return a member object, we have to
+ # build a new one and populate its errors in order to render them.
+ member = source.members.build(attributes_for_keys([:user_id, :access_level]))
+ member.valid? # populate the errors
+
+ # This is to ensure back-compatibility but 400 behavior should be used
+ # for all validation errors in 9.0!
+ render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
+ render_validation_error!(member)
+ end
+ end
+
+ # Update a group/project member
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the member
+ # access_level (required) - A valid access level
+ #
+ # Example Request:
+ # PUT /groups/:id/members/:user_id
+ # PUT /projects/:id/members/:user_id
+ put ":id/members/:user_id" do
+ source = find_source(source_type, params[:id])
+ authorize_admin_source!(source_type, source)
+ required_attributes! [:user_id, :access_level]
+
+ member = source.members.find_by!(user_id: params[:user_id])
+
+ if member.update_attributes(access_level: params[:access_level])
+ present member.user, with: Entities::Member, member: member
+ else
+ # This is to ensure back-compatibility but 400 behavior should be used
+ # for all validation errors in 9.0!
+ render_api_error!('Access level is not known', 422) if member.errors.key?(:access_level)
+ render_validation_error!(member)
+ end
+ end
+
+ # Remove a group/project member
+ #
+ # Parameters:
+ # id (required) - The group/project ID
+ # user_id (required) - The user ID of the member
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ # DELETE /projects/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ source = find_source(source_type, params[:id])
+ required_attributes! [:user_id]
+
+ # This is to ensure back-compatibility but find_by! should be used
+ # in that casse in 9.0!
+ member = source.members.find_by(user_id: params[:user_id])
+
+ # This is to ensure back-compatibility but this should be removed in
+ # favor of find_by! in 9.0!
+ not_found!("Member: user_id:#{params[:user_id]}") if source_type == 'group' && member.nil?
+
+ # This is to ensure back-compatibility but 204 behavior should be used
+ # for all DELETE endpoints in 9.0!
+ if member.nil?
+ { message: "Access revoked", id: params[:user_id].to_i }
+ else
+ ::Members::DestroyService.new(member, current_user).execute
+
+ present member.user, with: Entities::Member, member: member
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb
deleted file mode 100644
index 6a0b3e7d134..00000000000
--- a/lib/api/project_members.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-module API
- # Projects members API
- class ProjectMembers < Grape::API
- before { authenticate! }
-
- resource :projects do
- # Get a project team members
- #
- # Parameters:
- # id (required) - The ID of a project
- # query - Query string
- # Example Request:
- # GET /projects/:id/members
- get ":id/members" do
- if params[:query].present?
- @members = paginate user_project.users.where("username LIKE ?", "%#{params[:query]}%")
- else
- @members = paginate user_project.users
- end
- present @members, with: Entities::ProjectMember, project: user_project
- end
-
- # Get a project team members
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a user
- # Example Request:
- # GET /projects/:id/members/:user_id
- get ":id/members/:user_id" do
- @member = user_project.users.find params[:user_id]
- present @member, with: Entities::ProjectMember, project: user_project
- end
-
- # Add a new project team member
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a user
- # access_level (required) - Project access level
- # Example Request:
- # POST /projects/:id/members
- post ":id/members" do
- authorize! :admin_project, user_project
- required_attributes! [:user_id, :access_level]
-
- # either the user is already a team member or a new one
- project_member = user_project.project_member(params[:user_id])
- if project_member.nil?
- project_member = user_project.project_members.new(
- user_id: params[:user_id],
- access_level: params[:access_level]
- )
- end
-
- if project_member.save
- @member = project_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_member_errors project_member.errors
- end
- end
-
- # Update project team member
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a team member
- # access_level (required) - Project access level
- # Example Request:
- # PUT /projects/:id/members/:user_id
- put ":id/members/:user_id" do
- authorize! :admin_project, user_project
- required_attributes! [:access_level]
-
- project_member = user_project.project_members.find_by(user_id: params[:user_id])
- not_found!("User can not be found") if project_member.nil?
-
- if project_member.update_attributes(access_level: params[:access_level])
- @member = project_member.user
- present @member, with: Entities::ProjectMember, project: user_project
- else
- handle_member_errors project_member.errors
- end
- end
-
- # Remove a team member from project
- #
- # Parameters:
- # id (required) - The ID of a project
- # user_id (required) - The ID of a team member
- # Example Request:
- # DELETE /projects/:id/members/:user_id
- delete ":id/members/:user_id" do
- project_member = user_project.project_members.find_by(user_id: params[:user_id])
-
- unless current_user.can?(:admin_project, user_project) ||
- current_user.can?(:destroy_project_member, project_member)
- forbidden!
- end
-
- if project_member.nil?
- { message: "Access revoked", id: params[:user_id].to_i }
- else
- project_member.destroy
- end
- end
- end
- end
-end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 8fed7db8803..60cfc103afd 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -323,7 +323,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- ::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
+ ::Projects::DestroyService.new(user_project, current_user, {}).async_execute
end
# Mark this project as forked from another
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 26c24c3baff..19df13d8aac 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -61,9 +61,9 @@ module API
#
delete ':id' do
todo = current_user.todos.find(params[:id])
- todo.done
+ TodoService.new.mark_todos_as_done([todo], current_user)
- present todo, with: Entities::Todo, current_user: current_user
+ present todo.reload, with: Entities::Todo, current_user: current_user
end
# Mark all todos as done
@@ -73,9 +73,7 @@ module API
#
delete do
todos = find_todos
- todos.each(&:done)
-
- todos.length
+ TodoService.new.mark_todos_as_done(todos, current_user)
end
end
end
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 654b4d1c896..cedbb289f6a 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -27,7 +27,7 @@ module Backup
def backup_existing_files_dir
timestamped_files_path = File.join(files_parent_dir, "#{name}.#{Time.now.to_i}")
- if File.exists?(app_files_dir)
+ if File.exist?(app_files_dir)
FileUtils.mv(app_files_dir, File.expand_path(timestamped_files_path))
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 2ff3e3bdfb0..0dfffaf0bc6 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -114,7 +114,7 @@ module Backup
tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar")
- unless File.exists?(tar_file)
+ unless File.exist?(tar_file)
puts "The specified backup doesn't exist!"
exit 1
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 1f5917b8127..f117fc3d37d 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -28,7 +28,7 @@ module Backup
wiki = ProjectWiki.new(project)
- if File.exists?(path_to_repo(wiki))
+ if File.exist?(path_to_repo(wiki))
$progress.print " * #{wiki.path_with_namespace} ... "
if wiki.repository.empty?
$progress.puts " [SKIPPED]".color(:cyan)
@@ -49,7 +49,7 @@ module Backup
def restore
Gitlab.config.repositories.storages.each do |name, path|
- next unless File.exists?(path)
+ next unless File.exist?(path)
# Move repos dir to 'repositories.old' dir
bk_repos_path = File.join(path, '..', 'repositories.old.' + Time.now.to_i.to_s)
@@ -63,7 +63,7 @@ module Backup
project.ensure_dir_exist
- if File.exists?(path_to_bundle(project))
+ if File.exist?(path_to_bundle(project))
FileUtils.mkdir_p(path_to_repo(project))
cmd = %W(tar -xf #{path_to_bundle(project)} -C #{path_to_repo(project)})
else
@@ -80,7 +80,7 @@ module Backup
wiki = ProjectWiki.new(project)
- if File.exists?(path_to_bundle(wiki))
+ if File.exist?(path_to_bundle(wiki))
$progress.print " * #{wiki.path_with_namespace} ... "
# If a wiki bundle exists, first remove the empty repo
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 9ed45707515..799b83b1069 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -31,6 +31,14 @@ module Banzai
# Text matching LINK_PATTERN inside these elements will not be linked
IGNORE_PARENTS = %w(a code kbd pre script style).to_set
+ # The XPath query to use for finding text nodes to parse.
+ TEXT_QUERY = %Q(descendant-or-self::text()[
+ not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')})
+ and contains(., '://')
+ and not(starts-with(., 'http'))
+ and not(starts-with(., 'ftp'))
+ ])
+
def call
return doc if context[:autolink] == false
@@ -66,16 +74,11 @@ module Banzai
# Autolinks any text matching LINK_PATTERN that Rinku didn't already
# replace
def text_parse
- search_text_nodes(doc).each do |node|
+ doc.xpath(TEXT_QUERY).each do |node|
content = node.to_html
- next if has_ancestor?(node, IGNORE_PARENTS)
next unless content.match(LINK_PATTERN)
- # If Rinku didn't link this, there's probably a good reason, so we'll
- # skip it too
- next if content.start_with?(*%w(http https ftp))
-
html = autolink_filter(content)
next if html == content
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index ae7d31cf191..2492b5213ac 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -38,6 +38,11 @@ module Banzai
end
end
+ # Build a regexp that matches all valid :emoji: names.
+ def self.emoji_pattern
+ @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+ end
+
private
def emoji_url(name)
@@ -59,11 +64,6 @@ module Banzai
ActionController::Base.helpers.url_to_image(image)
end
- # Build a regexp that matches all valid :emoji: names.
- def self.emoji_pattern
- @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
- end
-
def emoji_pattern
self.class.emoji_pattern
end
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
index 9b209533a89..ff580ec68f8 100644
--- a/lib/banzai/filter/markdown_filter.rb
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -12,7 +12,12 @@ module Banzai
html
end
- private
+ def self.renderer
+ @renderer ||= begin
+ renderer = Redcarpet::Render::HTML.new
+ Redcarpet::Markdown.new(renderer, redcarpet_options)
+ end
+ end
def self.redcarpet_options
# https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
@@ -28,12 +33,7 @@ module Banzai
}.freeze
end
- def self.renderer
- @renderer ||= begin
- renderer = Redcarpet::Render::HTML.new
- Redcarpet::Markdown.new(renderer, redcarpet_options)
- end
- end
+ private_class_method :redcarpet_options
end
end
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 21ed0410f7f..4fa8d05481f 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -20,7 +20,7 @@ module Banzai
process_link_attr el.attribute('href')
end
- doc.search('img').each do |el|
+ doc.css('img, video').each do |el|
process_link_attr el.attribute('src')
end
@@ -35,6 +35,7 @@ module Banzai
def process_link_attr(html_attr)
return if html_attr.blank?
+ return if html_attr.value.start_with?('//')
uri = URI(html_attr.value)
if uri.relative? && uri.path.present?
@@ -51,7 +52,7 @@ module Banzai
relative_url_root,
context[:project].path_with_namespace,
uri_type(file_path),
- ref || context[:project].default_branch, # if no ref exists, point to the default branch
+ ref,
file_path
].compact.join('/').squeeze('/').chomp('/')
@@ -87,10 +88,13 @@ module Banzai
def build_relative_path(path, request_path)
return request_path if path.empty?
return path unless request_path
+ return path[1..-1] if path.start_with?('/')
parts = request_path.split('/')
parts.pop if uri_type(request_path) != :tree
+ path.sub!(%r{\A\./}, '')
+
while path.start_with?('../')
parts.pop
path.sub!('../', '')
@@ -112,7 +116,7 @@ module Banzai
end
def current_commit
- @current_commit ||= context[:commit] || ref ? repository.commit(ref) : repository.head_commit
+ @current_commit ||= context[:commit] || repository.commit(ref)
end
def relative_url_root
@@ -120,7 +124,7 @@ module Banzai
end
def ref
- context[:ref]
+ context[:ref] || context[:project].default_branch
end
def repository
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index ca80aac5a08..6e13282d5f4 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -7,7 +7,7 @@ module Banzai
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
def whitelist
- whitelist = super
+ whitelist = super.dup
customize_whitelist(whitelist)
@@ -42,6 +42,8 @@ module Banzai
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
+ whitelist[:transformers] = whitelist[:transformers].dup
+
# ...but then remove links with unsafe protocols
whitelist[:transformers].push(remove_unsafe_links)
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 91f0159f9a1..fcdb496aed2 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -17,15 +17,12 @@ module Banzai
def highlight_node(node)
language = node.attr('class')
- code = node.text
-
+ code = node.text
css_classes = "code highlight"
-
- lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText
- formatter = Rouge::Formatters::HTML.new
+ lexer = lexer_for(language)
begin
- code = formatter.format(lexer.lex(code))
+ code = format(lex(lexer, code))
css_classes << " js-syntax-highlight #{lexer.tag}"
rescue
@@ -41,14 +38,27 @@ module Banzai
private
+ # Separate method so it can be instrumented.
+ def lex(lexer, code)
+ lexer.lex(code)
+ end
+
+ def format(tokens)
+ rouge_formatter.format(tokens)
+ end
+
+ def lexer_for(language)
+ (Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new
+ end
+
def replace_parent_pre_element(node, highlighted)
# Replace the parent `pre` element with the entire highlighted block
node.parent.replace(highlighted)
end
# Override Rouge::Plugins::Redcarpet#rouge_formatter
- def rouge_formatter(lexer)
- Rouge::Formatters::HTML.new
+ def rouge_formatter(lexer = nil)
+ @rouge_formatter ||= Rouge::Formatters::HTML.new
end
end
end
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
index fd8b9a6f0cc..ac7bbcb0d10 100644
--- a/lib/banzai/filter/video_link_filter.rb
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -1,11 +1,9 @@
module Banzai
module Filter
-
# Find every image that isn't already wrapped in an `a` tag, and that has
# a `src` attribute ending with a video extension, add a new video node and
# a "Download" link in the case the video cannot be played.
class VideoLinkFilter < HTML::Pipeline::Filter
-
def call
doc.xpath(query).each do |el|
el.replace(video_node(doc, el))
@@ -54,6 +52,5 @@ module Banzai
container
end
end
-
end
end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index f306079d833..6c20dec5734 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -9,10 +9,11 @@ module Banzai
issues = issues_for_nodes(nodes)
- nodes.select do |node|
- issue = issue_for_node(issues, node)
+ readable_issues = Ability.
+ issues_readable_by_user(issues.values, user).to_set
- issue ? can?(user, :read_issue, issue) : false
+ nodes.select do |node|
+ readable_issues.include?(issue_for_node(issues, node))
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 910687a7b6a..a4ae27eefd8 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -1,5 +1,7 @@
module Banzai
module Renderer
+ extend self
+
# Convert a Markdown String into an HTML-safe String of HTML
#
# Note that while the returned HTML will have been sanitized of dangerous
@@ -14,7 +16,7 @@ module Banzai
# context - Hash of context options passed to our HTML Pipeline
#
# Returns an HTML-safe String
- def self.render(text, context = {})
+ def render(text, context = {})
cache_key = context.delete(:cache_key)
cache_key = full_cache_key(cache_key, context[:pipeline])
@@ -52,7 +54,7 @@ module Banzai
# texts_and_contexts
# => [{ text: '### Hello',
# context: { cache_key: [note, :note] } }]
- def self.cache_collection_render(texts_and_contexts)
+ def cache_collection_render(texts_and_contexts)
items_collection = texts_and_contexts.each_with_index do |item, index|
context = item[:context]
cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
@@ -81,7 +83,7 @@ module Banzai
items_collection.map { |item| item[:rendered] }
end
- def self.render_result(text, context = {})
+ def render_result(text, context = {})
text = Pipeline[:pre_process].to_html(text, context) if text
Pipeline[context[:pipeline]].call(text, context)
@@ -100,7 +102,7 @@ module Banzai
# :user - User object
#
# Returns an HTML-safe String
- def self.post_process(html, context)
+ def post_process(html, context)
context = Pipeline[context[:pipeline]].transform_context(context)
pipeline = Pipeline[:post_process]
@@ -113,7 +115,7 @@ module Banzai
private
- def self.cacheless_render(text, context = {})
+ def cacheless_render(text, context = {})
Gitlab::Metrics.measure(:banzai_cacheless_render) do
result = render_result(text, context)
@@ -126,7 +128,7 @@ module Banzai
end
end
- def self.full_cache_key(cache_key, pipeline_name)
+ def full_cache_key(cache_key, pipeline_name)
return unless cache_key
["banzai", *cache_key, pipeline_name || :full]
end
@@ -134,7 +136,7 @@ module Banzai
# To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
# Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key
# method.
- def self.full_cache_multi_key(cache_key, pipeline_name)
+ def full_cache_multi_key(cache_key, pipeline_name)
return unless cache_key
Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
end
diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb
index 1d7126a432d..3decc3b1a26 100644
--- a/lib/ci/charts.rb
+++ b/lib/ci/charts.rb
@@ -1,5 +1,37 @@
module Ci
module Charts
+ module DailyInterval
+ def grouped_count(query)
+ query.
+ group("DATE(#{Ci::Build.table_name}.created_at)").
+ count(:created_at).
+ transform_keys { |date| date.strftime(@format) }
+ end
+
+ def interval_step
+ @interval_step ||= 1.day
+ end
+ end
+
+ module MonthlyInterval
+ def grouped_count(query)
+ if Gitlab::Database.postgresql?
+ query.
+ group("to_char(#{Ci::Build.table_name}.created_at, '01 Month YYYY')").
+ count(:created_at).
+ transform_keys(&:squish)
+ else
+ query.
+ group("DATE_FORMAT(#{Ci::Build.table_name}.created_at, '01 %M %Y')").
+ count(:created_at)
+ end
+ end
+
+ def interval_step
+ @interval_step ||= 1.month
+ end
+ end
+
class Chart
attr_reader :labels, :total, :success, :project, :build_times
@@ -13,47 +45,59 @@ module Ci
collect
end
- def push(from, to, format)
- @labels << from.strftime(format)
- @total << project.builds.
- where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from).
- count(:all)
- @success << project.builds.
- where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from).
- success.count(:all)
+ def collect
+ query = project.builds.
+ where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", @to, @from)
+
+ totals_count = grouped_count(query)
+ success_count = grouped_count(query.success)
+
+ current = @from
+ while current < @to
+ label = current.strftime(@format)
+
+ @labels << label
+ @total << (totals_count[label] || 0)
+ @success << (success_count[label] || 0)
+
+ current += interval_step
+ end
end
end
class YearChart < Chart
- def collect
- 13.times do |i|
- start_month = (Date.today.years_ago(1) + i.month).beginning_of_month
- end_month = start_month.end_of_month
+ include MonthlyInterval
- push(start_month, end_month, "%d %B %Y")
- end
+ def initialize(*)
+ @to = Date.today.end_of_month
+ @from = @to.years_ago(1).beginning_of_month
+ @format = '%d %B %Y'
+
+ super
end
end
class MonthChart < Chart
- def collect
- 30.times do |i|
- start_day = Date.today - 30.days + i.days
- end_day = Date.today - 30.days + i.day + 1.day
+ include DailyInterval
- push(start_day, end_day, "%d %B")
- end
+ def initialize(*)
+ @to = Date.today
+ @from = @to - 30.days
+ @format = '%d %B'
+
+ super
end
end
class WeekChart < Chart
- def collect
- 7.times do |i|
- start_day = Date.today - 7.days + i.days
- end_day = Date.today - 7.days + i.day + 1.day
+ include DailyInterval
- push(start_day, end_day, "%d %B")
- end
+ def initialize(*)
+ @to = Date.today
+ @from = @to - 7.days
+ @format = '%d %B'
+
+ super
end
end
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 83afed9f49f..47efd5bd9f2 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -4,21 +4,11 @@ module Ci
include Gitlab::Ci::Config::Node::LegacyValidationHelpers
- DEFAULT_STAGE = 'test'
- ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
- ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
- :allow_failure, :type, :stage, :when, :artifacts, :cache,
- :dependencies, :before_script, :after_script, :variables,
- :environment]
- ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
- ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in]
-
attr_reader :path, :cache, :stages
def initialize(config, path = nil)
@ci_config = Gitlab::Ci::Config.new(config)
@config = @ci_config.to_hash
-
@path = path
unless @ci_config.valid?
@@ -26,7 +16,6 @@ module Ci
end
initial_parsing
- validate!
rescue Gitlab::Ci::Config::Loader::FormatError => e
raise ValidationError, e.message
end
@@ -73,7 +62,7 @@ module Ci
# - before script should be a concatenated command
commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
tag_list: job[:tags] || [],
- name: name,
+ name: job[:name].to_s,
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
@@ -92,6 +81,9 @@ module Ci
private
def initial_parsing
+ ##
+ # Global config
+ #
@before_script = @ci_config.before_script
@image = @ci_config.image
@after_script = @ci_config.after_script
@@ -100,34 +92,28 @@ module Ci
@stages = @ci_config.stages
@cache = @ci_config.cache
- @jobs = {}
-
- @config.except!(*ALLOWED_YAML_KEYS)
- @config.each { |name, param| add_job(name, param) }
-
- raise ValidationError, "Please define at least one job" if @jobs.none?
- end
-
- def add_job(name, job)
- return if name.to_s.start_with?('.')
+ ##
+ # Jobs
+ #
+ @jobs = @ci_config.jobs
- raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
+ @jobs.each do |name, job|
+ # logical validation for job
- stage = job[:stage] || job[:type] || DEFAULT_STAGE
- @jobs[name] = { stage: stage }.merge(job)
+ validate_job_stage!(name, job)
+ validate_job_dependencies!(name, job)
+ end
end
def yaml_variables(name)
- variables = global_variables.merge(job_variables(name))
+ variables = (@variables || {})
+ .merge(job_variables(name))
+
variables.map do |key, value|
{ key: key, value: value, public: true }
end
end
- def global_variables
- @variables || {}
- end
-
def job_variables(name)
job = @jobs[name.to_sym]
return {} unless job
@@ -135,154 +121,16 @@ module Ci
job[:variables] || {}
end
- def validate!
- @jobs.each do |name, job|
- validate_job!(name, job)
- end
-
- true
- end
-
- def validate_job!(name, job)
- validate_job_name!(name)
- validate_job_keys!(name, job)
- validate_job_types!(name, job)
- validate_job_script!(name, job)
-
- validate_job_stage!(name, job) if job[:stage]
- validate_job_variables!(name, job) if job[:variables]
- validate_job_cache!(name, job) if job[:cache]
- validate_job_artifacts!(name, job) if job[:artifacts]
- validate_job_dependencies!(name, job) if job[:dependencies]
- end
-
- def validate_job_name!(name)
- if name.blank? || !validate_string(name)
- raise ValidationError, "job name should be non-empty string"
- end
- end
-
- def validate_job_keys!(name, job)
- job.keys.each do |key|
- unless ALLOWED_JOB_KEYS.include? key
- raise ValidationError, "#{name} job: unknown parameter #{key}"
- end
- end
- end
-
- def validate_job_types!(name, job)
- if job[:image] && !validate_string(job[:image])
- raise ValidationError, "#{name} job: image should be a string"
- end
-
- if job[:services] && !validate_array_of_strings(job[:services])
- raise ValidationError, "#{name} job: services should be an array of strings"
- end
-
- if job[:tags] && !validate_array_of_strings(job[:tags])
- raise ValidationError, "#{name} job: tags parameter should be an array of strings"
- end
-
- if job[:only] && !validate_array_of_strings_or_regexps(job[:only])
- raise ValidationError, "#{name} job: only parameter should be an array of strings or regexps"
- end
-
- if job[:except] && !validate_array_of_strings_or_regexps(job[:except])
- raise ValidationError, "#{name} job: except parameter should be an array of strings or regexps"
- end
-
- if job[:allow_failure] && !validate_boolean(job[:allow_failure])
- raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
- end
-
- if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
- raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
- end
-
- if job[:environment] && !validate_environment(job[:environment])
- raise ValidationError, "#{name} job: environment parameter #{Gitlab::Regex.environment_name_regex_message}"
- end
- end
-
- def validate_job_script!(name, job)
- if !validate_string(job[:script]) && !validate_array_of_strings(job[:script])
- raise ValidationError, "#{name} job: script should be a string or an array of a strings"
- end
-
- if job[:before_script] && !validate_array_of_strings(job[:before_script])
- raise ValidationError, "#{name} job: before_script should be an array of strings"
- end
-
- if job[:after_script] && !validate_array_of_strings(job[:after_script])
- raise ValidationError, "#{name} job: after_script should be an array of strings"
- end
- end
-
def validate_job_stage!(name, job)
+ return unless job[:stage]
+
unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
end
end
- def validate_job_variables!(name, job)
- unless validate_variables(job[:variables])
- raise ValidationError,
- "#{name} job: variables should be a map of key-value strings"
- end
- end
-
- def validate_job_cache!(name, job)
- job[:cache].keys.each do |key|
- unless ALLOWED_CACHE_KEYS.include? key
- raise ValidationError, "#{name} job: cache unknown parameter #{key}"
- end
- end
-
- if job[:cache][:key] && !validate_string(job[:cache][:key])
- raise ValidationError, "#{name} job: cache:key parameter should be a string"
- end
-
- if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked])
- raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean"
- end
-
- if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths])
- raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings"
- end
- end
-
- def validate_job_artifacts!(name, job)
- job[:artifacts].keys.each do |key|
- unless ALLOWED_ARTIFACTS_KEYS.include? key
- raise ValidationError, "#{name} job: artifacts unknown parameter #{key}"
- end
- end
-
- if job[:artifacts][:name] && !validate_string(job[:artifacts][:name])
- raise ValidationError, "#{name} job: artifacts:name parameter should be a string"
- end
-
- if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked])
- raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean"
- end
-
- if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths])
- raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings"
- end
-
- if job[:artifacts][:when] && !job[:artifacts][:when].in?(%w[on_success on_failure always])
- raise ValidationError, "#{name} job: artifacts:when parameter should be on_success, on_failure or always"
- end
-
- if job[:artifacts][:expire_in] && !validate_duration(job[:artifacts][:expire_in])
- raise ValidationError, "#{name} job: artifacts:expire_in parameter should be a duration"
- end
- end
-
def validate_job_dependencies!(name, job)
- unless validate_array_of_strings(job[:dependencies])
- raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
- end
+ return unless job[:dependencies]
stage_index = @stages.index(job[:stage])
diff --git a/lib/ci/static_model.rb b/lib/ci/static_model.rb
deleted file mode 100644
index bb2bdbed495..00000000000
--- a/lib/ci/static_model.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# Provides an ActiveRecord-like interface to a model whose data is not persisted to a database.
-module Ci
- module StaticModel
- extend ActiveSupport::Concern
-
- module ClassMethods
- # Used by ActiveRecord's polymorphic association to set object_id
- def primary_key
- 'id'
- end
-
- # Used by ActiveRecord's polymorphic association to set object_type
- def base_class
- self
- end
- end
-
- # Used by AR for fetching attributes
- #
- # Pass it along if we respond to it.
- def [](key)
- send(key) if respond_to?(key)
- end
-
- def to_param
- id
- end
-
- def new_record?
- false
- end
-
- def persisted?
- false
- end
-
- def destroyed?
- false
- end
-
- def ==(other)
- if other.is_a? ::Ci::StaticModel
- id == other.id
- else
- super
- end
- end
- end
-end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 51e46da82cc..84688f6646e 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -94,7 +94,7 @@ module ExtractsPath
@options = params.select {|key, value| allowed_options.include?(key) && !value.blank? }
@options = HashWithIndifferentAccess.new(@options)
- @id = Addressable::URI.unescape(get_id)
+ @id = Addressable::URI.normalize_component(get_id)
@ref, @path = extract_ref(@id)
@repo = @project.repository
if @options[:extended_sha1].blank?
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index de41ea415a6..a533bac2692 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -7,6 +7,7 @@ module Gitlab
module Access
class AccessDeniedError < StandardError; end
+ NO_ACCESS = 0
GUEST = 10
REPORTER = 20
DEVELOPER = 30
diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb
deleted file mode 100644
index 04676fdb748..00000000000
--- a/lib/gitlab/akismet_helper.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module Gitlab
- module AkismetHelper
- def akismet_enabled?
- current_application_settings.akismet_enabled
- end
-
- def akismet_client
- @akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
- Gitlab.config.gitlab.url)
- end
-
- def client_ip(env)
- env['action_dispatch.remote_ip'].to_s
- end
-
- def user_agent(env)
- env['HTTP_USER_AGENT']
- end
-
- def check_for_spam?(project, user)
- akismet_enabled? && !project.team.member?(user)
- end
-
- def is_spam?(environment, user, text)
- client = akismet_client
- ip_address = client_ip(environment)
- user_agent = user_agent(environment)
-
- params = {
- type: 'comment',
- text: text,
- created_at: DateTime.now,
- author: user.name,
- author_email: user.email,
- referrer: environment['HTTP_REFERER'],
- }
-
- begin
- is_spam, is_blatant = client.check(ip_address, user_agent, params)
- is_spam || is_blatant
- rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
deleted file mode 100644
index ab94abeda77..00000000000
--- a/lib/gitlab/backend/grack_auth.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-module Grack
- class AuthSpawner
- def self.call(env)
- # Avoid issues with instance variables in Grack::Auth persisting across
- # requests by creating a new instance for each request.
- Auth.new({}).call(env)
- end
- end
-
- class Auth < Rack::Auth::Basic
- attr_accessor :user, :project, :env
-
- def call(env)
- @env = env
- @request = Rack::Request.new(env)
- @auth = Request.new(env)
-
- @ci = false
-
- # Need this patch due to the rails mount
- # Need this if under RELATIVE_URL_ROOT
- unless Gitlab.config.gitlab.relative_url_root.empty?
- # If website is mounted using relative_url_root need to remove it first
- @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root, '')
- else
- @env['PATH_INFO'] = @request.path
- end
-
- @env['SCRIPT_NAME'] = ""
-
- auth!
-
- lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
- return lfs_response unless lfs_response.nil?
-
- if @user.nil? && !@ci
- unauthorized
- else
- render_not_found
- end
- end
-
- private
-
- def auth!
- return unless @auth.provided?
-
- return bad_request unless @auth.basic?
-
- # Authentication with username and password
- login, password = @auth.credentials
-
- # Allow authentication for GitLab CI service
- # if valid token passed
- if ci_request?(login, password)
- @ci = true
- return
- end
-
- @user = authenticate_user(login, password)
- end
-
- def ci_request?(login, password)
- matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
-
- if project && matched_login.present?
- underscored_service = matched_login['s'].underscore
-
- if underscored_service == 'gitlab_ci'
- return project && project.valid_build_token?(password)
- elsif Service.available_services_names.include?(underscored_service)
- service_method = "#{underscored_service}_service"
- service = project.send(service_method)
-
- return service && service.activated? && service.valid_token?(password)
- end
- end
-
- false
- end
-
- def oauth_access_token_check(login, password)
- if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
- token = Doorkeeper::AccessToken.by_token(password)
- token && token.accessible? && User.find_by(id: token.resource_owner_id)
- end
- end
-
- def authenticate_user(login, password)
- user = Gitlab::Auth.find_with_user_password(login, password)
-
- unless user
- user = oauth_access_token_check(login, password)
- end
-
- # If the user authenticated successfully, we reset the auth failure count
- # from Rack::Attack for that IP. A client may attempt to authenticate
- # with a username and blank password first, and only after it receives
- # a 401 error does it present a password. Resetting the count prevents
- # false positives from occurring.
- #
- # Otherwise, we let Rack::Attack know there was a failed authentication
- # attempt from this IP. This information is stored in the Rails cache
- # (Redis) and will be used by the Rack::Attack middleware to decide
- # whether to block requests from this IP.
- config = Gitlab.config.rack_attack.git_basic_auth
-
- if config.enabled
- if user
- # A successful login will reset the auth failure count from this IP
- Rack::Attack::Allow2Ban.reset(@request.ip, config)
- else
- banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do
- # Unless the IP is whitelisted, return true so that Allow2Ban
- # increments the counter (stored in Rails.cache) for the IP
- if config.ip_whitelist.include?(@request.ip)
- false
- else
- true
- end
- end
-
- if banned
- Rails.logger.info "IP #{@request.ip} failed to login " \
- "as #{login} but has been temporarily banned from Git auth"
- end
- end
- end
-
- user
- end
-
- def git_cmd
- if @request.get?
- @request.params['service']
- elsif @request.post?
- File.basename(@request.path)
- else
- nil
- end
- end
-
- def project
- return @project if defined?(@project)
-
- @project = project_by_path(@request.path_info)
- end
-
- def project_by_path(path)
- if m = /^([\w\.\/-]+)\.git/.match(path).to_a
- path_with_namespace = m.last
- path_with_namespace.gsub!(/\.wiki$/, '')
-
- path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
- Project.find_with_namespace(path_with_namespace)
- end
- end
-
- def render_not_found
- [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
- end
- end
-end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 34e0143a82e..839a4fa30d5 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -60,16 +60,18 @@ module Gitlab
end
# Fork repository to new namespace
- # storage - project's storage path
+ # forked_from_storage - forked-from project's storage path
# path - project path with namespace
+ # forked_to_storage - forked-to project's storage path
# fork_namespace - namespace for forked project
#
# Ex.
- # fork_repository("/path/to/storage", "gitlab/gitlab-ci", "randx")
+ # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "randx")
#
- def fork_repository(storage, path, fork_namespace)
+ def fork_repository(forked_from_storage, path, forked_to_storage, fork_namespace)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
- storage, "#{path}.git", fork_namespace])
+ forked_from_storage, "#{path}.git", forked_to_storage,
+ fork_namespace])
end
# Remove repository from file system
diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb
new file mode 100644
index 00000000000..909fa24fa90
--- /dev/null
+++ b/lib/gitlab/badge/base.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module Badge
+ class Base
+ def entity
+ raise NotImplementedError
+ end
+
+ def status
+ raise NotImplementedError
+ end
+
+ def metadata
+ raise NotImplementedError
+ end
+
+ def template
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/build.rb b/lib/gitlab/badge/build.rb
deleted file mode 100644
index e5e9fab3f5c..00000000000
--- a/lib/gitlab/badge/build.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Gitlab
- module Badge
- ##
- # Build badge
- #
- class Build
- include Gitlab::Application.routes.url_helpers
- include ActionView::Helpers::AssetTagHelper
- include ActionView::Helpers::UrlHelper
-
- def initialize(project, ref)
- @project, @ref = project, ref
- @image = ::Ci::ImageForBuildService.new.execute(project, ref: ref)
- end
-
- def type
- 'image/svg+xml'
- end
-
- def data
- File.read(@image[:path])
- end
-
- def to_s
- @image[:name].sub(/\.svg$/, '')
- end
-
- def to_html
- link_to(image_tag(image_url, alt: 'build status'), link_url)
- end
-
- def to_markdown
- "[![build status](#{image_url})](#{link_url})"
- end
-
- def image_url
- build_namespace_project_badges_url(@project.namespace,
- @project, @ref, format: :svg)
- end
-
- def link_url
- namespace_project_commits_url(@project.namespace, @project, id: @ref)
- end
- end
- end
-end
diff --git a/lib/gitlab/badge/build/metadata.rb b/lib/gitlab/badge/build/metadata.rb
new file mode 100644
index 00000000000..f87a7b7942e
--- /dev/null
+++ b/lib/gitlab/badge/build/metadata.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ module Badge
+ module Build
+ ##
+ # Class that describes build badge metadata
+ #
+ class Metadata < Badge::Metadata
+ def initialize(badge)
+ @project = badge.project
+ @ref = badge.ref
+ end
+
+ def title
+ 'build status'
+ end
+
+ def image_url
+ build_namespace_project_badges_url(@project.namespace,
+ @project, @ref, format: :svg)
+ end
+
+ def link_url
+ namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/build/status.rb b/lib/gitlab/badge/build/status.rb
new file mode 100644
index 00000000000..50aa45e5406
--- /dev/null
+++ b/lib/gitlab/badge/build/status.rb
@@ -0,0 +1,37 @@
+module Gitlab
+ module Badge
+ module Build
+ ##
+ # Build status badge
+ #
+ class Status < Badge::Base
+ attr_reader :project, :ref
+
+ def initialize(project, ref)
+ @project = project
+ @ref = ref
+
+ @sha = @project.commit(@ref).try(:sha)
+ end
+
+ def entity
+ 'build'
+ end
+
+ def status
+ @project.pipelines
+ .where(sha: @sha, ref: @ref)
+ .status || 'unknown'
+ end
+
+ def metadata
+ @metadata ||= Build::Metadata.new(self)
+ end
+
+ def template
+ @template ||= Build::Template.new(self)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/build/template.rb b/lib/gitlab/badge/build/template.rb
new file mode 100644
index 00000000000..2b95ddfcb53
--- /dev/null
+++ b/lib/gitlab/badge/build/template.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module Badge
+ module Build
+ ##
+ # Class that represents a build badge template.
+ #
+ # Template object will be passed to badge.svg.erb template.
+ #
+ class Template < Badge::Template
+ STATUS_COLOR = {
+ success: '#4c1',
+ failed: '#e05d44',
+ running: '#dfb317',
+ pending: '#dfb317',
+ canceled: '#9f9f9f',
+ skipped: '#9f9f9f',
+ unknown: '#9f9f9f'
+ }
+
+ def initialize(badge)
+ @entity = badge.entity
+ @status = badge.status
+ end
+
+ def key_text
+ @entity.to_s
+ end
+
+ def value_text
+ @status.to_s
+ end
+
+ def key_width
+ 38
+ end
+
+ def value_width
+ 54
+ end
+
+ def value_color
+ STATUS_COLOR[@status.to_sym] || STATUS_COLOR[:unknown]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
new file mode 100644
index 00000000000..53588185622
--- /dev/null
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module Badge
+ module Coverage
+ ##
+ # Class that describes coverage badge metadata
+ #
+ class Metadata < Badge::Metadata
+ def initialize(badge)
+ @project = badge.project
+ @ref = badge.ref
+ @job = badge.job
+ end
+
+ def title
+ 'coverage report'
+ end
+
+ def image_url
+ coverage_namespace_project_badges_url(@project.namespace,
+ @project, @ref,
+ format: :svg)
+ end
+
+ def link_url
+ namespace_project_commits_url(@project.namespace, @project, id: @ref)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
new file mode 100644
index 00000000000..3d56ea3e47a
--- /dev/null
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -0,0 +1,56 @@
+module Gitlab
+ module Badge
+ module Coverage
+ ##
+ # Test coverage report badge
+ #
+ class Report < Badge::Base
+ attr_reader :project, :ref, :job
+
+ def initialize(project, ref, job = nil)
+ @project = project
+ @ref = ref
+ @job = job
+
+ @pipeline = @project.pipelines
+ .where(ref: @ref)
+ .where(sha: @project.commit(@ref).try(:sha))
+ .first
+ end
+
+ def entity
+ 'coverage'
+ end
+
+ def status
+ @coverage ||= raw_coverage
+ return unless @coverage
+
+ @coverage.to_i
+ end
+
+ def metadata
+ @metadata ||= Coverage::Metadata.new(self)
+ end
+
+ def template
+ @template ||= Coverage::Template.new(self)
+ end
+
+ private
+
+ def raw_coverage
+ return unless @pipeline
+
+ if @job.blank?
+ @pipeline.coverage
+ else
+ @pipeline.builds
+ .find_by(name: @job)
+ .try(:coverage)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb
new file mode 100644
index 00000000000..06e0d084e9f
--- /dev/null
+++ b/lib/gitlab/badge/coverage/template.rb
@@ -0,0 +1,52 @@
+module Gitlab
+ module Badge
+ module Coverage
+ ##
+ # Class that represents a coverage badge template.
+ #
+ # Template object will be passed to badge.svg.erb template.
+ #
+ class Template < Badge::Template
+ STATUS_COLOR = {
+ good: '#4c1',
+ acceptable: '#a3c51c',
+ medium: '#dfb317',
+ low: '#e05d44',
+ unknown: '#9f9f9f'
+ }
+
+ def initialize(badge)
+ @entity = badge.entity
+ @status = badge.status
+ end
+
+ def key_text
+ @entity.to_s
+ end
+
+ def value_text
+ @status ? "#{@status}%" : 'unknown'
+ end
+
+ def key_width
+ 62
+ end
+
+ def value_width
+ @status ? 36 : 58
+ end
+
+ def value_color
+ case @status
+ when 95..100 then STATUS_COLOR[:good]
+ when 90..95 then STATUS_COLOR[:acceptable]
+ when 75..90 then STATUS_COLOR[:medium]
+ when 0..75 then STATUS_COLOR[:low]
+ else
+ STATUS_COLOR[:unknown]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
new file mode 100644
index 00000000000..548f85b78bb
--- /dev/null
+++ b/lib/gitlab/badge/metadata.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Badge
+ ##
+ # Abstract class for badge metadata
+ #
+ class Metadata
+ include Gitlab::Application.routes.url_helpers
+ include ActionView::Helpers::AssetTagHelper
+ include ActionView::Helpers::UrlHelper
+
+ def initialize(badge)
+ @badge = badge
+ end
+
+ def to_html
+ link_to(image_tag(image_url, alt: title), link_url)
+ end
+
+ def to_markdown
+ "[![#{title}](#{image_url})](#{link_url})"
+ end
+
+ def title
+ raise NotImplementedError
+ end
+
+ def image_url
+ raise NotImplementedError
+ end
+
+ def link_url
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/badge/template.rb b/lib/gitlab/badge/template.rb
new file mode 100644
index 00000000000..bfeb0052642
--- /dev/null
+++ b/lib/gitlab/badge/template.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ module Badge
+ ##
+ # Abstract template class for badges
+ #
+ class Template
+ def initialize(badge)
+ @entity = badge.entity
+ @status = badge.status
+ end
+
+ def key_text
+ raise NotImplementedError
+ end
+
+ def value_text
+ raise NotImplementedError
+ end
+
+ def key_width
+ raise NotImplementedError
+ end
+
+ def value_width
+ raise NotImplementedError
+ end
+
+ def value_color
+ raise NotImplementedError
+ end
+
+ def key_color
+ '#555'
+ end
+
+ def key_text_anchor
+ key_width / 2
+ end
+
+ def value_text_anchor
+ key_width + (value_width / 2)
+ end
+
+ def width
+ key_width + value_width
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/changes_list.rb b/lib/gitlab/changes_list.rb
new file mode 100644
index 00000000000..95308aca95f
--- /dev/null
+++ b/lib/gitlab/changes_list.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ class ChangesList
+ include Enumerable
+
+ attr_reader :raw_changes
+
+ def initialize(changes)
+ @raw_changes = changes.kind_of?(String) ? changes.lines : changes
+ end
+
+ def each(&block)
+ changes.each(&block)
+ end
+
+ def changes
+ @changes ||= begin
+ @raw_changes.map do |change|
+ next if change.blank?
+ oldrev, newrev, ref = change.strip.split(' ')
+ { oldrev: oldrev, newrev: newrev, ref: ref }
+ end.compact
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 5551fac4b8b..4b32eb966aa 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -4,14 +4,14 @@ module Gitlab
attr_reader :user_access, :project
def initialize(change, user_access:, project:)
- @oldrev, @newrev, @ref = change.split(' ')
- @branch_name = branch_name(@ref)
+ @oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
+ @branch_name = Gitlab::Git.branch_name(@ref)
@user_access = user_access
@project = project
end
def exec
- error = protected_branch_checks || tag_checks || push_checks
+ error = push_checks || tag_checks || protected_branch_checks
if error
GitAccessStatus.new(false, error)
@@ -47,7 +47,7 @@ module Gitlab
end
def tag_checks
- tag_ref = tag_name(@ref)
+ tag_ref = Gitlab::Git.tag_name(@ref)
if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
"You are not allowed to change existing tags on this project."
@@ -73,24 +73,6 @@ module Gitlab
def matching_merge_request?
Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
end
-
- def branch_name(ref)
- ref = @ref.to_s
- if Gitlab::Git.branch_ref?(ref)
- Gitlab::Git.ref_name(ref)
- else
- nil
- end
- end
-
- def tag_name(ref)
- ref = @ref.to_s
- if Gitlab::Git.tag_ref?(ref)
- Gitlab::Git.ref_name(ref)
- else
- nil
- end
- end
end
end
end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index e6cc1529760..ae82c0db3f1 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -8,7 +8,7 @@ module Gitlab
# Temporary delegations that should be removed after refactoring
#
delegate :before_script, :image, :services, :after_script, :variables,
- :stages, :cache, to: :@global
+ :stages, :cache, :jobs, to: :@global
def initialize(config)
@config = Loader.new(config).load!
diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb
new file mode 100644
index 00000000000..844bd2fe998
--- /dev/null
+++ b/lib/gitlab/ci/config/node/artifacts.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a configuration of job artifacts.
+ #
+ class Artifacts < Entry
+ include Validatable
+ include Attributable
+
+ ALLOWED_KEYS = %i[name untracked paths when expire_in]
+
+ attributes ALLOWED_KEYS
+
+ validations do
+ validates :config, type: Hash
+ validates :config, allowed_keys: ALLOWED_KEYS
+
+ with_options allow_nil: true do
+ validates :name, type: String
+ validates :untracked, boolean: true
+ validates :paths, array_of_strings: true
+ validates :when,
+ inclusion: { in: %w[on_success on_failure always],
+ message: 'should be on_success, on_failure ' \
+ 'or always' }
+ validates :expire_in, duration: true
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/attributable.rb b/lib/gitlab/ci/config/node/attributable.rb
new file mode 100644
index 00000000000..221b666f9f6
--- /dev/null
+++ b/lib/gitlab/ci/config/node/attributable.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ module Attributable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def attributes(*attributes)
+ attributes.flatten.each do |attribute|
+ define_method(attribute) do
+ return unless config.is_a?(Hash)
+
+ config[attribute]
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb
index cdf8ba2e35d..b4bda2841ac 100644
--- a/lib/gitlab/ci/config/node/cache.rb
+++ b/lib/gitlab/ci/config/node/cache.rb
@@ -8,6 +8,12 @@ module Gitlab
class Cache < Entry
include Configurable
+ ALLOWED_KEYS = %i[key untracked paths]
+
+ validations do
+ validates :config, allowed_keys: ALLOWED_KEYS
+ end
+
node :key, Node::Key,
description: 'Cache key used to define a cache affinity.'
@@ -16,10 +22,6 @@ module Gitlab
node :paths, Node::Paths,
description: 'Specify which paths should be cached across builds.'
-
- validations do
- validates :config, allowed_keys: true
- end
end
end
end
diff --git a/lib/gitlab/ci/config/node/commands.rb b/lib/gitlab/ci/config/node/commands.rb
new file mode 100644
index 00000000000..d7657ae314b
--- /dev/null
+++ b/lib/gitlab/ci/config/node/commands.rb
@@ -0,0 +1,33 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a job script.
+ #
+ class Commands < Entry
+ include Validatable
+
+ validations do
+ include LegacyValidationHelpers
+
+ validate do
+ unless string_or_array_of_strings?(config)
+ errors.add(:config,
+ 'should be a string or an array of strings')
+ end
+ end
+
+ def string_or_array_of_strings?(field)
+ validate_string(field) || validate_array_of_strings(field)
+ end
+ end
+
+ def value
+ Array(@config)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 37936fc8242..2de82d40c9d 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -25,10 +25,14 @@ module Gitlab
private
- def create_node(key, factory)
- factory.with(value: @config[key], key: key, parent: self)
+ def compose!
+ self.class.nodes.each do |key, factory|
+ factory
+ .value(@config[key])
+ .with(key: key, parent: self)
- factory.create!
+ @entries[key] = factory.create!
+ end
end
class_methods do
@@ -36,24 +40,25 @@ module Gitlab
Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
end
- private
+ private # rubocop:disable Lint/UselessAccessModifier
- def node(symbol, entry_class, metadata)
- factory = Node::Factory.new(entry_class)
+ def node(key, node, metadata)
+ factory = Node::Factory.new(node)
.with(description: metadata[:description])
- (@nodes ||= {}).merge!(symbol.to_sym => factory)
+ (@nodes ||= {}).merge!(key.to_sym => factory)
end
def helpers(*nodes)
nodes.each do |symbol|
define_method("#{symbol}_defined?") do
- @nodes[symbol].try(:defined?)
+ @entries[symbol].specified? if @entries[symbol]
end
define_method("#{symbol}_value") do
- raise Entry::InvalidError unless valid?
- @nodes[symbol].try(:value)
+ return unless @entries[symbol] && @entries[symbol].valid?
+
+ @entries[symbol].value
end
alias_method symbol.to_sym, "#{symbol}_value".to_sym
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 9e79e170a4f..0c782c422b5 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -8,30 +8,31 @@ module Gitlab
class Entry
class InvalidError < StandardError; end
- attr_reader :config
+ attr_reader :config, :metadata
attr_accessor :key, :parent, :description
- def initialize(config)
+ def initialize(config, **metadata)
@config = config
- @nodes = {}
+ @metadata = metadata
+ @entries = {}
+
@validator = self.class.validator.new(self)
- @validator.validate
+ @validator.validate(:new)
end
def process!
- return if leaf?
return unless valid?
compose!
- process_nodes!
+ descendants.each(&:process!)
end
- def nodes
- @nodes.values
+ def leaf?
+ @entries.none?
end
- def leaf?
- self.class.nodes.none?
+ def descendants
+ @entries.values
end
def ancestors
@@ -43,27 +44,30 @@ module Gitlab
end
def errors
- @validator.messages + nodes.flat_map(&:errors)
+ @validator.messages + descendants.flat_map(&:errors)
end
def value
if leaf?
@config
else
- defined = @nodes.select { |_key, value| value.defined? }
- Hash[defined.map { |key, node| [key, node.value] }]
+ meaningful = @entries.select do |_key, value|
+ value.specified? && value.relevant?
+ end
+
+ Hash[meaningful.map { |key, entry| [key, entry.value] }]
end
end
- def defined?
+ def specified?
true
end
- def self.default
+ def relevant?
+ true
end
- def self.nodes
- {}
+ def self.default
end
def self.validator
@@ -73,17 +77,6 @@ module Gitlab
private
def compose!
- self.class.nodes.each do |key, essence|
- @nodes[key] = create_node(key, essence)
- end
- end
-
- def process_nodes!
- nodes.each(&:process!)
- end
-
- def create_node(key, essence)
- raise NotImplementedError
end
end
end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 5919a283283..707b052e6a8 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -10,35 +10,60 @@ module Gitlab
def initialize(node)
@node = node
+ @metadata = {}
@attributes = {}
end
+ def value(value)
+ @value = value
+ self
+ end
+
+ def metadata(metadata)
+ @metadata.merge!(metadata)
+ self
+ end
+
def with(attributes)
@attributes.merge!(attributes)
self
end
def create!
- raise InvalidFactory unless @attributes.has_key?(:value)
+ raise InvalidFactory unless defined?(@value)
- fabricate.tap do |entry|
- entry.key = @attributes[:key]
- entry.parent = @attributes[:parent]
- entry.description = @attributes[:description]
+ ##
+ # We assume that unspecified entry is undefined.
+ # See issue #18775.
+ #
+ if @value.nil?
+ Node::Undefined.new(
+ fabricate_undefined
+ )
+ else
+ fabricate(@node, @value)
end
end
private
- def fabricate
+ def fabricate_undefined
##
- # We assume that unspecified entry is undefined.
- # See issue #18775.
+ # If node has a default value we fabricate concrete node
+ # with default value.
#
- if @attributes[:value].nil?
- Node::Undefined.new(@node)
+ if @node.default.nil?
+ fabricate(Node::Null)
else
- @node.new(@attributes[:value])
+ fabricate(@node, @node.default)
+ end
+ end
+
+ def fabricate(node, value = nil)
+ node.new(value, @metadata).tap do |entry|
+ entry.key = @attributes[:key]
+ entry.parent = @attributes[:parent]
+ entry.description = @attributes[:description]
end
end
end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index f92e1eccbcf..ccd539fb003 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -34,10 +34,36 @@ module Gitlab
description: 'Configure caching between build jobs.'
helpers :before_script, :image, :services, :after_script,
- :variables, :stages, :types, :cache
+ :variables, :stages, :types, :cache, :jobs
- def stages
- stages_defined? ? stages_value : types_value
+ private
+
+ def compose!
+ super
+
+ compose_jobs!
+ compose_deprecated_entries!
+ end
+
+ def compose_jobs!
+ factory = Node::Factory.new(Node::Jobs)
+ .value(@config.except(*self.class.nodes.keys))
+ .with(key: :jobs, parent: self,
+ description: 'Jobs definition for this pipeline')
+
+ @entries[:jobs] = factory.create!
+ end
+
+ def compose_deprecated_entries!
+ ##
+ # Deprecated `:types` key workaround - if types are defined and
+ # stages are not defined we use types definition as stages.
+ #
+ if types_defined? && !stages_defined?
+ @entries[:stages] = @entries[:types]
+ end
+
+ @entries.delete(:types)
end
end
end
diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden_job.rb
new file mode 100644
index 00000000000..073044b66f8
--- /dev/null
+++ b/lib/gitlab/ci/config/node/hidden_job.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a hidden CI/CD job.
+ #
+ class HiddenJob < Entry
+ include Validatable
+
+ validations do
+ validates :config, type: Hash
+ validates :config, presence: true
+ end
+
+ def relevant?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
new file mode 100644
index 00000000000..e84737acbb9
--- /dev/null
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -0,0 +1,123 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a concrete CI/CD job.
+ #
+ class Job < Entry
+ include Configurable
+ include Attributable
+
+ ALLOWED_KEYS = %i[tags script only except type image services allow_failure
+ type stage when artifacts cache dependencies before_script
+ after_script variables environment]
+
+ attributes :tags, :allow_failure, :when, :environment, :dependencies
+
+ validations do
+ validates :config, allowed_keys: ALLOWED_KEYS
+
+ validates :config, presence: true
+ validates :name, presence: true
+ validates :name, type: Symbol
+
+ with_options allow_nil: true do
+ validates :tags, array_of_strings: true
+ validates :allow_failure, boolean: true
+ validates :when,
+ inclusion: { in: %w[on_success on_failure always manual],
+ message: 'should be on_success, on_failure, ' \
+ 'always or manual' }
+ validates :environment,
+ type: {
+ with: String,
+ message: Gitlab::Regex.environment_name_regex_message }
+ validates :environment,
+ format: {
+ with: Gitlab::Regex.environment_name_regex,
+ message: Gitlab::Regex.environment_name_regex_message }
+
+ validates :dependencies, array_of_strings: true
+ end
+ end
+
+ node :before_script, Script,
+ description: 'Global before script overridden in this job.'
+
+ node :script, Commands,
+ description: 'Commands that will be executed in this job.'
+
+ node :stage, Stage,
+ description: 'Pipeline stage this job will be executed into.'
+
+ node :type, Stage,
+ description: 'Deprecated: stage this job will be executed into.'
+
+ node :after_script, Script,
+ description: 'Commands that will be executed when finishing job.'
+
+ node :cache, Cache,
+ description: 'Cache definition for this job.'
+
+ node :image, Image,
+ description: 'Image that will be used to execute this job.'
+
+ node :services, Services,
+ description: 'Services that will be used to execute this job.'
+
+ node :only, Trigger,
+ description: 'Refs policy this job will be executed for.'
+
+ node :except, Trigger,
+ description: 'Refs policy this job will be executed for.'
+
+ node :variables, Variables,
+ description: 'Environment variables available for this job.'
+
+ node :artifacts, Artifacts,
+ description: 'Artifacts configuration for this job.'
+
+ helpers :before_script, :script, :stage, :type, :after_script,
+ :cache, :image, :services, :only, :except, :variables,
+ :artifacts
+
+ def name
+ @metadata[:name]
+ end
+
+ def value
+ @config.merge(to_hash.compact)
+ end
+
+ private
+
+ def to_hash
+ { name: name,
+ before_script: before_script,
+ script: script,
+ image: image,
+ services: services,
+ stage: stage,
+ cache: cache,
+ only: only,
+ except: except,
+ variables: variables_defined? ? variables : nil,
+ artifacts: artifacts,
+ after_script: after_script }
+ end
+
+ def compose!
+ super
+
+ if type_defined? && !stage_defined?
+ @entries[:stage] = @entries[:type]
+ end
+
+ @entries.delete(:type)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
new file mode 100644
index 00000000000..51683c82ceb
--- /dev/null
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a set of jobs.
+ #
+ class Jobs < Entry
+ include Validatable
+
+ validations do
+ validates :config, type: Hash
+
+ validate do
+ unless has_visible_job?
+ errors.add(:config, 'should contain at least one visible job')
+ end
+ end
+
+ def has_visible_job?
+ config.any? { |name, _| !hidden?(name) }
+ end
+ end
+
+ def hidden?(name)
+ name.to_s.start_with?('.')
+ end
+
+ private
+
+ def compose!
+ @config.each do |name, config|
+ node = hidden?(name) ? Node::HiddenJob : Node::Job
+
+ factory = Node::Factory.new(node)
+ .value(config || {})
+ .metadata(name: name)
+ .with(key: name, parent: self,
+ description: "#{name} job definition.")
+
+ @entries[name] = factory.create!
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
index 4d9a508796a..0c291efe6a5 100644
--- a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
+++ b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
@@ -41,10 +41,6 @@ module Gitlab
false
end
- def validate_environment(value)
- value.is_a?(String) && value =~ Gitlab::Regex.environment_name_regex
- end
-
def validate_boolean(value)
value.in?([true, false])
end
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
new file mode 100644
index 00000000000..88a5f53f13c
--- /dev/null
+++ b/lib/gitlab/ci/config/node/null.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # This class represents an undefined node.
+ #
+ # Implements the Null Object pattern.
+ #
+ class Null < Entry
+ def value
+ nil
+ end
+
+ def valid?
+ true
+ end
+
+ def errors
+ []
+ end
+
+ def specified?
+ false
+ end
+
+ def relevant?
+ false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb
new file mode 100644
index 00000000000..cbc97641f5a
--- /dev/null
+++ b/lib/gitlab/ci/config/node/stage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a stage for a job.
+ #
+ class Stage < Entry
+ include Validatable
+
+ validations do
+ validates :config, type: String
+ end
+
+ def self.default
+ 'test'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/trigger.rb b/lib/gitlab/ci/config/node/trigger.rb
new file mode 100644
index 00000000000..d8b31975088
--- /dev/null
+++ b/lib/gitlab/ci/config/node/trigger.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ ##
+ # Entry that represents a trigger policy for the job.
+ #
+ class Trigger < Entry
+ include Validatable
+
+ validations do
+ include LegacyValidationHelpers
+
+ validate :array_of_strings_or_regexps
+
+ def array_of_strings_or_regexps
+ unless validate_array_of_strings_or_regexps(config)
+ errors.add(:config, 'should be an array of strings or regexps')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
index 699605e1e3a..45fef8c3ae5 100644
--- a/lib/gitlab/ci/config/node/undefined.rb
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -3,24 +3,13 @@ module Gitlab
class Config
module Node
##
- # This class represents an undefined entry node.
+ # This class represents an unspecified entry node.
#
- # It takes original entry class as configuration and returns default
- # value of original entry as self value.
+ # It decorates original entry adding method that indicates it is
+ # unspecified.
#
- #
- class Undefined < Entry
- include Validatable
-
- validations do
- validates :config, type: Class
- end
-
- def value
- @config.default
- end
-
- def defined?
+ class Undefined < SimpleDelegator
+ def specified?
false
end
end
diff --git a/lib/gitlab/ci/config/node/validatable.rb b/lib/gitlab/ci/config/node/validatable.rb
index f6e2896dfb2..085e6e988d1 100644
--- a/lib/gitlab/ci/config/node/validatable.rb
+++ b/lib/gitlab/ci/config/node/validatable.rb
@@ -7,13 +7,11 @@ module Gitlab
class_methods do
def validator
- validator = Class.new(Node::Validator)
-
- if defined?(@validations)
- @validations.each { |rules| validator.class_eval(&rules) }
+ @validator ||= Class.new(Node::Validator).tap do |validator|
+ if defined?(@validations)
+ @validations.each { |rules| validator.class_eval(&rules) }
+ end
end
-
- validator
end
private
diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb
index 758a6cf4356..43c7e102b50 100644
--- a/lib/gitlab/ci/config/node/validator.rb
+++ b/lib/gitlab/ci/config/node/validator.rb
@@ -21,18 +21,19 @@ module Gitlab
'Validator'
end
- def unknown_keys
- return [] unless config.is_a?(Hash)
-
- config.keys - @node.class.nodes.keys
- end
-
private
def location
predecessors = ancestors.map(&:key).compact
- current = key || @node.class.name.demodulize.underscore
- predecessors.append(current).join(':')
+ predecessors.append(key_name).join(':')
+ end
+
+ def key_name
+ if key.blank?
+ @node.class.name.demodulize.underscore.humanize
+ else
+ key
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb
index 7b2f57990b5..e20908ad3cb 100644
--- a/lib/gitlab/ci/config/node/validators.rb
+++ b/lib/gitlab/ci/config/node/validators.rb
@@ -5,10 +5,11 @@ module Gitlab
module Validators
class AllowedKeysValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- if record.unknown_keys.any?
- unknown_list = record.unknown_keys.join(', ')
- record.errors.add(:config,
- "contains unknown keys: #{unknown_list}")
+ unknown_keys = record.config.try(:keys).to_a - options[:in]
+
+ if unknown_keys.any?
+ record.errors.add(:config, 'contains unknown keys: ' +
+ unknown_keys.join(', '))
end
end
end
@@ -33,6 +34,16 @@ module Gitlab
end
end
+ class DurationValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_duration(value)
+ record.errors.add(attribute, 'should be a duration')
+ end
+ end
+ end
+
class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
@@ -49,7 +60,8 @@ module Gitlab
raise unless type.is_a?(Class)
unless value.is_a?(type)
- record.errors.add(attribute, "should be a #{type.name}")
+ message = options[:message] || "should be a #{type.name}"
+ record.errors.add(attribute, message)
end
end
end
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 9bef9037ad6..58f86abc5c4 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -22,7 +22,9 @@ module Gitlab
@extractor.analyze(closing_statements.join(" "))
- @extractor.issues
+ @extractor.issues.reject do |issue|
+ @extractor.project.forked_from?(issue.project) # Don't extract issues on original project
+ end
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 078609c86f1..55b8f888d53 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -55,12 +55,12 @@ module Gitlab
end
end
- private
-
def self.connection
ActiveRecord::Base.connection
end
+ private_class_method :connection
+
def self.database_version
row = connection.execute("SELECT VERSION()").first
@@ -70,5 +70,7 @@ module Gitlab
row.first
end
end
+
+ private_class_method :database_version
end
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index b09ca1fb8b0..e47df508ca2 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -63,15 +63,18 @@ module Gitlab
diff_refs.try(:head_sha)
end
+ attr_writer :highlighted_diff_lines
+
# Array of Gitlab::Diff::Line objects
def diff_lines
- @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
+ @diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
def highlighted_diff_lines
@highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
+ # Array[<Hash>] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted
def parallel_diff_lines
@parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
new file mode 100644
index 00000000000..2b9fc65b985
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ module Diff
+ module FileCollection
+ class Base
+ attr_reader :project, :diff_options, :diff_view, :diff_refs
+
+ delegate :count, :size, :real_size, to: :diff_files
+
+ def self.default_options
+ ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false)
+ end
+
+ def initialize(diffable, project:, diff_options: nil, diff_refs: nil)
+ diff_options = self.class.default_options.merge(diff_options || {})
+
+ @diffable = diffable
+ @diffs = diffable.raw_diffs(diff_options)
+ @project = project
+ @diff_options = diff_options
+ @diff_refs = diff_refs
+ end
+
+ def diff_files
+ @diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
+ end
+
+ private
+
+ def decorate_diff!(diff)
+ Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file_collection/commit.rb b/lib/gitlab/diff/file_collection/commit.rb
new file mode 100644
index 00000000000..4dc297ec036
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/commit.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module Diff
+ module FileCollection
+ class Commit < Base
+ def initialize(commit, diff_options:)
+ super(commit,
+ project: commit.project,
+ diff_options: diff_options,
+ diff_refs: commit.diff_refs)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb
new file mode 100644
index 00000000000..20d8f891cc3
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/compare.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module Diff
+ module FileCollection
+ class Compare < Base
+ def initialize(compare, project:, diff_options:, diff_refs: nil)
+ super(compare,
+ project: project,
+ diff_options: diff_options,
+ diff_refs: diff_refs)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb
new file mode 100644
index 00000000000..4f946908e2f
--- /dev/null
+++ b/lib/gitlab/diff/file_collection/merge_request.rb
@@ -0,0 +1,73 @@
+module Gitlab
+ module Diff
+ module FileCollection
+ class MergeRequest < Base
+ def initialize(merge_request, diff_options:)
+ @merge_request = merge_request
+
+ super(merge_request,
+ project: merge_request.project,
+ diff_options: diff_options,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ def diff_files
+ super.tap { |_| store_highlight_cache }
+ end
+
+ private
+
+ # Extracted method to highlight in the same iteration to the diff_collection.
+ def decorate_diff!(diff)
+ diff_file = super
+ cache_highlight!(diff_file) if cacheable?
+ diff_file
+ end
+
+ def highlight_diff_file_from_cache!(diff_file, cache_diff_lines)
+ diff_file.highlighted_diff_lines = cache_diff_lines.map do |line|
+ Gitlab::Diff::Line.init_from_hash(line)
+ end
+ end
+
+ #
+ # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted)
+ # for the highlighted ones, so we just skip their execution.
+ # If the highlighted diff files lines are not cached we calculate and cache them.
+ #
+ # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of
+ # hashes that represent serialized diff lines.
+ #
+ def cache_highlight!(diff_file)
+ file_path = diff_file.file_path
+
+ if highlight_cache[file_path]
+ highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path])
+ else
+ highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash)
+ end
+ end
+
+ def highlight_cache
+ return @highlight_cache if defined?(@highlight_cache)
+
+ @highlight_cache = Rails.cache.read(cache_key) || {}
+ @highlight_cache_was_empty = @highlight_cache.empty?
+ @highlight_cache
+ end
+
+ def store_highlight_cache
+ Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty
+ end
+
+ def cacheable?
+ @merge_request.merge_request_diff.present?
+ end
+
+ def cache_key
+ [@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 649a265a02c..9ea976e18fa 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -40,8 +40,6 @@ module Gitlab
def highlight_line(diff_line)
return unless diff_file && diff_file.diff_refs
- line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
-
rich_line =
if diff_line.unchanged? || diff_line.added?
new_lines[diff_line.new_pos - 1]
@@ -51,7 +49,10 @@ module Gitlab
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
- "#{line_prefix}#{rich_line}".html_safe if rich_line
+ if rich_line
+ line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
+ "#{line_prefix}#{rich_line}".html_safe
+ end
end
def inline_diffs
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 28ad637fda4..55708d42161 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -19,24 +19,6 @@ module Gitlab
attr_accessor :old_line, :new_line, :offset
- def self.for_lines(lines)
- changed_line_pairs = self.find_changed_line_pairs(lines)
-
- inline_diffs = []
-
- changed_line_pairs.each do |old_index, new_index|
- old_line = lines[old_index]
- new_line = lines[new_index]
-
- old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
-
- inline_diffs[old_index] = old_diffs
- inline_diffs[new_index] = new_diffs
- end
-
- inline_diffs
- end
-
def initialize(old_line, new_line, offset: 0)
@old_line = old_line[offset..-1]
@new_line = new_line[offset..-1]
@@ -63,32 +45,54 @@ module Gitlab
[old_diffs, new_diffs]
end
- private
+ class << self
+ def for_lines(lines)
+ changed_line_pairs = find_changed_line_pairs(lines)
- # Finds pairs of old/new line pairs that represent the same line that changed
- def self.find_changed_line_pairs(lines)
- # Prefixes of all diff lines, indicating their types
- # For example: `" - + -+ ---+++ --+ -++"`
- line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+ inline_diffs = []
- changed_line_pairs = []
- line_prefixes.scan(LINE_PAIRS_PATTERN) do
- # For `"---+++"`, `begin_index == 0`, `end_index == 6`
- begin_index, end_index = Regexp.last_match.offset(:del_ins)
+ changed_line_pairs.each do |old_index, new_index|
+ old_line = lines[old_index]
+ new_line = lines[new_index]
- # For `"---+++"`, `changed_line_count == 3`
- changed_line_count = (end_index - begin_index) / 2
+ old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
- halfway_index = begin_index + changed_line_count
- (begin_index...halfway_index).each do |i|
- # For `"---+++"`, index 1 maps to 1 + 3 = 4
- changed_line_pairs << [i, i + changed_line_count]
+ inline_diffs[old_index] = old_diffs
+ inline_diffs[new_index] = new_diffs
end
+
+ inline_diffs
end
- changed_line_pairs
+ private
+
+ # Finds pairs of old/new line pairs that represent the same line that changed
+ def find_changed_line_pairs(lines)
+ # Prefixes of all diff lines, indicating their types
+ # For example: `" - + -+ ---+++ --+ -++"`
+ line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+
+ changed_line_pairs = []
+ line_prefixes.scan(LINE_PAIRS_PATTERN) do
+ # For `"---+++"`, `begin_index == 0`, `end_index == 6`
+ begin_index, end_index = Regexp.last_match.offset(:del_ins)
+
+ # For `"---+++"`, `changed_line_count == 3`
+ changed_line_count = (end_index - begin_index) / 2
+
+ halfway_index = begin_index + changed_line_count
+ (begin_index...halfway_index).each do |i|
+ # For `"---+++"`, index 1 maps to 1 + 3 = 4
+ changed_line_pairs << [i, i + changed_line_count]
+ end
+ end
+
+ changed_line_pairs
+ end
end
+ private
+
def longest_common_prefix(a, b)
max_length = [a.length, b.length].max
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index c6189d660c2..cf097e0d0de 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -9,6 +9,20 @@ module Gitlab
@old_pos, @new_pos = old_pos, new_pos
end
+ def self.init_from_hash(hash)
+ new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos])
+ end
+
+ def serialize_keys
+ @serialize_keys ||= %i(text type index old_pos new_pos)
+ end
+
+ def to_hash
+ hash = {}
+ serialize_keys.each { |key| hash[key] = send(key) }
+ hash
+ end
+
def old_line
old_pos unless added? || meta?
end
diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb
new file mode 100644
index 00000000000..bd3267e2a80
--- /dev/null
+++ b/lib/gitlab/email/handler.rb
@@ -0,0 +1,17 @@
+require 'gitlab/email/handler/create_note_handler'
+require 'gitlab/email/handler/create_issue_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ HANDLERS = [CreateNoteHandler, CreateIssueHandler]
+
+ def self.for(mail, mail_key)
+ HANDLERS.find do |klass|
+ handler = klass.new(mail, mail_key)
+ break handler if handler.can_handle?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb
new file mode 100644
index 00000000000..b7ed11cb638
--- /dev/null
+++ b/lib/gitlab/email/handler/base_handler.rb
@@ -0,0 +1,60 @@
+module Gitlab
+ module Email
+ module Handler
+ class BaseHandler
+ attr_reader :mail, :mail_key
+
+ def initialize(mail, mail_key)
+ @mail = mail
+ @mail_key = mail_key
+ end
+
+ def message
+ @message ||= process_message
+ end
+
+ def author
+ raise NotImplementedError
+ end
+
+ def project
+ raise NotImplementedError
+ end
+
+ private
+
+ def validate_permission!(permission)
+ raise UserNotFoundError unless author
+ raise UserBlockedError if author.blocked?
+ raise ProjectNotFound unless author.can?(:read_project, project)
+ raise UserNotAuthorizedError unless author.can?(permission, project)
+ end
+
+ def process_message
+ message = ReplyParser.new(mail).execute.strip
+ add_attachments(message)
+ end
+
+ def add_attachments(reply)
+ attachments = Email::AttachmentUploader.new(mail).execute(project)
+
+ reply + attachments.map do |link|
+ "\n\n#{link[:markdown]}"
+ end.join
+ end
+
+ def verify_record!(record:, invalid_exception:, record_name:)
+ return if record.persisted?
+
+ error_title = "The #{record_name} could not be created for the following reasons:"
+
+ msg = error_title + record.errors.full_messages.map do |error|
+ "\n\n- #{error}"
+ end.join
+
+ raise invalid_exception, msg
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
new file mode 100644
index 00000000000..4e6566af8ab
--- /dev/null
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -0,0 +1,52 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ class CreateIssueHandler < BaseHandler
+ attr_reader :project_path, :authentication_token
+
+ def initialize(mail, mail_key)
+ super(mail, mail_key)
+ @project_path, @authentication_token =
+ mail_key && mail_key.split('+', 2)
+ end
+
+ def can_handle?
+ !authentication_token.nil?
+ end
+
+ def execute
+ raise ProjectNotFound unless project
+
+ validate_permission!(:create_issue)
+
+ verify_record!(
+ record: create_issue,
+ invalid_exception: InvalidIssueError,
+ record_name: 'issue')
+ end
+
+ def author
+ @author ||= User.find_by(authentication_token: authentication_token)
+ end
+
+ def project
+ @project ||= Project.find_with_namespace(project_path)
+ end
+
+ private
+
+ def create_issue
+ Issues::CreateService.new(
+ project,
+ author,
+ title: mail.subject,
+ description: message
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb
new file mode 100644
index 00000000000..06dae31cc27
--- /dev/null
+++ b/lib/gitlab/email/handler/create_note_handler.rb
@@ -0,0 +1,55 @@
+
+require 'gitlab/email/handler/base_handler'
+
+module Gitlab
+ module Email
+ module Handler
+ class CreateNoteHandler < BaseHandler
+ def can_handle?
+ mail_key =~ /\A\w+\z/
+ end
+
+ def execute
+ raise SentNotificationNotFoundError unless sent_notification
+ raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/
+
+ validate_permission!(:create_note)
+
+ raise NoteableNotFoundError unless sent_notification.noteable
+ raise EmptyEmailError if message.blank?
+
+ verify_record!(
+ record: create_note,
+ invalid_exception: InvalidNoteError,
+ record_name: 'comment')
+ end
+
+ def author
+ sent_notification.recipient
+ end
+
+ def project
+ sent_notification.project
+ end
+
+ def sent_notification
+ @sent_notification ||= SentNotification.for(mail_key)
+ end
+
+ private
+
+ def create_note
+ Notes::CreateService.new(
+ project,
+ author,
+ note: message,
+ noteable_type: sent_notification.noteable_type,
+ noteable_id: sent_notification.noteable_id,
+ commit_id: sent_notification.commit_id,
+ line_code: sent_notification.line_code
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
index 97701b0cd42..0e3b65fceb4 100644
--- a/lib/gitlab/email/message/repository_push.rb
+++ b/lib/gitlab/email/message/repository_push.rb
@@ -35,21 +35,22 @@ module Gitlab
def commits
return unless compare
- @commits ||= Commit.decorate(compare.commits, project)
+ @commits ||= compare.commits
end
def diffs
return unless compare
-
- @diffs ||= safe_diff_files(compare.diffs(max_files: 30), diff_refs: diff_refs, repository: project.repository)
+
+ # This diff is more moderated in number of files and lines
+ @diffs ||= compare.diffs(max_files: 30, max_lines: 5000, no_collapse: true).diff_files
end
def diffs_count
- diffs.count if diffs
+ diffs.size if diffs
end
def compare
- @opts[:compare]
+ @opts[:compare] if @opts[:compare]
end
def diff_refs
@@ -97,16 +98,18 @@ module Gitlab
if commits.length > 1
namespace_project_compare_url(project_namespace,
project,
- from: Commit.new(compare.base, project),
- to: Commit.new(compare.head, project))
+ from: compare.start_commit,
+ to: compare.head_commit)
else
namespace_project_commit_url(project_namespace,
- project, commits.first)
+ project,
+ commits.first)
end
else
unless @action == :delete
namespace_project_tree_url(project_namespace,
- project, ref_name)
+ project,
+ ref_name)
end
end
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 1c671a7487b..a40c44eb1bc 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -1,18 +1,24 @@
+
+require_dependency 'gitlab/email/handler'
+
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
- class Receiver
- class ProcessingError < StandardError; end
- class EmailUnparsableError < ProcessingError; end
- class SentNotificationNotFoundError < ProcessingError; end
- class EmptyEmailError < ProcessingError; end
- class AutoGeneratedEmailError < ProcessingError; end
- class UserNotFoundError < ProcessingError; end
- class UserBlockedError < ProcessingError; end
- class UserNotAuthorizedError < ProcessingError; end
- class NoteableNotFoundError < ProcessingError; end
- class InvalidNoteError < ProcessingError; end
+ class ProcessingError < StandardError; end
+ class EmailUnparsableError < ProcessingError; end
+ class SentNotificationNotFoundError < ProcessingError; end
+ class ProjectNotFound < ProcessingError; end
+ class EmptyEmailError < ProcessingError; end
+ class AutoGeneratedEmailError < ProcessingError; end
+ class UserNotFoundError < ProcessingError; end
+ class UserBlockedError < ProcessingError; end
+ class UserNotAuthorizedError < ProcessingError; end
+ class NoteableNotFoundError < ProcessingError; end
+ class InvalidNoteError < ProcessingError; end
+ class InvalidIssueError < ProcessingError; end
+ class UnknownIncomingEmail < ProcessingError; end
+ class Receiver
def initialize(raw)
@raw = raw
end
@@ -20,91 +26,38 @@ module Gitlab
def execute
raise EmptyEmailError if @raw.blank?
- raise SentNotificationNotFoundError unless sent_notification
-
- raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/
-
- author = sent_notification.recipient
-
- raise UserNotFoundError unless author
-
- raise UserBlockedError if author.blocked?
-
- project = sent_notification.project
-
- raise UserNotAuthorizedError unless project && author.can?(:create_note, project)
-
- raise NoteableNotFoundError unless sent_notification.noteable
-
- reply = ReplyParser.new(message).execute.strip
-
- raise EmptyEmailError if reply.blank?
-
- reply = add_attachments(reply)
-
- note = create_note(reply)
+ mail = build_mail
+ mail_key = extract_mail_key(mail)
+ handler = Handler.for(mail, mail_key)
- unless note.persisted?
- msg = "The comment could not be created for the following reasons:"
- note.errors.full_messages.each do |error|
- msg << "\n\n- #{error}"
- end
+ raise UnknownIncomingEmail unless handler
- raise InvalidNoteError, msg
- end
+ handler.execute
end
- private
-
- def message
- @message ||= Mail::Message.new(@raw)
- rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
+ def build_mail
+ Mail::Message.new(@raw)
+ rescue Encoding::UndefinedConversionError,
+ Encoding::InvalidByteSequenceError => e
raise EmailUnparsableError, e
end
- def reply_key
- key_from_to_header || key_from_additional_headers
+ def extract_mail_key(mail)
+ key_from_to_header(mail) || key_from_additional_headers(mail)
end
- def key_from_to_header
- key = nil
- message.to.each do |address|
+ def key_from_to_header(mail)
+ mail.to.find do |address|
key = Gitlab::IncomingEmail.key_from_address(address)
- break if key
+ break key if key
end
-
- key
end
- def key_from_additional_headers
- reply_key = nil
-
- Array(message.references).each do |message_id|
- reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id)
- break if reply_key
+ def key_from_additional_headers(mail)
+ Array(mail.references).find do |mail_id|
+ key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id)
+ break key if key
end
-
- reply_key
- end
-
- def sent_notification
- return nil unless reply_key
-
- SentNotification.for(reply_key)
- end
-
- def add_attachments(reply)
- attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
-
- attachments.each do |link|
- reply << "\n\n#{link[:markdown]}"
- end
-
- reply
- end
-
- def create_note(reply)
- sent_notification.create_note(reply)
end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 191bea86ac3..7584efe4fa8 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -9,6 +9,24 @@ module Gitlab
ref.gsub(/\Arefs\/(tags|heads)\//, '')
end
+ def branch_name(ref)
+ ref = ref.to_s
+ if self.branch_ref?(ref)
+ self.ref_name(ref)
+ else
+ nil
+ end
+ end
+
+ def tag_name(ref)
+ ref = ref.to_s
+ if self.tag_ref?(ref)
+ self.ref_name(ref)
+ else
+ nil
+ end
+ end
+
def tag_ref?(ref)
ref.start_with?(TAG_REF_PREFIX)
end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 8e8f39d9cb2..1882eb8d050 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -14,7 +14,7 @@ module Gitlab
@user_access = UserAccess.new(user, project: project)
end
- def check(cmd, changes = nil)
+ def check(cmd, changes)
return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
unless actor
@@ -76,10 +76,10 @@ module Gitlab
return build_status_object(false, "A repository for this project does not exist yet.")
end
- changes = changes.lines if changes.kind_of?(String)
+ changes_list = Gitlab::ChangesList.new(changes)
# Iterate over all changes to find if user allowed all of them to be applied
- changes.map(&:strip).reject(&:blank?).each do |change|
+ changes_list.each do |change|
status = change_access_check(change)
unless status.allowed?
# If user does not have access to make at least one change - cancel all push
@@ -134,7 +134,7 @@ module Gitlab
end
def build_status_object(status, message = '')
- GitAccessStatus.new(status, message)
+ Gitlab::GitAccessStatus.new(status, message)
end
end
end
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index a088e19d1e7..d32bdd86427 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -39,7 +39,6 @@ module Gitlab
end
def deserialize_changes(changes)
- changes = Base64.decode64(changes) unless changes.include?(' ')
changes = utf8_encode_changes(changes)
changes.lines
end
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
index 7d2d545b84e..4750675ae9d 100644
--- a/lib/gitlab/github_import/branch_formatter.rb
+++ b/lib/gitlab/github_import/branch_formatter.rb
@@ -7,10 +7,6 @@ module Gitlab
branch_exists? && commit_exists?
end
- def name
- @name ||= exists? ? ref : "#{ref}-#{short_id}"
- end
-
def valid?
repo.present?
end
diff --git a/lib/gitlab/github_import/hook_formatter.rb b/lib/gitlab/github_import/hook_formatter.rb
deleted file mode 100644
index db1fabaa18a..00000000000
--- a/lib/gitlab/github_import/hook_formatter.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Gitlab
- module GithubImport
- class HookFormatter
- EVENTS = %w[* create delete pull_request push].freeze
-
- attr_reader :raw
-
- delegate :id, :name, :active, to: :raw
-
- def initialize(raw)
- @raw = raw
- end
-
- def config
- raw.config.attrs
- end
-
- def valid?
- (EVENTS & raw.events).any? && active
- end
- end
- end
-end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 3932fcb1eda..9ddc8905bd6 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -12,7 +12,6 @@ module Gitlab
if credentials
@client = Client.new(credentials[:user])
- @formatter = Gitlab::ImportFormatter.new
else
raise Projects::ImportService::Error, "Unable to find project import data credentials for project ID: #{@project.id}"
end
@@ -66,73 +65,45 @@ module Gitlab
end
def import_pull_requests
- disable_webhooks
-
pull_requests = client.pull_requests(repo, state: :all, sort: :created, direction: :asc, per_page: 100)
pull_requests = pull_requests.map { |raw| PullRequestFormatter.new(project, raw) }.select(&:valid?)
- source_branches_removed = pull_requests.reject(&:source_branch_exists?).map { |pr| [pr.source_branch_name, pr.source_branch_sha] }
- target_branches_removed = pull_requests.reject(&:target_branch_exists?).map { |pr| [pr.target_branch_name, pr.target_branch_sha] }
- branches_removed = source_branches_removed | target_branches_removed
-
- restore_branches(branches_removed)
-
pull_requests.each do |pull_request|
- merge_request = pull_request.create!
- apply_labels(merge_request)
- import_comments(merge_request)
- import_comments_on_diff(merge_request)
+ begin
+ restore_source_branch(pull_request) unless pull_request.source_branch_exists?
+ restore_target_branch(pull_request) unless pull_request.target_branch_exists?
+
+ merge_request = pull_request.create!
+ apply_labels(merge_request)
+ import_comments(merge_request)
+ import_comments_on_diff(merge_request)
+ rescue ActiveRecord::RecordInvalid => e
+ raise Projects::ImportService::Error, e.message
+ ensure
+ clean_up_restored_branches(pull_request)
+ end
end
true
- rescue ActiveRecord::RecordInvalid => e
- raise Projects::ImportService::Error, e.message
- ensure
- clean_up_restored_branches(branches_removed)
- clean_up_disabled_webhooks
end
- def disable_webhooks
- update_webhooks(hooks, active: false)
+ def restore_source_branch(pull_request)
+ project.repository.fetch_ref(repo_url, "pull/#{pull_request.number}/head", pull_request.source_branch_name)
end
- def clean_up_disabled_webhooks
- update_webhooks(hooks, active: true)
+ def restore_target_branch(pull_request)
+ project.repository.create_branch(pull_request.target_branch_name, pull_request.target_branch_sha)
end
- def update_webhooks(hooks, options)
- hooks.each do |hook|
- client.edit_hook(repo, hook.id, hook.name, hook.config, options)
- end
+ def remove_branch(name)
+ project.repository.delete_branch(name)
+ rescue Rugged::ReferenceError
+ nil
end
- def hooks
- @hooks ||=
- begin
- client.hooks(repo).map { |raw| HookFormatter.new(raw) }.select(&:valid?)
-
- # The GitHub Repository Webhooks API returns 404 for users
- # without admin access to the repository when listing hooks.
- # In this case we just want to return gracefully instead of
- # spitting out an error and stop the import process.
- rescue Octokit::NotFound
- []
- end
- end
-
- def restore_branches(branches)
- branches.each do |name, sha|
- client.create_ref(repo, "refs/heads/#{name}", sha)
- end
-
- project.repository.fetch_ref(repo_url, '+refs/heads/*', 'refs/heads/*')
- end
-
- def clean_up_restored_branches(branches)
- branches.each do |name, _|
- client.delete_ref(repo, "heads/#{name}")
- project.repository.delete_branch(name) rescue Rugged::ReferenceError
- end
+ def clean_up_restored_branches(pull_request)
+ remove_branch(pull_request.source_branch_name) unless pull_request.source_branch_exists?
+ remove_branch(pull_request.target_branch_name) unless pull_request.target_branch_exists?
project.repository.after_remove_branch
end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
index a4ea2210abd..b84538a090a 100644
--- a/lib/gitlab/github_import/pull_request_formatter.rb
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -1,8 +1,8 @@
module Gitlab
module GithubImport
class PullRequestFormatter < BaseFormatter
- delegate :exists?, :name, :project, :repo, :sha, to: :source_branch, prefix: true
- delegate :exists?, :name, :project, :repo, :sha, to: :target_branch, prefix: true
+ delegate :exists?, :project, :ref, :repo, :sha, to: :source_branch, prefix: true
+ delegate :exists?, :project, :ref, :repo, :sha, to: :target_branch, prefix: true
def attributes
{
@@ -33,17 +33,29 @@ module Gitlab
end
def valid?
- source_branch.valid? && target_branch.valid? && !cross_project?
+ source_branch.valid? && target_branch.valid?
end
def source_branch
@source_branch ||= BranchFormatter.new(project, raw_data.head)
end
+ def source_branch_name
+ @source_branch_name ||= begin
+ source_branch_exists? ? source_branch_ref : "pull/#{number}/#{source_branch_ref}"
+ end
+ end
+
def target_branch
@target_branch ||= BranchFormatter.new(project, raw_data.base)
end
+ def target_branch_name
+ @target_branch_name ||= begin
+ target_branch_exists? ? target_branch_ref : "pull/#{number}/#{target_branch_ref}"
+ end
+ end
+
private
def assigned?
@@ -68,10 +80,6 @@ module Gitlab
raw_data.body || ""
end
- def cross_project?
- source_branch_repo.id != target_branch_repo.id
- end
-
def description
formatter.author_line(author) + body
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index d6d14bd98a0..bb562bdcd2c 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,7 @@ module Gitlab
module ImportExport
extend self
- VERSION = '0.1.2'
+ VERSION = '0.1.3'
FILENAME_LIMIT = 50
def export_path(relative_path:)
@@ -13,6 +13,10 @@ module Gitlab
File.join(Settings.shared['path'], 'tmp/project_exports')
end
+ def import_upload_path(filename:)
+ File.join(storage_path, 'uploads', filename)
+ end
+
def project_filename
"project.json"
end
diff --git a/lib/gitlab/import_export/avatar_restorer.rb b/lib/gitlab/import_export/avatar_restorer.rb
index 352539eb594..cfa595629f4 100644
--- a/lib/gitlab/import_export/avatar_restorer.rb
+++ b/lib/gitlab/import_export/avatar_restorer.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class AvatarRestorer
-
def initialize(project:, shared:)
@project = project
@shared = shared
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index 5dd0e34c18e..e522a0fc8f6 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -17,6 +17,10 @@ module Gitlab
execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path}))
end
+ def git_restore_hooks
+ execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args)
+ end
+
private
def tar_with_options(archive:, dir:, options:)
@@ -45,6 +49,10 @@ module Gitlab
FileUtils.copy_entry(source, destination)
true
end
+
+ def repository_storage_paths_args
+ Gitlab.config.repositories.storages.values
+ end
end
end
end
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 82d1e1805c5..eca6e5b6d51 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -3,6 +3,8 @@ module Gitlab
class FileImporter
include Gitlab::ImportExport::CommandLineUtil
+ MAX_RETRIES = 8
+
def self.import(*args)
new(*args).import
end
@@ -14,7 +16,10 @@ module Gitlab
def import
FileUtils.mkdir_p(@shared.export_path)
- decompress_archive
+
+ wait_for_archived_file do
+ decompress_archive
+ end
rescue => e
@shared.error(e)
false
@@ -22,6 +27,17 @@ module Gitlab
private
+ # Exponentially sleep until I/O finishes copying the file
+ def wait_for_archived_file
+ MAX_RETRIES.times do |retry_number|
+ break if File.exist?(@archive_file)
+
+ sleep(2**retry_number)
+ end
+
+ yield
+ end
+
def decompress_archive
result = untar_zxf(archive: @archive_file, dir: @shared.export_path)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 15afe8174a4..1da51043611 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -3,11 +3,12 @@ project_tree:
- issues:
- :events
- notes:
- - :author
- - :events
- - :labels
- - milestones:
- - :events
+ - :author
+ - :events
+ - label_links:
+ - :label
+ - milestone:
+ - :events
- snippets:
- notes:
:author
@@ -20,6 +21,10 @@ project_tree:
- :events
- :merge_request_diff
- :events
+ - label_links:
+ - :label
+ - milestone:
+ - :events
- pipelines:
- notes:
- :author
@@ -31,6 +36,9 @@ project_tree:
- :services
- :hooks
- :protected_branches
+ - :labels
+ - milestones:
+ - :events
# Only include the following attributes for the models specified.
included_attributes:
@@ -55,6 +63,10 @@ excluded_attributes:
- :expired_at
merge_request_diff:
- :st_diffs
+ issues:
+ - :milestone_id
+ merge_requests:
+ - :milestone_id
methods:
statuses:
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
new file mode 100644
index 00000000000..008300bde45
--- /dev/null
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -0,0 +1,110 @@
+module Gitlab
+ module ImportExport
+ # Generates a hash that conforms with http://apidock.com/rails/Hash/to_json
+ # and its peculiar options.
+ class JsonHashBuilder
+ def self.build(model_objects, attributes_finder)
+ new(model_objects, attributes_finder).build
+ end
+
+ def initialize(model_objects, attributes_finder)
+ @model_objects = model_objects
+ @attributes_finder = attributes_finder
+ end
+
+ def build
+ process_model_objects(@model_objects)
+ end
+
+ private
+
+ # Called when the model is actually a hash containing other relations (more models)
+ # Returns the config in the right format for calling +to_json+
+ #
+ # +model_object_hash+ - A model relationship such as:
+ # {:merge_requests=>[:merge_request_diff, :notes]}
+ def process_model_objects(model_object_hash)
+ json_config_hash = {}
+ current_key = model_object_hash.keys.first
+
+ model_object_hash.values.flatten.each do |model_object|
+ @attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash }
+ handle_model_object(current_key, model_object, json_config_hash)
+ end
+
+ json_config_hash
+ end
+
+ # Creates or adds to an existing hash an individual model or list
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +model_object+ model or list of models to include in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def handle_model_object(current_key, model_object, json_config_hash)
+ model_or_sub_model = model_object.is_a?(Hash) ? process_model_objects(model_object) : model_object
+
+ if json_config_hash[current_key]
+ add_model_value(current_key, model_or_sub_model, json_config_hash)
+ else
+ create_model_value(current_key, model_or_sub_model, json_config_hash)
+ end
+ end
+
+ # Constructs a new hash that will hold the configuration for that particular object
+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def create_model_value(current_key, value, json_config_hash)
+ parsed_hash = { include: value }
+ parse_hash(value, parsed_hash)
+
+ json_config_hash[current_key] = parsed_hash
+ end
+
+ # Calls attributes finder to parse the hash and add any attributes to it
+ #
+ # +value+ existing model to be included in the hash
+ # +parsed_hash+ the original hash
+ def parse_hash(value, parsed_hash)
+ @attributes_finder.parse(value) do |hash|
+ parsed_hash = { include: hash_or_merge(value, hash) }
+ end
+ end
+
+ # Adds new model configuration to an existing hash with key +current_key+
+ # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def add_model_value(current_key, value, json_config_hash)
+ @attributes_finder.parse(value) { |hash| value = { value => hash } }
+
+ add_to_array(current_key, json_config_hash, value)
+ end
+
+ # Adds new model configuration to an existing hash with key +current_key+
+ # it creates a new array if it was previously a single value
+ #
+ # +current_key+ main model that will be a key in the hash
+ # +value+ existing model to be included in the hash
+ # +json_config_hash+ the original hash containing the root model
+ def add_to_array(current_key, json_config_hash, value)
+ old_values = json_config_hash[current_key][:include]
+
+ json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
+ end
+
+ # Construct a new hash or merge with an existing one a model configuration
+ # This is to fulfil +to_json+ requirements.
+ #
+ # +hash+ hash containing configuration generated mainly from +@attributes_finder+
+ # +value+ existing model to be included in the hash
+ def hash_or_merge(value, hash)
+ value.is_a?(Hash) ? value.merge(hash) : { value => hash }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index b459054c198..36c4cf6efa0 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -18,11 +18,14 @@ module Gitlab
@map ||=
begin
@exported_members.inject(missing_keys_tracking_hash) do |hash, member|
- existing_user = User.where(find_project_user_query(member)).first
- old_user_id = member['user']['id']
- if existing_user && add_user_as_team_member(existing_user, member)
- hash[old_user_id] = existing_user.id
+ if member['user']
+ old_user_id = member['user']['id']
+ existing_user = User.where(find_project_user_query(member)).first
+ hash[old_user_id] = existing_user.id if existing_user && add_team_member(member, existing_user)
+ else
+ add_team_member(member)
end
+
hash
end
end
@@ -45,7 +48,7 @@ module Gitlab
ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true)
end
- def add_user_as_team_member(existing_user, member)
+ def add_team_member(member, existing_user = nil)
member['user'] = existing_user
ProjectMember.create(member_hash(member)).persisted?
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 051110c23cf..c7b3551b84c 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -47,7 +47,7 @@ module Gitlab
relation_key = relation.is_a?(Hash) ? relation.keys.first : relation
relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s])
- saved << restored_project.update_attribute(relation_key, relation_hash)
+ saved << restored_project.append_or_update_attribute(relation_key, relation_hash)
end
saved.all?
end
@@ -78,7 +78,7 @@ module Gitlab
relation_key = relation.keys.first.to_s
return if tree_hash[relation_key].blank?
- tree_hash[relation_key].each do |relation_item|
+ [tree_hash[relation_key]].flatten.each do |relation_item|
relation.values.flatten.each do |sub_relation|
# We just use author to get the user ID, do not attempt to create an instance.
next if sub_relation == :author
diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb
index 15f5dd31035..5021a1a14ce 100644
--- a/lib/gitlab/import_export/reader.rb
+++ b/lib/gitlab/import_export/reader.rb
@@ -29,87 +29,12 @@ module Gitlab
def build_hash(model_list)
model_list.map do |model_objects|
if model_objects.is_a?(Hash)
- build_json_config_hash(model_objects)
+ Gitlab::ImportExport::JsonHashBuilder.build(model_objects, @attributes_finder)
else
@attributes_finder.find(model_objects)
end
end
end
-
- # Called when the model is actually a hash containing other relations (more models)
- # Returns the config in the right format for calling +to_json+
- # +model_object_hash+ - A model relationship such as:
- # {:merge_requests=>[:merge_request_diff, :notes]}
- def build_json_config_hash(model_object_hash)
- @json_config_hash = {}
-
- model_object_hash.values.flatten.each do |model_object|
- current_key = model_object_hash.keys.first
-
- @attributes_finder.parse(current_key) { |hash| @json_config_hash[current_key] ||= hash }
-
- handle_model_object(current_key, model_object)
- process_sub_model(current_key, model_object) if model_object.is_a?(Hash)
- end
- @json_config_hash
- end
-
- # If the model is a hash, process the sub_models, which could also be hashes
- # If there is a list, add to an existing array, otherwise use hash syntax
- # +current_key+ main model that will be a key in the hash
- # +model_object+ model or list of models to include in the hash
- def process_sub_model(current_key, model_object)
- sub_model_json = build_json_config_hash(model_object).dup
- @json_config_hash.slice!(current_key)
-
- if @json_config_hash[current_key] && @json_config_hash[current_key][:include]
- @json_config_hash[current_key][:include] << sub_model_json
- else
- @json_config_hash[current_key] = { include: sub_model_json }
- end
- end
-
- # Creates or adds to an existing hash an individual model or list
- # +current_key+ main model that will be a key in the hash
- # +model_object+ model or list of models to include in the hash
- def handle_model_object(current_key, model_object)
- if @json_config_hash[current_key]
- add_model_value(current_key, model_object)
- else
- create_model_value(current_key, model_object)
- end
- end
-
- # Constructs a new hash that will hold the configuration for that particular object
- # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
- # +current_key+ main model that will be a key in the hash
- # +value+ existing model to be included in the hash
- def create_model_value(current_key, value)
- parsed_hash = { include: value }
-
- @attributes_finder.parse(value) do |hash|
- parsed_hash = { include: hash_or_merge(value, hash) }
- end
- @json_config_hash[current_key] = parsed_hash
- end
-
- # Adds new model configuration to an existing hash with key +current_key+
- # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+
- # +current_key+ main model that will be a key in the hash
- # +value+ existing model to be included in the hash
- def add_model_value(current_key, value)
- @attributes_finder.parse(value) { |hash| value = { value => hash } }
- old_values = @json_config_hash[current_key][:include]
- @json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten
- end
-
- # Construct a new hash or merge with an existing one a model configuration
- # This is to fulfil +to_json+ requirements.
- # +value+ existing model to be included in the hash
- # +hash+ hash containing configuration generated mainly from +@attributes_finder+
- def hash_or_merge(value, hash)
- value.is_a?(Hash) ? value.merge(hash) : { value => hash }
- end
end
end
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index e41c7e6bf4f..b0726268ca6 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -13,6 +13,10 @@ module Gitlab
BUILD_MODELS = %w[Ci::Build commit_status].freeze
+ IMPORTED_OBJECT_MAX_RETRIES = 5.freeze
+
+ EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze
+
def self.create(*args)
new(*args).create
end
@@ -22,24 +26,35 @@ module Gitlab
@relation_hash = relation_hash.except('id', 'noteable_id')
@members_mapper = members_mapper
@user = user
+ @imported_object_retries = 0
end
# Creates an object from an actual model with name "relation_sym" with params from
# the relation_hash, updating references with new object IDs, mapping users using
# the "members_mapper" object, also updating notes if required.
def create
- set_note_author if @relation_name == :notes
+ setup_models
+
+ generate_imported_object
+ end
+
+ private
+
+ def setup_models
+ if @relation_name == :notes
+ set_note_author
+
+ # attachment is deprecated and note uploads are handled by Markdown uploader
+ @relation_hash['attachment'] = nil
+ end
+
update_user_references
update_project_references
reset_ci_tokens if @relation_name == 'Ci::Trigger'
@relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
set_st_diffs if @relation_name == :merge_request_diff
-
- generate_imported_object
end
- private
-
def update_user_references
USER_REFERENCES.each do |reference|
if @relation_hash[reference]
@@ -87,17 +102,19 @@ module Gitlab
def update_project_references
project_id = @relation_hash.delete('project_id')
+ # If source and target are the same, populate them with the new project ID.
+ if @relation_hash['source_project_id']
+ @relation_hash['source_project_id'] = same_source_and_target? ? project_id : -1
+ end
+
# project_id may not be part of the export, but we always need to populate it if required.
@relation_hash['project_id'] = project_id
@relation_hash['gl_project_id'] = project_id if @relation_hash['gl_project_id']
@relation_hash['target_project_id'] = project_id if @relation_hash['target_project_id']
- @relation_hash['source_project_id'] = -1 if @relation_hash['source_project_id']
+ end
- # If source and target are the same, populate them with the new project ID.
- if @relation_hash['source_project_id'] && @relation_hash['target_project_id'] &&
- @relation_hash['target_project_id'] == @relation_hash['source_project_id']
- @relation_hash['source_project_id'] = project_id
- end
+ def same_source_and_target?
+ @relation_hash['target_project_id'] && @relation_hash['target_project_id'] == @relation_hash['source_project_id']
end
def reset_ci_tokens
@@ -112,10 +129,14 @@ module Gitlab
end
def imported_object
- imported_object = relation_class.new(parsed_relation_hash)
- yield(imported_object) if block_given?
- imported_object.importing = true if imported_object.respond_to?(:importing)
- imported_object
+ yield(existing_or_new_object) if block_given?
+ existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing)
+ existing_or_new_object
+ rescue ActiveRecord::RecordNotUnique
+ # as the operation is not atomic, retry in the unlikely scenario an INSERT is
+ # performed on the same object between the SELECT and the INSERT
+ @imported_object_retries += 1
+ retry if @imported_object_retries < IMPORTED_OBJECT_MAX_RETRIES
end
def update_note_for_missing_author(author_name)
@@ -134,6 +155,20 @@ module Gitlab
def set_st_diffs
@relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
end
+
+ def existing_or_new_object
+ # Only find existing records to avoid mapping tables such as milestones
+ # Otherwise always create the record, skipping the extra SELECT clause.
+ @existing_or_new_object ||= begin
+ if EXISTING_OBJECT_CHECK.include?(@relation_name)
+ existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id'))
+ existing_object.assign_attributes(parsed_relation_hash)
+ existing_object
+ else
+ relation_class.new(parsed_relation_hash)
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index f84de652a57..6d9379acf25 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -14,7 +14,7 @@ module Gitlab
FileUtils.mkdir_p(path_to_repo)
- git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle)
+ git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks
rescue => e
@shared.error(e)
false
@@ -29,6 +29,16 @@ module Gitlab
def path_to_repo
@project.repository.path_to_repo
end
+
+ def repo_restore_hooks
+ return true if wiki?
+
+ git_restore_hooks
+ end
+
+ def wiki?
+ @project.class.name == 'ProjectWiki'
+ end
end
end
end
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index abfc694b879..de3fe6d822e 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -25,7 +25,7 @@ module Gitlab
def verify_version!(version)
if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version)
- raise Gitlab::ImportExport::Error("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+ raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
else
true
end
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 8ce9d32abe0..d7be50bd437 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -1,7 +1,7 @@
module Gitlab
module IncomingEmail
class << self
- FALLBACK_REPLY_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
+ FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze
def enabled?
config.enabled && config.address
@@ -21,8 +21,8 @@ module Gitlab
match[1]
end
- def key_from_fallback_reply_message_id(message_id)
- match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX)
+ def key_from_fallback_message_id(mail_id)
+ match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX)
return unless match
match[1]
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index f2b649e50a2..2f326d00a2f 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -25,7 +25,7 @@ module Gitlab
end
end
- def initialize(user, adapter=nil)
+ def initialize(user, adapter = nil)
@adapter = adapter
@user = user
@provider = user.ldap_identity.provider
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index df65179bfea..9a5bcfb5c9b 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -13,7 +13,7 @@ module Gitlab
Gitlab::LDAP::Config.new(provider)
end
- def initialize(provider, ldap=nil)
+ def initialize(provider, ldap = nil)
@provider = provider
@ldap = ldap || Net::LDAP.new(config.adapter_options)
end
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
deleted file mode 100644
index a1ee1aa81ff..00000000000
--- a/lib/gitlab/lfs/response.rb
+++ /dev/null
@@ -1,329 +0,0 @@
-module Gitlab
- module Lfs
- class Response
- def initialize(project, user, ci, request)
- @origin_project = project
- @project = storage_project(project)
- @user = user
- @ci = ci
- @env = request.env
- @request = request
- end
-
- def render_download_object_response(oid)
- render_response_to_download do
- if check_download_sendfile_header?
- render_lfs_sendfile(oid)
- else
- render_not_found
- end
- end
- end
-
- def render_batch_operation_response
- request_body = JSON.parse(@request.body.read)
- case request_body["operation"]
- when "download"
- render_batch_download(request_body)
- when "upload"
- render_batch_upload(request_body)
- else
- render_not_found
- end
- end
-
- def render_storage_upload_authorize_response(oid, size)
- render_response_to_push do
- [
- 200,
- { "Content-Type" => "application/json; charset=utf-8" },
- [JSON.dump({
- 'StoreLFSPath' => "#{Gitlab.config.lfs.storage_path}/tmp/upload",
- 'LfsOid' => oid,
- 'LfsSize' => size
- })]
- ]
- end
- end
-
- def render_storage_upload_store_response(oid, size, tmp_file_name)
- return render_forbidden unless tmp_file_name
-
- render_response_to_push do
- render_lfs_upload_ok(oid, size, tmp_file_name)
- end
- end
-
- def render_unsupported_deprecated_api
- [
- 501,
- { "Content-Type" => "application/json; charset=utf-8" },
- [JSON.dump({
- 'message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
- 'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
- })]
- ]
- end
-
- private
-
- def render_not_enabled
- [
- 501,
- {
- "Content-Type" => "application/json; charset=utf-8",
- },
- [JSON.dump({
- 'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.',
- 'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
- })]
- ]
- end
-
- def render_unauthorized
- [
- 401,
- {
- 'Content-Type' => 'text/plain'
- },
- ['Unauthorized']
- ]
- end
-
- def render_not_found
- [
- 404,
- {
- "Content-Type" => "application/vnd.git-lfs+json"
- },
- [JSON.dump({
- 'message' => 'Not found.',
- 'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
- })]
- ]
- end
-
- def render_forbidden
- [
- 403,
- {
- "Content-Type" => "application/vnd.git-lfs+json"
- },
- [JSON.dump({
- 'message' => 'Access forbidden. Check your access level.',
- 'documentation_url' => "#{Gitlab.config.gitlab.url}/help",
- })]
- ]
- end
-
- def render_lfs_sendfile(oid)
- return render_not_found unless oid.present?
-
- lfs_object = object_for_download(oid)
-
- if lfs_object && lfs_object.file.exists?
- [
- 200,
- {
- # GitLab-workhorse will forward Content-Type header
- "Content-Type" => "application/octet-stream",
- "X-Sendfile" => lfs_object.file.path
- },
- []
- ]
- else
- render_not_found
- end
- end
-
- def render_batch_upload(body)
- return render_not_found if body.empty? || body['objects'].nil?
-
- render_response_to_push do
- response = build_upload_batch_response(body['objects'])
- [
- 200,
- {
- "Content-Type" => "application/json; charset=utf-8",
- "Cache-Control" => "private",
- },
- [JSON.dump(response)]
- ]
- end
- end
-
- def render_batch_download(body)
- return render_not_found if body.empty? || body['objects'].nil?
-
- render_response_to_download do
- response = build_download_batch_response(body['objects'])
- [
- 200,
- {
- "Content-Type" => "application/json; charset=utf-8",
- "Cache-Control" => "private",
- },
- [JSON.dump(response)]
- ]
- end
- end
-
- def render_lfs_upload_ok(oid, size, tmp_file)
- if store_file(oid, size, tmp_file)
- [
- 200,
- {
- 'Content-Type' => 'text/plain',
- 'Content-Length' => 0
- },
- []
- ]
- else
- [
- 422,
- { 'Content-Type' => 'text/plain' },
- ["Unprocessable entity"]
- ]
- end
- end
-
- def render_response_to_download
- return render_not_enabled unless Gitlab.config.lfs.enabled
-
- unless @project.public?
- return render_unauthorized unless @user || @ci
- return render_forbidden unless user_can_fetch?
- end
-
- yield
- end
-
- def render_response_to_push
- return render_not_enabled unless Gitlab.config.lfs.enabled
- return render_unauthorized unless @user
- return render_forbidden unless user_can_push?
-
- yield
- end
-
- def check_download_sendfile_header?
- @env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile"
- end
-
- def user_can_fetch?
- # Check user access against the project they used to initiate the pull
- @ci || @user.can?(:download_code, @origin_project)
- end
-
- def user_can_push?
- # Check user access against the project they used to initiate the push
- @user.can?(:push_code, @origin_project)
- end
-
- def storage_project(project)
- if project.forked?
- storage_project(project.forked_from_project)
- else
- project
- end
- end
-
- def store_file(oid, size, tmp_file)
- tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file)
-
- object = LfsObject.find_or_create_by(oid: oid, size: size)
- if object.file.exists?
- success = true
- else
- success = move_tmp_file_to_storage(object, tmp_file_path)
- end
-
- if success
- success = link_to_project(object)
- end
-
- success
- ensure
- # Ensure that the tmp file is removed
- FileUtils.rm_f(tmp_file_path)
- end
-
- def object_for_download(oid)
- @project.lfs_objects.find_by(oid: oid)
- end
-
- def move_tmp_file_to_storage(object, path)
- File.open(path) do |f|
- object.file = f
- end
-
- object.file.store!
- object.save
- end
-
- def link_to_project(object)
- if object && !object.projects.exists?(@project.id)
- object.projects << @project
- object.save
- end
- end
-
- def select_existing_objects(objects)
- objects_oids = objects.map { |o| o['oid'] }
- @project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set
- end
-
- def build_upload_batch_response(objects)
- selected_objects = select_existing_objects(objects)
-
- upload_hypermedia_links(objects, selected_objects)
- end
-
- def build_download_batch_response(objects)
- selected_objects = select_existing_objects(objects)
-
- download_hypermedia_links(objects, selected_objects)
- end
-
- def download_hypermedia_links(all_objects, existing_objects)
- all_objects.each do |object|
- if existing_objects.include?(object['oid'])
- object['actions'] = {
- 'download' => {
- 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}",
- 'header' => {
- 'Authorization' => @env['HTTP_AUTHORIZATION']
- }.compact
- }
- }
- else
- object['error'] = {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- end
- end
-
- { 'objects' => all_objects }
- end
-
- def upload_hypermedia_links(all_objects, existing_objects)
- all_objects.each do |object|
- # generate actions only for non-existing objects
- next if existing_objects.include?(object['oid'])
-
- object['actions'] = {
- 'upload' => {
- 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
- 'header' => {
- 'Authorization' => @env['HTTP_AUTHORIZATION']
- }.compact
- }
- }
- end
-
- { 'objects' => all_objects }
- end
- end
- end
-end
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
deleted file mode 100644
index f2a76a56b8f..00000000000
--- a/lib/gitlab/lfs/router.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-module Gitlab
- module Lfs
- class Router
- attr_reader :project, :user, :ci, :request
-
- def initialize(project, user, ci, request)
- @project = project
- @user = user
- @ci = ci
- @env = request.env
- @request = request
- end
-
- def try_call
- return unless @request && @request.path.present?
-
- case @request.request_method
- when 'GET'
- get_response
- when 'POST'
- post_response
- when 'PUT'
- put_response
- else
- nil
- end
- end
-
- private
-
- def get_response
- path_match = @request.path.match(/\/(info\/lfs|gitlab-lfs)\/objects\/([0-9a-f]{64})$/)
- return nil unless path_match
-
- oid = path_match[2]
- return nil unless oid
-
- case path_match[1]
- when "info/lfs"
- lfs.render_unsupported_deprecated_api
- when "gitlab-lfs"
- lfs.render_download_object_response(oid)
- else
- nil
- end
- end
-
- def post_response
- post_path = @request.path.match(/\/info\/lfs\/objects(\/batch)?$/)
- return nil unless post_path
-
- # Check for Batch API
- if post_path[0].ends_with?("/info/lfs/objects/batch")
- lfs.render_batch_operation_response
- elsif post_path[0].ends_with?("/info/lfs/objects")
- lfs.render_unsupported_deprecated_api
- else
- nil
- end
- end
-
- def put_response
- object_match = @request.path.match(/\/gitlab-lfs\/objects\/([0-9a-f]{64})\/([0-9]+)(|\/authorize){1}$/)
- return nil if object_match.nil?
-
- oid = object_match[1]
- size = object_match[2].try(:to_i)
- return nil if oid.nil? || size.nil?
-
- # GitLab-workhorse requests
- # 1. Try to authorize the request
- # 2. send a request with a header containing the name of the temporary file
- if object_match[3] && object_match[3] == '/authorize'
- lfs.render_storage_upload_authorize_response(oid, size)
- else
- tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
- lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
- end
- end
-
- def lfs
- return unless @project
-
- Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
- end
-
- def sanitize_tmp_filename(name)
- if name.present?
- name.gsub!(/^.*(\\|\/)/, '')
- name = name.match(/[0-9a-f]{73}/)
- name[0] if name
- else
- nil
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/mail_room.rb b/lib/gitlab/mail_room.rb
new file mode 100644
index 00000000000..12999a90a29
--- /dev/null
+++ b/lib/gitlab/mail_room.rb
@@ -0,0 +1,47 @@
+require 'yaml'
+require 'json'
+require_relative 'redis' unless defined?(Gitlab::Redis)
+
+module Gitlab
+ module MailRoom
+ class << self
+ def enabled?
+ config[:enabled] && config[:address]
+ end
+
+ def config
+ @config ||= fetch_config
+ end
+
+ def reset_config!
+ @config = nil
+ end
+
+ private
+
+ def fetch_config
+ return {} unless File.exist?(config_file)
+
+ rails_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
+ all_config = YAML.load_file(config_file)[rails_env].deep_symbolize_keys
+
+ config = all_config[:incoming_email] || {}
+ config[:enabled] = false if config[:enabled].nil?
+ config[:port] = 143 if config[:port].nil?
+ config[:ssl] = false if config[:ssl].nil?
+ config[:start_tls] = false if config[:start_tls].nil?
+ config[:mailbox] = 'inbox' if config[:mailbox].nil?
+
+ if config[:enabled] && config[:address]
+ config[:redis_url] = Gitlab::Redis.new(rails_env).url
+ end
+
+ config
+ end
+
+ def config_file
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] || File.expand_path('../../../config/gitlab.yml', __FILE__)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 49f702f91f6..41fcd971c22 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -124,6 +124,11 @@ module Gitlab
trans.action = action if trans
end
+ # Returns the prefix to use for the name of a series.
+ def self.series_prefix
+ @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
+ end
+
# When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe.
if enabled?
@@ -136,8 +141,7 @@ module Gitlab
end
end
- private
-
+ # Allow access from other metrics related middlewares
def self.current_transaction
Transaction.current
end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index dcec7543c13..4b7a791e497 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -9,14 +9,17 @@ module Gitlab
#
# Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
module Instrumentation
- SERIES = 'method_calls'
-
PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure
yield self
end
+ # Returns the name of the series to use for storing method calls.
+ def self.series
+ @series ||= "#{Metrics.series_prefix}method_calls"
+ end
+
# Instruments a class method.
#
# mod - The module to instrument as a Module/Class.
@@ -141,15 +144,15 @@ module Gitlab
# generated method _only_ accepts regular arguments if the underlying
# method also accepts them.
if method.arity == 0
- args_signature = '&block'
+ args_signature = ''
else
- args_signature = '*args, &block'
+ args_signature = '*args'
end
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature})
if trans = Gitlab::Metrics::Instrumentation.transaction
- trans.measure_method(#{label.inspect}) { super }
+ trans.method_call_for(#{label.to_sym.inspect}).measure { super }
else
super
end
diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb
index c048fe20ba7..d3465e5ec19 100644
--- a/lib/gitlab/metrics/method_call.rb
+++ b/lib/gitlab/metrics/method_call.rb
@@ -11,8 +11,8 @@ module Gitlab
def initialize(name, series)
@name = name
@series = series
- @real_time = 0.0
- @cpu_time = 0.0
+ @real_time = 0
+ @cpu_time = 0
@call_count = 0
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 82c18bb108b..287b7a83547 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -35,12 +35,12 @@ module Gitlab
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time
Process.
- clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f
+ clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end
else
def self.cpu_time
Process.
- clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f
+ clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end
end
@@ -48,14 +48,14 @@ module Gitlab
#
# Returns the time as a Float.
def self.real_time(precision = :millisecond)
- Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f
+ Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end
# Returns the current monotonic clock time in a given precision.
#
# Returns the time as a Float.
def self.monotonic_time(precision = :millisecond)
- Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
end
end
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index bded245da43..968f3218950 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -52,23 +52,16 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- @metrics << Metric.new("#{series_prefix}#{series}", values, tags)
+ @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
end
- # Measures the time it takes to execute a method.
- #
- # Multiple calls to the same method add up to the total runtime of the
- # method.
- #
- # name - The full name of the method to measure (e.g. `User#sign_in`).
- def measure_method(name, &block)
- unless @methods[name]
- series = "#{series_prefix}#{Instrumentation::SERIES}"
-
- @methods[name] = MethodCall.new(name, series)
+ # Returns a MethodCall object for the given name.
+ def method_call_for(name)
+ unless method = @methods[name]
+ @methods[name] = method = MethodCall.new(name, Instrumentation.series)
end
- @methods[name].measure(&block)
+ method
end
def increment(name, value)
@@ -115,14 +108,6 @@ module Gitlab
Metrics.submit_metrics(submit_hashes)
end
-
- def sidekiq?
- Sidekiq.server?
- end
-
- def series_prefix
- sidekiq? ? 'sidekiq_' : 'rails_'
- end
end
end
end
diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb
index 43e07e09160..ca23ccef25b 100644
--- a/lib/gitlab/popen.rb
+++ b/lib/gitlab/popen.rb
@@ -5,7 +5,7 @@ module Gitlab
module Popen
extend self
- def popen(cmd, path=nil)
+ def popen(cmd, path = nil)
unless cmd.is_a?(Array)
raise "System commands must be given as an array of strings"
end
diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb
index 40766f35f77..9376b54f43b 100644
--- a/lib/gitlab/redis.rb
+++ b/lib/gitlab/redis.rb
@@ -1,50 +1,94 @@
+# This file should not have any direct dependency on Rails environment
+# please require all dependencies below:
+require 'active_support/core_ext/hash/keys'
+
module Gitlab
class Redis
CACHE_NAMESPACE = 'cache:gitlab'
SESSION_NAMESPACE = 'session:gitlab'
SIDEKIQ_NAMESPACE = 'resque:gitlab'
-
- attr_reader :url
+ MAILROOM_NAMESPACE = 'mail_room:gitlab'
+ DEFAULT_REDIS_URL = 'redis://localhost:6379'
# To be thread-safe we must be careful when writing the class instance
# variables @url and @pool. Because @pool depends on @url we need two
# mutexes to prevent deadlock.
- URL_MUTEX = Mutex.new
+ PARAMS_MUTEX = Mutex.new
POOL_MUTEX = Mutex.new
- private_constant :URL_MUTEX, :POOL_MUTEX
+ private_constant :PARAMS_MUTEX, :POOL_MUTEX
- def self.url
- @url || URL_MUTEX.synchronize { @url = new.url }
- end
+ class << self
+ def params
+ @params || PARAMS_MUTEX.synchronize { @params = new.params }
+ end
+
+ # @deprecated Use .params instead to get sentinel support
+ def url
+ new.url
+ end
- def self.with
- if @pool.nil?
- POOL_MUTEX.synchronize do
- @pool = ConnectionPool.new { ::Redis.new(url: url) }
+ def with
+ if @pool.nil?
+ POOL_MUTEX.synchronize do
+ @pool = ConnectionPool.new { ::Redis.new(params) }
+ end
end
+ @pool.with { |redis| yield redis }
+ end
+
+ def reset_params!
+ @params = nil
end
- @pool.with { |redis| yield redis }
end
- def self.redis_store_options
- url = new.url
- redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url)
- # Redis::Store does not handle Unix sockets well, so let's do it for them
- redis_uri = URI.parse(url)
+ def initialize(rails_env = nil)
+ @rails_env = rails_env || ::Rails.env
+ end
+
+ def params
+ redis_store_options
+ end
+
+ def url
+ raw_config_hash[:url]
+ end
+
+ private
+
+ def redis_store_options
+ config = raw_config_hash
+ redis_url = config.delete(:url)
+ redis_uri = URI.parse(redis_url)
+
if redis_uri.scheme == 'unix'
- redis_config_hash[:path] = redis_uri.path
+ # Redis::Store does not handle Unix sockets well, so let's do it for them
+ config[:path] = redis_uri.path
+ config
+ else
+ redis_hash = ::Redis::Store::Factory.extract_host_options_from_uri(redis_url)
+ # order is important here, sentinels must be after the connection keys.
+ # {url: ..., port: ..., sentinels: [...]}
+ redis_hash.merge(config)
end
- redis_config_hash
end
- def initialize(rails_env=nil)
- rails_env ||= Rails.env
- config_file = File.expand_path('../../../config/resque.yml', __FILE__)
+ def raw_config_hash
+ config_data = fetch_config
- @url = "redis://localhost:6379"
- if File.exist?(config_file)
- @url = YAML.load_file(config_file)[rails_env]
+ if config_data
+ config_data.is_a?(String) ? { url: config_data } : config_data.deep_symbolize_keys
+ else
+ { url: DEFAULT_REDIS_URL }
end
end
+
+ def fetch_config
+ file = config_file
+ File.exist?(file) ? YAML.load_file(file)[@rails_env] : false
+ end
+
+ def config_file
+ File.expand_path('../../../config/resque.yml', __FILE__)
+ end
end
end
diff --git a/lib/gitlab/request_profiler.rb b/lib/gitlab/request_profiler.rb
new file mode 100644
index 00000000000..8130e55351e
--- /dev/null
+++ b/lib/gitlab/request_profiler.rb
@@ -0,0 +1,19 @@
+require 'fileutils'
+
+module Gitlab
+ module RequestProfiler
+ PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
+
+ def profile_token
+ Rails.cache.fetch('profile-token') do
+ Devise.friendly_token
+ end
+ end
+ module_function :profile_token
+
+ def remove_all_profiles
+ FileUtils.rm_rf(PROFILES_DIR)
+ end
+ module_function :remove_all_profiles
+ end
+end
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
new file mode 100644
index 00000000000..786e1d49f5e
--- /dev/null
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -0,0 +1,54 @@
+require 'ruby-prof'
+require_dependency 'gitlab/request_profiler'
+
+module Gitlab
+ module RequestProfiler
+ class Middleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if profile?(env)
+ call_with_profiling(env)
+ else
+ @app.call(env)
+ end
+ end
+
+ def profile?(env)
+ header_token = env['HTTP_X_PROFILE_TOKEN']
+ return unless header_token.present?
+
+ profile_token = RequestProfiler.profile_token
+ return unless profile_token.present?
+
+ header_token == profile_token
+ end
+
+ def call_with_profiling(env)
+ ret = nil
+ result = RubyProf::Profile.profile do
+ ret = catch(:warden) do
+ @app.call(env)
+ end
+ end
+
+ printer = RubyProf::CallStackPrinter.new(result)
+ file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
+ file_path = "#{PROFILES_DIR}/#{file_name}"
+
+ FileUtils.mkdir_p(PROFILES_DIR)
+ File.open(file_path, 'wb') do |file|
+ printer.print(file)
+ end
+
+ if ret.is_a?(Array)
+ ret
+ else
+ throw(:warden, ret)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
new file mode 100644
index 00000000000..f89d56903ef
--- /dev/null
+++ b/lib/gitlab/request_profiler/profile.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module RequestProfiler
+ class Profile
+ attr_reader :name, :time, :request_path
+
+ alias_method :to_param, :name
+
+ def self.all
+ Dir["#{PROFILES_DIR}/*.html"].map do |path|
+ new(File.basename(path))
+ end
+ end
+
+ def self.find(name)
+ name_dup = name.dup
+ name_dup << '.html' unless name.end_with?('.html')
+
+ file_path = "#{PROFILES_DIR}/#{name_dup}"
+ return unless File.exist?(file_path)
+
+ new(name_dup)
+ end
+
+ def initialize(name)
+ @name = name
+
+ set_attributes
+ end
+
+ def content
+ File.read("#{PROFILES_DIR}/#{name}")
+ end
+
+ private
+
+ def set_attributes
+ _, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
+ @request_path = path.tr('|', '/')
+ @time = Time.at(timestamp.to_i).utc
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/request_store_middleware.rb b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
new file mode 100644
index 00000000000..b1fa0e3cb4e
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/request_store_middleware.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module SidekiqMiddleware
+ class RequestStoreMiddleware
+ def call(worker, job, queue)
+ RequestStore.begin!
+ yield
+ ensure
+ RequestStore.end!
+ RequestStore.clear!
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
index 83f91de810c..d4020af76f9 100644
--- a/lib/gitlab/themes.rb
+++ b/lib/gitlab/themes.rb
@@ -2,6 +2,8 @@ module Gitlab
# Module containing GitLab's application theme definitions and helper methods
# for accessing them.
module Themes
+ extend self
+
# Theme ID used when no `default_theme` configuration setting is provided.
APPLICATION_DEFAULT = 2
@@ -22,7 +24,7 @@ module Gitlab
# classes that might be applied to the `body` element
#
# Returns a String
- def self.body_classes
+ def body_classes
THEMES.collect(&:css_class).uniq.join(' ')
end
@@ -33,26 +35,26 @@ module Gitlab
# id - Integer ID
#
# Returns a Theme
- def self.by_id(id)
+ def by_id(id)
THEMES.detect { |t| t.id == id } || default
end
# Returns the number of defined Themes
- def self.count
+ def count
THEMES.size
end
# Get the default Theme
#
# Returns a Theme
- def self.default
+ def default
by_id(default_id)
end
# Iterate through each Theme
#
# Yields the Theme object
- def self.each(&block)
+ def each(&block)
THEMES.each(&block)
end
@@ -61,7 +63,7 @@ module Gitlab
# user - User record
#
# Returns a Theme
- def self.for_user(user)
+ def for_user(user)
if user
by_id(user.theme_id)
else
@@ -71,7 +73,7 @@ module Gitlab
private
- def self.default_id
+ def default_id
id = Gitlab.config.gitlab.default_theme.to_i
# Prevent an invalid configuration setting from causing an infinite loop
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index c0f85e9b3a8..c55a7fc4d3d 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -29,8 +29,11 @@ module Gitlab
def can_push_to_branch?(ref)
return false unless user
- if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
- user.can?(:push_code_to_protected_branches, project)
+ if project.protected_branch?(ref)
+ return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user)
+
+ access_levels = project.protected_branches.matching(ref).map(&:push_access_level)
+ access_levels.any? { |access_level| access_level.check_access(user) }
else
user.can?(:push_code, project)
end
@@ -39,8 +42,9 @@ module Gitlab
def can_merge_to_branch?(ref)
return false unless user
- if project.protected_branch?(ref) && !project.developers_can_merge_to_protected_branch?(ref)
- user.can?(:push_code_to_protected_branches, project)
+ if project.protected_branch?(ref)
+ access_levels = project.protected_branches.matching(ref).map(&:merge_access_level)
+ access_levels.any? { |access_level| access_level.check_access(user) }
else
user.can?(:push_code, project)
end
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index f818dc78d34..4edfd015074 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -18,7 +18,7 @@ module Rouge
is_first = false
yield %(<span id="LC#{@line_number}" class="line">)
- line.each { |token, value| yield span(token, value) }
+ line.each { |token, value| yield span(token, value.chomp) }
yield %(</span>)
@line_number += 1
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 4a4892a2e07..d521de28e8a 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -49,12 +49,7 @@ server {
proxy_http_version 1.1;
- ## By overwriting Host and clearing X-Forwarded-Host we ensure that
- ## internal HTTP redirects generated by GitLab always send users to
- ## YOUR_SERVER_FQDN.
- proxy_set_header Host YOUR_SERVER_FQDN;
- proxy_set_header X-Forwarded-Host "";
-
+ proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 0b93d7f292f..bf014b56cf6 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -93,12 +93,7 @@ server {
proxy_http_version 1.1;
- ## By overwriting Host and clearing X-Forwarded-Host we ensure that
- ## internal HTTP redirects generated by GitLab always send users to
- ## YOUR_SERVER_FQDN.
- proxy_set_header Host YOUR_SERVER_FQDN;
- proxy_set_header X-Forwarded-Host "";
-
+ proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/lib/tasks/downtime_check.rake b/lib/tasks/downtime_check.rake
index 30a2e9be5ce..afe5d42910c 100644
--- a/lib/tasks/downtime_check.rake
+++ b/lib/tasks/downtime_check.rake
@@ -1,26 +1,12 @@
desc 'Checks if migrations in a branch require downtime'
task downtime_check: :environment do
- # First we'll want to make sure we're comparing with the right upstream
- # repository/branch.
- current_branch = `git rev-parse --abbrev-ref HEAD`.strip
-
- # Either the developer ran this task directly on the master branch, or they're
- # making changes directly on the master branch.
- if current_branch == 'master'
- if defined?(Gitlab::License)
- repo = 'gitlab-ee'
- else
- repo = 'gitlab-ce'
- end
-
- `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
-
- compare_with = 'FETCH_HEAD'
- # The developer is working on a different branch, in this case we can just
- # compare with the master branch.
+ if defined?(Gitlab::License)
+ repo = 'gitlab-ee'
else
- compare_with = 'master'
+ repo = 'gitlab-ce'
end
- Rake::Task['gitlab:db:downtime_check'].invoke(compare_with)
+ `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1`
+
+ Rake::Task['gitlab:db:downtime_check'].invoke('FETCH_HEAD')
end
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index 5dbf7d61e06..83dd870fa31 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -4,13 +4,13 @@ namespace :gitlab do
task all_users_to_all_projects: :environment do |t, args|
user_ids = User.where(admin: false).pluck(:id)
admin_ids = User.where(admin: true).pluck(:id)
- projects_ids = Project.pluck(:id)
+ project_ids = Project.pluck(:id)
- puts "Importing #{user_ids.size} users into #{projects_ids.size} projects"
- ProjectMember.add_users_into_projects(projects_ids, user_ids, ProjectMember::DEVELOPER)
+ puts "Importing #{user_ids.size} users into #{project_ids.size} projects"
+ ProjectMember.add_users_to_projects(project_ids, user_ids, ProjectMember::DEVELOPER)
- puts "Importing #{admin_ids.size} admins into #{projects_ids.size} projects"
- ProjectMember.add_users_into_projects(projects_ids, admin_ids, ProjectMember::MASTER)
+ puts "Importing #{admin_ids.size} admins into #{project_ids.size} projects"
+ ProjectMember.add_users_to_projects(project_ids, admin_ids, ProjectMember::MASTER)
end
desc "GitLab | Add a specific user to all projects (as a developer)"
@@ -18,7 +18,7 @@ namespace :gitlab do
user = User.find_by(email: args.email)
project_ids = Project.pluck(:id)
puts "Importing #{user.email} users into #{project_ids.size} projects"
- ProjectMember.add_users_into_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER)
+ ProjectMember.add_users_to_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER)
end
desc "GitLab | Add all users to all groups (admin users are added as owners)"
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 60f4636e737..5f4a6bbfa35 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -46,7 +46,7 @@ namespace :gitlab do
}
correct_options = options.map do |name, value|
- run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
+ run_command(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
end
if correct_options.all?
@@ -64,7 +64,7 @@ namespace :gitlab do
for_more_information(
see_installation_guide_section "GitLab"
)
- end
+ end
end
end
@@ -73,7 +73,7 @@ namespace :gitlab do
database_config_file = Rails.root.join("config", "database.yml")
- if File.exists?(database_config_file)
+ if File.exist?(database_config_file)
puts "yes".color(:green)
else
puts "no".color(:red)
@@ -94,7 +94,7 @@ namespace :gitlab do
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
- if File.exists?(gitlab_config_file)
+ if File.exist?(gitlab_config_file)
puts "yes".color(:green)
else
puts "no".color(:red)
@@ -113,7 +113,7 @@ namespace :gitlab do
print "GitLab config outdated? ... "
gitlab_config_file = Rails.root.join("config", "gitlab.yml")
- unless File.exists?(gitlab_config_file)
+ unless File.exist?(gitlab_config_file)
puts "can't check because of previous errors".color(:magenta)
end
@@ -144,7 +144,7 @@ namespace :gitlab do
script_path = "/etc/init.d/gitlab"
- if File.exists?(script_path)
+ if File.exist?(script_path)
puts "yes".color(:green)
else
puts "no".color(:red)
@@ -169,7 +169,7 @@ namespace :gitlab do
recipe_path = Rails.root.join("lib/support/init.d/", "gitlab")
script_path = "/etc/init.d/gitlab"
- unless File.exists?(script_path)
+ unless File.exist?(script_path)
puts "can't check because of previous errors".color(:magenta)
return
end
@@ -316,7 +316,7 @@ namespace :gitlab do
min_redis_version = "2.8.0"
print "Redis version >= #{min_redis_version}? ... "
- redis_version = run(%W(redis-cli --version))
+ redis_version = run_command(%W(redis-cli --version))
redis_version = redis_version.try(:match, /redis-cli (\d+\.\d+\.\d+)/)
if redis_version &&
(Gem::Version.new(redis_version[1]) > Gem::Version.new(min_redis_version))
@@ -361,7 +361,7 @@ namespace :gitlab do
Gitlab.config.repositories.storages.each do |name, repo_base_path|
print "#{name}... "
- if File.exists?(repo_base_path)
+ if File.exist?(repo_base_path)
puts "yes".color(:green)
else
puts "no".color(:red)
@@ -385,7 +385,7 @@ namespace :gitlab do
Gitlab.config.repositories.storages.each do |name, repo_base_path|
print "#{name}... "
- unless File.exists?(repo_base_path)
+ unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
return
end
@@ -408,7 +408,7 @@ namespace :gitlab do
Gitlab.config.repositories.storages.each do |name, repo_base_path|
print "#{name}... "
- unless File.exists?(repo_base_path)
+ unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
return
end
@@ -438,7 +438,7 @@ namespace :gitlab do
Gitlab.config.repositories.storages.each do |name, repo_base_path|
print "#{name}... "
- unless File.exists?(repo_base_path)
+ unless File.exist?(repo_base_path)
puts "can't check because of previous errors".color(:magenta)
return
end
@@ -893,7 +893,7 @@ namespace :gitlab do
def check_ruby_version
required_version = Gitlab::VersionInfo.new(2, 1, 0)
- current_version = Gitlab::VersionInfo.parse(run(%W(ruby --version)))
+ current_version = Gitlab::VersionInfo.parse(run_command(%W(ruby --version)))
print "Ruby version >= #{required_version} ? ... "
@@ -910,7 +910,7 @@ namespace :gitlab do
def check_git_version
required_version = Gitlab::VersionInfo.new(2, 7, 3)
- current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version)))
+ current_version = Gitlab::VersionInfo.parse(run_command(%W(#{Gitlab.config.git.bin_path} --version)))
puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
print "Git version >= #{required_version} ? ... "
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 0ec19e1a625..7c96bc864ce 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -25,6 +25,10 @@ namespace :gitlab do
desc 'Drop all tables'
task :drop_tables => :environment do
connection = ActiveRecord::Base.connection
+
+ # If MySQL, turn off foreign key checks
+ connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
+
tables = connection.tables
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
@@ -35,6 +39,9 @@ namespace :gitlab do
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
+
+ # If MySQL, re-enable foreign key checks
+ connection.execute('SET FOREIGN_KEY_CHECKS=1') if Gitlab::Database.mysql?
end
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index fe43d40e6d2..dffea8ed155 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -8,7 +8,7 @@ namespace :gitlab do
# check Ruby version
ruby_version = run_and_match(%W(ruby --version), /[\d\.p]+/).try(:to_s)
# check Gem version
- gem_version = run(%W(gem --version))
+ gem_version = run_command(%W(gem --version))
# check Bundler version
bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
# check Bundler version
@@ -17,7 +17,7 @@ namespace :gitlab do
puts ""
puts "System information".color(:yellow)
puts "System:\t\t#{os_name || "unknown".color(:red)}"
- puts "Current User:\t#{run(%W(whoami))}"
+ puts "Current User:\t#{run_command(%W(whoami))}"
puts "Using RVM:\t#{rvm_version.present? ? "yes".color(:green) : "no"}"
puts "RVM Version:\t#{rvm_version}" if rvm_version.present?
puts "Ruby Version:\t#{ruby_version || "unknown".color(:red)}"
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index c85ebdf8619..bb7eb852f1b 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -5,7 +5,8 @@ namespace :gitlab do
warn_user_is_not_gitlab
default_version = Gitlab::Shell.version_required
- args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
+ default_version_tag = 'v' + default_version
+ args.with_defaults(tag: default_version_tag, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
user = Gitlab.config.gitlab.user
home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
@@ -15,7 +16,12 @@ namespace :gitlab do
target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed
- unless File.directory?(target_dir)
+ if File.directory?(target_dir)
+ Dir.chdir(target_dir) do
+ system(*%W(Gitlab.config.git.bin_path} fetch --tags --quiet))
+ system(*%W(Gitlab.config.git.bin_path} checkout --quiet #{default_version_tag}))
+ end
+ else
system(*%W(#{Gitlab.config.git.bin_path} clone -- #{args.repo} #{target_dir}))
end
@@ -84,7 +90,7 @@ namespace :gitlab do
task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project|
path_to_repo = project.repository.path_to_repo
- if File.exists?(path_to_repo)
+ if File.exist?(path_to_repo)
print '-'
else
if Gitlab::Shell.new.add_repository(project.repository_storage_path,
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index ab96b1d3593..74be413423a 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -23,7 +23,7 @@ namespace :gitlab do
# It will primarily use lsb_relase to determine the OS.
# It has fallbacks to Debian, SuSE, OS X and systems running systemd.
def os_name
- os_name = run(%W(lsb_release -irs))
+ os_name = run_command(%W(lsb_release -irs))
os_name ||= if File.readable?('/etc/system-release')
File.read('/etc/system-release')
end
@@ -34,7 +34,7 @@ namespace :gitlab do
os_name ||= if File.readable?('/etc/SuSE-release')
File.read('/etc/SuSE-release')
end
- os_name ||= if os_x_version = run(%W(sw_vers -productVersion))
+ os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion))
"Mac OS X #{os_x_version}"
end
os_name ||= if File.readable?('/etc/os-release')
@@ -62,10 +62,10 @@ namespace :gitlab do
# Returns nil if nothing matched
# Returns the MatchData if the pattern matched
#
- # see also #run
+ # see also #run_command
# see also String#match
def run_and_match(command, regexp)
- run(command).try(:match, regexp)
+ run_command(command).try(:match, regexp)
end
# Runs the given command
@@ -74,7 +74,7 @@ namespace :gitlab do
# Returns the output of the command otherwise
#
# see also #run_and_match
- def run(command)
+ def run_command(command)
output, _ = Gitlab::Popen.popen(command)
output
rescue Errno::ENOENT
@@ -82,7 +82,7 @@ namespace :gitlab do
end
def uid_for(user_name)
- run(%W(id -u #{user_name})).chomp.to_i
+ run_command(%W(id -u #{user_name})).chomp.to_i
end
def gid_for(group_name)
@@ -96,7 +96,7 @@ namespace :gitlab do
def warn_user_is_not_gitlab
unless @warned_user_not_gitlab
gitlab_user = Gitlab.config.gitlab.user
- current_user = run(%W(whoami)).chomp
+ current_user = run_command(%W(whoami)).chomp
unless current_user == gitlab_user
puts " Warning ".color(:black).background(:yellow)
puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing."
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index f467cc0ee29..49530e7a372 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -26,10 +26,10 @@ namespace :gitlab do
namespace_path = ENV['NAMESPACE']
projects = find_projects(namespace_path)
- projects_ids = projects.pluck(:id)
+ project_ids = projects.pluck(:id)
puts "Removing webhooks with the url '#{web_hook_url}' ... "
- count = WebHook.where(url: web_hook_url, project_id: projects_ids, type: 'ProjectHook').delete_all
+ count = WebHook.where(url: web_hook_url, project_id: project_ids, type: 'ProjectHook').delete_all
puts "#{count} webhooks were removed."
end
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index da255f5464b..8dbfa7751dc 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -34,21 +34,19 @@ task :spinach do
run_spinach_tests(nil)
end
-def run_command(cmd)
+def run_system_command(cmd)
system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd)
end
def run_spinach_command(args)
- run_command(%w(spinach -r rerun) + args)
+ run_system_command(%w(spinach -r rerun) + args)
end
def run_spinach_tests(tags)
- #run_command(%w(rake gitlab:setup)) or raise('gitlab:setup failed!')
-
success = run_spinach_command(%W(--tags #{tags}))
3.times do |_|
break if success
- break unless File.exists?('tmp/spinach-rerun.txt')
+ break unless File.exist?('tmp/spinach-rerun.txt')
tests = File.foreach('tmp/spinach-rerun.txt').map(&:chomp)
puts ''
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index 21c0e5f1d41..d3dcbd2c29b 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -7,5 +7,5 @@ end
unless Rails.env.production?
desc "GitLab | Run all tests on CI with simplecov"
- task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec]
+ task test_ci: [:rubocop, :brakeman, :teaspoon, :spinach, :spec]
end
diff --git a/public/404.html b/public/404.html
index 4862770cc2a..92b7f4da0b9 100644
--- a/public/404.html
+++ b/public/404.html
@@ -1,55 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>The page you're looking for could not be found (404)</title>
<style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
404
</h1>
- <h3>The page you're looking for could not be found.</h3>
- <hr/>
- <p>Make sure the address is correct and that the page hasn't moved.</p>
- <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ <div class="container">
+ <h3>The page you're looking for could not be found.</h3>
+ <hr />
+ <p>Make sure the address is correct and that the page hasn't moved.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ </div>
</body>
</html>
diff --git a/public/422.html b/public/422.html
index 055b0bde165..f625f8a33b7 100644
--- a/public/422.html
+++ b/public/422.html
@@ -1,55 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>The change you requested was rejected (422)</title>
<style>
body {
color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
+
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
+
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ img {
+ max-width: 40vw;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
- </style>
+ .container {
+ margin: auto 20px;
+ }
+ </style>
</head>
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
422
</h1>
- <h3>The change you requested was rejected.</h3>
- <hr />
- <p>Make sure you have access to the thing you tried to change.</p>
- <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ <div class="container">
+ <h3>The change you requested was rejected.</h3>
+ <hr />
+ <p>Make sure you have access to the thing you tried to change.</p>
+ <p>Please contact your GitLab administrator if you think this is a mistake.</p>
+ </div>
</body>
</html>
diff --git a/public/500.html b/public/500.html
index 3d59d1392f5..d76c66ba92a 100644
--- a/public/500.html
+++ b/public/500.html
@@ -1,54 +1,65 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>Something went wrong (500)</title>
<style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
500
</h1>
- <h3>Whoops, something went wrong on our end.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, something went wrong on our end.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/502.html b/public/502.html
index 67dfd8a2743..1a3c7efc769 100644
--- a/public/502.html
+++ b/public/502.html
@@ -1,14 +1,13 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (502)</title>
<style>
body {
color: #666;
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
margin: auto;
font-size: 14px;
}
@@ -34,21 +33,33 @@
}
hr {
- margin: 18px 0;
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
502
</h1>
- <h3>Whoops, GitLab is taking too much time to respond.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, GitLab is taking too much time to respond.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/503.html b/public/503.html
index 6ab1185658d..c1c4e3ffdb8 100644
--- a/public/503.html
+++ b/public/503.html
@@ -1,14 +1,13 @@
<!DOCTYPE html>
<html>
<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
<title>GitLab is not responding (503)</title>
<style>
body {
color: #666;
text-align: center;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
margin: auto;
font-size: 14px;
}
@@ -34,21 +33,33 @@
}
hr {
- margin: 18px 0;
+ max-width: 800px;
+ margin: 18px auto;
border: 0;
border-top: 1px solid #EEE;
border-bottom: 1px solid white;
}
+
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
</style>
</head>
+
<body>
<h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo"/><br />
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
503
</h1>
- <h3>Whoops, GitLab is currently unavailable.</h3>
- <hr/>
- <p>Try refreshing the page, or going back and attempting the action again.</p>
- <p>Please contact your GitLab administrator if this problem persists.</p>
+ <div class="container">
+ <h3>Whoops, GitLab is currently unavailable.</h3>
+ <hr />
+ <p>Try refreshing the page, or going back and attempting the action again.</p>
+ <p>Please contact your GitLab administrator if this problem persists.</p>
+ </div>
</body>
</html>
diff --git a/public/deploy.html b/public/deploy.html
index 48976dacf41..142472b6c35 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -1,54 +1,64 @@
<!DOCTYPE html>
<html>
- <head>
- <title>Deploy in progress</title>
- <style>
- body {
- color: #666;
- text-align: center;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- margin: 0;
- width: 800px;
- margin: auto;
- font-size: 14px;
- }
+<head>
+ <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+ <title>Deploy in progress</title>
+ <style>
+ body {
+ color: #666;
+ text-align: center;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ font-size: 14px;
+ }
- h1 {
- font-size: 56px;
- line-height: 100px;
- font-weight: normal;
- color: #456;
- }
+ h1 {
+ font-size: 56px;
+ line-height: 100px;
+ font-weight: normal;
+ color: #456;
+ }
- h2 {
- font-size: 24px;
- color: #666;
- line-height: 1.5em;
- }
+ h2 {
+ font-size: 24px;
+ color: #666;
+ line-height: 1.5em;
+ }
- h3 {
- color: #456;
- font-size: 20px;
- font-weight: normal;
- line-height: 28px;
- }
+ h3 {
+ color: #456;
+ font-size: 20px;
+ font-weight: normal;
+ line-height: 28px;
+ }
- hr {
- margin: 18px 0;
- border: 0;
- border-top: 1px solid #EEE;
- border-bottom: 1px solid white;
- }
- </style>
- </head>
+ hr {
+ max-width: 800px;
+ margin: 18px auto;
+ border: 0;
+ border-top: 1px solid #EEE;
+ border-bottom: 1px solid white;
+ }
- <body>
- <h1>
- <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" /><br />
- Deploy in progress
- </h1>
+ img {
+ max-width: 40vw;
+ }
+
+ .container {
+ margin: auto 20px;
+ }
+ </style>
+</head>
+
+<body>
+ <h1>
+ <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br />
+ Deploy in progress
+ </h1>
+ <div class="container">
<h3>Please try again in a few minutes.</h3>
- <hr/>
+ <hr />
<p>Please contact your GitLab administrator if this problem persists.</p>
- </body>
+ </div>
+</body>
</html>
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
new file mode 100755
index 00000000000..bc6e4d94061
--- /dev/null
+++ b/scripts/lint-doc.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+cd "$(dirname "$0")/.."
+
+# Use long options (e.g. --header instead of -H) for curl examples in documentation.
+grep --perl-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/
+if [ $? == 0 ]
+then
+ echo '✖ ERROR: Short options should not be used in documentation!' >&2
+ exit 1
+fi
+
+echo "✔ Linting passed"
+exit 0
+
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
new file mode 100755
index 00000000000..65f93f8830b
--- /dev/null
+++ b/scripts/merge-simplecov
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+require_relative '../spec/simplecov_env'
+SimpleCovEnv.configure_profile
+
+module SimpleCov
+ module ResultMerger
+ class << self
+ def resultset_files
+ Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json'))
+ end
+
+ def resultset_hashes
+ resultset_files.map do |path|
+ begin
+ JSON.parse(File.read(path))
+ rescue
+ {}
+ end
+ end
+ end
+
+ def resultset
+ resultset_hashes.reduce({}, :merge)
+ end
+ end
+ end
+end
+
+SimpleCov::ResultMerger.merged_result.format!
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 7e71a030901..76b2178c79c 100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -20,10 +20,11 @@ if [ -f /.dockerenv ] || [ -f ./dockerinit ]; then
# Install phantomjs package
pushd vendor/apt
- if [ ! -e phantomjs_1.9.8-0jessie_amd64.deb ]; then
- wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb
+ PHANTOMJS_FILE="phantomjs-$PHANTOMJS_VERSION-linux-x86_64"
+ if [ ! -d "$PHANTOMJS_FILE" ]; then
+ curl -q -L "https://s3.amazonaws.com/gitlab-build-helpers/$PHANTOMJS_FILE.tar.bz2" | tar jx
fi
- dpkg -i phantomjs_1.9.8-0jessie_amd64.deb
+ cp "$PHANTOMJS_FILE/bin/phantomjs" "/usr/bin/"
popd
# Try to install packages
diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb
index 6fad7e2b9e7..c5d3cd70acc 100644
--- a/spec/config/mail_room_spec.rb
+++ b/spec/config/mail_room_spec.rb
@@ -1,53 +1,48 @@
-require "spec_helper"
+require 'spec_helper'
-describe "mail_room.yml" do
- let(:config_path) { "config/mail_room.yml" }
+describe 'mail_room.yml' do
+ let(:config_path) { 'config/mail_room.yml' }
let(:configuration) { YAML.load(ERB.new(File.read(config_path)).result) }
- context "when incoming email is disabled" do
+ context 'when incoming email is disabled' do
before do
- ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_disabled.yml").to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_disabled.yml').to_s
+ Gitlab::MailRoom.reset_config!
end
after do
- ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
end
- it "contains no configuration" do
+ it 'contains no configuration' do
expect(configuration[:mailboxes]).to be_nil
end
end
- context "when incoming email is enabled" do
+ context 'when incoming email is enabled' do
before do
- ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = Rails.root.join("spec/fixtures/mail_room_enabled.yml").to_s
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_enabled.yml').to_s
+ Gitlab::MailRoom.reset_config!
end
after do
- ENV["MAIL_ROOM_GITLAB_CONFIG_FILE"] = nil
+ ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = nil
end
- it "contains the intended configuration" do
+ it 'contains the intended configuration' do
expect(configuration[:mailboxes].length).to eq(1)
mailbox = configuration[:mailboxes].first
- expect(mailbox[:host]).to eq("imap.gmail.com")
+ expect(mailbox[:host]).to eq('imap.gmail.com')
expect(mailbox[:port]).to eq(993)
expect(mailbox[:ssl]).to eq(true)
expect(mailbox[:start_tls]).to eq(false)
- expect(mailbox[:email]).to eq("gitlab-incoming@gmail.com")
- expect(mailbox[:password]).to eq("[REDACTED]")
- expect(mailbox[:name]).to eq("inbox")
-
- redis_config_file = Rails.root.join('config', 'resque.yml')
-
- redis_url =
- if File.exist?(redis_config_file)
- YAML.load_file(redis_config_file)[Rails.env]
- else
- "redis://localhost:6379"
- end
+ expect(mailbox[:email]).to eq('gitlab-incoming@gmail.com')
+ expect(mailbox[:password]).to eq('[REDACTED]')
+ expect(mailbox[:name]).to eq('inbox')
+
+ redis_url = Gitlab::Redis.url
expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url)
expect(mailbox[:arbitration_options][:redis_url]).to eq(redis_url)
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
new file mode 100644
index 00000000000..602de72d23f
--- /dev/null
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Admin::GroupsController do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'DELETE #destroy' do
+ it 'schedules a group destroy' do
+ Sidekiq::Testing.fake! do
+ expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
+ end
+ end
+
+ it 'redirects to the admin group path' do
+ delete :destroy, id: project.group.path
+
+ expect(response).to redirect_to(admin_groups_path)
+ end
+ end
+end
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
index 520a4f6f9c5..585ca31389d 100644
--- a/spec/controllers/admin/spam_logs_controller_spec.rb
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -34,4 +34,16 @@ describe Admin::SpamLogsController do
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
+
+ describe '#mark_as_ham' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:submit_ham).and_return(true)
+ end
+ it 'submits the log as ham' do
+ post :mark_as_ham, id: first_spam.id
+
+ expect(response).to have_http_status(302)
+ expect(SpamLog.find(first_spam.id).submitted_as_ham).to be_truthy
+ end
+ end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index ab9aa65f7b9..33fe3c73822 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -39,7 +39,7 @@ describe Admin::UsersController do
user.ldap_block
end
- it 'will not unblock user' do
+ it 'does not unblock user' do
put :unblock, id: user.username
user.reload
expect(user.blocked?).to be_truthy
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 8bd210cbc3d..98e912f000c 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -5,7 +5,7 @@ describe ApplicationController do
let(:user) { create(:user) }
let(:controller) { ApplicationController.new }
- it 'should redirect if the user is over their password expiry' do
+ it 'redirects if the user is over their password expiry' do
user.password_expires_at = Time.new(2002)
expect(user.ldap_user?).to be_falsey
allow(controller).to receive(:current_user).and_return(user)
@@ -14,7 +14,7 @@ describe ApplicationController do
controller.send(:check_password_expiration)
end
- it 'should not redirect if the user is under their password expiry' do
+ it 'does not redirect if the user is under their password expiry' do
user.password_expires_at = Time.now + 20010101
expect(user.ldap_user?).to be_falsey
allow(controller).to receive(:current_user).and_return(user)
@@ -22,7 +22,7 @@ describe ApplicationController do
controller.send(:check_password_expiration)
end
- it 'should not redirect if the user is over their password expiry but they are an ldap user' do
+ it 'does not redirect if the user is over their password expiry but they are an ldap user' do
user.password_expires_at = Time.new(2002)
allow(user).to receive(:ldap_user?).and_return(true)
allow(controller).to receive(:current_user).and_return(user)
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 60c654f622d..ed0b7f9e240 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -163,4 +163,17 @@ describe AutocompleteController do
expect(body.collect { |u| u['id'] }).not_to include(99999)
end
end
+
+ context 'skip_users parameter included' do
+ before { sign_in(user) }
+
+ it 'skips the user IDs passed' do
+ get(:users, skip_users: [user, user2].map(&:id))
+
+ other_user_ids = [non_member, project.owner, project.creator].map(&:id)
+ response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
+
+ expect(response_user_ids).to contain_exactly(*other_user_ids)
+ end
+ end
end
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
index 91d639218e5..506aeee7d2a 100644
--- a/spec/controllers/groups/avatars_controller_spec.rb
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -9,7 +9,7 @@ describe Groups::AvatarsController do
sign_in(user)
end
- it 'destroy should remove avatar from DB' do
+ it 'removes avatar from DB calling destroy' do
delete :destroy, group_id: group.path
@group = assigns(:group)
expect(@group.avatar.present?).to be_falsey
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index b0793cb1655..8c52f615b8b 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -15,7 +15,7 @@ describe Groups::MilestonesController do
end
describe "#create" do
- it "should create group milestone with Chinese title" do
+ it "creates group milestone with Chinese title" do
post :create,
group_id: group.id,
milestone: { project_ids: [project.id, project2.id], title: title }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index cd98fecd0c7..a763e2c5ba8 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -75,4 +75,34 @@ describe GroupsController do
end
end
end
+
+ describe 'DELETE #destroy' do
+ context 'as another user' do
+ it 'returns 404' do
+ sign_in(create(:user))
+
+ delete :destroy, id: group.path
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'as the group owner' do
+ before do
+ sign_in(user)
+ end
+
+ it 'schedules a group destroy' do
+ Sidekiq::Testing.fake! do
+ expect { delete :destroy, id: group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
+ end
+ end
+
+ it 'redirects to the root path' do
+ delete :destroy, id: group.path
+
+ expect(response).to redirect_to(root_path)
+ end
+ end
+ end
end
diff --git a/spec/controllers/profiles/avatars_controller_spec.rb b/spec/controllers/profiles/avatars_controller_spec.rb
index ad5855df0a4..4fa0462ccdf 100644
--- a/spec/controllers/profiles/avatars_controller_spec.rb
+++ b/spec/controllers/profiles/avatars_controller_spec.rb
@@ -8,7 +8,7 @@ describe Profiles::AvatarsController do
controller.instance_variable_set(:@user, user)
end
- it 'destroy should remove avatar from DB' do
+ it 'removes avatar from DB by calling destroy' do
delete :destroy
@user = assigns(:user)
expect(@user.avatar.present?).to be_falsey
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 3a82083717f..6bcfae0fc13 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -6,7 +6,7 @@ describe Profiles::KeysController do
describe '#new' do
before { sign_in(user) }
- it 'redirect to #index' do
+ it 'redirects to #index' do
get :new
expect(response).to redirect_to(profile_keys_path)
@@ -15,7 +15,7 @@ describe Profiles::KeysController do
describe "#get_keys" do
describe "non existant user" do
- it "should generally not work" do
+ it "does not generally work" do
get :get_keys, username: 'not-existent'
expect(response).not_to be_success
@@ -23,19 +23,19 @@ describe Profiles::KeysController do
end
describe "user with no keys" do
- it "should generally work" do
+ it "does generally work" do
get :get_keys, username: user.username
expect(response).to be_success
end
- it "should render all keys separated with a new line" do
+ it "renders all keys separated with a new line" do
get :get_keys, username: user.username
expect(response.body).to eq("")
end
- it "should respond with text/plain content type" do
+ it "responds with text/plain content type" do
get :get_keys, username: user.username
expect(response.content_type).to eq("text/plain")
end
@@ -47,13 +47,13 @@ describe Profiles::KeysController do
user.keys << create(:another_key)
end
- it "should generally work" do
+ it "does generally work" do
get :get_keys, username: user.username
expect(response).to be_success
end
- it "should render all keys separated with a new line" do
+ it "renders all keys separated with a new line" do
get :get_keys, username: user.username
expect(response.body).not_to eq("")
@@ -65,13 +65,13 @@ describe Profiles::KeysController do
expect(response.body).to match(/AQDmTillFzNTrrGgwaCKaSj/)
end
- it "should not render the comment of the key" do
+ it "does not render the comment of the key" do
get :get_keys, username: user.username
expect(response.body).not_to match(/dummy@gitlab.com/)
end
- it "should respond with text/plain content type" do
+ it "responds with text/plain content type" do
get :get_keys, username: user.username
expect(response.content_type).to eq("text/plain")
end
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 4d724ca9ed0..f5ea097af8b 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::AvatarsController do
controller.instance_variable_set(:@project, project)
end
- it 'destroy should remove avatar from DB' do
+ it 'removes avatar from DB by calling destroy' do
delete :destroy, namespace_id: project.namespace.id, project_id: project.id
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 3001d32e719..7e440193d7b 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -24,15 +24,6 @@ describe Projects::CommitController do
get :show, params.merge(extra_params)
end
- let(:project) { create(:project) }
-
- before do
- user = create(:user)
- project.team << [user, :master]
-
- sign_in(user)
- end
-
context 'with valid id' do
it 'responds with 200' do
go(id: commit.id)
@@ -56,25 +47,25 @@ describe Projects::CommitController do
end
shared_examples "export as" do |format|
- it "should generally work" do
+ it "does generally work" do
go(id: commit.id, format: format)
expect(response).to be_success
end
- it "should generate it" do
+ it "generates it" do
expect_any_instance_of(Commit).to receive(:"to_#{format}")
go(id: commit.id, format: format)
end
- it "should render it" do
+ it "renders it" do
go(id: commit.id, format: format)
expect(response.body).to eq(commit.send(:"to_#{format}"))
end
- it "should not escape Html" do
+ it "does not escape Html" do
allow_any_instance_of(Commit).to receive(:"to_#{format}").
and_return('HTML entities &<>" ')
@@ -92,17 +83,18 @@ describe Projects::CommitController do
let(:format) { :diff }
it "should really only be a git diff" do
- go(id: commit.id, format: format)
+ go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format)
expect(response.body).to start_with("diff --git")
end
- it "should really only be a git diff without whitespace changes" do
+ it "is only be a git diff without whitespace changes" do
go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1)
expect(response.body).to start_with("diff --git")
- # without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs).first.diff.split("\n")
+
+ # without whitespace option, there are more than 2 diff_splits for other formats
+ diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
end
@@ -111,13 +103,13 @@ describe Projects::CommitController do
include_examples "export as", :patch
let(:format) { :patch }
- it "should really be a git email patch" do
+ it "is a git email patch" do
go(id: commit.id, format: format)
expect(response.body).to start_with("From #{commit.id}")
end
- it "should contain a git diff" do
+ it "contains a git diff" do
go(id: commit.id, format: format)
expect(response.body).to match(/^diff --git/)
@@ -155,7 +147,7 @@ describe Projects::CommitController do
describe 'POST revert' do
context 'when target branch is not provided' do
- it 'should render the 404 page' do
+ it 'renders the 404 page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -167,7 +159,7 @@ describe Projects::CommitController do
end
context 'when the revert was successful' do
- it 'should redirect to the commits page' do
+ it 'redirects to the commits page' do
post(:revert,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -188,7 +180,7 @@ describe Projects::CommitController do
id: commit.id)
end
- it 'should redirect to the commit page' do
+ it 'redirects to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
namespace_id: project.namespace.to_param,
@@ -204,7 +196,7 @@ describe Projects::CommitController do
describe 'POST cherry_pick' do
context 'when target branch is not provided' do
- it 'should render the 404 page' do
+ it 'renders the 404 page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -216,7 +208,7 @@ describe Projects::CommitController do
end
context 'when the cherry-pick was successful' do
- it 'should redirect to the commits page' do
+ it 'redirects to the commits page' do
post(:cherry_pick,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -237,7 +229,7 @@ describe Projects::CommitController do
id: master_pickable_commit.id)
end
- it 'should redirect to the commit page' do
+ it 'redirects to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
namespace_id: project.namespace.to_param,
@@ -275,9 +267,9 @@ describe Projects::CommitController do
end
it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
- expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs, diff_refs, project)
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
end
diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 7d8089c4bc6..2518a48e336 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -11,7 +11,7 @@ describe Projects::CommitsController do
describe "GET show" do
context "as atom feed" do
- it "should render as atom" do
+ it "renders as atom" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 4058d5e2453..7a57801c437 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -11,7 +11,7 @@ describe Projects::CompareController do
project.team << [user, :master]
end
- it 'compare should show some diffs' do
+ it 'compare shows some diffs' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -19,11 +19,11 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs).first).not_to be_nil
+ expect(assigns(:diffs).diff_files.first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
end
- it 'compare should show some diffs with ignore whitespace change option' do
+ it 'compare shows some diffs with ignore whitespace change option' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -32,15 +32,16 @@ describe Projects::CompareController do
w: 1)
expect(response).to be_success
- expect(assigns(:diffs).first).not_to be_nil
+ diff_file = assigns(:diffs).diff_files.first
+ expect(diff_file).not_to be_nil
expect(assigns(:commits).length).to be >= 1
# without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs).first.diff.split("\n")
+ diff_splits = diff_file.diff.diff.split("\n")
expect(diff_splits.length).to be <= 2
end
describe 'non-existent refs' do
- it 'invalid source ref' do
+ it 'uses invalid source ref' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -48,11 +49,11 @@ describe Projects::CompareController do
to: ref_to)
expect(response).to be_success
- expect(assigns(:diffs).to_a).to eq([])
+ expect(assigns(:diffs).diff_files.to_a).to eq([])
expect(assigns(:commits)).to eq([])
end
- it 'invalid target ref' do
+ it 'uses invalid target ref' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -87,9 +88,9 @@ describe Projects::CompareController do
end
it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
- expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs, diff_refs, project)
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
end
diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
new file mode 100644
index 00000000000..768105cae95
--- /dev/null
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::EnvironmentsController do
+ let(:environment) { create(:environment) }
+ let(:project) { environment.project }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ describe 'GET show' do
+ context 'with valid id' do
+ it 'responds with a status code 200' do
+ get :show, environment_params
+
+ expect(response).to be_ok
+ end
+ end
+
+ context 'with invalid id' do
+ it 'responds with a status code 404' do
+ params = environment_params
+ params[:id] = 12345
+ get :show, params
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'GET edit' do
+ it 'responds with a status code 200' do
+ get :edit, environment_params
+
+ expect(response).to be_ok
+ end
+ end
+
+ describe 'PATCH #update' do
+ it 'responds with a 302' do
+ patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' })
+ patch :update, patch_params
+
+ expect(response).to have_http_status(302)
+ end
+ end
+
+ def environment_params
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: environment.id
+ }
+ end
+end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index f66bcb8099c..ac3469cb8a9 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::ForksController do
context 'when fork is public' do
before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) }
- it 'should be visible for non logged in users' do
+ it 'is visible for non logged in users' do
get_forks
expect(assigns[:forks]).to be_present
@@ -28,7 +28,7 @@ describe Projects::ForksController do
forked_project.update_attributes(visibility_level: Project::PRIVATE, group: group)
end
- it 'should not be visible for non logged in users' do
+ it 'is not be visible for non logged in users' do
get_forks
expect(assigns[:forks]).to be_blank
@@ -38,7 +38,7 @@ describe Projects::ForksController do
before { sign_in(project.creator) }
context 'when user is not a Project member neither a group member' do
- it 'should not see the Project listed' do
+ it 'does not see the Project listed' do
get_forks
expect(assigns[:forks]).to be_blank
@@ -48,7 +48,7 @@ describe Projects::ForksController do
context 'when user is a member of the Project' do
before { forked_project.team << [project.creator, :developer] }
- it 'should see the project listed' do
+ it 'sees the project listed' do
get_forks
expect(assigns[:forks]).to be_present
@@ -58,7 +58,7 @@ describe Projects::ForksController do
context 'when user is a member of the Group' do
before { forked_project.group.add_developer(project.creator) }
- it 'should see the project listed' do
+ it 'sees the project listed' do
get_forks
expect(assigns[:forks]).to be_present
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 7cf09fa4a4a..0836b71056c 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -6,37 +6,65 @@ describe Projects::IssuesController do
let(:issue) { create(:issue, project: project) }
describe "GET #index" do
- before do
- sign_in(user)
- project.team << [user, :developer]
- end
+ context 'external issue tracker' do
+ it 'redirects to the external issue tracker' do
+ external = double(issues_url: 'https://example.com/issues')
+ allow(project).to receive(:external_issue_tracker).and_return(external)
+ controller.instance_variable_set(:@project, project)
- it "returns index" do
- get :index, namespace_id: project.namespace.path, project_id: project.path
+ get :index, namespace_id: project.namespace.path, project_id: project
- expect(response).to have_http_status(200)
+ expect(response).to redirect_to('https://example.com/issues')
+ end
end
- it "return 301 if request path doesn't match project path" do
- get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+ context 'internal issue tracker' do
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
- expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
- end
+ it "returns index" do
+ get :index, namespace_id: project.namespace.path, project_id: project.path
- it "returns 404 when issues are disabled" do
- project.issues_enabled = false
- project.save
+ expect(response).to have_http_status(200)
+ end
+
+ it "returns 301 if request path doesn't match project path" do
+ get :index, namespace_id: project.namespace.path, project_id: project.path.upcase
+
+ expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project))
+ end
- get :index, namespace_id: project.namespace.path, project_id: project.path
- expect(response).to have_http_status(404)
+ it "returns 404 when issues are disabled" do
+ project.issues_enabled = false
+ project.save
+
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 404 when external issue tracker is enabled" do
+ controller.instance_variable_set(:@project, project)
+ allow(project).to receive(:default_issues_tracker?).and_return(false)
+
+ get :index, namespace_id: project.namespace.path, project_id: project.path
+ expect(response).to have_http_status(404)
+ end
end
+ end
+
+ describe 'GET #new' do
+ context 'external issue tracker' do
+ it 'redirects to the external issue tracker' do
+ external = double(new_issue_path: 'https://example.com/issues/new')
+ allow(project).to receive(:external_issue_tracker).and_return(external)
+ controller.instance_variable_set(:@project, project)
- it "returns 404 when external issue tracker is enabled" do
- controller.instance_variable_set(:@project, project)
- allow(project).to receive(:default_issues_tracker?).and_return(false)
+ get :new, namespace_id: project.namespace.path, project_id: project
- get :index, namespace_id: project.namespace.path, project_id: project.path
- expect(response).to have_http_status(404)
+ expect(response).to redirect_to('https://example.com/issues/new')
+ end
end
end
@@ -91,21 +119,21 @@ describe Projects::IssuesController do
let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
describe 'GET #index' do
- it 'should not list confidential issues for guests' do
+ it 'does not list confidential issues for guests' do
sign_out(:user)
get_issues
expect(assigns(:issues)).to eq [issue]
end
- it 'should not list confidential issues for non project members' do
+ it 'does not list confidential issues for non project members' do
sign_in(non_member)
get_issues
expect(assigns(:issues)).to eq [issue]
end
- it 'should not list confidential issues for project members with guest role' do
+ it 'does not list confidential issues for project members with guest role' do
sign_in(member)
project.team << [member, :guest]
@@ -114,7 +142,7 @@ describe Projects::IssuesController do
expect(assigns(:issues)).to eq [issue]
end
- it 'should list confidential issues for author' do
+ it 'lists confidential issues for author' do
sign_in(author)
get_issues
@@ -122,7 +150,7 @@ describe Projects::IssuesController do
expect(assigns(:issues)).not_to include request_forgery_timing_attack
end
- it 'should list confidential issues for assignee' do
+ it 'lists confidential issues for assignee' do
sign_in(assignee)
get_issues
@@ -130,7 +158,7 @@ describe Projects::IssuesController do
expect(assigns(:issues)).to include request_forgery_timing_attack
end
- it 'should list confidential issues for project members' do
+ it 'lists confidential issues for project members' do
sign_in(member)
project.team << [member, :developer]
@@ -140,7 +168,7 @@ describe Projects::IssuesController do
expect(assigns(:issues)).to include request_forgery_timing_attack
end
- it 'should list confidential issues for admin' do
+ it 'lists confidential issues for admin' do
sign_in(admin)
get_issues
@@ -243,6 +271,83 @@ describe Projects::IssuesController do
end
end
+ describe 'POST #create' do
+ context 'Akismet is enabled' do
+ before do
+ allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ def post_spam_issue
+ sign_in(user)
+ spam_project = create(:empty_project, :public)
+ post :create, {
+ namespace_id: spam_project.namespace.to_param,
+ project_id: spam_project.to_param,
+ issue: { title: 'Spam Title', description: 'Spam lives here' }
+ }
+ end
+
+ it 'rejects an issue recognized as spam' do
+ expect{ post_spam_issue }.not_to change(Issue, :count)
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ post_spam_issue
+ spam_logs = SpamLog.all
+ expect(spam_logs.count).to eq(1)
+ expect(spam_logs[0].title).to eq('Spam Title')
+ end
+ end
+
+ context 'user agent details are saved' do
+ before do
+ request.env['action_dispatch.remote_ip'] = '127.0.0.1'
+ end
+
+ def post_new_issue
+ sign_in(user)
+ project = create(:empty_project, :public)
+ post :create, {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ issue: { title: 'Title', description: 'Description' }
+ }
+ end
+
+ it 'creates a user agent detail' do
+ expect{ post_new_issue }.to change(UserAgentDetail, :count).by(1)
+ end
+ end
+ end
+
+ describe 'POST #mark_as_spam' do
+ context 'properly submits to Akismet' do
+ before do
+ allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+ allow_any_instance_of(ApplicationSetting).to receive_messages(akismet_enabled: true)
+ end
+
+ def post_spam
+ admin = create(:admin)
+ create(:user_agent_detail, subject: issue)
+ project.team << [admin, :master]
+ sign_in(admin)
+ post :mark_as_spam, {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: issue.iid
+ }
+ end
+
+ it 'updates issue' do
+ post_spam
+ expect(issue.submittable_as_spam?).to be_falsey
+ end
+ end
+ end
+
describe "DELETE #destroy" do
context "when the user is a developer" do
before { sign_in(user) }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 210085e3b1a..69758494543 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -36,7 +36,7 @@ describe Projects::MergeRequestsController do
describe "GET show" do
shared_examples "export merge as" do |format|
- it "should generally work" do
+ it "does generally work" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -46,7 +46,7 @@ describe Projects::MergeRequestsController do
expect(response).to be_success
end
- it "should generate it" do
+ it "generates it" do
expect_any_instance_of(MergeRequest).to receive(:"to_#{format}")
get(:show,
@@ -56,7 +56,7 @@ describe Projects::MergeRequestsController do
format: format)
end
- it "should render it" do
+ it "renders it" do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
@@ -66,7 +66,7 @@ describe Projects::MergeRequestsController do
expect(response.body).to eq(merge_request.send(:"to_#{format}").to_s)
end
- it "should not escape Html" do
+ it "does not escape Html" do
allow_any_instance_of(MergeRequest).to receive(:"to_#{format}").
and_return('HTML entities &<>" ')
@@ -118,7 +118,7 @@ describe Projects::MergeRequestsController do
context 'when filtering by opened state' do
context 'with opened merge requests' do
- it 'should list those merge requests' do
+ it 'lists those merge requests' do
get_merge_requests
expect(assigns(:merge_requests)).to include(merge_request)
@@ -131,7 +131,7 @@ describe Projects::MergeRequestsController do
merge_request.reopen!
end
- it 'should list those merge requests' do
+ it 'lists those merge requests' do
get_merge_requests
expect(assigns(:merge_requests)).to include(merge_request)
@@ -392,9 +392,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
- expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs, diff_refs, project)
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
end
diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
@@ -455,9 +455,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
- expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs, diff_refs, project)
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
end
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
@@ -477,9 +477,9 @@ describe Projects::MergeRequestsController do
end
it 'only renders the diffs for the path given' do
- expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
- expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
- meth.call(diffs, diff_refs, project)
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs|
+ expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs)
end
diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index d173bb350f1..4e3ef5dc6fa 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -14,7 +14,7 @@ describe Projects::MilestonesController do
end
describe "#destroy" do
- it "should remove milestone" do
+ it "removes milestone" do
expect(issue.milestone_id).to eq(milestone.id)
delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index 596d8d34b7c..da6112a13f7 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -3,7 +3,7 @@ require('spec_helper')
describe Projects::ProtectedBranchesController do
describe "GET #index" do
let(:project) { create(:project_empty_repo, :public) }
- it "redirect empty repo to projects page" do
+ it "redirects empty repo to projects page" do
get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param)
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 48f799d8ca1..04bd9a01f7b 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -24,7 +24,7 @@ describe Projects::RawController do
context 'image header' do
let(:id) { 'master/files/images/6049019_460s.jpg' }
- it 'set image content type header' do
+ it 'sets image content type header' do
get(:show,
namespace_id: public_project.namespace.to_param,
project_id: public_project.to_param,
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index ccd8c741c83..cccd492ef06 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::ServicesController do
describe "#test" do
context 'success' do
- it "should redirect and show success message" do
+ it "redirects and show success message" do
expect(service).to receive(:test).and_return({ success: true, result: 'done' })
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
expect(response.status).to redirect_to('/')
@@ -28,7 +28,7 @@ describe Projects::ServicesController do
end
context 'failure' do
- it "should redirect and show failure message" do
+ it "redirects and show failure message" do
expect(service).to receive(:test).and_return({ success: false, result: 'Bad test' })
get :test, namespace_id: project.namespace.id, project_id: project.id, id: service.id, format: :html
expect(response.status).to redirect_to('/')
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
new file mode 100644
index 00000000000..a6995145cc1
--- /dev/null
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Projects::TagsController do
+ let(:project) { create(:project, :public) }
+ let!(:release) { create(:release, project: project) }
+ let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
+
+ describe 'GET index' do
+ before { get :index, namespace_id: project.namespace.to_param, project_id: project.to_param }
+
+ it 'returns the tags for the page' do
+ expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
+ end
+
+ it 'returns releases matching those tags' do
+ expect(assigns(:releases)).to include(release)
+ expect(assigns(:releases)).not_to include(invalid_release)
+ end
+ end
+end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 3edce4d339c..ffe0641ddd7 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -128,7 +128,7 @@ describe ProjectsController do
context "when the url contains .atom" do
let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
- it 'expect an error creating the project' do
+ it 'expects an error creating the project' do
expect(public_project_with_dot_atom).not_to be_valid
end
end
@@ -222,7 +222,7 @@ describe ProjectsController do
create(:forked_project_link, forked_to_project: project_fork)
end
- it 'should remove fork from project' do
+ it 'removes fork from project' do
delete(:remove_fork,
namespace_id: project_fork.namespace.to_param,
id: project_fork.to_param, format: :js)
@@ -236,7 +236,7 @@ describe ProjectsController do
context 'when project not forked' do
let(:unforked_project) { create(:project, namespace: user.namespace) }
- it 'should do nothing if project was not forked' do
+ it 'does nothing if project was not forked' do
delete(:remove_fork,
namespace_id: unforked_project.namespace.to_param,
id: unforked_project.to_param, format: :js)
@@ -256,7 +256,7 @@ describe ProjectsController do
end
describe "GET refs" do
- it "should get a list of branches and tags" do
+ it "gets a list of branches and tags" do
get :refs, namespace_id: public_project.namespace.path, id: public_project.path
parsed_body = JSON.parse(response.body)
@@ -265,7 +265,7 @@ describe ProjectsController do
expect(parsed_body["Commits"]).to be_nil
end
- it "should get a list of branches, tags and commits" do
+ it "gets a list of branches, tags and commits" do
get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
parsed_body = JSON.parse(response.body)
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index efe9803b1a7..c2fdf89213a 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,8 +1,8 @@
FactoryGirl.define do
factory :broadcast_message do
message "MyText"
- starts_at Date.yesterday
- ends_at Date.tomorrow
+ starts_at 1.day.ago
+ ends_at 1.day.from_now
trait :expired do
starts_at 5.days.ago
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5e19e403c6b..0c93bbdfe26 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -7,6 +7,7 @@ FactoryGirl.define do
stage_idx 0
ref 'master'
tag false
+ status 'pending'
created_at 'Di 29. Okt 09:50:00 CET 2013'
started_at 'Di 29. Okt 09:51:28 CET 2013'
finished_at 'Di 29. Okt 09:53:28 CET 2013'
@@ -45,6 +46,10 @@ FactoryGirl.define do
status 'pending'
end
+ trait :created do
+ status 'created'
+ end
+
trait :manual do
status 'skipped'
self.when 'manual'
@@ -90,5 +95,21 @@ FactoryGirl.define do
build.save!
end
end
+
+ trait :artifacts_expired do
+ after(:create) do |build, _|
+ build.artifacts_file =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'),
+ 'application/zip')
+
+ build.artifacts_metadata =
+ fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'),
+ 'application/x-gzip')
+
+ build.artifacts_expire_at = 1.minute.ago
+
+ build.save!
+ end
+ end
end
end
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index a039bef6f3c..04d66020c87 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -18,7 +18,9 @@
FactoryGirl.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
+ ref 'master'
sha '97de212e80737a608d939f648d959671fb0a0142'
+ status 'pending'
project factory: :empty_project
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 6d47d05f8ad..b8d8fab0e0b 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -5,7 +5,8 @@ FactoryGirl.define do
variables do
{
- TRIGGER_KEY: 'TRIGGER_VALUE'
+ TRIGGER_KEY_1: 'TRIGGER_VALUE_1',
+ TRIGGER_KEY_2: 'TRIGGER_VALUE_2'
}
end
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 1e5c479616c..995f2080f10 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -7,6 +7,30 @@ FactoryGirl.define do
started_at 'Tue, 26 Jan 2016 08:21:42 +0100'
finished_at 'Tue, 26 Jan 2016 08:23:42 +0100'
+ trait :success do
+ status 'success'
+ end
+
+ trait :failed do
+ status 'failed'
+ end
+
+ trait :canceled do
+ status 'canceled'
+ end
+
+ trait :running do
+ status 'running'
+ end
+
+ trait :pending do
+ status 'pending'
+ end
+
+ trait :created do
+ status 'created'
+ end
+
after(:build) do |build, evaluator|
build.project = build.pipeline.project
end
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 07265c26ca3..846cccfc7fa 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -3,5 +3,6 @@ FactoryGirl.define do
sequence(:name) { |n| "environment#{n}" }
project factory: :empty_project
+ sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" }
end
end
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 28ed8078157..5575852c2d7 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -2,5 +2,28 @@ FactoryGirl.define do
factory :protected_branch do
name
project
+
+ after(:create) do |protected_branch|
+ protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER)
+ protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER)
+ end
+
+ trait :developers_can_push do
+ after(:create) do |protected_branch|
+ protected_branch.push_access_level.update!(access_level: Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ trait :developers_can_merge do
+ after(:create) do |protected_branch|
+ protected_branch.merge_access_level.update!(access_level: Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ trait :no_one_can_push do
+ after(:create) do |protected_branch|
+ protected_branch.push_access_level.update!(access_level: Gitlab::Access::NO_ACCESS)
+ end
+ end
end
end
diff --git a/spec/factories/user_agent_details.rb b/spec/factories/user_agent_details.rb
new file mode 100644
index 00000000000..9763cc0cf15
--- /dev/null
+++ b/spec/factories/user_agent_details.rb
@@ -0,0 +1,7 @@
+FactoryGirl.define do
+ factory :user_agent_detail do
+ ip_address '127.0.0.1'
+ user_agent 'AppleWebKit/537.36'
+ association :subject, factory: :issue
+ end
+end
diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb
index 675d9bd18b7..786e1456f5f 100644
--- a/spec/factories_spec.rb
+++ b/spec/factories_spec.rb
@@ -9,7 +9,7 @@ describe 'factories' do
expect { entity }.not_to raise_error
end
- it 'should be valid', if: factory.build_class < ActiveRecord::Base do
+ it 'is valid', if: factory.build_class < ActiveRecord::Base do
expect(entity).to be_valid
end
end
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 16baf7e9516..c1731e6414a 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -11,7 +11,7 @@ describe "Admin::AbuseReports", feature: true, js: true do
end
describe 'in the abuse report view' do
- it "should present a link to the user's profile" do
+ it "presents a link to the user's profile" do
visit admin_abuse_reports_path
expect(page).to have_link user.name, href: user_path(user)
@@ -19,7 +19,7 @@ describe "Admin::AbuseReports", feature: true, js: true do
end
describe 'in the profile page of the user' do
- it 'should show a link to the admin view of the user' do
+ it 'shows a link to the admin view of the user' do
visit user_path(user)
expect(page).to have_link '', href: admin_user_path(user)
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index 5b1c0460274..66044b44495 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -45,7 +45,6 @@ feature 'Admin disables Git access protocol', feature: true do
expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
expect(page).to have_selector('#clone-dropdown')
end
-
end
def visit_project
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 7964951ae99..b3ce72b1452 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -9,7 +9,7 @@ describe "Admin::Hooks", feature: true do
end
describe "GET /admin/hooks" do
- it "should be ok" do
+ it "is ok" do
visit admin_root_path
page.within ".layout-nav" do
@@ -19,7 +19,7 @@ describe "Admin::Hooks", feature: true do
expect(current_path).to eq(admin_hooks_path)
end
- it "should have hooks list" do
+ it "has hooks list" do
visit admin_hooks_path
expect(page).to have_content(@system_hook.url)
end
@@ -33,7 +33,7 @@ describe "Admin::Hooks", feature: true do
expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1)
end
- it "should open new hook popup" do
+ it "opens new hook popup" do
expect(current_path).to eq(admin_hooks_path)
expect(page).to have_content(@url)
end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 101d955d693..30ded9202a4 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -11,11 +11,11 @@ describe "Admin::Projects", feature: true do
visit admin_namespaces_projects_path
end
- it "should be ok" do
+ it "is ok" do
expect(current_path).to eq(admin_namespaces_projects_path)
end
- it "should have projects list" do
+ it "has projects list" do
expect(page).to have_content(@project.name)
end
end
@@ -26,7 +26,7 @@ describe "Admin::Projects", feature: true do
click_link "#{@project.name}"
end
- it "should have project info" do
+ it "has project info" do
expect(page).to have_content(@project.path)
expect(page).to have_content(@project.name)
end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 767504df251..cb3191dfdde 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -8,11 +8,11 @@ describe "Admin::Users", feature: true do
visit admin_users_path
end
- it "should be ok" do
+ it "is ok" do
expect(current_path).to eq(admin_users_path)
end
- it "should have users list" do
+ it "has users list" do
expect(page).to have_content(@user.email)
expect(page).to have_content(@user.name)
end
@@ -66,11 +66,11 @@ describe "Admin::Users", feature: true do
fill_in "user_email", with: "bigbang@mail.com"
end
- it "should create new user" do
+ it "creates new user" do
expect { click_button "Create user" }.to change {User.count}.by(1)
end
- it "should apply defaults to user" do
+ it "applies defaults to user" do
click_button "Create user"
user = User.find_by(username: 'bang')
expect(user.projects_limit).
@@ -79,20 +79,20 @@ describe "Admin::Users", feature: true do
to eq(Gitlab.config.gitlab.default_can_create_group)
end
- it "should create user with valid data" do
+ it "creates user with valid data" do
click_button "Create user"
user = User.find_by(username: 'bang')
expect(user.name).to eq('Big Bang')
expect(user.email).to eq('bigbang@mail.com')
end
- it "should call send mail" do
+ it "calls send mail" do
expect_any_instance_of(NotificationService).to receive(:new_user)
click_button "Create user"
end
- it "should send valid email to user with email & password" do
+ it "sends valid email to user with email & password" do
perform_enqueued_jobs do
click_button "Create user"
end
@@ -106,7 +106,7 @@ describe "Admin::Users", feature: true do
end
describe "GET /admin/users/:id" do
- it "should have user info" do
+ it "has user info" do
visit admin_users_path
click_link @user.name
@@ -123,13 +123,13 @@ describe "Admin::Users", feature: true do
expect(page).to have_content('Impersonate')
end
- it 'should not show impersonate button for admin itself' do
+ it 'does not show impersonate button for admin itself' do
visit admin_user_path(@user)
expect(page).not_to have_content('Impersonate')
end
- it 'should not show impersonate button for blocked user' do
+ it 'does not show impersonate button for blocked user' do
another_user.block
visit admin_user_path(another_user)
@@ -153,7 +153,7 @@ describe "Admin::Users", feature: true do
expect(icon).not_to eql nil
end
- it 'can log out of impersonated user back to original user' do
+ it 'logs out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username)
@@ -197,7 +197,7 @@ describe "Admin::Users", feature: true do
click_link "edit_user_#{@simple_user.id}"
end
- it "should have user edit page" do
+ it "has user edit page" do
expect(page).to have_content('Name')
expect(page).to have_content('Password')
end
@@ -212,12 +212,12 @@ describe "Admin::Users", feature: true do
click_button "Save changes"
end
- it "should show page with new data" do
+ it "shows page with new data" do
expect(page).to have_content('bigbang@mail.com')
expect(page).to have_content('Big Bang')
end
- it "should change user entry" do
+ it "changes user entry" do
@simple_user.reload
expect(@simple_user.name).to eq('Big Bang')
expect(@simple_user.is_admin?).to be_truthy
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index f81a3c117ff..746df36bb25 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -5,7 +5,7 @@ describe "Dashboard Feed", feature: true do
let!(:user) { create(:user, name: "Jonh") }
context "projects atom feed via private token" do
- it "should render projects atom feed" do
+ it "renders projects atom feed" do
visit dashboard_projects_path(:atom, private_token: user.private_token)
expect(body).to have_selector('feed title')
end
@@ -23,11 +23,11 @@ describe "Dashboard Feed", feature: true do
visit dashboard_projects_path(:atom, private_token: user.private_token)
end
- it "should have issue opened event" do
+ it "has issue opened event" do
expect(body).to have_content("#{user.name} opened issue ##{issue.iid}")
end
- it "should have issue comment event" do
+ it "has issue comment event" do
expect(body).
to have_content("#{user.name} commented on issue ##{issue.iid}")
end
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index baa7814e96a..09c140868fb 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -9,7 +9,7 @@ describe 'Issues Feed', feature: true do
before { project.team << [user, :developer] }
context 'when authenticated' do
- it 'should render atom feed' do
+ it 'renders atom feed' do
login_with user
visit namespace_project_issues_path(project.namespace, project, :atom)
@@ -22,7 +22,7 @@ describe 'Issues Feed', feature: true do
end
context 'when authenticated via private token' do
- it 'should render atom feed' do
+ it 'renders atom feed' do
visit namespace_project_issues_path(project.namespace, project, :atom,
private_token: user.private_token)
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 91704377a07..a8833194421 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -5,7 +5,7 @@ describe "User Feed", feature: true do
let!(:user) { create(:user) }
context 'user atom feed via private token' do
- it "should render user atom feed" do
+ it "renders user atom feed" do
visit user_path(user, :atom, private_token: user.private_token)
expect(body).to have_selector('feed title')
end
@@ -43,24 +43,24 @@ describe "User Feed", feature: true do
visit user_path(user, :atom, private_token: user.private_token)
end
- it 'should have issue opened event' do
+ it 'has issue opened event' do
expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}")
end
- it 'should have issue comment event' do
+ it 'has issue comment event' do
expect(body).
to have_content("#{safe_name} commented on issue ##{issue.iid}")
end
- it 'should have XHTML summaries in issue descriptions' do
+ it 'has XHTML summaries in issue descriptions' do
expect(body).to match /we have a bug!<\/p>\n\n<hr ?\/>\n\n<p>I guess/
end
- it 'should have XHTML summaries in notes' do
+ it 'has XHTML summaries in notes' do
expect(body).to match /Bug confirmed <img[^>]*\/>/
end
- it 'should have XHTML summaries in merge request descriptions' do
+ it 'has XHTML summaries in merge request descriptions' do
expect(body).to match /Here is the fix: <\/p><div[^>]*><a[^>]*><img[^>]*\/><\/a><\/div>/
end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index cab3dc1d167..0cfeb2e57d8 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -199,9 +199,13 @@ describe "Builds" do
click_link 'Retry'
end
- it { expect(page.status_code).to eq(200) }
- it { expect(page).to have_content 'pending' }
- it { expect(page).to have_content 'Cancel' }
+ it 'shows the right status and buttons' do
+ expect(page).to have_http_status(200)
+ expect(page).to have_content 'pending'
+ page.within('aside.right-sidebar') do
+ expect(page).to have_content 'Cancel'
+ end
+ end
end
context "Build from other project" do
@@ -212,7 +216,25 @@ describe "Builds" do
page.driver.post(retry_namespace_project_build_path(@project.namespace, @project, @build2))
end
- it { expect(page.status_code).to eq(404) }
+ it { expect(page).to have_http_status(404) }
+ end
+
+ context "Build that current user is not allowed to retry" do
+ before do
+ @build.run!
+ @build.cancel!
+ @project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+
+ logout_direct
+ login_with(create(:user))
+ visit namespace_project_build_path(@project.namespace, @project, @build)
+ end
+
+ it 'does not show the Retry button' do
+ page.within('aside.right-sidebar') do
+ expect(page).not_to have_content 'Retry'
+ end
+ end
end
end
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index 30e29d9d552..81077f4b005 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -17,7 +17,7 @@ describe 'CI Lint' do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
- it 'Yaml parsing' do
+ it 'parses Yaml' do
within "table" do
expect(page).to have_content('Job - rspec')
expect(page).to have_content('Job - spinach')
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 45e1a157a1f..5910803df51 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -52,7 +52,7 @@ describe 'Commits' do
visit namespace_project_commits_path(project.namespace, project, :master)
end
- it 'should show build status' do
+ it 'shows build status' do
page.within("//li[@id='commit-#{pipeline.short_sha}']") do
expect(page).to have_css(".ci-status-link")
end
diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb
index c62556948e0..ca7f73e24cc 100644
--- a/spec/features/compare_spec.rb
+++ b/spec/features/compare_spec.rb
@@ -11,11 +11,11 @@ describe "Compare", js: true do
end
describe "branches" do
- it "should pre-populate fields" do
+ it "pre-populates fields" do
expect(page.find_field("from").value).to eq("master")
end
- it "should compare branches" do
+ it "compares branches" do
fill_in "from", with: "fea"
find("#from").click
@@ -28,7 +28,7 @@ describe "Compare", js: true do
end
describe "tags" do
- it "should compare tags" do
+ it "compares tags" do
fill_in "from", with: "v1.0"
find("#from").click
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 24e83d44010..4cff12de854 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -16,7 +16,7 @@ describe 'Dashboard > label filter', feature: true, js: true do
end
context 'duplicate labels' do
- it 'should remove duplicate labels' do
+ it 'removes duplicate labels' do
page.within('.labels-filter') do
click_button 'Label'
end
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index 39805da9d0b..3fb1cb37544 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -16,7 +16,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
visit_issues
end
- it 'should show all issues with no milestone' do
+ it 'shows all issues with no milestone' do
show_milestone_dropdown
click_link 'No Milestone'
@@ -24,7 +24,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_selector('.issue', count: 1)
end
- it 'should show all issues with any milestone' do
+ it 'shows all issues with any milestone' do
show_milestone_dropdown
click_link 'Any Milestone'
@@ -32,7 +32,7 @@ describe "Dashboard Issues filtering", feature: true, js: true do
expect(page).to have_selector('.issue', count: 2)
end
- it 'should show all issues with the selected milestone' do
+ it 'shows all issues with the selected milestone' do
show_milestone_dropdown
page.within '.dropdown-content' do
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index a7d9f2a0c72..fcd41b38413 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -140,7 +140,7 @@ feature 'Environments', feature: true do
context 'for valid name' do
before do
fill_in('Name', with: 'production')
- click_on 'Create environment'
+ click_on 'Save'
end
scenario 'does create a new pipeline' do
@@ -151,7 +151,7 @@ feature 'Environments', feature: true do
context 'for invalid name' do
before do
fill_in('Name', with: 'name with spaces')
- click_on 'Create environment'
+ click_on 'Save'
end
scenario 'does show errors' do
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index a89ac09f236..84d73d693bc 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -23,25 +23,25 @@ describe "GitLab Flavored Markdown", feature: true do
end
describe "for commits" do
- it "should render title in commits#index" do
+ it "renders title in commits#index" do
visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
expect(page).to have_link(issue.to_reference)
end
- it "should render title in commits#show" do
+ it "renders title in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
expect(page).to have_link(issue.to_reference)
end
- it "should render description in commits#show" do
+ it "renders description in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
expect(page).to have_link(fred.to_reference)
end
- it "should render title in repositories#branches" do
+ it "renders title in repositories#branches" do
visit namespace_project_branches_path(project.namespace, project)
expect(page).to have_link(issue.to_reference)
@@ -62,19 +62,19 @@ describe "GitLab Flavored Markdown", feature: true do
description: "ask #{fred.to_reference} for details")
end
- it "should render subject in issues#index" do
+ it "renders subject in issues#index" do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_link(@other_issue.to_reference)
end
- it "should render subject in issues#show" do
+ it "renders subject in issues#show" do
visit namespace_project_issue_path(project.namespace, project, @issue)
expect(page).to have_link(@other_issue.to_reference)
end
- it "should render details in issues#show" do
+ it "renders details in issues#show" do
visit namespace_project_issue_path(project.namespace, project, @issue)
expect(page).to have_link(fred.to_reference)
@@ -86,13 +86,13 @@ describe "GitLab Flavored Markdown", feature: true do
@merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}")
end
- it "should render title in merge_requests#index" do
+ it "renders title in merge_requests#index" do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_link(issue.to_reference)
end
- it "should render title in merge_requests#show" do
+ it "renders title in merge_requests#show" do
visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
expect(page).to have_link(issue.to_reference)
@@ -107,19 +107,19 @@ describe "GitLab Flavored Markdown", feature: true do
description: "ask #{fred.to_reference} for details")
end
- it "should render title in milestones#index" do
+ it "renders title in milestones#index" do
visit namespace_project_milestones_path(project.namespace, project)
expect(page).to have_link(issue.to_reference)
end
- it "should render title in milestones#show" do
+ it "renders title in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
expect(page).to have_link(issue.to_reference)
end
- it "should render description in milestones#show" do
+ it "renders description in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
expect(page).to have_link(fred.to_reference)
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 1e2306d7f59..e2101b333e2 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -5,7 +5,7 @@ describe 'Help Pages', feature: true do
before do
login_as :user
end
- it 'replace the variable $your_email with the email of the user' do
+ it 'replaces the variable $your_email with the email of the user' do
visit help_page_path('ssh/README')
expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
end
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
new file mode 100644
index 00000000000..9114f751b55
--- /dev/null
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -0,0 +1,171 @@
+require 'spec_helper'
+
+describe 'Projects > Issuables > Default sort order', feature: true do
+ let(:project) { create(:empty_project, :public) }
+
+ let(:first_created_issuable) { issuables.order_created_asc.first }
+ let(:last_created_issuable) { issuables.order_created_desc.first }
+
+ let(:first_updated_issuable) { issuables.order_updated_asc.first }
+ let(:last_updated_issuable) { issuables.order_updated_desc.first }
+
+ context 'for merge requests' do
+ include MergeRequestHelpers
+
+ let!(:issuables) do
+ timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+ { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+ { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+ timestamps.each_with_index do |ts, i|
+ create issuable_type, { title: "#{issuable_type}_#{i}",
+ source_branch: "#{issuable_type}_#{i}",
+ source_project: project }.merge(ts)
+ end
+
+ MergeRequest.all
+ end
+
+ context 'in the "merge requests" tab', js: true do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "last created"' do
+ visit_merge_requests project
+
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / open" tab', js: true do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "last created"' do
+ visit_merge_requests_with_state(project, 'open')
+
+ expect(selected_sort_order).to eq('last created')
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / merged" tab', js: true do
+ let(:issuable_type) { :merged_merge_request }
+
+ it 'is "last updated"' do
+ visit_merge_requests_with_state(project, 'merged')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / closed" tab', js: true do
+ let(:issuable_type) { :closed_merge_request }
+
+ it 'is "last updated"' do
+ visit_merge_requests_with_state(project, 'closed')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / all" tab', js: true do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "last created"' do
+ visit_merge_requests_with_state(project, 'all')
+
+ expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+ end
+
+ context 'for issues' do
+ include IssueHelpers
+
+ let!(:issuables) do
+ timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+ { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+ { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+ timestamps.each_with_index do |ts, i|
+ create issuable_type, { title: "#{issuable_type}_#{i}",
+ project: project }.merge(ts)
+ end
+
+ Issue.all
+ end
+
+ context 'in the "issues" tab', js: true do
+ let(:issuable_type) { :issue }
+
+ it 'is "last created"' do
+ visit_issues project
+
+ expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "issues / open" tab', js: true do
+ let(:issuable_type) { :issue }
+
+ it 'is "last created"' do
+ visit_issues_with_state(project, 'open')
+
+ expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "issues / closed" tab', js: true do
+ let(:issuable_type) { :closed_issue }
+
+ it 'is "last updated"' do
+ visit_issues_with_state(project, 'closed')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_issue).to include(last_updated_issuable.title)
+ expect(last_issue).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "issues / all" tab', js: true do
+ let(:issuable_type) { :issue }
+
+ it 'is "last created"' do
+ visit_issues_with_state(project, 'all')
+
+ expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+ end
+
+ def selected_sort_order
+ find('.pull-right .dropdown button').text.downcase
+ end
+
+ def visit_merge_requests_with_state(project, state)
+ visit_merge_requests project
+ visit_issuables_with_state state
+ end
+
+ def visit_issues_with_state(project, state)
+ visit_issues project
+ visit_issuables_with_state state
+ end
+
+ def visit_issuables_with_state(state)
+ within('.issues-state-filters') { find("span", text: state.titleize).click }
+ end
+end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 07a854ea014..6eb04cf74c5 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -21,32 +21,32 @@ describe 'Awards Emoji', feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should increment the thumbsdown emoji', js: true do
+ it 'increments the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click
sleep 2
expect(thumbsdown_emoji).to have_text("1")
end
context 'click the thumbsup emoji' do
- it 'should increment the thumbsup emoji', js: true do
+ it 'increments the thumbsup emoji', js: true do
find('[data-emoji="thumbsup"]').click
sleep 2
expect(thumbsup_emoji).to have_text("1")
end
- it 'should decrement the thumbsdown emoji', js: true do
+ it 'decrements the thumbsdown emoji', js: true do
expect(thumbsdown_emoji).to have_text("0")
end
end
context 'click the thumbsdown emoji' do
- it 'should increment the thumbsdown emoji', js: true do
+ it 'increments the thumbsdown emoji', js: true do
find('[data-emoji="thumbsdown"]').click
sleep 2
expect(thumbsdown_emoji).to have_text("1")
end
- it 'should decrement the thumbsup emoji', js: true do
+ it 'decrements the thumbsup emoji', js: true do
expect(thumbsup_emoji).to have_text("0")
end
end
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index 63efecf8780..401e1ea2b89 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -11,7 +11,7 @@ feature 'Issue awards', js: true, feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should add award to issue' do
+ it 'adds award to issue' do
first('.js-emoji-btn').click
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
@@ -20,7 +20,7 @@ feature 'Issue awards', js: true, feature: true do
expect(first('.js-emoji-btn')).to have_content '1'
end
- it 'should remove award from issue' do
+ it 'removes award from issue' do
first('.js-emoji-btn').click
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
@@ -29,7 +29,7 @@ feature 'Issue awards', js: true, feature: true do
expect(first('.js-emoji-btn')).to have_content '0'
end
- it 'should only have one menu on the page' do
+ it 'only has one menu on the page' do
first('.js-add-award').click
expect(page).to have_selector('.emoji-menu')
@@ -42,7 +42,7 @@ feature 'Issue awards', js: true, feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should not see award menu button' do
+ it 'does not see award menu button' do
expect(page).not_to have_selector('.js-award-holder')
end
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index afc093cc1f5..bc2c087c9b9 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -175,7 +175,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
- it 'labels are kept' do
+ it 'keeps labels' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
@@ -197,7 +197,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
- it 'existing label is kept and new label is present' do
+ it 'keeps existing label and new label is present' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
check 'check_all_issues'
@@ -222,7 +222,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
- it 'existing label is kept and new label is present' do
+ it 'keeps existing label and new label is present' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
@@ -252,7 +252,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
- it 'labels are kept' do
+ it 'keeps labels' do
expect(find("#issue_#{issue1.id}")).to have_content 'bug'
expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
expect(find("#issue_#{issue2.id}")).to have_content 'feature'
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb
index 5ea02b8d39c..908b18e5339 100644
--- a/spec/features/issues/filter_by_labels_spec.rb
+++ b/spec/features/issues/filter_by_labels_spec.rb
@@ -37,25 +37,25 @@ feature 'Issue filtering by Labels', feature: true do
wait_for_ajax
end
- it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do
+ it 'shows issue "Bugfix1" and "Bugfix2" in issues list' do
expect(page).to have_content "Bugfix1"
expect(page).to have_content "Bugfix2"
end
- it 'should not show "Feature1" in issues list' do
+ it 'does not show "Feature1" in issues list' do
expect(page).not_to have_content "Feature1"
end
- it 'should show label "bug" in filtered-labels' do
+ it 'shows label "bug" in filtered-labels' do
expect(find('.filtered-labels')).to have_content "bug"
end
- it 'should not show label "feature" and "enhancement" in filtered-labels' do
+ it 'does not show label "feature" and "enhancement" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "feature"
expect(find('.filtered-labels')).not_to have_content "enhancement"
end
- it 'should remove label "bug"' do
+ it 'removes label "bug"' do
find('.js-label-filter-remove').click
wait_for_ajax
expect(find('.filtered-labels', visible: false)).to have_no_content "bug"
@@ -71,20 +71,20 @@ feature 'Issue filtering by Labels', feature: true do
wait_for_ajax
end
- it 'should show issue "Feature1" in issues list' do
+ it 'shows issue "Feature1" in issues list' do
expect(page).to have_content "Feature1"
end
- it 'should not show "Bugfix1" and "Bugfix2" in issues list' do
+ it 'does not show "Bugfix1" and "Bugfix2" in issues list' do
expect(page).not_to have_content "Bugfix2"
expect(page).not_to have_content "Bugfix1"
end
- it 'should show label "feature" in filtered-labels' do
+ it 'shows label "feature" in filtered-labels' do
expect(find('.filtered-labels')).to have_content "feature"
end
- it 'should not show label "bug" and "enhancement" in filtered-labels' do
+ it 'does not show label "bug" and "enhancement" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "bug"
expect(find('.filtered-labels')).not_to have_content "enhancement"
end
@@ -99,20 +99,20 @@ feature 'Issue filtering by Labels', feature: true do
wait_for_ajax
end
- it 'should show issue "Bugfix2" in issues list' do
+ it 'shows issue "Bugfix2" in issues list' do
expect(page).to have_content "Bugfix2"
end
- it 'should not show "Feature1" and "Bugfix1" in issues list' do
+ it 'does not show "Feature1" and "Bugfix1" in issues list' do
expect(page).not_to have_content "Feature1"
expect(page).not_to have_content "Bugfix1"
end
- it 'should show label "enhancement" in filtered-labels' do
+ it 'shows label "enhancement" in filtered-labels' do
expect(find('.filtered-labels')).to have_content "enhancement"
end
- it 'should not show label "feature" and "bug" in filtered-labels' do
+ it 'does not show label "feature" and "bug" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "bug"
expect(find('.filtered-labels')).not_to have_content "feature"
end
@@ -128,21 +128,21 @@ feature 'Issue filtering by Labels', feature: true do
wait_for_ajax
end
- it 'should not show "Bugfix1" or "Feature1" in issues list' do
+ it 'does not show "Bugfix1" or "Feature1" in issues list' do
expect(page).not_to have_content "Bugfix1"
expect(page).not_to have_content "Feature1"
end
- it 'should show label "enhancement" and "feature" in filtered-labels' do
+ it 'shows label "enhancement" and "feature" in filtered-labels' do
expect(find('.filtered-labels')).to have_content "enhancement"
expect(find('.filtered-labels')).to have_content "feature"
end
- it 'should not show label "bug" in filtered-labels' do
+ it 'does not show label "bug" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "bug"
end
- it 'should remove label "enhancement"' do
+ it 'removes label "enhancement"' do
find('.js-label-filter-remove', match: :first).click
wait_for_ajax
expect(find('.filtered-labels')).to have_no_content "enhancement"
@@ -159,20 +159,20 @@ feature 'Issue filtering by Labels', feature: true do
wait_for_ajax
end
- it 'should show issue "Bugfix2" in issues list' do
+ it 'shows issue "Bugfix2" in issues list' do
expect(page).to have_content "Bugfix2"
end
- it 'should not show "Feature1"' do
+ it 'does not show "Feature1"' do
expect(page).not_to have_content "Feature1"
end
- it 'should show label "bug" and "enhancement" in filtered-labels' do
+ it 'shows label "bug" and "enhancement" in filtered-labels' do
expect(find('.filtered-labels')).to have_content "bug"
expect(find('.filtered-labels')).to have_content "enhancement"
end
- it 'should not show label "feature" in filtered-labels' do
+ it 'does not show label "feature" in filtered-labels' do
expect(find('.filtered-labels')).not_to have_content "feature"
end
end
@@ -191,7 +191,7 @@ feature 'Issue filtering by Labels', feature: true do
end
end
- it 'should allow user to remove filtered labels' do
+ it 'allows user to remove filtered labels' do
first('.js-label-filter-remove').click
wait_for_ajax
@@ -201,11 +201,11 @@ feature 'Issue filtering by Labels', feature: true do
end
context 'dropdown filtering', js: true do
- it 'should filter by label name' do
+ it 'filters by label name' do
page.within '.labels-filter' do
click_button 'Label'
wait_for_ajax
- fill_in 'label-name', with: 'bug'
+ find('.dropdown-input input').set 'bug'
page.within '.dropdown-content' do
expect(page).not_to have_content 'enhancement'
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index 99445185893..485dc560061 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -15,7 +15,7 @@ feature 'Issue filtering by Milestone', feature: true do
end
context 'filters by upcoming milestone', js: true do
- it 'should not show issues with no expiry' do
+ it 'does not show issues with no expiry' do
create(:issue, project: project)
create(:issue, project: project, milestone: milestone)
@@ -25,7 +25,7 @@ feature 'Issue filtering by Milestone', feature: true do
expect(page).to have_css('.issue', count: 0)
end
- it 'should show issues in future' do
+ it 'shows issues in future' do
milestone = create(:milestone, project: project, due_date: Date.tomorrow)
create(:issue, project: project)
create(:issue, project: project, milestone: milestone)
@@ -36,7 +36,7 @@ feature 'Issue filtering by Milestone', feature: true do
expect(page).to have_css('.issue', count: 1)
end
- it 'should not show issues in past' do
+ it 'does not show issues in past' do
milestone = create(:milestone, project: project, due_date: Date.yesterday)
create(:issue, project: project)
create(:issue, project: project, milestone: milestone)
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 4b9b5394b61..e262f285868 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -26,17 +26,17 @@ describe 'Filter issues', feature: true do
end
context 'assignee', js: true do
- it 'should update to current user' do
+ it 'updates to current user' do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
- it 'should not change when closed link is clicked' do
+ it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
- it 'should not change when all link is clicked' do
+ it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
@@ -56,17 +56,17 @@ describe 'Filter issues', feature: true do
end
context 'milestone', js: true do
- it 'should update to current milestone' do
+ it 'updates to current milestone' do
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
- it 'should not change when closed link is clicked' do
+ it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
end
- it 'should not change when all link is clicked' do
+ it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-milestone-select .dropdown-toggle-text')).to have_content(milestone.title)
@@ -81,7 +81,7 @@ describe 'Filter issues', feature: true do
wait_for_ajax
end
- it 'should filter by any label' do
+ it 'filters by any label' do
find('.dropdown-menu-labels a', text: 'Any Label').click
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
wait_for_ajax
@@ -89,7 +89,7 @@ describe 'Filter issues', feature: true do
expect(find('.labels-filter')).to have_content 'Label'
end
- it 'should filter by no label' do
+ it 'filters by no label' do
find('.dropdown-menu-labels a', text: 'No Label').click
page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click
wait_for_ajax
@@ -100,7 +100,7 @@ describe 'Filter issues', feature: true do
expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
end
- it 'should filter by no label' do
+ it 'filters by no label' do
find('.dropdown-menu-labels a', text: label.title).click
page.within '.labels-filter' do
expect(page).to have_content label.title
@@ -117,7 +117,7 @@ describe 'Filter issues', feature: true do
find('.dropdown-menu-user-link', text: user.username).click
- wait_for_ajax
+ expect(page).not_to have_selector('.issues-list .issue')
find('.js-label-select').click
@@ -128,19 +128,19 @@ describe 'Filter issues', feature: true do
end
context 'assignee and label', js: true do
- it 'should update to current assignee and label' do
+ it 'updates to current assignee and label' do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
- it 'should not change when closed link is clicked' do
+ it 'does not change when closed link is clicked' do
find('.issues-state-filters a', text: "Closed").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end
- it 'should not change when all link is clicked' do
+ it 'does not change when all link is clicked' do
find('.issues-state-filters a', text: "All").click
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
@@ -168,7 +168,7 @@ describe 'Filter issues', feature: true do
end
context 'only text', js: true do
- it 'should filter issues by searched text' do
+ it 'filters issues by searched text' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
@@ -176,7 +176,7 @@ describe 'Filter issues', feature: true do
end
end
- it 'should not show any issues' do
+ it 'does not show any issues' do
fill_in 'issue_search', with: 'testing'
page.within '.issues-list' do
@@ -186,7 +186,7 @@ describe 'Filter issues', feature: true do
end
context 'text and dropdown options', js: true do
- it 'should filter by text and label' do
+ it 'filters by text and label' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
@@ -204,7 +204,7 @@ describe 'Filter issues', feature: true do
end
end
- it 'should filter by text and milestone' do
+ it 'filters by text and milestone' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
@@ -221,7 +221,7 @@ describe 'Filter issues', feature: true do
end
end
- it 'should filter by text and assignee' do
+ it 'filters by text and assignee' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
@@ -238,7 +238,7 @@ describe 'Filter issues', feature: true do
end
end
- it 'should filter by text and author' do
+ it 'filters by text and author' do
fill_in 'issue_search', with: 'Bug'
page.within '.issues-list' do
@@ -269,7 +269,7 @@ describe 'Filter issues', feature: true do
visit namespace_project_issues_path(project.namespace, project)
end
- it 'should be able to filter and sort issues' do
+ it 'is able to filter and sort issues' do
click_button 'Label'
wait_for_ajax
page.within '.labels-filter' do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 5739bc64dfb..4b1aec8bf71 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -17,7 +17,7 @@ feature 'Issue Sidebar', feature: true do
end
describe 'when clicking on edit labels', js: true do
- it 'dropdown has an option to create a new label' do
+ it 'shows dropdown option to create a new label' do
find('.block.labels .edit-link').click
page.within('.block.labels') do
@@ -27,7 +27,7 @@ feature 'Issue Sidebar', feature: true do
end
context 'creating a new label', js: true do
- it 'option to crate a new label is present' do
+ it 'shows option to crate a new label is present' do
page.within('.block.labels') do
find('.edit-link').click
@@ -35,7 +35,7 @@ feature 'Issue Sidebar', feature: true do
end
end
- it 'dropdown switches to "create label" section' do
+ it 'shows dropdown switches to "create label" section' do
page.within('.block.labels') do
find('.edit-link').click
click_link 'Create new'
@@ -44,7 +44,7 @@ feature 'Issue Sidebar', feature: true do
end
end
- it 'new label is added' do
+ it 'adds new label' do
page.within('.block.labels') do
find('.edit-link').click
sleep 1
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index 16e188d2a8a..e528aff4d41 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -41,7 +41,7 @@ feature 'Start new branch from an issue', feature: true do
end
context "for visiters" do
- it 'no button is shown', js: true do
+ it 'shows no buttons', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).not_to have_css('#new-branch')
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index bc0f437a8ce..de8fdda388d 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -11,7 +11,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should create todo when clicking button' do
+ it 'creates todo when clicking button' do
page.within '.issuable-sidebar' do
click_button 'Add Todo'
expect(page).to have_content 'Mark Done'
@@ -28,7 +28,7 @@ feature 'Manually create a todo item from issue', feature: true, js: true do
end
end
- it 'should mark a todo as done' do
+ it 'marks a todo as done' do
page.within '.issuable-sidebar' do
click_button 'Add Todo'
click_button 'Mark Done'
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index ddbd69b2891..ae5da3877a8 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -13,7 +13,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
end
context 'status', js: true do
- it 'should be set to closed' do
+ it 'sets to closed' do
visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click
@@ -24,7 +24,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
expect(page).to have_selector('.issue', count: 0)
end
- it 'should be set to open' do
+ it 'sets to open' do
create_closed
visit namespace_project_issues_path(project.namespace, project, state: 'closed')
@@ -38,7 +38,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
end
context 'assignee', js: true do
- it 'should update to current user' do
+ it 'updates to current user' do
visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click
@@ -52,7 +52,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
end
end
- it 'should update to unassigned' do
+ it 'updates to unassigned' do
create_assigned
visit namespace_project_issues_path(project.namespace, project)
@@ -68,7 +68,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
context 'milestone', js: true do
let(:milestone) { create(:milestone, project: project) }
- it 'should update milestone' do
+ it 'updates milestone' do
visit namespace_project_issues_path(project.namespace, project)
find('#check_all_issues').click
@@ -80,7 +80,7 @@ feature 'Multiple issue updating from issues#index', feature: true do
expect(find('.issue')).to have_content milestone.title
end
- it 'should set to no milestone' do
+ it 'sets to no milestone' do
create_with_milestone
visit namespace_project_issues_path(project.namespace, project)
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d51c9abea19..cb445e22af0 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe 'Issues', feature: true do
+ include IssueHelpers
include SortingHelper
let(:project) { create(:project) }
@@ -25,7 +26,7 @@ describe 'Issues', feature: true do
find('.js-zen-enter').click
end
- it 'should open new issue popup' do
+ it 'opens new issue popup' do
expect(page).to have_content("Issue ##{issue.iid}")
end
@@ -70,7 +71,7 @@ describe 'Issues', feature: true do
visit new_namespace_project_issue_path(project.namespace, project)
end
- it 'should save with due date' do
+ it 'saves with due date' do
date = Date.today.at_beginning_of_month
fill_in 'issue_title', with: 'bug 345'
@@ -98,7 +99,7 @@ describe 'Issues', feature: true do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should save with due date' do
+ it 'saves with due date' do
date = Date.today.at_beginning_of_month
expect(find('#issuable-due-date').value).to eq date.to_s
@@ -154,7 +155,7 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
- it 'should allow filtering by issues with no specified assignee' do
+ it 'allows filtering by issues with no specified assignee' do
visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
expect(page).to have_content 'foobar'
@@ -162,7 +163,7 @@ describe 'Issues', feature: true do
expect(page).not_to have_content 'gitlab'
end
- it 'should allow filtering by a specified assignee' do
+ it 'allows filtering by a specified assignee' do
visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
expect(page).not_to have_content 'foobar'
@@ -186,15 +187,15 @@ describe 'Issues', feature: true do
it 'sorts by newest' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created)
- expect(first_issue).to include('baz')
- expect(last_issue).to include('foo')
+ expect(first_issue).to include('foo')
+ expect(last_issue).to include('baz')
end
it 'sorts by oldest' do
visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created)
- expect(first_issue).to include('foo')
- expect(last_issue).to include('baz')
+ expect(first_issue).to include('baz')
+ expect(last_issue).to include('foo')
end
it 'sorts by most recently updated' do
@@ -350,8 +351,8 @@ describe 'Issues', feature: true do
sort: sort_value_oldest_created,
assignee_id: user2.id)
- expect(first_issue).to include('foo')
- expect(last_issue).to include('bar')
+ expect(first_issue).to include('bar')
+ expect(last_issue).to include('foo')
expect(page).not_to have_content 'baz'
end
end
@@ -513,7 +514,7 @@ describe 'Issues', feature: true do
visit new_namespace_project_issue_path(project.namespace, project)
end
- it 'should upload file when dragging into textarea' do
+ it 'uploads file when dragging into textarea' do
drop_in_dropzone test_image_file
# Wait for the file to upload
@@ -524,6 +525,35 @@ describe 'Issues', feature: true do
end
end
+ describe 'new issue by email' do
+ shared_examples 'show the email in the modal' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+
+ visit namespace_project_issues_path(project.namespace, project)
+ click_button('Email a new issue')
+ end
+
+ it 'click the button to show modal for the new email' do
+ page.within '#issue-email-modal' do
+ email = project.new_issue_address(@user)
+
+ expect(page).to have_selector("input[value='#{email}']")
+ end
+ end
+ end
+
+ context 'with existing issues' do
+ let!(:issue) { create(:issue, project: project, author: @user) }
+
+ it_behaves_like 'show the email in the modal'
+ end
+
+ context 'without existing issues' do
+ it_behaves_like 'show the email in the modal'
+ end
+ end
+
describe 'due date' do
context 'update due on issue#show', js: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
@@ -532,7 +562,7 @@ describe 'Issues', feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'should add due date to issue' do
+ it 'adds due date to issue' do
page.within '.due_date' do
click_link 'Edit'
@@ -544,7 +574,7 @@ describe 'Issues', feature: true do
end
end
- it 'should remove due date from issue' do
+ it 'removes due date from issue' do
page.within '.due_date' do
click_link 'Edit'
@@ -561,14 +591,6 @@ describe 'Issues', feature: true do
end
end
- def first_issue
- page.all('ul.issues-list > li').first.text
- end
-
- def last_issue
- page.all('ul.issues-list > li').last.text
- end
-
def drop_in_dropzone(file_path)
# Generate a fake input selector
page.execute_script <<-JS
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 58753ff21f6..2523b4b7898 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -128,10 +128,10 @@ feature 'Login', feature: true do
end
allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
allow(Gitlab.config.omniauth).to receive_messages(messages)
- allow_any_instance_of(Object).to receive(:user_omniauth_authorize_path).with('saml').and_return('/users/auth/saml')
+ expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml')
end
- it 'should show 2FA prompt after OAuth login' do
+ it 'shows 2FA prompt after OAuth login' do
stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
login_via('saml', user, 'my-uid')
diff --git a/spec/features/merge_requests/award_spec.rb b/spec/features/merge_requests/award_spec.rb
index 007f67d6080..ac260e118d0 100644
--- a/spec/features/merge_requests/award_spec.rb
+++ b/spec/features/merge_requests/award_spec.rb
@@ -11,7 +11,7 @@ feature 'Merge request awards', js: true, feature: true do
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
- it 'should add award to merge request' do
+ it 'adds award to merge request' do
first('.js-emoji-btn').click
expect(page).to have_selector('.js-emoji-btn.active')
expect(first('.js-emoji-btn')).to have_content '1'
@@ -20,7 +20,7 @@ feature 'Merge request awards', js: true, feature: true do
expect(first('.js-emoji-btn')).to have_content '1'
end
- it 'should remove award from merge request' do
+ it 'removes award from merge request' do
first('.js-emoji-btn').click
find('.js-emoji-btn.active').click
expect(first('.js-emoji-btn')).to have_content '0'
@@ -29,7 +29,7 @@ feature 'Merge request awards', js: true, feature: true do
expect(first('.js-emoji-btn')).to have_content '0'
end
- it 'should only have one menu on the page' do
+ it 'has only one menu on the page' do
first('.js-add-award').click
expect(page).to have_selector('.emoji-menu')
@@ -42,7 +42,7 @@ feature 'Merge request awards', js: true, feature: true do
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
- it 'should not see award menu button' do
+ it 'does not see award menu button' do
expect(page).not_to have_selector('.js-award-holder')
end
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index e296078bad8..11c9de3c4bf 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -13,6 +13,8 @@ feature 'Create New Merge Request', feature: true, js: true do
it 'generates a diff for an orphaned branch' do
click_link 'New Merge Request'
+ expect(page).to have_content('Source branch')
+ expect(page).to have_content('Target branch')
first('.js-source-branch').click
first('.dropdown-source-branch .dropdown-content a', text: 'orphaned-branch').click
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index f676200ecf3..4d5d4aa121a 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -29,12 +29,16 @@ feature 'Merge request created from fork' do
include WaitForAjax
given(:pipeline) do
- create(:ci_pipeline_with_two_job, project: fork_project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
+ create(:ci_pipeline,
+ project: fork_project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch)
end
- background { pipeline.create_builds(user) }
+ background do
+ create(:ci_build, pipeline: pipeline, name: 'rspec')
+ create(:ci_build, pipeline: pipeline, name: 'spinach')
+ end
scenario 'user visits a pipelines page', js: true do
visit_merge_request(merge_request)
diff --git a/spec/features/merge_requests/diff_notes_spec.rb b/spec/features/merge_requests/diff_notes_spec.rb
new file mode 100644
index 00000000000..12e89742b79
--- /dev/null
+++ b/spec/features/merge_requests/diff_notes_spec.rb
@@ -0,0 +1,159 @@
+require 'spec_helper'
+
+feature 'Diff notes', js: true, feature: true do
+ include WaitForAjax
+
+ before do
+ login_as :admin
+ @merge_request = create(:merge_request)
+ @project = @merge_request.source_project
+ end
+
+ context 'merge request diffs' do
+ let(:comment_button_class) { '.add-diff-note' }
+ let(:notes_holder_input_class) { 'js-temp-notes-holder' }
+ let(:notes_holder_input_xpath) { './following-sibling::*[contains(concat(" ", @class, " "), " notes_holder ")]' }
+ let(:test_note_comment) { 'this is a test note!' }
+
+ context 'when hovering over the parallel view diff file' do
+ before(:each) do
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ click_link 'Side-by-side'
+ end
+
+ context 'with an old line on the left and no line on the right' do
+ it 'should allow commenting on the left side' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'should not allow commenting on the right side' do
+ should_not_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_23_22"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with no line on the left and a new line on the right' do
+ it 'should not allow commenting on the left side' do
+ should_not_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'should allow commenting on the right side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with an old line on the left and a new line on the right' do
+ it 'should allow commenting on the left side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'should allow commenting on the right side' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with an unchanged line on the left and an unchanged line on the right' do
+ it 'should allow commenting on the left side' do
+ should_allow_commenting(first('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]').find(:xpath, '..'), 'left')
+ end
+
+ it 'should allow commenting on the right side' do
+ should_allow_commenting(first('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]').find(:xpath, '..'), 'right')
+ end
+ end
+
+ context 'with a match line' do
+ it 'should not allow commenting on the left side' do
+ should_not_allow_commenting(first('.match').find(:xpath, '..'), 'left')
+ end
+
+ it 'should not allow commenting on the right side' do
+ should_not_allow_commenting(first('.match').find(:xpath, '..'), 'right')
+ end
+ end
+ end
+
+ context 'when hovering over the inline view diff file' do
+ before do
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+ click_link 'Inline'
+ end
+
+ context 'with a new line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
+ end
+ end
+
+ context 'with an old line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9_22_22"]'))
+ end
+ end
+
+ context 'with an unchanged line' do
+ it 'should allow commenting' do
+ should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"]'))
+ end
+ end
+
+ context 'with a match line' do
+ it 'should not allow commenting' do
+ should_not_allow_commenting(first('.match'))
+ end
+ end
+ end
+
+ def should_allow_commenting(line_holder, diff_side = nil)
+ line = get_line_components(line_holder, diff_side)
+ line[:content].hover
+ expect(line[:num]).to have_css comment_button_class
+
+ comment_on_line(line_holder, line)
+ wait_for_ajax
+
+ assert_comment_persistence(line_holder)
+ end
+
+ def should_not_allow_commenting(line_holder, diff_side = nil)
+ line = get_line_components(line_holder, diff_side)
+ line[:content].hover
+ expect(line[:num]).not_to have_css comment_button_class
+ end
+
+ def get_line_components(line_holder, diff_side = nil)
+ if diff_side.nil?
+ get_inline_line_components(line_holder)
+ else
+ get_parallel_line_components(line_holder, diff_side)
+ end
+ end
+
+ def get_inline_line_components(line_holder)
+ { content: line_holder.first('.line_content'), num: line_holder.first('.diff-line-num') }
+ end
+
+ def get_parallel_line_components(line_holder, diff_side = nil)
+ side_index = diff_side == 'left' ? 0 : 1
+ { content: line_holder.all('.line_content')[side_index], num: line_holder.all('.diff-line-num')[side_index] }
+ end
+
+ def comment_on_line(line_holder, line)
+ line[:num].find(comment_button_class).trigger 'click'
+ expect(line_holder).to have_xpath notes_holder_input_xpath
+
+ notes_holder_input = line_holder.find(:xpath, notes_holder_input_xpath)
+ expect(notes_holder_input[:class]).to include(notes_holder_input_class)
+
+ notes_holder_input.fill_in 'note[note]', with: test_note_comment
+ click_button 'Comment'
+ end
+
+ def assert_comment_persistence(line_holder)
+ expect(line_holder).to have_xpath notes_holder_input_xpath
+
+ notes_holder_saved = line_holder.find(:xpath, notes_holder_input_xpath)
+ expect(notes_holder_saved[:class]).not_to include(notes_holder_input_class)
+ expect(notes_holder_saved).to have_content test_note_comment
+ end
+ end
+end
diff --git a/spec/features/merge_requests/edit_mr_spec.rb b/spec/features/merge_requests/edit_mr_spec.rb
index 9e007ab7635..4109e78f560 100644
--- a/spec/features/merge_requests/edit_mr_spec.rb
+++ b/spec/features/merge_requests/edit_mr_spec.rb
@@ -14,7 +14,7 @@ feature 'Edit Merge Request', feature: true do
end
context 'editing a MR' do
- it 'form should have class js-quick-submit' do
+ it 'has class js-quick-submit in form' do
expect(page).to have_selector('.js-quick-submit')
end
end
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
index e3ecd60a5f3..bb0bb590a46 100644
--- a/spec/features/merge_requests/filter_by_milestone_spec.rb
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -21,7 +21,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
end
context 'filters by upcoming milestone', js: true do
- it 'should not show issues with no expiry' do
+ it 'does not show issues with no expiry' do
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
@@ -31,7 +31,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
expect(page).to have_css('.merge-request', count: 0)
end
- it 'should show issues in future' do
+ it 'shows issues in future' do
milestone = create(:milestone, project: project, due_date: Date.tomorrow)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
@@ -42,7 +42,7 @@ feature 'Merge Request filtering by Milestone', feature: true do
expect(page).to have_css('.merge-request', count: 1)
end
- it 'should not show issues in past' do
+ it 'does not show issues in past' do
milestone = create(:milestone, project: project, due_date: Date.yesterday)
create(:merge_request, :with_diffs, source_project: project)
create(:merge_request, :simple, source_project: project, milestone: milestone)
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index 96f7b8c9932..60bc07bd1a0 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -73,7 +73,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
context 'Build is not active' do
- it "should not allow for enabling" do
+ it "does not allow for enabling" do
visit_merge_request(merge_request)
expect(page).not_to have_link "Merge When Build Succeeds"
end
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 1c130057c56..cabb8e455f9 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe 'Projects > Merge requests > User lists merge requests', feature: true do
+ include MergeRequestHelpers
include SortingHelper
let(:project) { create(:project, :public) }
@@ -23,10 +24,12 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
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: 'lfs',
+ title: 'merge_lfs',
source_project: project,
- source_branch: 'lfs',
+ source_branch: 'merge_lfs',
created_at: 3.minutes.ago,
updated_at: 10.seconds.ago)
end
@@ -35,7 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
- expect(page).to have_content 'lfs'
+ expect(page).to have_content 'merge_lfs'
expect(page).not_to have_content 'fix'
expect(page).not_to have_content 'markdown'
expect(count_merge_requests).to eq(1)
@@ -44,7 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
it 'filters on a specific assignee' do
visit_merge_requests(project, assignee_id: user.id)
- expect(page).not_to have_content 'lfs'
+ expect(page).not_to have_content 'merge_lfs'
expect(page).to have_content 'fix'
expect(page).to have_content 'markdown'
expect(count_merge_requests).to eq(2)
@@ -53,23 +56,23 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
it 'sorts by newest' do
visit_merge_requests(project, sort: sort_value_recently_created)
- expect(first_merge_request).to include('lfs')
- expect(last_merge_request).to include('fix')
+ expect(first_merge_request).to include('fix')
+ expect(last_merge_request).to include('merge_lfs')
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('fix')
- expect(last_merge_request).to include('lfs')
+ expect(first_merge_request).to include('merge_lfs')
+ expect(last_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
- expect(first_merge_request).to include('lfs')
+ expect(first_merge_request).to include('merge_lfs')
expect(count_merge_requests).to eq(3)
end
@@ -143,18 +146,6 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true
end
end
- def visit_merge_requests(project, opts = {})
- visit namespace_project_merge_requests_path(project.namespace, project, opts)
- end
-
- def first_merge_request
- page.all('ul.mr-list > li').first.text
- end
-
- def last_merge_request
- page.all('ul.mr-list > li').last.text
- end
-
def count_merge_requests
page.all('ul.mr-list > li').count
end
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index c2c7acff3e8..c43661e5681 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -13,7 +13,7 @@ feature 'Milestone', feature: true do
end
feature 'Create a milestone' do
- scenario 'should show an informative message for a new issue' do
+ scenario 'shows an informative message for a new issue' do
visit new_namespace_project_milestone_path(project.namespace, project)
page.within '.milestone-form' do
fill_in "milestone_title", with: '8.7'
@@ -25,7 +25,7 @@ feature 'Milestone', feature: true do
end
feature 'Open a milestone with closed issues' do
- scenario 'should show an informative message' do
+ scenario 'shows an informative message' do
create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed")
visit namespace_project_milestone_path(project.namespace, project, milestone)
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 0b38c413f44..7a9edbbe339 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -23,7 +23,7 @@ describe 'Comments', feature: true do
subject { page }
describe 'the note form' do
- it 'should be valid' do
+ it 'is valid' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
expect(find('.js-main-target-form input[type=submit]').value).
to eq('Comment')
@@ -39,7 +39,7 @@ describe 'Comments', feature: true do
end
end
- it 'should have enable submit button and preview button' do
+ it 'has enable submit button and preview button' do
page.within('.js-main-target-form') do
expect(page).not_to have_css('.js-comment-button[disabled]')
expect(page).to have_css('.js-md-preview-button', visible: true)
@@ -57,7 +57,7 @@ describe 'Comments', feature: true do
end
end
- it 'should be added and form reset' do
+ it 'is added and form reset' do
is_expected.to have_content('This is awsome!')
page.within('.js-main-target-form') do
expect(page).to have_no_field('note[note]', with: 'This is awesome!')
@@ -70,7 +70,7 @@ describe 'Comments', feature: true do
end
describe 'when editing a note', js: true do
- it 'should contain the hidden edit form' do
+ it 'contains the hidden edit form' do
page.within("#note_#{note.id}") do
is_expected.to have_css('.note-edit-form', visible: false)
end
@@ -82,7 +82,7 @@ describe 'Comments', feature: true do
find(".js-note-edit").click
end
- it 'should show the note edit form and hide the note body' do
+ it 'shows the note edit form and hide the note body' do
page.within("#note_#{note.id}") do
expect(find('.current-note-edit-form', visible: true)).to be_visible
expect(find('.note-edit-form', visible: true)).to be_visible
@@ -234,7 +234,7 @@ describe 'Comments', feature: true do
end
end
- it 'should be added as discussion' do
+ it 'adds as discussion' do
is_expected.to have_content('Another comment on line 10')
is_expected.to have_css('.notes_holder')
is_expected.to have_css('.notes_holder .note', count: 1)
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index c7c00a3266a..a78a1c9c890 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -12,17 +12,17 @@ feature 'Member autocomplete', feature: true do
end
shared_examples "open suggestions" do
- it 'suggestions are displayed' do
+ it 'displays suggestions' do
expect(page).to have_selector('.atwho-view', visible: true)
end
- it 'author is suggested' do
+ it 'suggests author' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(author.username)
end
end
- it 'participant is suggested' do
+ it 'suggests participant' do
page.within('.atwho-view', visible: true) do
expect(page).to have_content(participant.username)
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index c80253fead8..c3d8c349ca4 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -15,7 +15,7 @@ describe 'Profile account page', feature: true do
it { expect(page).to have_content('Remove account') }
- it 'should delete the account' do
+ it 'deletes the account' do
expect { click_link 'Delete account' }.to change { User.count }.by(-1)
expect(current_path).to eq(new_user_session_path)
end
@@ -27,7 +27,7 @@ describe 'Profile account page', feature: true do
visit profile_account_path
end
- it 'should not have option to remove account' do
+ it 'does not have option to remove account' do
expect(page).not_to have_content('Remove account')
expect(current_path).to eq(profile_account_path)
end
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
new file mode 100644
index 00000000000..4cbdd89d46f
--- /dev/null
+++ b/spec/features/profiles/password_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe 'Profile > Password', feature: true do
+ let(:user) { create(:user, password_automatically_set: true) }
+
+ before do
+ login_as(user)
+ visit edit_profile_password_path
+ end
+
+ def fill_passwords(password, confirmation)
+ fill_in 'New password', with: password
+ fill_in 'Password confirmation', with: confirmation
+
+ click_button 'Save password'
+ end
+
+ context 'User with password automatically set' do
+ describe 'User puts different passwords in the field and in the confirmation' do
+ it 'shows an error message' do
+ fill_passwords('mypassword', 'mypassword2')
+
+ page.within('.alert-danger') do
+ expect(page).to have_content("Password confirmation doesn't match Password")
+ end
+ end
+
+ it 'does not contains the current password field after an error' do
+ fill_passwords('mypassword', 'mypassword2')
+
+ expect(page).to have_no_field('user[current_password]')
+ end
+ end
+
+ describe 'User puts the same passwords in the field and in the confirmation' do
+ it 'shows a success message' do
+ fill_passwords('mypassword', 'mypassword')
+
+ page.within('.flash-notice') do
+ expect(page).to have_content('Password was successfully updated. Please login with it')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 787bf42d048..d14a1158b67 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -68,10 +68,14 @@ describe 'Profile > Preferences', feature: true do
allowing_for_delay do
find('#logo').click
+
+ expect(page).to have_content("You don't have starred projects yet")
expect(page.current_path).to eq starred_dashboard_projects_path
end
click_link 'Your Projects'
+
+ expect(page).not_to have_content("You don't have starred projects yet")
expect(page.current_path).to eq dashboard_projects_path
end
end
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
new file mode 100644
index 00000000000..af86d3c338a
--- /dev/null
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+feature 'test coverage badge' do
+ given!(:user) { create(:user) }
+ given!(:project) { create(:project, :private) }
+
+ given!(:pipeline) do
+ create(:ci_pipeline, project: project,
+ ref: 'master',
+ sha: project.commit.id)
+ end
+
+ context 'when user has access to view badge' do
+ background do
+ project.team << [user, :developer]
+ login_as(user)
+ end
+
+ scenario 'user requests coverage badge image for pipeline' do
+ create_job(coverage: 100, name: 'test:1')
+ create_job(coverage: 90, name: 'test:2')
+
+ show_test_coverage_badge
+
+ expect_coverage_badge('95%')
+ end
+
+ scenario 'user requests coverage badge for specific job' do
+ create_job(coverage: 50, name: 'test:1')
+ create_job(coverage: 50, name: 'test:2')
+ create_job(coverage: 85, name: 'coverage')
+
+ show_test_coverage_badge(job: 'coverage')
+
+ expect_coverage_badge('85%')
+ end
+
+ scenario 'user requests coverage badge for pipeline without coverage' do
+ create_job(coverage: nil, name: 'test')
+
+ show_test_coverage_badge
+
+ expect_coverage_badge('unknown')
+ end
+ end
+
+ context 'when user does not have access to view badge' do
+ background { login_as(user) }
+
+ scenario 'user requests test coverage badge image' do
+ show_test_coverage_badge
+
+ expect(page).to have_http_status(404)
+ end
+ end
+
+ def create_job(coverage:, name:)
+ create(:ci_build, name: name,
+ coverage: coverage,
+ pipeline: pipeline)
+ end
+
+ def show_test_coverage_badge(job: nil)
+ visit coverage_namespace_project_badges_path(
+ project.namespace, project, ref: :master, job: job, format: :svg)
+ end
+
+ def expect_coverage_badge(coverage)
+ svg = Nokogiri::XML.parse(page.body)
+ expect(page.response_headers['Content-Type']).to include('image/svg+xml')
+ expect(svg.at(%Q{text:contains("#{coverage}")})).to be_truthy
+ end
+end
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 75166bca119..67a4a5d1ab1 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -9,25 +9,43 @@ feature 'list of badges' do
visit namespace_project_pipelines_settings_path(project.namespace, project)
end
- scenario 'user displays list of badges' do
- expect(page).to have_content 'build status'
- expect(page).to have_content 'Markdown'
- expect(page).to have_content 'HTML'
- expect(page).to have_css('.highlight', count: 2)
- expect(page).to have_xpath("//img[@alt='build status']")
-
- page.within('.highlight', match: :first) do
- expect(page).to have_content 'badges/master/build.svg'
+ scenario 'user wants to see build status badge' do
+ page.within('.build-status') do
+ expect(page).to have_content 'build status'
+ expect(page).to have_content 'Markdown'
+ expect(page).to have_content 'HTML'
+ expect(page).to have_css('.highlight', count: 2)
+ expect(page).to have_xpath("//img[@alt='build status']")
+
+ page.within('.highlight', match: :first) do
+ expect(page).to have_content 'badges/master/build.svg'
+ end
end
end
- scenario 'user changes current ref on badges list page', js: true do
- first('.js-project-refs-dropdown').click
+ scenario 'user wants to see coverage report badge' do
+ page.within('.coverage-report') do
+ expect(page).to have_content 'coverage report'
+ expect(page).to have_content 'Markdown'
+ expect(page).to have_content 'HTML'
+ expect(page).to have_css('.highlight', count: 2)
+ expect(page).to have_xpath("//img[@alt='coverage report']")
- page.within '.project-refs-form' do
- click_link 'improve/awesome'
+ page.within('.highlight', match: :first) do
+ expect(page).to have_content 'badges/master/coverage.svg'
+ end
end
+ end
+
+ scenario 'user changes current ref of build status badge', js: true do
+ page.within('.build-status') do
+ first('.js-project-refs-dropdown').click
- expect(page).to have_content 'badges/improve/awesome/build.svg'
+ page.within '.project-refs-form' do
+ click_link 'improve/awesome'
+ end
+
+ expect(page).to have_content 'badges/improve/awesome/build.svg'
+ end
end
end
diff --git a/spec/features/projects/branches_spec.rb~HEAD b/spec/features/projects/branches_spec.rb~HEAD
deleted file mode 100644
index 79abba21854..00000000000
--- a/spec/features/projects/branches_spec.rb~HEAD
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe 'Branches', feature: true do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
-
- before do
- login_as :user
- project.team << [@user, :developer]
- end
-
- describe 'Initial branches page' do
- it 'shows all the branches' do
- visit namespace_project_branches_path(project.namespace, project)
-
- repository.branches { |branch| expect(page).to have_content("#{branch.name}") }
- expect(page).to have_content("Protected branches can be managed in project settings")
- end
- end
-
- describe 'Find branches' do
- it 'shows filtered branches', js: true do
- visit namespace_project_branches_path(project.namespace, project, project.id)
-
- fill_in 'branch-search', with: 'fix'
- find('#branch-search').native.send_keys(:enter)
-
- expect(page).to have_content('fix')
- expect(find('.all-branches')).to have_selector('li', count: 1)
- end
- end
-end
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
new file mode 100644
index 00000000000..fe047e00409
--- /dev/null
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+feature 'User wants to edit a file', feature: true do
+ include WaitForAjax
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:commit_params) do
+ {
+ source_branch: project.default_branch,
+ target_branch: project.default_branch,
+ commit_message: "Committing First Update",
+ file_path: ".gitignore",
+ file_content: "First Update",
+ last_commit_sha: Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch,
+ ".gitignore").sha
+ }
+ end
+
+ background do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_edit_blob_path(project.namespace, project,
+ File.join(project.default_branch, '.gitignore'))
+ end
+
+ scenario 'file has been updated since the user opened the edit page' do
+ Files::UpdateService.new(project, user, commit_params).execute
+
+ click_button 'Commit Changes'
+
+ expect(page).to have_content 'Someone edited the file the same time you did.'
+ end
+end
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
new file mode 100644
index 00000000000..10b91d8990b
--- /dev/null
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+feature 'User views files page', feature: true do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:forked_project_with_submodules) }
+
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_tree_path(project.namespace, project, project.repository.root_ref)
+ end
+
+ scenario 'user sees folders and submodules sorted together, followed by files' do
+ rows = all('td.tree-item-file-name').map(&:text)
+ tree = project.repository.tree
+
+ folders = tree.trees.map(&:name)
+ files = tree.blobs.map(&:name)
+ submodules = tree.submodules.map do |submodule|
+ submodule.name + " @ " + submodule.id[0..7]
+ end
+
+ sorted_titles = (folders + submodules).sort + files
+
+ expect(rows).to eq(sorted_titles)
+ end
+end
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index e1e105e6bbe..dbd07464444 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -39,6 +39,7 @@ feature 'project owner creates a license file', feature: true, js: true do
scenario 'project master creates a license file from the "Add license" link' do
click_link 'Add License'
+ expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
expect(find('#file_name').value).to eq('LICENSE')
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 67aac25e427..45bf0c0d038 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -14,6 +14,7 @@ feature 'project owner sees a link to create a license file in empty project', f
visit namespace_project_path(project.namespace, project)
click_link 'Create empty bare repository'
click_on 'LICENSE'
+ expect(page).to have_content('New File')
expect(current_path).to eq(
namespace_project_new_blob_path(project.namespace, project, 'master'))
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 2d1e3bbebe5..7835e1678ad 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -8,6 +8,7 @@ feature 'project import', feature: true, js: true do
let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:export_path) { "#{Dir::tmpdir}/import_file_spec" }
let(:project) { Project.last }
+ let(:project_hook) { Gitlab::Git::Hook.new('post-receive', project.repository.path) }
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
@@ -37,7 +38,7 @@ feature 'project import', feature: true, js: true do
expect(project).not_to be_nil
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
- expect(project.repo_exists?).to be true
+ expect(project_hook).to exist
expect(wiki_exists?).to be true
expect(project.import_status).to eq('finished')
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 98ba93b4036..cb7495da8eb 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -87,7 +87,7 @@ feature 'Prioritize labels', feature: true do
end
context 'as a guest' do
- it 'can not prioritize labels' do
+ it 'does not prioritize labels' do
user = create(:user)
guest = create(:user)
project = create(:project, name: 'test', namespace: user.namespace)
@@ -102,7 +102,7 @@ feature 'Prioritize labels', feature: true do
end
context 'as a non signed in user' do
- it 'can not prioritize labels' do
+ it 'does not prioritize labels' do
user = create(:user)
project = create(:project, name: 'test', namespace: user.namespace)
diff --git a/spec/features/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb
index 7f861db1969..29d150bc597 100644
--- a/spec/features/pipelines_spec.rb
+++ b/spec/features/projects/pipelines_spec.rb
@@ -12,7 +12,7 @@ describe "Pipelines" do
end
describe 'GET /:project/pipelines' do
- let!(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', status: 'running') }
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running') }
[:all, :running, :branches].each do |scope|
context "displaying #{scope}" do
@@ -31,9 +31,12 @@ describe "Pipelines" do
end
context 'cancelable pipeline' do
- let!(:running) { create(:ci_build, :running, pipeline: pipeline, stage: 'test', commands: 'test') }
+ let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before do
+ build.run
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
it { expect(page).to have_link('Cancel') }
it { expect(page).to have_selector('.ci-running') }
@@ -47,9 +50,12 @@ describe "Pipelines" do
end
context 'retryable pipelines' do
- let!(:failed) { create(:ci_build, :failed, pipeline: pipeline, stage: 'test', commands: 'test') }
+ let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before do
+ build.drop
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
it { expect(page).to have_link('Retry') }
it { expect(page).to have_selector('.ci-failed') }
@@ -58,7 +64,7 @@ describe "Pipelines" do
before { click_link('Retry') }
it { expect(page).not_to have_link('Retry') }
- it { expect(page).to have_selector('.ci-pending') }
+ it { expect(page).to have_selector('.ci-running') }
end
end
@@ -80,27 +86,32 @@ describe "Pipelines" do
context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
- it 'not be cancelable' do
+ it 'is not cancelable' do
expect(page).not_to have_link('Cancel')
end
- it 'pipeline is running' do
+ it 'has pipeline running' do
expect(page).to have_selector('.ci-running')
end
end
context 'when failed' do
- let!(:running) { create(:generic_commit_status, status: 'failed', pipeline: pipeline, stage: 'test') }
+ let!(:status) { create(:generic_commit_status, :pending, pipeline: pipeline, stage: 'test') }
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before do
+ status.drop
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
- it 'not be retryable' do
+ it 'is not retryable' do
expect(page).not_to have_link('Retry')
end
- it 'pipeline is failed' do
+ it 'has failed pipeline' do
expect(page).to have_selector('.ci-failed')
end
end
@@ -116,9 +127,19 @@ describe "Pipelines" do
it { expect(page).to have_link(with_artifacts.name) }
end
+ context 'with artifacts expired' do
+ let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).not_to have_selector('.build-artifacts') }
+ end
+
context 'without artifacts' do
let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
it { expect(page).not_to have_selector('.build-artifacts') }
end
end
@@ -137,7 +158,7 @@ describe "Pipelines" do
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
- it 'showing a list of builds' do
+ it 'shows a list of builds' do
expect(page).to have_content('Test')
expect(page).to have_content(@success.id)
expect(page).to have_content('Deploy')
@@ -184,7 +205,7 @@ describe "Pipelines" do
before { visit new_namespace_project_pipeline_path(project.namespace, project) }
context 'for valid commit' do
- before { fill_in('Create for', with: 'master') }
+ before { fill_in('pipeline[ref]', with: 'master') }
context 'with gitlab-ci.yml' do
before { stub_ci_pipeline_to_return_yaml_file }
@@ -201,11 +222,37 @@ describe "Pipelines" do
context 'for invalid commit' do
before do
- fill_in('Create for', with: 'invalid reference')
+ fill_in('pipeline[ref]', with: 'invalid-reference')
click_on 'Create pipeline'
end
it { expect(page).to have_content('Reference not found') }
end
end
+
+ describe 'Create pipelines', feature: true do
+ let(:project) { create(:project) }
+
+ before do
+ visit new_namespace_project_pipeline_path(project.namespace, project)
+ end
+
+ describe 'new pipeline page' do
+ it 'has field to add a new pipeline' do
+ expect(page).to have_field('pipeline[ref]')
+ expect(page).to have_content('Create for')
+ end
+ end
+
+ describe 'find pipelines' do
+ it 'shows filtered pipelines', js: true do
+ fill_in('pipeline[ref]', with: 'fix')
+ find('input#ref').native.send_keys(:keydown)
+
+ within('.ui-autocomplete') do
+ expect(page).to have_selector('li', text: 'fix')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb
new file mode 100644
index 00000000000..3de25d7af7d
--- /dev/null
+++ b/spec/features/projects/project_settings_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe 'Edit Project Settings', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') }
+
+ before do
+ login_as(user)
+ project.team << [user, :master]
+ end
+
+ describe 'Project settings', js: true do
+ it 'shows errors for invalid project name' do
+ visit edit_namespace_project_path(project.namespace, project)
+
+ fill_in 'project_name_edit', with: 'foo&bar'
+
+ click_button 'Save changes'
+
+ expect(page).to have_field 'project_name_edit', with: 'foo&bar'
+ expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+ expect(page).to have_button 'Save changes'
+ end
+ end
+
+ describe 'Rename repository' do
+ it 'shows errors for invalid project path/name' do
+ visit edit_namespace_project_path(project.namespace, project)
+
+ fill_in 'Project name', with: 'foo&bar'
+ fill_in 'Path', with: 'foo&bar'
+
+ click_button 'Rename project'
+
+ expect(page).to have_field 'Project name', with: 'foo&bar'
+ expect(page).to have_field 'Path', with: 'foo&bar'
+ expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'."
+ expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
+ end
+ end
+end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
new file mode 100644
index 00000000000..b3ba40b35af
--- /dev/null
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+feature 'Ref switcher', feature: true, js: true do
+ include WaitForAjax
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ visit namespace_project_tree_path(project.namespace, project, 'master')
+ end
+
+ it 'allow user to change ref by enter key' do
+ click_button 'master'
+ wait_for_ajax
+
+ page.within '.project-refs-form' do
+ input = find('input[type="search"]')
+ input.set 'expand'
+
+ input.native.send_keys :down
+ input.native.send_keys :down
+ input.native.send_keys :enter
+
+ expect(page).to have_content 'expand-collapse-files'
+ end
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 6fa8298d489..1b14c66fe28 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -44,7 +44,7 @@ feature 'Project', feature: true do
visit edit_namespace_project_path(project.namespace, project)
end
- it 'should remove fork' do
+ it 'removes fork' do
expect(page).to have_content 'Remove fork relationship'
remove_with_confirm('Remove fork relationship', project.path)
@@ -65,7 +65,7 @@ feature 'Project', feature: true do
visit edit_namespace_project_path(project.namespace, project)
end
- it 'should remove project' do
+ it 'removes project' do
expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1)
end
end
@@ -82,7 +82,7 @@ feature 'Project', feature: true do
visit namespace_project_path(project.namespace, project)
end
- it 'click toggle and show dropdown', js: true do
+ it 'clicks toggle and shows dropdown', js: true do
find('.js-projects-dropdown-toggle').click
expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 1)
end
@@ -102,7 +102,7 @@ feature 'Project', feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it 'click toggle and show dropdown' do
+ it 'clicks toggle and shows dropdown' do
find('.js-projects-dropdown-toggle').click
expect(page).to have_css('.dropdown-menu-projects .dropdown-content li', count: 2)
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index d94dee0c797..3499460c84d 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Projected Branches', feature: true, js: true do
+ include WaitForAjax
+
let(:user) { create(:user, :admin) }
let(:project) { create(:project) }
@@ -9,7 +11,7 @@ feature 'Projected Branches', feature: true, js: true do
def set_protected_branch_name(branch_name)
find(".js-protected-branch-select").click
find(".dropdown-input-field").set(branch_name)
- click_on "Create Protected Branch: #{branch_name}"
+ click_on("Create wildcard #{branch_name}")
end
describe "explicit protected branches" do
@@ -81,4 +83,68 @@ feature 'Projected Branches', feature: true, js: true do
end
end
end
+
+ describe "access control" do
+ ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
+ it "allows creating protected branches that #{access_type_name} can push to" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('master')
+ within('.new_protected_branch') do
+ find(".js-allowed-to-push").click
+ within(".dropdown.open .dropdown-menu") { click_on access_type_name }
+ end
+ click_on "Protect"
+
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id)
+ end
+
+ it "allows updating protected branches so that #{access_type_name} can push to them" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('master')
+ click_on "Protect"
+
+ expect(ProtectedBranch.count).to eq(1)
+
+ within(".protected-branches-list") do
+ find(".js-allowed-to-push").click
+ within('.js-allowed-to-push-container') { click_on access_type_name }
+ end
+
+ wait_for_ajax
+ expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id)
+ end
+ end
+
+ ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)|
+ it "allows creating protected branches that #{access_type_name} can merge to" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('master')
+ within('.new_protected_branch') do
+ find(".js-allowed-to-merge").click
+ within(".dropdown.open .dropdown-menu") { click_on access_type_name }
+ end
+ click_on "Protect"
+
+ expect(ProtectedBranch.count).to eq(1)
+ expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id)
+ end
+
+ it "allows updating protected branches so that #{access_type_name} can merge to them" do
+ visit namespace_project_protected_branches_path(project.namespace, project)
+ set_protected_branch_name('master')
+ click_on "Protect"
+
+ expect(ProtectedBranch.count).to eq(1)
+
+ within(".protected-branches-list") do
+ find(".js-allowed-to-merge").click
+ within('.js-allowed-to-merge-container') { click_on access_type_name }
+ end
+
+ wait_for_ajax
+ expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id)
+ end
+ end
+ end
end
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index d0a301038c4..b7a25d80fec 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -12,7 +12,7 @@ describe "Search", feature: true do
visit search_path
end
- it 'top right search form is not present' do
+ it 'does not show top right search form' do
expect(page).not_to have_selector('.search')
end
@@ -28,6 +28,26 @@ describe "Search", feature: true do
end
context 'search for comments' do
+ context 'when comment belongs to a invalid commit' do
+ let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') }
+
+ before { note.update_attributes(commit_id: 12345678) }
+
+ it 'finds comment' do
+ visit namespace_project_path(project.namespace, project)
+
+ page.within '.search' do
+ fill_in 'search', with: note.note
+ click_button 'Go'
+ end
+
+ click_link 'Comments'
+
+ expect(page).to have_text("Commit deleted")
+ expect(page).to have_text("12345678")
+ end
+ end
+
it 'finds a snippet' do
snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title')
note = create(:note,
@@ -56,16 +76,16 @@ describe "Search", feature: true do
visit namespace_project_path(project.namespace, project)
end
- it 'top right search form is present' do
+ it 'shows top right search form' do
expect(page).to have_selector('#search')
end
- it 'top right search form contains location badge' do
+ it 'contains location badge in top right search form' do
expect(page).to have_selector('.has-location-badge')
end
context 'clicking the search field', js: true do
- it 'should show category search dropdown' do
+ it 'shows category search dropdown' do
page.find('#search').click
expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
@@ -77,7 +97,7 @@ describe "Search", feature: true do
page.find('#search').click
end
- it 'should take user to her issues page when issues assigned is clicked' do
+ it 'takes user to her issues page when issues assigned is clicked' do
find('.dropdown-menu').click_link 'Issues assigned to me'
sleep 2
@@ -85,7 +105,7 @@ describe "Search", feature: true do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
- it 'should take user to her issues page when issues authored is clicked' do
+ it 'takes user to her issues page when issues authored is clicked' do
find('.dropdown-menu').click_link "Issues I've created"
sleep 2
@@ -93,7 +113,7 @@ describe "Search", feature: true do
expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
end
- it 'should take user to her MR page when MR assigned is clicked' do
+ it 'takes user to her MR page when MR assigned is clicked' do
find('.dropdown-menu').click_link 'Merge requests assigned to me'
sleep 2
@@ -101,7 +121,7 @@ describe "Search", feature: true do
expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
end
- it 'should take user to her MR page when MR authored is clicked' do
+ it 'takes user to her MR page when MR authored is clicked' do
find('.dropdown-menu').click_link "Merge requests I've created"
sleep 2
@@ -117,7 +137,7 @@ describe "Search", feature: true do
end
end
- it 'should not display the category search dropdown' do
+ it 'does not display the category search dropdown' do
expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 0bdb1628c74..0342f4f1d97 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -24,7 +24,7 @@ describe 'Dashboard Todos', feature: true do
visit dashboard_todos_path
end
- it 'todo is present' do
+ it 'has todo present' do
expect(page).to have_selector('.todos-list .todo', count: 1)
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index 9335f5bf120..d370f90f7d9 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,8 +1,16 @@
require 'spec_helper'
feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
+ include WaitForAjax
+
before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) }
+ def manage_two_factor_authentication
+ click_on 'Manage Two-Factor Authentication'
+ expect(page).to have_content("Setup New U2F Device")
+ wait_for_ajax
+ end
+
def register_u2f_device(u2f_device = nil)
u2f_device ||= FakeU2fDevice.new(page)
u2f_device.respond_to_u2f_registration
@@ -34,7 +42,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
describe 'when 2FA via OTP is enabled' do
it 'allows registering a new device' do
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
expect(page.body).to match("You've already enabled two-factor authentication using mobile")
register_u2f_device
@@ -46,15 +54,15 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
visit profile_account_path
# First device
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
expect(page.body).to match('Your U2F device was registered')
# Second device
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
expect(page.body).to match('Your U2F device was registered')
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
expect(page.body).to match('You have 2 U2F devices registered')
end
end
@@ -62,7 +70,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it 'allows the same device to be registered for multiple users' do
# First user
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
u2f_device = register_u2f_device
expect(page.body).to match('Your U2F device was registered')
logout
@@ -71,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device(u2f_device)
expect(page.body).to match('Your U2F device was registered')
@@ -81,7 +89,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
context "when there are form errors" do
it "doesn't register the device if there are errors" do
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
# Have the "u2f device" respond with bad data
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
@@ -96,7 +104,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
it "allows retrying registration" do
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
# Failed registration
page.execute_script("u2f.register = function(_,_,_,callback) { callback('bad response'); };")
@@ -122,7 +130,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
login_as(user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
@u2f_device = register_u2f_device
logout
end
@@ -161,7 +169,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device
logout
@@ -182,7 +190,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
current_user = login_as(:user)
current_user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
register_u2f_device(@u2f_device)
logout
@@ -248,7 +256,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
user = login_as(:user)
user.update_attribute(:otp_required_for_login, true)
visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
+ manage_two_factor_authentication
expect(page).to have_content("Your U2F device needs to be set up.")
register_u2f_device
end
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index a2b8f7b6931..d7880d5778f 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -13,13 +13,13 @@ describe 'Project variables', js: true do
visit namespace_project_variables_path(project.namespace, project)
end
- it 'should show list of variables' do
+ it 'shows list of variables' do
page.within('.variables-table') do
expect(page).to have_content(variable.key)
end
end
- it 'should add new variable' do
+ it 'adds new variable' do
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Add new variable')
@@ -29,7 +29,7 @@ describe 'Project variables', js: true do
end
end
- it 'should delete variable' do
+ it 'deletes variable' do
page.within('.variables-table') do
find('.btn-variable-delete').click
end
@@ -37,11 +37,12 @@ describe 'Project variables', js: true do
expect(page).not_to have_selector('variables-table')
end
- it 'should edit variable' do
+ it 'edits variable' do
page.within('.variables-table') do
find('.btn-variable-edit').click
end
+ expect(page).to have_content('Update variable')
fill_in('variable_key', with: 'key')
fill_in('variable_value', with: 'key value')
click_button('Save variable')
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 9c9763d746b..6fce11de30f 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -16,15 +16,19 @@ describe BranchesFinder do
end
it 'sorts by recently_updated' do
- branches_finder = described_class.new(repository, { sort: 'recently_updated' })
+ branches_finder = described_class.new(repository, { sort: 'updated_desc' })
result = branches_finder.execute
- expect(result.first.name).to eq('expand-collapse-lines')
+ recently_updated_branch = repository.branches.max do |a, b|
+ repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date
+ end
+
+ expect(result.first.name).to eq(recently_updated_branch.name)
end
it 'sorts by last_updated' do
- branches_finder = described_class.new(repository, { sort: 'last_updated' })
+ branches_finder = described_class.new(repository, { sort: 'updated_asc' })
result = branches_finder.execute
@@ -53,7 +57,7 @@ describe BranchesFinder do
context 'filter and sort' do
it 'filters branches by name and sorts by recently_updated' do
- params = { sort: 'recently_updated', search: 'feature' }
+ params = { sort: 'updated_desc', search: 'feature' }
branches_finder = described_class.new(repository, params)
result = branches_finder.execute
@@ -63,7 +67,7 @@ describe BranchesFinder do
end
it 'filters branches by name and sorts by last_updated' do
- params = { sort: 'last_updated', search: 'feature' }
+ params = { sort: 'updated_asc', search: 'feature' }
branches_finder = described_class.new(repository, params)
result = branches_finder.execute
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index bc385fd0d69..535aabfc18d 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -18,13 +18,13 @@ describe MergeRequestsFinder do
end
describe "#execute" do
- it 'should filter by scope' do
+ it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(2)
end
- it 'should filter by project' do
+ it 'filters by project' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new(user, params).execute
expect(merge_requests.size).to eq(1)
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 8db897b1646..7c6860372cc 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -19,12 +19,12 @@ describe NotesFinder do
note2
end
- it 'should find all notes' do
+ it 'finds all notes' do
notes = NotesFinder.new.execute(project, user, params)
expect(notes.size).to eq(2)
end
- it 'should raise an exception for an invalid target_type' do
+ it 'raises an exception for an invalid target_type' do
params.merge!(target_type: 'invalid')
expect { NotesFinder.new.execute(project, user, params) }.to raise_error('invalid target_type')
end
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index 0a1cc3b3df7..7a3a74335e8 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -23,73 +23,36 @@ describe ProjectsFinder do
let(:finder) { described_class.new }
- describe 'without a group' do
- describe 'without a user' do
- subject { finder.execute }
+ describe 'without a user' do
+ subject { finder.execute }
- it { is_expected.to eq([public_project]) }
- end
-
- describe 'with a user' do
- subject { finder.execute(user) }
-
- describe 'without private projects' do
- it { is_expected.to eq([public_project, internal_project]) }
- end
-
- describe 'with private projects' do
- before do
- private_project.team.add_user(user, Gitlab::Access::MASTER)
- end
-
- it do
- is_expected.to eq([public_project, internal_project,
- private_project])
- end
- end
- end
+ it { is_expected.to eq([public_project]) }
end
- describe 'with a group' do
- describe 'without a user' do
- subject { finder.execute(nil, group: group) }
+ describe 'with a user' do
+ subject { finder.execute(user) }
- it { is_expected.to eq([public_project]) }
+ describe 'without private projects' do
+ it { is_expected.to eq([public_project, internal_project]) }
end
- describe 'with a user' do
- subject { finder.execute(user, group: group) }
-
- describe 'without shared projects' do
- it { is_expected.to eq([public_project, internal_project]) }
+ describe 'with private projects' do
+ before do
+ private_project.team.add_user(user, Gitlab::Access::MASTER)
end
- describe 'with shared projects and group membership' do
- before do
- group.add_user(user, Gitlab::Access::DEVELOPER)
-
- shared_project.project_group_links.
- create(group_access: Gitlab::Access::MASTER, group: group)
- end
-
- it do
- is_expected.to eq([shared_project, public_project, internal_project])
- end
+ it do
+ is_expected.to eq([public_project, internal_project, private_project])
end
+ end
+ end
- describe 'with shared projects and project membership' do
- before do
- shared_project.team.add_user(user, Gitlab::Access::DEVELOPER)
+ describe 'with project_ids_relation' do
+ let(:project_ids_relation) { Project.where(id: internal_project.id) }
- shared_project.project_group_links.
- create(group_access: Gitlab::Access::MASTER, group: group)
- end
+ subject { finder.execute(user, project_ids_relation) }
- it do
- is_expected.to eq([shared_project, public_project, internal_project])
- end
- end
- end
+ it { is_expected.to eq([internal_project]) }
end
end
end
diff --git a/spec/fixtures/config/redis_new_format_host.yml b/spec/fixtures/config/redis_new_format_host.yml
new file mode 100644
index 00000000000..13772677a45
--- /dev/null
+++ b/spec/fixtures/config/redis_new_format_host.yml
@@ -0,0 +1,29 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development:
+ url: redis://:mynewpassword@localhost:6379/99
+ sentinels:
+ -
+ host: localhost
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
+test:
+ url: redis://:mynewpassword@localhost:6379/99
+ sentinels:
+ -
+ host: localhost
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
+production:
+ url: redis://:mynewpassword@localhost:6379/99
+ sentinels:
+ -
+ host: slave1
+ port: 26380 # point to sentinel, not to redis port
+ -
+ host: slave2
+ port: 26381 # point to sentinel, not to redis port
diff --git a/spec/fixtures/config/redis_new_format_socket.yml b/spec/fixtures/config/redis_new_format_socket.yml
new file mode 100644
index 00000000000..4e76830c281
--- /dev/null
+++ b/spec/fixtures/config/redis_new_format_socket.yml
@@ -0,0 +1,6 @@
+development:
+ url: unix:/path/to/redis.sock
+test:
+ url: unix:/path/to/redis.sock
+production:
+ url: unix:/path/to/redis.sock
diff --git a/spec/fixtures/config/redis_old_format_host.yml b/spec/fixtures/config/redis_old_format_host.yml
new file mode 100644
index 00000000000..253d0a994f5
--- /dev/null
+++ b/spec/fixtures/config/redis_old_format_host.yml
@@ -0,0 +1,5 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development: redis://:mypassword@localhost:6379/99
+test: redis://:mypassword@localhost:6379/99
+production: redis://:mypassword@localhost:6379/99
diff --git a/spec/fixtures/config/redis_old_format_socket.yml b/spec/fixtures/config/redis_old_format_socket.yml
new file mode 100644
index 00000000000..fd31ce8ea3d
--- /dev/null
+++ b/spec/fixtures/config/redis_old_format_socket.yml
@@ -0,0 +1,3 @@
+development: unix:/path/to/old/redis.sock
+test: unix:/path/to/old/redis.sock
+production: unix:/path/to/old/redis.sock
diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml
new file mode 100644
index 00000000000..3cf53a656a5
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue.eml
@@ -0,0 +1,23 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+The reply by email functionality should be extended to allow creating a new issue by email.
+
+* Allow an admin to specify which project the issue should be created under by checking the sender domain.
+* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under.
diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml
new file mode 100644
index 00000000000..fc1d52a3f42
--- /dev/null
+++ b/spec/fixtures/emails/valid_new_issue_empty.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_authentication_token.eml b/spec/fixtures/emails/wrong_authentication_token.eml
new file mode 100644
index 00000000000..0994c2f7775
--- /dev/null
+++ b/spec/fixtures/emails/wrong_authentication_token.eml
@@ -0,0 +1,18 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+Subject: New Issue by email
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_mail_key.eml
index 491e078fb5b..491e078fb5b 100644
--- a/spec/fixtures/emails/wrong_reply_key.eml
+++ b/spec/fixtures/emails/wrong_mail_key.eml
diff --git a/spec/fixtures/project_services/campfire/rooms.json b/spec/fixtures/project_services/campfire/rooms.json
new file mode 100644
index 00000000000..71e9645c955
--- /dev/null
+++ b/spec/fixtures/project_services/campfire/rooms.json
@@ -0,0 +1,22 @@
+{
+ "rooms": [
+ {
+ "name": "test-room",
+ "locked": false,
+ "created_at": "2009/01/07 20:43:11 +0000",
+ "updated_at": "2009/03/18 14:31:39 +0000",
+ "topic": "The room topic\n",
+ "id": 123,
+ "membership_limit": 4
+ },
+ {
+ "name": "another room",
+ "locked": true,
+ "created_at": "2009/03/18 14:30:42 +0000",
+ "updated_at": "2013/01/27 14:14:27 +0000",
+ "topic": "Comment, ideas, GitHub notifications for eCommittee App",
+ "id": 456,
+ "membership_limit": 4
+ }
+ ]
+}
diff --git a/spec/fixtures/project_services/campfire/rooms2.json b/spec/fixtures/project_services/campfire/rooms2.json
new file mode 100644
index 00000000000..3d5f635d8b3
--- /dev/null
+++ b/spec/fixtures/project_services/campfire/rooms2.json
@@ -0,0 +1,22 @@
+{
+ "rooms": [
+ {
+ "name": "test-room-not-found",
+ "locked": false,
+ "created_at": "2009/01/07 20:43:11 +0000",
+ "updated_at": "2009/03/18 14:31:39 +0000",
+ "topic": "The room topic\n",
+ "id": 123,
+ "membership_limit": 4
+ },
+ {
+ "name": "another room",
+ "locked": true,
+ "created_at": "2009/03/18 14:30:42 +0000",
+ "updated_at": "2013/01/27 14:14:27 +0000",
+ "topic": "Comment, ideas, GitHub notifications for eCommittee App",
+ "id": 456,
+ "membership_limit": 4
+ }
+ ]
+}
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index bb28866f010..73f5470cf35 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -54,7 +54,7 @@ describe ApplicationHelper do
describe 'project_icon' do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
- it 'should return an url for the avatar' do
+ it 'returns an url for the avatar' do
project = create(:project, avatar: File.open(avatar_file_path))
avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif"
@@ -62,7 +62,7 @@ describe ApplicationHelper do
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
end
- it 'should give uploaded icon when present' do
+ it 'gives uploaded icon when present' do
project = create(:project)
allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true)
@@ -76,14 +76,14 @@ describe ApplicationHelper do
describe 'avatar_icon' do
let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
- it 'should return an url for the avatar' do
+ it 'returns an url for the avatar' do
user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user.email).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
end
- it 'should return an url for the avatar with relative url' do
+ it 'returns an url for the avatar with relative url' do
stub_config_setting(relative_url_root: '/gitlab')
# Must be stubbed after the stub above, and separately
stub_config_setting(url: Settings.send(:build_gitlab_url))
@@ -94,14 +94,14 @@ describe ApplicationHelper do
to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
end
- it 'should call gravatar_icon when no User exists with the given email' do
+ it 'calls gravatar_icon when no User exists with the given email' do
expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
helper.avatar_icon('foo@example.com', 20, 2)
end
describe 'using a User' do
- it 'should return an URL for the avatar' do
+ it 'returns an URL for the avatar' do
user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user).to_s).
@@ -146,7 +146,7 @@ describe ApplicationHelper do
to match('https://secure.gravatar.com')
end
- it 'should return custom gravatar path when gravatar_url is set' do
+ it 'returns custom gravatar path when gravatar_url is set' do
stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
expect(gravatar_icon(user_email, 20)).
@@ -218,12 +218,12 @@ describe ApplicationHelper do
end
it 'includes a default js-timeago class' do
- expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending'
+ expect(element.attr('class')).to eq 'js-timeago js-timeago-pending'
end
it 'accepts a custom html_class' do
expect(element(html_class: 'custom_class').attr('class')).
- to eq 'custom_class js-timeago js-timeago-pending'
+ to eq 'js-timeago custom_class js-timeago-pending'
end
it 'accepts a custom tooltip placement' do
@@ -244,6 +244,19 @@ describe ApplicationHelper do
it 'converts to Time' do
expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error
end
+
+ it 'add class for the short format and includes inline script' do
+ timeago_element = element(short_format: 'short')
+ expect(timeago_element.attr('class')).to eq 'js-short-timeago js-timeago-pending'
+ script_element = timeago_element.next_element
+ expect(script_element.name).to eq 'script'
+ end
+
+ it 'add class for the short format and does not include inline script' do
+ timeago_element = element(short_format: 'short', skip_js: true)
+ expect(timeago_element.attr('class')).to eq 'js-short-timeago'
+ expect(timeago_element.next_element).to eq nil
+ end
end
describe 'render_markup' do
@@ -253,19 +266,19 @@ describe ApplicationHelper do
allow(helper).to receive(:current_user).and_return(user)
end
- it 'should preserve encoding' do
+ it 'preserves encoding' do
expect(content.encoding.name).to eq('UTF-8')
expect(helper.render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
end
- it "should delegate to #markdown when file name corresponds to Markdown" do
+ it "delegates to #markdown when file name corresponds to Markdown" do
expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
expect(helper).to receive(:markdown).and_return('NOEL')
expect(helper.render_markup('foo.md', content)).to eq('NOEL')
end
- it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do
+ it "delegates to #asciidoc when file name corresponds to AsciiDoc" do
expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
expect(helper).to receive(:asciidoc).and_return('NOEL')
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index bd0108f9938..94972eed945 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe BlobHelper do
+ include TreeHelper
+
let(:blob_name) { 'test.lisp' }
let(:no_context_content) { ":type \"assem\"))" }
let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" }
@@ -15,19 +17,19 @@ describe BlobHelper do
end
describe '#highlight' do
- it 'should return plaintext for unknown lexer context' do
+ it 'returns plaintext for unknown lexer context' do
result = helper.highlight(blob_name, no_context_content)
expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line">:type "assem"))</span></code></pre>])
end
- it 'should highlight single block' do
+ it 'highlights single block' do
expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
expect(helper.highlight(blob_name, blob_content)).to eq(expected)
end
- it 'should highlight multi-line comments' do
+ it 'highlights multi-line comments' do
result = helper.highlight(blob_name, multiline_content)
html = Nokogiri::HTML(result)
lines = html.search('.s')
@@ -47,7 +49,7 @@ describe BlobHelper do
<span id="LC4" class="line"> ddd</span></code></pre>)
end
- it 'should highlight each line properly' do
+ it 'highlights each line properly' do
result = helper.highlight(blob_name, blob_content)
expect(result).to eq(expected)
end
@@ -60,9 +62,25 @@ describe BlobHelper do
let(:expected_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'sanitized.svg') }
let(:expected) { open(expected_svg_path).read }
- it 'should retain essential elements' do
+ it 'retains essential elements' do
blob = OpenStruct.new(data: data)
expect(sanitize_svg(blob).data).to eq(expected)
end
end
+
+ describe "#edit_blob_link" do
+ let(:project) { create(:project) }
+
+ before do
+ allow(self).to receive(:current_user).and_return(double)
+ end
+
+ it 'verifies blob is text' do
+ expect(self).not_to receive(:blob_text_viewable?)
+
+ button = edit_blob_link(project, 'refs/heads/master', 'README.md')
+
+ expect(button).to start_with('<button')
+ end
+ end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index c2fd2c8a533..9c7c79f57c6 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -6,7 +6,7 @@ describe DiffHelper do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
- let(:diffs) { commit.diffs }
+ let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_refs) { [commit.parent, commit] }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
@@ -15,74 +15,56 @@ describe DiffHelper do
it 'returns a valid value when cookie is set' do
helper.request.cookies[:diff_view] = 'parallel'
- expect(helper.diff_view).to eq 'parallel'
+ expect(helper.diff_view).to eq :parallel
end
it 'returns a default value when cookie is invalid' do
helper.request.cookies[:diff_view] = 'invalid'
- expect(helper.diff_view).to eq 'inline'
+ expect(helper.diff_view).to eq :inline
end
it 'returns a default value when cookie is nil' do
expect(helper.request.cookies).to be_empty
- expect(helper.diff_view).to eq 'inline'
+ expect(helper.diff_view).to eq :inline
end
end
-
- describe 'diff_options' do
- it 'should return hard limit for a diff if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(diff_options).to include(Commit.max_diff_options)
- end
-
- it 'should return hard limit for a diff if expand_all_diffs is true' do
- allow(controller).to receive(:params) { { expand_all_diffs: true } }
- expect(diff_options).to include(Commit.max_diff_options)
- end
- it 'should return no collapse false' do
+ describe 'diff_options' do
+ it 'returns no collapse false' do
expect(diff_options).to include(no_collapse: false)
end
- it 'should return no collapse true if expand_all_diffs' do
+ it 'returns no collapse true if expand_all_diffs' do
allow(controller).to receive(:params) { { expand_all_diffs: true } }
expect(diff_options).to include(no_collapse: true)
end
- it 'should return no collapse true if action name diff_for_path' do
+ it 'returns no collapse true if action name diff_for_path' do
allow(controller).to receive(:action_name) { 'diff_for_path' }
expect(diff_options).to include(no_collapse: true)
end
- end
-
- describe 'unfold_bottom_class' do
- it 'should return empty string when bottom line shouldnt be unfolded' do
- expect(unfold_bottom_class(false)).to eq('')
- end
-
- it 'should return js class when bottom lines should be unfolded' do
- expect(unfold_bottom_class(true)).to include('js-unfold-bottom')
- end
- end
- describe 'unfold_class' do
- it 'returns empty on false' do
- expect(unfold_class(false)).to eq('')
+ it 'returns paths if action name diff_for_path and param old path' do
+ allow(controller).to receive(:params) { { old_path: 'lib/wadus.rb' } }
+ allow(controller).to receive(:action_name) { 'diff_for_path' }
+ expect(diff_options[:paths]).to include('lib/wadus.rb')
end
- it 'returns a class on true' do
- expect(unfold_class(true)).to eq('unfold js-unfold')
+ it 'returns paths if action name diff_for_path and param new path' do
+ allow(controller).to receive(:params) { { new_path: 'lib/wadus.rb' } }
+ allow(controller).to receive(:action_name) { 'diff_for_path' }
+ expect(diff_options[:paths]).to include('lib/wadus.rb')
end
end
describe '#diff_line_content' do
- it 'should return non breaking space when line is empty' do
+ it 'returns non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;')
end
- it 'should return the line itself' do
+ it 'returns the line itself' do
expect(diff_line_content(diff_file.diff_lines.first.text)).
to eq('@@ -6,12 +6,18 @@ module Popen')
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
@@ -103,4 +85,56 @@ describe DiffHelper do
expect(marked_new_line).to be_html_safe
end
end
+
+ describe "#diff_match_line" do
+ let(:old_pos) { 40 }
+ let(:new_pos) { 50 }
+ let(:text) { 'some_text' }
+
+ it "should generate foldable top match line for inline view with empty text by default" do
+ output = diff_match_line old_pos, new_pos
+
+ expect(output).to be_html_safe
+ expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
+ expect(output).to have_css "td:nth-child(2):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: ''
+ end
+
+ it "should allow to define text and bottom option" do
+ output = diff_match_line old_pos, new_pos, text: text, bottom: true
+
+ expect(output).to be_html_safe
+ expect(output).to have_css "td:nth-child(1).diff-line-num.unfold.js-unfold.js-unfold-bottom.old_line[data-linenumber='#{old_pos}']", text: '...'
+ expect(output).to have_css "td:nth-child(2).diff-line-num.unfold.js-unfold.js-unfold-bottom.new_line[data-linenumber='#{new_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text
+ end
+
+ it "should generate match line for parallel view" do
+ output = diff_match_line old_pos, new_pos, text: text, view: :parallel
+
+ expect(output).to be_html_safe
+ expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
+ expect(output).to have_css "td:nth-child(3):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text
+ end
+
+ it "should allow to generate only left match line for parallel view" do
+ output = diff_match_line old_pos, nil, text: text, view: :parallel
+
+ expect(output).to be_html_safe
+ expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
+ expect(output).not_to have_css 'td:nth-child(3)'
+ end
+
+ it "should allow to generate only right match line for parallel view" do
+ output = diff_match_line nil, new_pos, text: text, view: :parallel
+
+ expect(output).to be_html_safe
+ expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...'
+ expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text
+ expect(output).not_to have_css 'td:nth-child(3)'
+ end
+ end
end
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 7a3e38d7e63..3223556e1d3 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -8,37 +8,37 @@ describe EmailsHelper do
end
context 'when time limit is less than 2 hours' do
- it 'should display the time in hours using a singular unit' do
+ it 'displays the time in hours using a singular unit' do
validate_time_string(1.hour, '1 hour')
end
end
context 'when time limit is 2 or more hours' do
- it 'should display the time in hours using a plural unit' do
+ it 'displays the time in hours using a plural unit' do
validate_time_string(2.hours, '2 hours')
end
end
context 'when time limit contains fractions of an hour' do
- it 'should round down to the nearest hour' do
+ it 'rounds down to the nearest hour' do
validate_time_string(96.minutes, '1 hour')
end
end
context 'when time limit is 24 or more hours' do
- it 'should display the time in days using a singular unit' do
+ it 'displays the time in days using a singular unit' do
validate_time_string(24.hours, '1 day')
end
end
context 'when time limit is 2 or more days' do
- it 'should display the time in days using a plural unit' do
+ it 'displays the time in days using a plural unit' do
validate_time_string(2.days, '2 days')
end
end
context 'when time limit contains fractions of a day' do
- it 'should round down to the nearest day' do
+ it 'rounds down to the nearest day' do
validate_time_string(57.hours, '2 days')
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 6b5e3d93d48..022aba0c0d0 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -6,34 +6,34 @@ describe EventsHelper do
allow(helper).to receive(:current_user).and_return(double)
end
- it 'should display one line of plain text without alteration' do
+ it 'displays one line of plain text without alteration' do
input = 'A short, plain note'
expect(helper.event_note(input)).to match(input)
expect(helper.event_note(input)).not_to match(/\.\.\.\z/)
end
- it 'should display inline code' do
+ it 'displays inline code' do
input = 'A note with `inline code`'
expected = 'A note with <code>inline code</code>'
expect(helper.event_note(input)).to match(expected)
end
- it 'should truncate a note with multiple paragraphs' do
+ it 'truncates a note with multiple paragraphs' do
input = "Paragraph 1\n\nParagraph 2"
expected = 'Paragraph 1...'
expect(helper.event_note(input)).to match(expected)
end
- it 'should display the first line of a code block' do
+ it 'displays the first line of a code block' do
input = "```\nCode block\nwith two lines\n```"
expected = %r{<pre.+><code>Code block\.\.\.</code></pre>}
expect(helper.event_note(input)).to match(expected)
end
- it 'should truncate a single long line of text' do
+ it 'truncates a single long line of text' do
text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars
input = text * 4
expected = (text * 2).sub(/.{3}/, '...')
@@ -41,7 +41,7 @@ describe EventsHelper do
expect(helper.event_note(input)).to match(expected)
end
- it 'should preserve a link href when link text is truncated' do
+ it 'preserves a link href when link text is truncated' do
text = 'The quick brown fox jumped over the lazy dog' # 44 chars
input = "#{text}#{text}#{text} " # 133 chars
link_url = 'http://example.com/foo/bar/baz' # 30 chars
@@ -52,7 +52,7 @@ describe EventsHelper do
expect(helper.event_note(input)).to match(expected_link_text)
end
- it 'should preserve code color scheme' do
+ it 'preserves code color scheme' do
input = "```ruby\ndef test\n 'hello world'\nend\n```"
expected = '<pre class="code highlight js-syntax-highlight ruby">' \
"<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index ade5c3b02d9..5368e5fab06 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -26,17 +26,17 @@ describe GitlabMarkdownHelper do
describe "referencing multiple objects" do
let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
- it "should link to the merge request" do
+ it "links to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
expect(helper.markdown(actual)).to match(expected)
end
- it "should link to the commit" do
+ it "links to the commit" do
expected = namespace_project_commit_path(project.namespace, project, commit)
expect(helper.markdown(actual)).to match(expected)
end
- it "should link to the issue" do
+ it "links to the issue" do
expected = namespace_project_issue_path(project.namespace, project, issue)
expect(helper.markdown(actual)).to match(expected)
end
@@ -47,7 +47,7 @@ describe GitlabMarkdownHelper do
let(:second_project) { create(:project, :public) }
let(:second_issue) { create(:issue, project: second_project) }
- it 'should link to the issue' do
+ it 'links to the issue' do
expected = namespace_project_issue_path(second_project.namespace, second_project, second_issue)
expect(markdown(actual, project: second_project)).to match(expected)
end
@@ -58,7 +58,7 @@ describe GitlabMarkdownHelper do
let(:commit_path) { namespace_project_commit_path(project.namespace, project, commit) }
let(:issues) { create_list(:issue, 2, project: project) }
- it 'should handle references nested in links with all the text' do
+ it 'handles references nested in links with all the text' do
actual = helper.link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
@@ -88,7 +88,7 @@ describe GitlabMarkdownHelper do
expect(doc.css('a')[4].text).to eq ' for real'
end
- it 'should forward HTML options' do
+ it 'forwards HTML options' do
actual = helper.link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo')
doc = Nokogiri::HTML.parse(actual)
@@ -110,7 +110,7 @@ describe GitlabMarkdownHelper do
expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
end
- it 'should replace commit message with emoji to link' do
+ it 'replaces commit message with emoji to link' do
actual = link_to_gfm(':book:Book', '/foo')
expect(actual).
to eq %Q(<img class="emoji" title=":book:" alt=":book:" src="http://localhost/assets/1F4D6.png" height="20" width="20" align="absmiddle"><a href="/foo">Book</a>)
@@ -125,7 +125,7 @@ describe GitlabMarkdownHelper do
helper.instance_variable_set(:@project_wiki, @wiki)
end
- it "should use Wiki pipeline for markdown files" do
+ it "uses Wiki pipeline for markdown files" do
allow(@wiki).to receive(:format).and_return(:markdown)
expect(helper).to receive(:markdown).with('wiki content', pipeline: :wiki, project_wiki: @wiki, page_slug: "nested/page")
@@ -133,7 +133,7 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
- it "should use Asciidoctor for asciidoc files" do
+ it "uses Asciidoctor for asciidoc files" do
allow(@wiki).to receive(:format).and_return(:asciidoc)
expect(helper).to receive(:asciidoc).with('wiki content')
@@ -141,7 +141,7 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
- it "should use the Gollum renderer for all other file types" do
+ it "uses the Gollum renderer for all other file types" do
allow(@wiki).to receive(:format).and_return(:rdoc)
formatted_content_stub = double('formatted_content')
expect(formatted_content_stub).to receive(:html_safe)
diff --git a/spec/helpers/graph_helper_spec.rb b/spec/helpers/graph_helper_spec.rb
index 4acf38771b7..51c49f0e587 100644
--- a/spec/helpers/graph_helper_spec.rb
+++ b/spec/helpers/graph_helper_spec.rb
@@ -6,7 +6,7 @@ describe GraphHelper do
let(:commit) { project.commit("master") }
let(:graph) { Network::Graph.new(project, 'master', commit, '') }
- it 'filter our refs used by GitLab' do
+ it 'filters our refs used by GitLab' do
allow(commit).to receive(:ref_names).and_return(['refs/merge-requests/abc', 'master', 'refs/tmp/xyz'])
self.instance_variable_set(:@graph, graph)
refs = get_refs(project.repository, commit)
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 4ea90a80a92..0807534720a 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -4,7 +4,7 @@ describe GroupsHelper do
describe 'group_icon' do
avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
- it 'should return an url for the avatar' do
+ it 'returns an url for the avatar' do
group = create(:group)
group.avatar = File.open(avatar_file_path)
group.save!
@@ -12,7 +12,7 @@ describe GroupsHelper do
to match("/uploads/group/avatar/#{group.id}/banana_sample.gif")
end
- it 'should give default avatar_icon when no avatar is present' do
+ it 'gives default avatar_icon when no avatar is present' do
group = create(:group)
group.save!
expect(group_icon(group.path)).to match('group_avatar.png')
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 831ae7fb69c..5e4655dfc95 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -5,69 +5,24 @@ describe IssuesHelper do
let(:issue) { create :issue, project: project }
let(:ext_project) { create :redmine_project }
- describe "url_for_project_issues" do
- let(:project_url) { ext_project.external_issue_tracker.project_url }
- let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) }
- let(:int_expected) { polymorphic_path([@project.namespace, project]) }
-
- it "should return internal path if used internal tracker" do
- @project = project
- expect(url_for_project_issues).to match(int_expected)
- end
-
- it "should return path to external tracker" do
- @project = ext_project
-
- expect(url_for_project_issues).to match(ext_expected)
- end
-
- it "should return empty string if project nil" do
- @project = nil
-
- expect(url_for_project_issues).to eq ""
- end
-
- it 'returns an empty string if project_url is invalid' do
- expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
-
- expect(url_for_project_issues(project)).to eq ''
- end
-
- it 'returns an empty string if project_path is invalid' do
- expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
-
- expect(url_for_project_issues(project, only_path: true)).to eq ''
- end
-
- describe "when external tracker was enabled and then config removed" do
- before do
- @project = ext_project
- allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
- end
-
- it "should return path to external tracker" do
- expect(url_for_project_issues).to match(ext_expected)
- end
- end
- end
-
describe "url_for_issue" do
let(:issues_url) { ext_project.external_issue_tracker.issues_url}
let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) }
let(:int_expected) { polymorphic_path([@project.namespace, project, issue]) }
- it "should return internal path if used internal tracker" do
+ it "returns internal path if used internal tracker" do
@project = project
+
expect(url_for_issue(issue.iid)).to match(int_expected)
end
- it "should return path to external tracker" do
+ it "returns path to external tracker" do
@project = ext_project
expect(url_for_issue(issue.iid)).to match(ext_expected)
end
- it "should return empty string if project nil" do
+ it "returns empty string if project nil" do
@project = nil
expect(url_for_issue(issue.iid)).to eq ""
@@ -91,58 +46,12 @@ describe IssuesHelper do
allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
end
- it "should return external path" do
+ it "returns external path" do
expect(url_for_issue(issue.iid)).to match(ext_expected)
end
end
end
- describe 'url_for_new_issue' do
- let(:issues_url) { ext_project.external_issue_tracker.new_issue_url }
- let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) }
- let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) }
-
- it "should return internal path if used internal tracker" do
- @project = project
- expect(url_for_new_issue).to match(int_expected)
- end
-
- it "should return path to external tracker" do
- @project = ext_project
-
- expect(url_for_new_issue).to match(ext_expected)
- end
-
- it "should return empty string if project nil" do
- @project = nil
-
- expect(url_for_new_issue).to eq ""
- end
-
- it 'returns an empty string if issue_url is invalid' do
- expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
-
- expect(url_for_new_issue(project)).to eq ''
- end
-
- it 'returns an empty string if issue_path is invalid' do
- expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
-
- expect(url_for_new_issue(project, only_path: true)).to eq ''
- end
-
- describe "when external tracker was enabled and then config removed" do
- before do
- @project = ext_project
- allow(Gitlab.config).to receive(:issues_tracker).and_return(nil)
- end
-
- it "should return internal path" do
- expect(url_for_new_issue).to match(ext_expected)
- end
- end
- end
-
describe "merge_requests_sentence" do
subject { merge_requests_sentence(merge_requests)}
let(:merge_requests) do
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index f75fdb739f6..7998209b7b0 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -9,54 +9,6 @@ describe MembersHelper do
it { expect(action_member_permission(:admin, group_member)).to eq :admin_group_member }
end
- describe '#default_show_roles' do
- let(:user) { double }
- let(:member) { build(:project_member) }
-
- before do
- allow(helper).to receive(:current_user).and_return(user)
- allow(helper).to receive(:can?).with(user, :update_project_member, member).and_return(false)
- allow(helper).to receive(:can?).with(user, :destroy_project_member, member).and_return(false)
- allow(helper).to receive(:can?).with(user, :admin_project_member, member.source).and_return(false)
- end
-
- context 'when the current cannot update, destroy or admin the passed member' do
- it 'returns false' do
- expect(helper.default_show_roles(member)).to be_falsy
- end
- end
-
- context 'when the current can update the passed member' do
- before do
- allow(helper).to receive(:can?).with(user, :update_project_member, member).and_return(true)
- end
-
- it 'returns true' do
- expect(helper.default_show_roles(member)).to be_truthy
- end
- end
-
- context 'when the current can destroy the passed member' do
- before do
- allow(helper).to receive(:can?).with(user, :destroy_project_member, member).and_return(true)
- end
-
- it 'returns true' do
- expect(helper.default_show_roles(member)).to be_truthy
- end
- end
-
- context 'when the current can admin the passed member source' do
- before do
- allow(helper).to receive(:can?).with(user, :admin_project_member, member.source).and_return(true)
- end
-
- it 'returns true' do
- expect(helper.default_show_roles(member)).to be_truthy
- end
- end
- end
-
describe '#remove_member_message' do
let(:requester) { build(:user) }
let(:project) { create(:project) }
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 08a93503258..153f1864ceb 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -1,37 +1,30 @@
require "spec_helper"
describe NotesHelper do
- describe "#notes_max_access_for_users" do
- let(:owner) { create(:owner) }
- let(:group) { create(:group) }
- let(:project) { create(:empty_project, namespace: group) }
- let(:master) { create(:user) }
- let(:reporter) { create(:user) }
- let(:guest) { create(:user) }
-
- let(:owner_note) { create(:note, author: owner, project: project) }
- let(:master_note) { create(:note, author: master, project: project) }
- let(:reporter_note) { create(:note, author: reporter, project: project) }
- let!(:notes) { [owner_note, master_note, reporter_note] }
-
- before do
- group.add_owner(owner)
- project.team << [master, :master]
- project.team << [reporter, :reporter]
- project.team << [guest, :guest]
- end
+ let(:owner) { create(:owner) }
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, namespace: group) }
+ let(:master) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
- it 'return human access levels' do
- original_method = project.team.method(:human_max_access)
- expect_any_instance_of(ProjectTeam).to receive(:human_max_access).exactly(3).times do |*args|
- original_method.call(args[1])
- end
+ let(:owner_note) { create(:note, author: owner, project: project) }
+ let(:master_note) { create(:note, author: master, project: project) }
+ let(:reporter_note) { create(:note, author: reporter, project: project) }
+ let!(:notes) { [owner_note, master_note, reporter_note] }
+ before do
+ group.add_owner(owner)
+ project.team << [master, :master]
+ project.team << [reporter, :reporter]
+ project.team << [guest, :guest]
+ end
+
+ describe "#notes_max_access_for_users" do
+ it 'returns human access levels' do
expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
expect(helper.note_max_access_for_user(master_note)).to eq('Master')
expect(helper.note_max_access_for_user(reporter_note)).to eq('Reporter')
- # Call it again to ensure value is cached
- expect(helper.note_max_access_for_user(owner_note)).to eq('Owner')
end
it 'handles access in different projects' do
@@ -43,4 +36,16 @@ describe NotesHelper do
expect(helper.note_max_access_for_user(other_note)).to eq('Reporter')
end
end
+
+ describe '#preload_max_access_for_authors' do
+ it 'loads multiple users' do
+ expected_access = {
+ owner.id => Gitlab::Access::OWNER,
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER
+ }
+
+ expect(helper.preload_max_access_for_authors(notes, project)).to eq(expected_access)
+ end
+ end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 601b6915e27..b0bb991539b 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -42,7 +42,7 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1)
end
- it "should not include the public group" do
+ it "does not include the public group" do
group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(0)
end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index 10121759132..37ac6a2699d 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -17,35 +17,35 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab).to receive(:protocol).and_return('http') # set this just to be sure
end
- it 'should detect ssh on standard port' do
+ it 'detects ssh on standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ config.user, '@', config.host, ':gitlab-org/gitlab-ce.git' ].join(''))
expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
- it 'should detect ssh on non-standard port' do
+ it 'detects ssh on non-standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ 'ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git' ].join(''))
expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
- it 'should detect http on standard port' do
+ it 'detects http on standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, '/gitlab-org/gitlab-ce.git' ].join(''))
expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
- it 'should detect http on non-standard port' do
+ it 'detects http on non-standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, ':3000/gitlab-org/gitlab-ce.git' ].join(''))
expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
- it 'should work with relative_url_root' do
+ it 'works with relative_url_root' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80) # set this just to be sure
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
@@ -55,22 +55,22 @@ describe SubmoduleHelper do
end
context 'submodule on github.com' do
- it 'should detect ssh' do
+ it 'detects ssh' do
stub_url('git@github.com:gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should detect http' do
+ it 'detects http' do
stub_url('http://github.com/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should detect https' do
+ it 'detects https' do
stub_url('https://github.com/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should return original with non-standard url' do
+ it 'returns original with non-standard url' do
stub_url('http://github.com/gitlab-org/gitlab-ce')
expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
@@ -80,22 +80,22 @@ describe SubmoduleHelper do
end
context 'submodule on gitlab.com' do
- it 'should detect ssh' do
+ it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should detect http' do
+ it 'detects http' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should detect https' do
+ it 'detects https' do
stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git')
expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ])
end
- it 'should return original with non-standard url' do
+ it 'returns original with non-standard url' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce')
expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
@@ -105,7 +105,7 @@ describe SubmoduleHelper do
end
context 'submodule on unsupported' do
- it 'should return original' do
+ it 'returns original' do
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce')
expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ])
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index c70dd8076e0..8d6537ba4b5 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -12,7 +12,7 @@ describe TreeHelper do
context "on a directory containing more than one file/directory" do
let(:tree_item) { double(name: "files", path: "files") }
- it "should return the directory name" do
+ it "returns the directory name" do
expect(flatten_tree(tree_item)).to match('files')
end
end
@@ -20,7 +20,7 @@ describe TreeHelper do
context "on a directory containing only one directory" do
let(:tree_item) { double(name: "foo", path: "foo") }
- it "should return the flattened path" do
+ it "returns the flattened path" do
expect(flatten_tree(tree_item)).to match('foo/bar')
end
end
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
new file mode 100644
index 00000000000..837b0de9a4c
--- /dev/null
+++ b/spec/initializers/secret_token_spec.rb
@@ -0,0 +1,200 @@
+require 'spec_helper'
+require_relative '../../config/initializers/secret_token'
+
+describe 'create_tokens', lib: true do
+ let(:secrets) { ActiveSupport::OrderedOptions.new }
+
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(File).to receive(:write)
+ allow(File).to receive(:delete)
+ allow(Rails).to receive_message_chain(:application, :secrets).and_return(secrets)
+ allow(Rails).to receive_message_chain(:root, :join) { |string| string }
+ allow(self).to receive(:warn)
+ allow(self).to receive(:exit)
+ end
+
+ context 'setting secret_key_base and otp_key_base' do
+ context 'when none of the secrets exist' do
+ before do
+ allow(ENV).to receive(:[]).with('SECRET_KEY_BASE').and_return(nil)
+ allow(File).to receive(:exist?).with('.secret').and_return(false)
+ allow(File).to receive(:exist?).with('config/secrets.yml').and_return(false)
+ allow(self).to receive(:warn_missing_secret)
+ end
+
+ it 'generates different secrets for secret_key_base, otp_key_base, and db_key_base' do
+ create_tokens
+
+ keys = secrets.values_at(:secret_key_base, :otp_key_base, :db_key_base)
+
+ expect(keys.uniq).to eq(keys)
+ expect(keys.map(&:length)).to all(eq(128))
+ end
+
+ it 'warns about the secrets to add to secrets.yml' do
+ expect(self).to receive(:warn_missing_secret).with('secret_key_base')
+ expect(self).to receive(:warn_missing_secret).with('otp_key_base')
+ expect(self).to receive(:warn_missing_secret).with('db_key_base')
+
+ create_tokens
+ end
+
+ it 'writes the secrets to secrets.yml' do
+ expect(File).to receive(:write).with('config/secrets.yml', any_args) do |filename, contents, options|
+ new_secrets = YAML.load(contents)[Rails.env]
+
+ expect(new_secrets['secret_key_base']).to eq(secrets.secret_key_base)
+ expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
+ expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
+ end
+
+ create_tokens
+ end
+
+ it 'does not write a .secret file' do
+ expect(File).not_to receive(:write).with('.secret')
+
+ create_tokens
+ end
+ end
+
+ context 'when the other secrets all exist' do
+ before do
+ secrets.db_key_base = 'db_key_base'
+
+ allow(File).to receive(:exist?).with('.secret').and_return(true)
+ allow(File).to receive(:read).with('.secret').and_return('file_key')
+ end
+
+ context 'when secret_key_base exists in the environment and secrets.yml' do
+ before do
+ allow(ENV).to receive(:[]).with('SECRET_KEY_BASE').and_return('env_key')
+ secrets.secret_key_base = 'secret_key_base'
+ secrets.otp_key_base = 'otp_key_base'
+ end
+
+ it 'does not issue a warning' do
+ expect(self).not_to receive(:warn)
+
+ create_tokens
+ end
+
+ it 'uses the environment variable' do
+ create_tokens
+
+ expect(secrets.secret_key_base).to eq('env_key')
+ end
+
+ it 'does not update secrets.yml' do
+ expect(File).not_to receive(:write)
+
+ create_tokens
+ end
+ end
+
+ context 'when secret_key_base and otp_key_base exist' do
+ before do
+ secrets.secret_key_base = 'secret_key_base'
+ secrets.otp_key_base = 'otp_key_base'
+ end
+
+ it 'does not write any files' do
+ expect(File).not_to receive(:write)
+
+ create_tokens
+ end
+
+ it 'sets the the keys to the values from the environment and secrets.yml' do
+ create_tokens
+
+ expect(secrets.secret_key_base).to eq('secret_key_base')
+ expect(secrets.otp_key_base).to eq('otp_key_base')
+ expect(secrets.db_key_base).to eq('db_key_base')
+ end
+
+ it 'deletes the .secret file' do
+ expect(File).to receive(:delete).with('.secret')
+
+ create_tokens
+ end
+ end
+
+ context 'when secret_key_base and otp_key_base do not exist' do
+ before do
+ allow(File).to receive(:exist?).with('config/secrets.yml').and_return(true)
+ allow(YAML).to receive(:load_file).with('config/secrets.yml').and_return('test' => secrets.to_h.stringify_keys)
+ allow(self).to receive(:warn_missing_secret)
+ end
+
+ it 'uses the file secret' do
+ expect(File).to receive(:write) do |filename, contents, options|
+ new_secrets = YAML.load(contents)[Rails.env]
+
+ expect(new_secrets['secret_key_base']).to eq('file_key')
+ expect(new_secrets['otp_key_base']).to eq('file_key')
+ expect(new_secrets['db_key_base']).to eq('db_key_base')
+ end
+
+ create_tokens
+
+ expect(secrets.otp_key_base).to eq('file_key')
+ end
+
+ it 'keeps the other secrets as they were' do
+ create_tokens
+
+ expect(secrets.db_key_base).to eq('db_key_base')
+ end
+
+ it 'warns about the missing secrets' do
+ expect(self).to receive(:warn_missing_secret).with('secret_key_base')
+ expect(self).to receive(:warn_missing_secret).with('otp_key_base')
+
+ create_tokens
+ end
+
+ it 'deletes the .secret file' do
+ expect(File).to receive(:delete).with('.secret')
+
+ create_tokens
+ end
+ end
+ end
+
+ context 'when db_key_base is blank but exists in secrets.yml' do
+ before do
+ secrets.otp_key_base = 'otp_key_base'
+ secrets.secret_key_base = 'secret_key_base'
+ yaml_secrets = secrets.to_h.stringify_keys.merge('db_key_base' => '<%= an_erb_expression %>')
+
+ allow(File).to receive(:exist?).with('.secret').and_return(false)
+ allow(File).to receive(:exist?).with('config/secrets.yml').and_return(true)
+ allow(YAML).to receive(:load_file).with('config/secrets.yml').and_return('test' => yaml_secrets)
+ allow(self).to receive(:warn_missing_secret)
+ end
+
+ it 'warns about updating db_key_base' do
+ expect(self).to receive(:warn_missing_secret).with('db_key_base')
+
+ create_tokens
+ end
+
+ it 'warns about the blank value existing in secrets.yml and exits' do
+ expect(self).to receive(:warn) do |warning|
+ expect(warning).to include('db_key_base')
+ expect(warning).to include('<%= an_erb_expression %>')
+ end
+
+ create_tokens
+ end
+
+ it 'does not update secrets.yml' do
+ expect(self).to receive(:exit).with(1).and_call_original
+ expect(File).not_to receive(:write)
+
+ expect { create_tokens }.to raise_error(SystemExit)
+ end
+ end
+ end
+end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 52d5a7dffc9..290e47763eb 100644
--- a/spec/initializers/trusted_proxies_spec.rb
+++ b/spec/initializers/trusted_proxies_spec.rb
@@ -47,6 +47,12 @@ describe 'trusted_proxies', lib: true do
expect(request.remote_ip).to eq('1.1.1.1')
expect(request.ip).to eq('1.1.1.1')
end
+
+ it 'handles invalid ip addresses' do
+ request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 1.1.1.1:12345, 1.1.1.1')
+ expect(request.remote_ip).to eq('1.1.1.1')
+ expect(request.ip).to eq('1.1.1.1')
+ end
end
def stub_request(headers = {})
diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee
new file mode 100644
index 00000000000..6b9617341fe
--- /dev/null
+++ b/spec/javascripts/datetime_utility_spec.js.coffee
@@ -0,0 +1,31 @@
+#= require lib/utils/datetime_utility
+
+describe 'Date time utils', ->
+ describe 'get day name', ->
+ it 'should return Sunday', ->
+ day = gl.utils.getDayName(new Date('07/17/2016'))
+ expect(day).toBe('Sunday')
+
+ it 'should return Monday', ->
+ day = gl.utils.getDayName(new Date('07/18/2016'))
+ expect(day).toBe('Monday')
+
+ it 'should return Tuesday', ->
+ day = gl.utils.getDayName(new Date('07/19/2016'))
+ expect(day).toBe('Tuesday')
+
+ it 'should return Wednesday', ->
+ day = gl.utils.getDayName(new Date('07/20/2016'))
+ expect(day).toBe('Wednesday')
+
+ it 'should return Thursday', ->
+ day = gl.utils.getDayName(new Date('07/21/2016'))
+ expect(day).toBe('Thursday')
+
+ it 'should return Friday', ->
+ day = gl.utils.getDayName(new Date('07/22/2016'))
+ expect(day).toBe('Friday')
+
+ it 'should return Saturday', ->
+ day = gl.utils.getDayName(new Date('07/23/2016'))
+ expect(day).toBe('Saturday')
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 2401875a057..6b58f3e43ee 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::Filter::RelativeLinkFilter, lib: true do
def filter(doc, contexts = {})
contexts.reverse_merge!({
- commit: project.commit,
+ commit: commit,
project: project,
project_wiki: project_wiki,
ref: ref,
@@ -17,6 +17,10 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
%(<img src="#{path}" />)
end
+ def video(path)
+ %(<video src="#{path}"></video>)
+ end
+
def link(path)
%(<a href="#{path}">#{path}</a>)
end
@@ -24,6 +28,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
let(:project) { create(:project) }
let(:project_path) { project.path_with_namespace }
let(:ref) { 'markdown' }
+ let(:commit) { project.commit(ref) }
let(:project_wiki) { nil }
let(:requested_path) { '/' }
@@ -37,6 +42,12 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
doc = filter(image('files/images/logo-black.png'))
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
end
+
+ it 'does not modify any relative URL in video' do
+ doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
+
+ expect(doc.at_css('video')['src']).to eq 'files/videos/intro.mp4'
+ end
end
shared_examples :relative_to_requested do
@@ -67,13 +78,36 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
expect { filter(act) }.not_to raise_error
end
- context 'with a valid repository' do
+ it 'ignores ref if commit is passed' do
+ doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') )
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/#{ref}/non/existent.file" # non-existent files have no leading blob/raw/tree
+ end
+
+ shared_examples :valid_repository do
+ it 'rebuilds absolute URL for a file in the repo' do
+ doc = filter(link('/doc/api/README.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
+ it 'ignores absolute URLs with two leading slashes' do
+ doc = filter(link('//doc/api/README.md'))
+ expect(doc.at_css('a')['href']).to eq '//doc/api/README.md'
+ end
+
it 'rebuilds relative URL for a file in the repo' do
doc = filter(link('doc/api/README.md'))
expect(doc.at_css('a')['href']).
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
end
+ it 'rebuilds relative URL for a file in the repo with leading ./' do
+ doc = filter(link('./doc/api/README.md'))
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
+ end
+
it 'rebuilds relative URL for a file in the repo up one directory' do
relative_link = link('../api/README.md')
doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md')
@@ -111,11 +145,26 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
end
it 'rebuilds relative URL for an image in the repo' do
+ doc = filter(image('files/images/logo-black.png'))
+
+ expect(doc.at_css('img')['src']).
+ to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
+ end
+
+ it 'rebuilds relative URL for link to an image in the repo' do
doc = filter(link('files/images/logo-black.png'))
+
expect(doc.at_css('a')['href']).
to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png"
end
+ it 'rebuilds relative URL for a video in the repo' do
+ doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video')
+
+ expect(doc.at_css('video')['src']).
+ to eq "/#{project_path}/raw/video/files/videos/intro.mp4"
+ end
+
it 'does not modify relative URL with an anchor only' do
doc = filter(link('#section-1'))
expect(doc.at_css('a')['href']).to eq '#section-1'
@@ -147,4 +196,13 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
include_examples :relative_to_requested
end
end
+
+ context 'with a valid commit' do
+ include_examples :valid_repository
+ end
+
+ context 'with a valid ref' do
+ let(:commit) { nil } # force filter to use ref instead of commit
+ include_examples :valid_repository
+ end
end
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index cc4349f80ba..6ab1be9ccb7 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -47,5 +47,4 @@ describe Banzai::Filter::VideoLinkFilter, lib: true do
expect(element['src']).to eq '/path/my_image.jpg'
end
end
-
end
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 514c752546d..85cfe728b6a 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -16,17 +16,17 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do
end
it 'returns the nodes when the user can read the issue' do
- expect(Ability.abilities).to receive(:allowed?).
- with(user, :read_issue, issue).
- and_return(true)
+ expect(Ability).to receive(:issues_readable_by_user).
+ with([issue], user).
+ and_return([issue])
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
end
it 'returns an empty Array when the user can not read the issue' do
- expect(Ability.abilities).to receive(:allowed?).
- with(user, :read_issue, issue).
- and_return(false)
+ expect(Ability).to receive(:issues_readable_by_user).
+ with([issue], user).
+ and_return([])
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
end
diff --git a/spec/lib/ci/charts_spec.rb b/spec/lib/ci/charts_spec.rb
index 97f2e97b062..fb6cc398307 100644
--- a/spec/lib/ci/charts_spec.rb
+++ b/spec/lib/ci/charts_spec.rb
@@ -2,21 +2,23 @@ require 'spec_helper'
describe Ci::Charts, lib: true do
context "build_times" do
+ let(:project) { create(:empty_project) }
+ let(:chart) { Ci::Charts::BuildTime.new(project) }
+
+ subject { chart.build_times }
+
before do
- @pipeline = FactoryGirl.create(:ci_pipeline)
- FactoryGirl.create(:ci_build, pipeline: @pipeline)
+ create(:ci_empty_pipeline, project: project, duration: 120)
end
- it 'should return build times in minutes' do
- chart = Ci::Charts::BuildTime.new(@pipeline.project)
- expect(chart.build_times).to eq([2])
+ it 'returns build times in minutes' do
+ is_expected.to contain_exactly(2)
end
- it 'should handle nil build times' do
- create(:ci_pipeline, duration: nil, project: @pipeline.project)
+ it 'handles nil build times' do
+ create(:ci_empty_pipeline, project: project, duration: nil)
- chart = Ci::Charts::BuildTime.new(@pipeline.project)
- expect(chart.build_times).to eq([2, 0])
+ is_expected.to contain_exactly(2, 0)
end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index d20fd4ab7dd..be51d942af7 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -19,7 +19,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
stage: "test",
stage_idx: 1,
- name: :rspec,
+ name: "rspec",
commands: "pwd\nrspec",
tag_list: [],
options: {},
@@ -162,7 +162,7 @@ module Ci
shared_examples 'raises an error' do
it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: only parameter should be an array of strings or regexps')
+ expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:only config should be an array of strings or regexps')
end
end
@@ -318,7 +318,7 @@ module Ci
shared_examples 'raises an error' do
it do
- expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: except parameter should be an array of strings or regexps')
+ expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:except config should be an array of strings or regexps')
end
end
@@ -433,7 +433,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
stage: "test",
stage_idx: 1,
- name: :rspec,
+ name: "rspec",
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -461,7 +461,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
stage: "test",
stage_idx: 1,
- name: :rspec,
+ name: "rspec",
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -533,10 +533,6 @@ module Ci
}
end
- context 'when also global variables are defined' do
-
- end
-
context 'when syntax is correct' do
let(:variables) do
{ VAR1: 'value1', VAR2: 'value2' }
@@ -559,7 +555,7 @@ module Ci
it 'raises error' do
expect { subject }
.to raise_error(GitlabCiYamlProcessor::ValidationError,
- /job: variables should be a map/)
+ /jobs:rspec:variables config should be a hash of key value pairs/)
end
end
@@ -704,7 +700,7 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
stage: "test",
stage_idx: 1,
- name: :rspec,
+ name: "rspec",
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -774,7 +770,7 @@ module Ci
let(:environment) { 1 }
it 'raises error' do
- expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}")
+ expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
end
end
@@ -782,7 +778,7 @@ module Ci
let(:environment) { 'production staging' }
it 'raises error' do
- expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}")
+ expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}")
end
end
end
@@ -841,7 +837,7 @@ module Ci
expect(subject.first).to eq({
stage: "test",
stage_idx: 1,
- name: :normal_job,
+ name: "normal_job",
commands: "test",
tag_list: [],
options: {},
@@ -886,7 +882,7 @@ module Ci
expect(subject.first).to eq({
stage: "build",
stage_idx: 0,
- name: :job1,
+ name: "job1",
commands: "execute-script-for-job",
tag_list: [],
options: {},
@@ -898,7 +894,7 @@ module Ci
expect(subject.second).to eq({
stage: "build",
stage_idx: 0,
- name: :job2,
+ name: "job2",
commands: "execute-script-for-job",
tag_list: [],
options: {},
@@ -973,7 +969,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", tags: "mysql" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings")
end
it "returns errors if before_script parameter is invalid" do
@@ -987,7 +983,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings")
end
it "returns errors if after_script parameter is invalid" do
@@ -1001,7 +997,7 @@ EOT
config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: after_script should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings")
end
it "returns errors if image parameter is invalid" do
@@ -1015,21 +1011,21 @@ EOT
config = YAML.dump({ '' => { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:job name can't be blank")
end
it "returns errors if job name is non-string" do
config = YAML.dump({ 10 => { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:10 name should be a symbol")
end
it "returns errors if job image parameter is invalid" do
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string")
end
it "returns errors if services parameter is not an array" do
@@ -1050,49 +1046,56 @@ EOT
config = YAML.dump({ rspec: { script: "test", services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end
it "returns errors if job services parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end
- it "returns errors if there are unknown parameters" do
+ it "returns error if job configuration is invalid" do
config = YAML.dump({ extra: "bundle update" })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra config should be a hash")
end
it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do
config = YAML.dump({ extra: { services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings")
end
it "returns errors if there are no jobs defined" do
config = YAML.dump({ before_script: ["bundle update"] })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
+ end
+
+ it "returns errors if there are no visible jobs defined" do
+ config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } })
+ expect do
+ GitlabCiYamlProcessor.new(config, path)
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job")
end
it "returns errors if job allow_failure parameter is not an boolean" do
config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value")
end
it "returns errors if job stage is not a string" do
config = YAML.dump({ rspec: { script: "test", type: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be a string")
end
it "returns errors if job stage is not a pre-defined stage" do
@@ -1141,49 +1144,49 @@ EOT
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always or manual")
end
it "returns errors if job artifacts:name is not an a string" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string")
end
it "returns errors if job artifacts:when is not an a predefined value" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:when parameter should be on_success, on_failure or always")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always")
end
it "returns errors if job artifacts:expire_in is not an a string" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
end
it "returns errors if job artifacts:expire_in is not an a valid duration" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration")
end
it "returns errors if job artifacts:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value")
end
it "returns errors if job artifacts:paths is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings")
end
it "returns errors if cache:untracked is not an array of strings" do
@@ -1211,28 +1214,28 @@ EOT
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol")
end
it "returns errors if job cache:untracked is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value")
end
it "returns errors if job cache:paths is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings")
end
it "returns errors if job dependencies is not an array of strings" do
config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } })
expect do
GitlabCiYamlProcessor.new(config)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings")
end
end
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
index 309a88151cf..8f51474476d 100644
--- a/spec/lib/disable_email_interceptor_spec.rb
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -5,7 +5,7 @@ describe DisableEmailInterceptor, lib: true do
Mail.register_interceptor(DisableEmailInterceptor)
end
- it 'should not send emails' do
+ it 'does not send emails' do
allow(Gitlab.config.gitlab).to receive(:email_enabled).and_return(false)
expect { deliver_mail }.not_to change(ActionMailer::Base.deliveries, :count)
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 566035c60d0..36c77206a3f 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -25,20 +25,33 @@ describe ExtractsPath, lib: true do
@project = create(:project)
end
- it "log tree path should have no escape sequences" do
+ it "log tree path has no escape sequences" do
assign_ref_vars
expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
end
- context 'escaped sequences in ref' do
- let(:ref) { "improve%2Fawesome" }
+ context 'escaped slash character in ref' do
+ let(:ref) { 'improve%2Fawesome' }
- it "id should have no escape sequences" do
+ it 'has no escape sequences in @ref or @logs_path' do
assign_ref_vars
+
expect(@ref).to eq('improve/awesome')
expect(@logs_path).to eq("/#{@project.path_with_namespace}/refs/#{ref}/logs_tree/files/ruby/popen.rb")
end
end
+
+ context 'ref contains %20' do
+ let(:ref) { 'foo%20bar' }
+
+ it 'is not converted to a space in @id' do
+ @project.repository.add_branch(@project.owner, 'foo%20bar', 'master')
+
+ assign_ref_vars
+
+ expect(@id).to start_with('foo%20bar/')
+ end
+ end
end
describe '#extract_ref' do
diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb
deleted file mode 100644
index 88a71528867..00000000000
--- a/spec/lib/gitlab/akismet_helper_spec.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::AkismetHelper, type: :helper do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
-
- before do
- allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
- allow_any_instance_of(ApplicationSetting).to receive(:akismet_enabled).and_return(true)
- allow_any_instance_of(ApplicationSetting).to receive(:akismet_api_key).and_return('12345')
- end
-
- describe '#check_for_spam?' do
- it 'returns true for non-member' do
- expect(helper.check_for_spam?(project, user)).to eq(true)
- end
-
- it 'returns false for member' do
- project.team << [user, :guest]
- expect(helper.check_for_spam?(project, user)).to eq(false)
- end
- end
-
- describe '#is_spam?' do
- it 'returns true for spam' do
- environment = {
- 'action_dispatch.remote_ip' => '127.0.0.1',
- 'HTTP_USER_AGENT' => 'Test User Agent'
- }
-
- allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
- expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
- end
- end
-end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 32ca8239845..4aba783dc33 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -8,7 +8,7 @@ module Gitlab
let(:html) { 'H<sub>2</sub>O' }
context "without project" do
- it "should convert the input using Asciidoctor and default options" do
+ it "converts the input using Asciidoctor and default options" do
expected_asciidoc_opts = {
safe: :secure,
backend: :html5,
@@ -24,7 +24,7 @@ module Gitlab
context "with asciidoc_opts" do
let(:asciidoc_opts) { { safe: :safe, attributes: ['foo'] } }
- it "should merge the options with default ones" do
+ it "merges the options with default ones" do
expected_asciidoc_opts = {
safe: :safe,
backend: :html5,
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 7bec1367156..b0772cad312 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -51,24 +51,24 @@ describe Gitlab::Auth, lib: true do
let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
- it "should find user by valid login/password" do
+ it "finds user by valid login/password" do
expect( gl_auth.find_with_user_password(username, password) ).to eql user
end
- it 'should find user by valid email/password with case-insensitive email' do
+ it 'finds user by valid email/password with case-insensitive email' do
expect(gl_auth.find_with_user_password(user.email.upcase, password)).to eql user
end
- it 'should find user by valid username/password with case-insensitive username' do
+ it 'finds user by valid username/password with case-insensitive username' do
expect(gl_auth.find_with_user_password(username.upcase, password)).to eql user
end
- it "should not find user with invalid password" do
+ it "does not find user with invalid password" do
password = 'wrong'
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end
- it "should not find user with invalid login" do
+ it "does not find user with invalid login" do
user = 'wrong'
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end
diff --git a/spec/lib/gitlab/badge/build/metadata_spec.rb b/spec/lib/gitlab/badge/build/metadata_spec.rb
new file mode 100644
index 00000000000..d678e522721
--- /dev/null
+++ b/spec/lib/gitlab/badge/build/metadata_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'lib/gitlab/badge/shared/metadata'
+
+describe Gitlab::Badge::Build::Metadata do
+ let(:badge) { double(project: create(:project), ref: 'feature') }
+ let(:metadata) { described_class.new(badge) }
+
+ it_behaves_like 'badge metadata'
+
+ describe '#title' do
+ it 'returns build status title' do
+ expect(metadata.title).to eq 'build status'
+ end
+ end
+
+ describe '#image_url' do
+ it 'returns valid url' do
+ expect(metadata.image_url).to include 'badges/feature/build.svg'
+ end
+ end
+
+ describe '#link_url' do
+ it 'returns valid link' do
+ expect(metadata.link_url).to include 'commits/feature'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/build/status_spec.rb b/spec/lib/gitlab/badge/build/status_spec.rb
new file mode 100644
index 00000000000..38eebb2a176
--- /dev/null
+++ b/spec/lib/gitlab/badge/build/status_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Gitlab::Badge::Build::Status do
+ let(:project) { create(:project) }
+ let(:sha) { project.commit.sha }
+ let(:branch) { 'master' }
+ let(:badge) { described_class.new(project, branch) }
+
+ describe '#entity' do
+ it 'always says build' do
+ expect(badge.entity).to eq 'build'
+ end
+ end
+
+ describe '#template' do
+ it 'returns badge template' do
+ expect(badge.template.key_text).to eq 'build'
+ end
+ end
+
+ describe '#metadata' do
+ it 'returns badge metadata' do
+ expect(badge.metadata.image_url)
+ .to include 'badges/master/build.svg'
+ end
+ end
+
+ context 'build exists' do
+ let!(:build) { create_build(project, sha, branch) }
+
+ context 'build success' do
+ before { build.success! }
+
+ describe '#status' do
+ it 'is successful' do
+ expect(badge.status).to eq 'success'
+ end
+ end
+ end
+
+ context 'build failed' do
+ before { build.drop! }
+
+ describe '#status' do
+ it 'failed' do
+ expect(badge.status).to eq 'failed'
+ end
+ end
+ end
+
+ context 'when outdated pipeline for given ref exists' do
+ before do
+ build.success!
+
+ old_build = create_build(project, '11eeffdd', branch)
+ old_build.drop!
+ end
+
+ it 'does not take outdated pipeline into account' do
+ expect(badge.status).to eq 'success'
+ end
+ end
+
+ context 'when multiple pipelines exist for given sha' do
+ before do
+ build.drop!
+
+ new_build = create_build(project, sha, branch)
+ new_build.success!
+ end
+
+ it 'reports the compound status' do
+ expect(badge.status).to eq 'failed'
+ end
+ end
+ end
+
+ context 'build does not exist' do
+ describe '#status' do
+ it 'is unknown' do
+ expect(badge.status).to eq 'unknown'
+ end
+ end
+ end
+
+ def create_build(project, sha, branch)
+ pipeline = create(:ci_empty_pipeline,
+ project: project,
+ sha: sha,
+ ref: branch)
+
+ create(:ci_build, pipeline: pipeline, stage: 'notify')
+ end
+end
diff --git a/spec/lib/gitlab/badge/build/template_spec.rb b/spec/lib/gitlab/badge/build/template_spec.rb
new file mode 100644
index 00000000000..a7e21fb8bb1
--- /dev/null
+++ b/spec/lib/gitlab/badge/build/template_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::Badge::Build::Template do
+ let(:badge) { double(entity: 'build', status: 'success') }
+ let(:template) { described_class.new(badge) }
+
+ describe '#key_text' do
+ it 'is always says build' do
+ expect(template.key_text).to eq 'build'
+ end
+ end
+
+ describe '#value_text' do
+ it 'is status value' do
+ expect(template.value_text).to eq 'success'
+ end
+ end
+
+ describe 'widths and text anchors' do
+ it 'has fixed width and text anchors' do
+ expect(template.width).to eq 92
+ expect(template.key_width).to eq 38
+ expect(template.value_width).to eq 54
+ expect(template.key_text_anchor).to eq 19
+ expect(template.value_text_anchor).to eq 65
+ end
+ end
+
+ describe '#key_color' do
+ it 'is always the same' do
+ expect(template.key_color).to eq '#555'
+ end
+ end
+
+ describe '#value_color' do
+ context 'when status is success' do
+ it 'has expected color' do
+ expect(template.value_color).to eq '#4c1'
+ end
+ end
+
+ context 'when status is failed' do
+ before do
+ allow(badge).to receive(:status).and_return('failed')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#e05d44'
+ end
+ end
+
+ context 'when status is running' do
+ before do
+ allow(badge).to receive(:status).and_return('running')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#dfb317'
+ end
+ end
+
+ context 'when status is unknown' do
+ before do
+ allow(badge).to receive(:status).and_return('unknown')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#9f9f9f'
+ end
+ end
+
+ context 'when status does not match any known statuses' do
+ before do
+ allow(badge).to receive(:status).and_return('invalid')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#9f9f9f'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
deleted file mode 100644
index f3b522a02f5..00000000000
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Badge::Build do
- let(:project) { create(:project) }
- let(:sha) { project.commit.sha }
- let(:branch) { 'master' }
- let(:badge) { described_class.new(project, branch) }
-
- describe '#type' do
- subject { badge.type }
- it { is_expected.to eq 'image/svg+xml' }
- end
-
- describe '#to_html' do
- let(:html) { Nokogiri::HTML.parse(badge.to_html) }
- let(:a_href) { html.at('a') }
-
- it 'points to link' do
- expect(a_href[:href]).to eq badge.link_url
- end
-
- it 'contains clickable image' do
- expect(a_href.children.first.name).to eq 'img'
- end
- end
-
- describe '#to_markdown' do
- subject { badge.to_markdown }
-
- it { is_expected.to include badge.image_url }
- it { is_expected.to include badge.link_url }
- end
-
- describe '#image_url' do
- subject { badge.image_url }
- it { is_expected.to include "badges/#{branch}/build.svg" }
- end
-
- describe '#link_url' do
- subject { badge.link_url }
- it { is_expected.to include "commits/#{branch}" }
- end
-
- context 'build exists' do
- let!(:build) { create_build(project, sha, branch) }
-
- context 'build success' do
- before { build.success! }
-
- describe '#to_s' do
- subject { badge.to_s }
- it { is_expected.to eq 'build-success' }
- end
-
- describe '#data' do
- let(:data) { badge.data }
-
- it 'contains information about success' do
- expect(status_node(data, 'success')).to be_truthy
- end
- end
- end
-
- context 'build failed' do
- before { build.drop! }
-
- describe '#to_s' do
- subject { badge.to_s }
- it { is_expected.to eq 'build-failed' }
- end
-
- describe '#data' do
- let(:data) { badge.data }
-
- it 'contains information about failure' do
- expect(status_node(data, 'failed')).to be_truthy
- end
- end
- end
- end
-
- context 'build does not exist' do
- describe '#to_s' do
- subject { badge.to_s }
- it { is_expected.to eq 'build-unknown' }
- end
-
- describe '#data' do
- let(:data) { badge.data }
-
- it 'contains infromation about unknown build' do
- expect(status_node(data, 'unknown')).to be_truthy
- end
- end
- end
-
- context 'when outdated pipeline for given ref exists' do
- before do
- build = create_build(project, sha, branch)
- build.success!
-
- old_build = create_build(project, '11eeffdd', branch)
- old_build.drop!
- end
-
- it 'does not take outdated pipeline into account' do
- expect(badge.to_s).to eq 'build-success'
- end
- end
-
- def create_build(project, sha, branch)
- pipeline = create(:ci_pipeline, project: project,
- sha: sha,
- ref: branch)
-
- create(:ci_build, pipeline: pipeline, stage: 'notify')
- end
-
- def status_node(data, status)
- xml = Nokogiri::XML.parse(data)
- xml.at(%Q{text:contains("#{status}")})
- end
-end
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
new file mode 100644
index 00000000000..74eaf7eaf8b
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+require 'lib/gitlab/badge/shared/metadata'
+
+describe Gitlab::Badge::Coverage::Metadata do
+ let(:badge) do
+ double(project: create(:project), ref: 'feature', job: 'test')
+ end
+
+ let(:metadata) { described_class.new(badge) }
+
+ it_behaves_like 'badge metadata'
+
+ describe '#title' do
+ it 'returns coverage report title' do
+ expect(metadata.title).to eq 'coverage report'
+ end
+ end
+
+ describe '#image_url' do
+ it 'returns valid url' do
+ expect(metadata.image_url).to include 'badges/feature/coverage.svg'
+ end
+ end
+
+ describe '#link_url' do
+ it 'returns valid link' do
+ expect(metadata.link_url).to include 'commits/feature'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
new file mode 100644
index 00000000000..1ff49602486
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Gitlab::Badge::Coverage::Report do
+ let(:project) { create(:project) }
+ let(:job_name) { nil }
+
+ let(:badge) do
+ described_class.new(project, 'master', job_name)
+ end
+
+ describe '#entity' do
+ it 'describes a coverage' do
+ expect(badge.entity).to eq 'coverage'
+ end
+ end
+
+ describe '#metadata' do
+ it 'returns correct metadata' do
+ expect(badge.metadata.image_url).to include 'coverage.svg'
+ end
+ end
+
+ describe '#template' do
+ it 'returns correct template' do
+ expect(badge.template.key_text).to eq 'coverage'
+ end
+ end
+
+ shared_examples 'unknown coverage report' do
+ context 'particular job specified' do
+ let(:job_name) { '' }
+
+ it 'returns nil' do
+ expect(badge.status).to be_nil
+ end
+ end
+
+ context 'particular job not specified' do
+ let(:job_name) { nil }
+
+ it 'returns nil' do
+ expect(badge.status).to be_nil
+ end
+ end
+ end
+
+ context 'pipeline exists' do
+ let!(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: 'master')
+ end
+
+ context 'builds exist' do
+ before do
+ create(:ci_build, name: 'first', pipeline: pipeline, coverage: 40)
+ create(:ci_build, pipeline: pipeline, coverage: 60)
+ end
+
+ context 'particular job specified' do
+ let(:job_name) { 'first' }
+
+ it 'returns coverage for the particular job' do
+ expect(badge.status).to eq 40
+ end
+ end
+
+ context 'particular job not specified' do
+ let(:job_name) { '' }
+
+ it 'returns arithemetic mean for the pipeline' do
+ expect(badge.status).to eq 50
+ end
+ end
+ end
+
+ context 'builds do not exist' do
+ it_behaves_like 'unknown coverage report'
+
+ context 'particular job specified' do
+ let(:job_name) { 'nonexistent' }
+
+ it 'retruns nil' do
+ expect(badge.status).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'pipeline does not exist' do
+ it_behaves_like 'unknown coverage report'
+ end
+end
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
new file mode 100644
index 00000000000..383bae6e087
--- /dev/null
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -0,0 +1,130 @@
+require 'spec_helper'
+
+describe Gitlab::Badge::Coverage::Template do
+ let(:badge) { double(entity: 'coverage', status: 90) }
+ let(:template) { described_class.new(badge) }
+
+ describe '#key_text' do
+ it 'is always says coverage' do
+ expect(template.key_text).to eq 'coverage'
+ end
+ end
+
+ describe '#value_text' do
+ context 'when coverage is known' do
+ it 'returns coverage percentage' do
+ expect(template.value_text).to eq '90%'
+ end
+ end
+
+ context 'when coverage is unknown' do
+ before do
+ allow(badge).to receive(:status).and_return(nil)
+ end
+
+ it 'returns string that says coverage is unknown' do
+ expect(template.value_text).to eq 'unknown'
+ end
+ end
+ end
+
+ describe '#key_width' do
+ it 'has a fixed key width' do
+ expect(template.key_width).to eq 62
+ end
+ end
+
+ describe '#value_width' do
+ context 'when coverage is known' do
+ it 'is narrower when coverage is known' do
+ expect(template.value_width).to eq 36
+ end
+ end
+
+ context 'when coverage is unknown' do
+ before do
+ allow(badge).to receive(:status).and_return(nil)
+ end
+
+ it 'is wider when coverage is unknown to fit text' do
+ expect(template.value_width).to eq 58
+ end
+ end
+ end
+
+ describe '#key_color' do
+ it 'always has the same color' do
+ expect(template.key_color).to eq '#555'
+ end
+ end
+
+ describe '#value_color' do
+ context 'when coverage is good' do
+ before do
+ allow(badge).to receive(:status).and_return(98)
+ end
+
+ it 'is green' do
+ expect(template.value_color).to eq '#4c1'
+ end
+ end
+
+ context 'when coverage is acceptable' do
+ before do
+ allow(badge).to receive(:status).and_return(90)
+ end
+
+ it 'is green-orange' do
+ expect(template.value_color).to eq '#a3c51c'
+ end
+ end
+
+ context 'when coverage is medium' do
+ before do
+ allow(badge).to receive(:status).and_return(75)
+ end
+
+ it 'is orange-yellow' do
+ expect(template.value_color).to eq '#dfb317'
+ end
+ end
+
+ context 'when coverage is low' do
+ before do
+ allow(badge).to receive(:status).and_return(50)
+ end
+
+ it 'is red' do
+ expect(template.value_color).to eq '#e05d44'
+ end
+ end
+
+ context 'when coverage is unknown' do
+ before do
+ allow(badge).to receive(:status).and_return(nil)
+ end
+
+ it 'is grey' do
+ expect(template.value_color).to eq '#9f9f9f'
+ end
+ end
+ end
+
+ describe '#width' do
+ context 'when coverage is known' do
+ it 'returns the key width plus value width' do
+ expect(template.width).to eq 98
+ end
+ end
+
+ context 'when coverage is unknown' do
+ before do
+ allow(badge).to receive(:status).and_return(nil)
+ end
+
+ it 'returns key width plus wider value width' do
+ expect(template.width).to eq 120
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
new file mode 100644
index 00000000000..0cf18514251
--- /dev/null
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -0,0 +1,21 @@
+shared_examples 'badge metadata' do
+ describe '#to_html' do
+ let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
+ let(:a_href) { html.at('a') }
+
+ it 'points to link' do
+ expect(a_href[:href]).to eq metadata.link_url
+ end
+
+ it 'contains clickable image' do
+ expect(a_href.children.first.name).to eq 'img'
+ end
+ end
+
+ describe '#to_markdown' do
+ subject { metadata.to_markdown }
+
+ it { is_expected.to include metadata.image_url }
+ it { is_expected.to include metadata.link_url }
+ end
+end
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
new file mode 100644
index 00000000000..69d86144e32
--- /dev/null
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -0,0 +1,30 @@
+require "spec_helper"
+
+describe Gitlab::ChangesList do
+ let(:valid_changes_string) { "\n000000 570e7b2 refs/heads/my_branch\nd14d6c 6fd24d refs/heads/master" }
+ let(:invalid_changes) { 1 }
+
+ context 'when changes is a valid string' do
+ let(:changes_list) { Gitlab::ChangesList.new(valid_changes_string) }
+
+ it 'splits elements by newline character' do
+ expect(changes_list).to contain_exactly({
+ oldrev: "000000",
+ newrev: "570e7b2",
+ ref: "refs/heads/my_branch"
+ }, {
+ oldrev: "d14d6c",
+ newrev: "6fd24d",
+ ref: "refs/heads/master"
+ })
+ end
+
+ it 'behaves like a list' do
+ expect(changes_list.first).to eq({
+ oldrev: "000000",
+ newrev: "570e7b2",
+ ref: "refs/heads/my_branch"
+ })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
new file mode 100644
index 00000000000..39069b49978
--- /dev/null
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe Gitlab::Checks::ChangeAccess, lib: true do
+ describe '#exec' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:user_access) { Gitlab::UserAccess.new(user, project: project) }
+ let(:changes) do
+ {
+ oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
+ newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
+ ref: 'refs/heads/master'
+ }
+ end
+
+ subject { described_class.new(changes, project: project, user_access: user_access).exec }
+
+ before { allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) }
+
+ context 'without failed checks' do
+ it "doesn't return any error" do
+ expect(subject.status).to be(true)
+ end
+ end
+
+ context 'when the user is not allowed to push code' do
+ it 'returns an error' do
+ expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to push code to this project.')
+ end
+ end
+
+ context 'tags check' do
+ let(:changes) do
+ {
+ oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
+ newrev: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51',
+ ref: 'refs/tags/v1.0.0'
+ }
+ end
+
+ it 'returns an error if the user is not allowed to update tags' do
+ expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to change existing tags on this project.')
+ end
+ end
+
+ context 'protected branches check' do
+ before do
+ allow(project).to receive(:protected_branch?).with('master').and_return(true)
+ end
+
+ it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
+ expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
+ expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
+ end
+
+ it 'returns an error if the user is not allowed to merge to protected branches' do
+ expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
+ expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
+ expect(user_access).to receive(:can_push_to_branch?).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.')
+ end
+
+ it 'returns an error if the user is not allowed to push to protected branches' do
+ expect(user_access).to receive(:can_push_to_branch?).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.')
+ end
+
+ context 'branch deletion' do
+ let(:changes) do
+ {
+ oldrev: 'be93687618e4b132087f430a4d8fc3a609c9b77c',
+ newrev: '0000000000000000000000000000000000000000',
+ ref: 'refs/heads/master'
+ }
+ end
+
+ it 'returns an error if the user is not allowed to delete protected branches' do
+ expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
+
+ expect(subject.status).to be(false)
+ expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb
new file mode 100644
index 00000000000..c09a0a9c793
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Artifacts do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validation' do
+ context 'when entry config value is correct' do
+ let(:config) { { paths: %w[public/] } }
+
+ describe '#value' do
+ it 'returns artifacs configuration' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'when value of attribute is invalid' do
+ let(:config) { { name: 10 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'artifacts name should be a string'
+ end
+ end
+
+ context 'when there is an unknown key present' do
+ let(:config) { { test: 100 } }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include 'artifacts config contains unknown keys: test'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/attributable_spec.rb b/spec/lib/gitlab/ci/config/node/attributable_spec.rb
new file mode 100644
index 00000000000..24d9daafd88
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/attributable_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Attributable do
+ let(:node) { Class.new }
+ let(:instance) { node.new }
+
+ before do
+ node.include(described_class)
+
+ node.class_eval do
+ attributes :name, :test
+ end
+ end
+
+ context 'config is a hash' do
+ before do
+ allow(instance)
+ .to receive(:config)
+ .and_return({ name: 'some name', test: 'some test' })
+ end
+
+ it 'returns the value of config' do
+ expect(instance.name).to eq 'some name'
+ expect(instance.test).to eq 'some test'
+ end
+
+ it 'returns no method error for unknown attributes' do
+ expect { instance.unknown }.to raise_error(NoMethodError)
+ end
+ end
+
+ context 'config is not a hash' do
+ before do
+ allow(instance)
+ .to receive(:config)
+ .and_return('some test')
+ end
+
+ it 'returns nil' do
+ expect(instance.test).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/commands_spec.rb b/spec/lib/gitlab/ci/config/node/commands_spec.rb
new file mode 100644
index 00000000000..e373c40706f
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/commands_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Commands do
+ let(:entry) { described_class.new(config) }
+
+ context 'when entry config value is an array' do
+ let(:config) { ['ls', 'pwd'] }
+
+ describe '#value' do
+ it 'returns array of strings' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ describe '#errors' do
+ it 'does not append errors' do
+ expect(entry.errors).to be_empty
+ end
+ end
+ end
+
+ context 'when entry config value is a string' do
+ let(:config) { 'ls' }
+
+ describe '#value' do
+ it 'returns array with single element' do
+ expect(entry.value).to eq ['ls']
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not valid' do
+ let(:config) { 1 }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors)
+ .to include 'commands config should be a ' \
+ 'string or an array of strings'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index 91ddef7bfbf..d26185ba585 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Factory do
describe '#create!' do
- let(:factory) { described_class.new(entry_class) }
- let(:entry_class) { Gitlab::Ci::Config::Node::Script }
+ let(:factory) { described_class.new(node) }
+ let(:node) { Gitlab::Ci::Config::Node::Script }
- context 'when setting up a value' do
+ context 'when setting a concrete value' do
it 'creates entry with valid value' do
entry = factory
- .with(value: ['ls', 'pwd'])
+ .value(['ls', 'pwd'])
.create!
expect(entry.value).to eq ['ls', 'pwd']
@@ -17,7 +17,7 @@ describe Gitlab::Ci::Config::Node::Factory do
context 'when setting description' do
it 'creates entry with description' do
entry = factory
- .with(value: ['ls', 'pwd'])
+ .value(['ls', 'pwd'])
.with(description: 'test description')
.create!
@@ -29,7 +29,8 @@ describe Gitlab::Ci::Config::Node::Factory do
context 'when setting key' do
it 'creates entry with custom key' do
entry = factory
- .with(value: ['ls', 'pwd'], key: 'test key')
+ .value(['ls', 'pwd'])
+ .with(key: 'test key')
.create!
expect(entry.key).to eq 'test key'
@@ -37,19 +38,20 @@ describe Gitlab::Ci::Config::Node::Factory do
end
context 'when setting a parent' do
- let(:parent) { Object.new }
+ let(:object) { Object.new }
it 'creates entry with valid parent' do
entry = factory
- .with(value: 'ls', parent: parent)
+ .value('ls')
+ .with(parent: object)
.create!
- expect(entry.parent).to eq parent
+ expect(entry.parent).to eq object
end
end
end
- context 'when not setting up a value' do
+ context 'when not setting a value' do
it 'raises error' do
expect { factory.create! }.to raise_error(
Gitlab::Ci::Config::Node::Factory::InvalidFactory
@@ -60,11 +62,25 @@ describe Gitlab::Ci::Config::Node::Factory do
context 'when creating entry with nil value' do
it 'creates an undefined entry' do
entry = factory
- .with(value: nil)
+ .value(nil)
.create!
expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
end
end
+
+ context 'when passing metadata' do
+ let(:node) { spy('node') }
+
+ it 'passes metadata as a parameter' do
+ factory
+ .value('some value')
+ .metadata(some: 'hash')
+ .create!
+
+ expect(node).to have_received(:new)
+ .with('some value', { some: 'hash' })
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index c87c9e97bc8..2f87d270b36 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -22,38 +22,40 @@ describe Gitlab::Ci::Config::Node::Global do
variables: { VAR: 'value' },
after_script: ['make clean'],
stages: ['build', 'pages'],
- cache: { key: 'k', untracked: true, paths: ['public/'] } }
+ cache: { key: 'k', untracked: true, paths: ['public/'] },
+ rspec: { script: %w[rspec ls] },
+ spinach: { script: 'spinach' } }
end
describe '#process!' do
before { global.process! }
it 'creates nodes hash' do
- expect(global.nodes).to be_an Array
+ expect(global.descendants).to be_an Array
end
it 'creates node object for each entry' do
- expect(global.nodes.count).to eq 8
+ expect(global.descendants.count).to eq 8
end
it 'creates node object using valid class' do
- expect(global.nodes.first)
+ expect(global.descendants.first)
.to be_an_instance_of Gitlab::Ci::Config::Node::Script
- expect(global.nodes.second)
+ expect(global.descendants.second)
.to be_an_instance_of Gitlab::Ci::Config::Node::Image
end
it 'sets correct description for nodes' do
- expect(global.nodes.first.description)
+ expect(global.descendants.first.description)
.to eq 'Script that will be executed before each job.'
- expect(global.nodes.second.description)
+ expect(global.descendants.second.description)
.to eq 'Docker image that will be used to execute jobs.'
end
- end
- describe '#leaf?' do
- it 'is not leaf' do
- expect(global).not_to be_leaf
+ describe '#leaf?' do
+ it 'is not leaf' do
+ expect(global).not_to be_leaf
+ end
end
end
@@ -63,6 +65,12 @@ describe Gitlab::Ci::Config::Node::Global do
expect(global.before_script).to be nil
end
end
+
+ describe '#leaf?' do
+ it 'is leaf' do
+ expect(global).to be_leaf
+ end
+ end
end
context 'when processed' do
@@ -106,7 +114,10 @@ describe Gitlab::Ci::Config::Node::Global do
end
context 'when deprecated types key defined' do
- let(:hash) { { types: ['test', 'deploy'] } }
+ let(:hash) do
+ { types: ['test', 'deploy'],
+ rspec: { script: 'rspec' } }
+ end
it 'returns array of types as stages' do
expect(global.stages).to eq %w[test deploy]
@@ -120,20 +131,33 @@ describe Gitlab::Ci::Config::Node::Global do
.to eq(key: 'k', untracked: true, paths: ['public/'])
end
end
+
+ describe '#jobs' do
+ it 'returns jobs configuration' do
+ expect(global.jobs).to eq(
+ rspec: { name: :rspec,
+ script: %w[rspec ls],
+ stage: 'test' },
+ spinach: { name: :spinach,
+ script: %w[spinach],
+ stage: 'test' }
+ )
+ end
+ end
end
end
context 'when most of entires not defined' do
- let(:hash) { { cache: { key: 'a' }, rspec: {} } }
+ let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
before { global.process! }
describe '#nodes' do
it 'instantizes all nodes' do
- expect(global.nodes.count).to eq 8
+ expect(global.descendants.count).to eq 8
end
it 'contains undefined nodes' do
- expect(global.nodes.first)
+ expect(global.descendants.first)
.to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
end
end
@@ -164,7 +188,7 @@ describe Gitlab::Ci::Config::Node::Global do
# details.
#
context 'when entires specified but not defined' do
- let(:hash) { { variables: nil } }
+ let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
before { global.process! }
describe '#variables' do
@@ -196,10 +220,8 @@ describe Gitlab::Ci::Config::Node::Global do
end
describe '#before_script' do
- it 'raises error' do
- expect { global.before_script }.to raise_error(
- Gitlab::Ci::Config::Node::Entry::InvalidError
- )
+ it 'returns nil' do
+ expect(global.before_script).to be_nil
end
end
end
@@ -220,9 +242,9 @@ describe Gitlab::Ci::Config::Node::Global do
end
end
- describe '#defined?' do
+ describe '#specified?' do
it 'is concrete entry that is defined' do
- expect(global.defined?).to be true
+ expect(global.specified?).to be true
end
end
end
diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
new file mode 100644
index 00000000000..cc44e2cc054
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::HiddenJob do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validations' do
+ context 'when entry config value is correct' do
+ let(:config) { { image: 'ruby:2.2' } }
+
+ describe '#value' do
+ it 'returns key value' do
+ expect(entry.value).to eq(image: 'ruby:2.2')
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ context 'incorrect config value type' do
+ let(:config) { ['incorrect'] }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors)
+ .to include 'hidden job config should be a hash'
+ end
+ end
+ end
+
+ context 'when config is empty' do
+ let(:config) { {} }
+
+ describe '#valid' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+ end
+ end
+
+ describe '#leaf?' do
+ it 'is a leaf' do
+ expect(entry).to be_leaf
+ end
+ end
+
+ describe '#relevant?' do
+ it 'is not a relevant entry' do
+ expect(entry).not_to be_relevant
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
new file mode 100644
index 00000000000..1484fb60dd8
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Job do
+ let(:entry) { described_class.new(config, name: :rspec) }
+
+ before { entry.process! }
+
+ describe 'validations' do
+ context 'when entry config value is correct' do
+ let(:config) { { script: 'rspec' } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ context 'when job name is empty' do
+ let(:entry) { described_class.new(config, name: ''.to_sym) }
+
+ it 'reports error' do
+ expect(entry.errors)
+ .to include "job name can't be blank"
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ context 'incorrect config value type' do
+ let(:config) { ['incorrect'] }
+
+ describe '#errors' do
+ it 'reports error about a config type' do
+ expect(entry.errors)
+ .to include 'job config should be a hash'
+ end
+ end
+ end
+
+ context 'when config is empty' do
+ let(:config) { {} }
+
+ describe '#valid' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+
+ context 'when unknown keys detected' do
+ let(:config) { { unknown: true } }
+
+ describe '#valid' do
+ it 'is not valid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+ end
+ end
+
+ describe '#value' do
+ context 'when entry is correct' do
+ let(:config) do
+ { before_script: %w[ls pwd],
+ script: 'rspec',
+ after_script: %w[cleanup] }
+ end
+
+ it 'returns correct value' do
+ expect(entry.value)
+ .to eq(name: :rspec,
+ before_script: %w[ls pwd],
+ script: %w[rspec],
+ stage: 'test',
+ after_script: %w[cleanup])
+ end
+ end
+ end
+
+ describe '#relevant?' do
+ it 'is a relevant entry' do
+ expect(entry).to be_relevant
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
new file mode 100644
index 00000000000..b8d9c70479c
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -0,0 +1,87 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Jobs do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validations' do
+ before { entry.process! }
+
+ context 'when entry config value is correct' do
+ let(:config) { { rspec: { script: 'rspec' } } }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when entry value is not correct' do
+ describe '#errors' do
+ context 'incorrect config value type' do
+ let(:config) { ['incorrect'] }
+
+ it 'returns error about incorrect type' do
+ expect(entry.errors)
+ .to include 'jobs config should be a hash'
+ end
+ end
+
+ context 'when job is unspecified' do
+ let(:config) { { rspec: nil } }
+
+ it 'reports error' do
+ expect(entry.errors).to include "rspec config can't be blank"
+ end
+ end
+
+ context 'when no visible jobs present' do
+ let(:config) { { '.hidden'.to_sym => { script: [] } } }
+
+ it 'returns error about no visible jobs defined' do
+ expect(entry.errors)
+ .to include 'jobs config should contain at least one visible job'
+ end
+ end
+ end
+ end
+ end
+
+ context 'when valid job entries processed' do
+ before { entry.process! }
+
+ let(:config) do
+ { rspec: { script: 'rspec' },
+ spinach: { script: 'spinach' },
+ '.hidden'.to_sym => {} }
+ end
+
+ describe '#value' do
+ it 'returns key value' do
+ expect(entry.value).to eq(
+ rspec: { name: :rspec,
+ script: %w[rspec],
+ stage: 'test' },
+ spinach: { name: :spinach,
+ script: %w[spinach],
+ stage: 'test' })
+ end
+ end
+
+ describe '#descendants' do
+ it 'creates valid descendant nodes' do
+ expect(entry.descendants.count).to eq 3
+ expect(entry.descendants.first(2))
+ .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job))
+ expect(entry.descendants.last)
+ .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob)
+ end
+ end
+
+ describe '#value' do
+ it 'returns value of visible jobs only' do
+ expect(entry.value.keys).to eq [:rspec, :spinach]
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
new file mode 100644
index 00000000000..1ab5478dcfa
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/null_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Null do
+ let(:null) { described_class.new(nil) }
+
+ describe '#leaf?' do
+ it 'is leaf node' do
+ expect(null).to be_leaf
+ end
+ end
+
+ describe '#valid?' do
+ it 'is always valid' do
+ expect(null).to be_valid
+ end
+ end
+
+ describe '#errors' do
+ it 'is does not contain errors' do
+ expect(null.errors).to be_empty
+ end
+ end
+
+ describe '#value' do
+ it 'returns nil' do
+ expect(null.value).to eq nil
+ end
+ end
+
+ describe '#relevant?' do
+ it 'is not relevant' do
+ expect(null.relevant?).to eq false
+ end
+ end
+
+ describe '#specified?' do
+ it 'is not defined' do
+ expect(null.specified?).to eq false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb
new file mode 100644
index 00000000000..fb9ec70762a
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Stage do
+ let(:stage) { described_class.new(config) }
+
+ describe 'validations' do
+ context 'when stage config value is correct' do
+ let(:config) { 'build' }
+
+ describe '#value' do
+ it 'returns a stage key' do
+ expect(stage.value).to eq config
+ end
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(stage).to be_valid
+ end
+ end
+ end
+
+ context 'when value has a wrong type' do
+ let(:config) { { test: true } }
+
+ it 'reports errors about wrong type' do
+ expect(stage.errors)
+ .to include 'stage config should be a string'
+ end
+ end
+ end
+
+ describe '.default' do
+ it 'returns default stage' do
+ expect(described_class.default).to eq 'test'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/trigger_spec.rb b/spec/lib/gitlab/ci/config/node/trigger_spec.rb
new file mode 100644
index 00000000000..a4a3e36754e
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/trigger_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Trigger do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validations' do
+ context 'when entry config value is valid' do
+ context 'when config is a branch or tag name' do
+ let(:config) { %w[master feature/branch] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns key value' do
+ expect(entry.value).to eq config
+ end
+ end
+ end
+
+ context 'when config is a regexp' do
+ let(:config) { ['/^issue-.*$/'] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when config is a special keyword' do
+ let(:config) { %w[tags triggers branches] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+ end
+
+ context 'when entry value is not valid' do
+ let(:config) { [1] }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors)
+ .to include 'trigger config should be an array of strings or regexps'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 0c6608d906d..2d43e1c1a9d 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -2,39 +2,31 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Undefined do
let(:undefined) { described_class.new(entry) }
- let(:entry) { Class.new }
-
- describe '#leaf?' do
- it 'is leaf node' do
- expect(undefined).to be_leaf
- end
- end
+ let(:entry) { spy('Entry') }
describe '#valid?' do
- it 'is always valid' do
- expect(undefined).to be_valid
+ it 'delegates method to entry' do
+ expect(undefined.valid).to eq entry
end
end
describe '#errors' do
- it 'is does not contain errors' do
- expect(undefined.errors).to be_empty
+ it 'delegates method to entry' do
+ expect(undefined.errors).to eq entry
end
end
describe '#value' do
- before do
- allow(entry).to receive(:default).and_return('some value')
- end
-
- it 'returns default value for entry' do
- expect(undefined.value).to eq 'some value'
+ it 'delegates method to entry' do
+ expect(undefined.value).to eq entry
end
end
- describe '#undefined?' do
- it 'is not a defined entry' do
- expect(undefined.defined?).to be false
+ describe '#specified?' do
+ it 'is always false' do
+ allow(entry).to receive(:specified?).and_return(true)
+
+ expect(undefined.specified?).to be false
end
end
end
diff --git a/spec/lib/gitlab/ci/config/node/validatable_spec.rb b/spec/lib/gitlab/ci/config/node/validatable_spec.rb
index 10cd01afcd1..64b77fd6e03 100644
--- a/spec/lib/gitlab/ci/config/node/validatable_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/validatable_spec.rb
@@ -23,6 +23,10 @@ describe Gitlab::Ci::Config::Node::Validatable do
.to be Gitlab::Ci::Config::Node::Validator
end
+ it 'returns only one validator to mitigate leaks' do
+ expect { node.validator }.not_to change { node.validator }
+ end
+
context 'when validating node instance' do
let(:node_instance) { node.new }
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index e9b8ce6b5bb..de3f64249a2 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -3,10 +3,12 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor, lib: true do
let(:project) { create(:project) }
let(:project2) { create(:project) }
+ let(:forked_project) { Projects::ForkService.new(project, project.creator).execute }
let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) }
+ let(:fork_cross_reference) { issue.to_reference(forked_project) }
subject { described_class.new(project, project.creator) }
@@ -278,6 +280,15 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
end
end
+ context "with a cross-project fork reference" do
+ subject { described_class.new(forked_project, forked_project.creator) }
+
+ it do
+ message = "Closes #{fork_cross_reference}"
+ expect(subject.closed_by_message(message)).to be_empty
+ end
+ end
+
context "with an invalid URL" do
it do
message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index e883a6eb9c2..0650cb291e5 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Diff::File, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
- let(:diff) { commit.diffs.first }
+ let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
describe '#diff_lines' do
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 88e4115c453..1c2ddeed692 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
- let(:diff) { commit.diffs.first }
+ let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
describe '#highlight' do
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 4e50e03bb7e..4b943fa382d 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Diff::LineMapper, lib: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
- let(:diffs) { commit.diffs }
+ let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index 2aa5ae44f54..af18d3c25a6 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:commit) { project.commit(sample_commit.id) }
- let(:diffs) { commit.diffs }
+ let(:diffs) { commit.raw_diffs }
let(:diff) { diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index c3359627652..b983d73f8be 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Diff::Parser, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
- let(:diff) { commit.diffs.first }
+ let(:diff) { commit.raw_diffs.first }
let(:parser) { Gitlab::Diff::Parser.new }
describe '#parse' do
diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb
new file mode 100644
index 00000000000..19298e261e3
--- /dev/null
+++ b/spec/lib/gitlab/email/email_shared_blocks.rb
@@ -0,0 +1,41 @@
+require 'gitlab/email/receiver'
+
+shared_context :email_shared_context do
+ let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
+ let(:receiver) { Gitlab::Email::Receiver.new(email_raw) }
+ let(:markdown) { "![image](uploads/image.png)" }
+
+ def setup_attachment
+ allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
+ [
+ {
+ url: "uploads/image.png",
+ alt: "image",
+ markdown: markdown
+ }
+ ]
+ )
+ end
+end
+
+shared_examples :email_shared_examples do
+ context "when the user could not be found" do
+ before do
+ user.destroy
+ end
+
+ it "raises a UserNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+ end
+ end
+
+ context "when the user is not authorized to the project" do
+ before do
+ project.update_attribute(:visibility_level, Project::PRIVATE)
+ end
+
+ it "raises a ProjectNotFound" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
new file mode 100644
index 00000000000..e1153154778
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do
+ include_context :email_shared_context
+ it_behaves_like :email_shared_examples
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { fixture_file('emails/valid_new_issue.eml') }
+ let(:namespace) { create(:namespace, path: 'gitlabhq') }
+
+ let!(:project) { create(:project, :public, namespace: namespace) }
+ let!(:user) do
+ create(
+ :user,
+ email: 'jake@adventuretime.ooo',
+ authentication_token: 'auth_token'
+ )
+ end
+
+ context "when everything is fine" do
+ it "creates a new issue" do
+ setup_attachment
+
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to include('reply by email')
+ expect(issue.description).to include(markdown)
+ end
+
+ context "when the reply is blank" do
+ let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") }
+
+ it "creates a new issue" do
+ expect { receiver.execute }.to change { project.issues.count }.by(1)
+ issue = project.issues.last
+
+ expect(issue.author).to eq(user)
+ expect(issue.title).to eq('New Issue by email')
+ expect(issue.description).to eq('')
+ end
+ end
+ end
+
+ context "something is wrong" do
+ context "when the issue could not be saved" do
+ before do
+ allow_any_instance_of(Issue).to receive(:persisted?).and_return(false)
+ end
+
+ it "raises an InvalidIssueError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError)
+ end
+ end
+
+ context "when we can't find the authentication_token" do
+ let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") }
+
+ it "raises an UserNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError)
+ end
+ end
+
+ context "when project is private" do
+ let(:project) { create(:project, :private, namespace: namespace) }
+
+ it "raises a ProjectNotFound if the user is not a member" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
new file mode 100644
index 00000000000..a2119b0dadf
--- /dev/null
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -0,0 +1,116 @@
+require 'spec_helper'
+require_relative '../email_shared_blocks'
+
+describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do
+ include_context :email_shared_context
+ it_behaves_like :email_shared_examples
+
+ before do
+ stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
+ stub_config_setting(host: 'localhost')
+ end
+
+ let(:email_raw) { fixture_file('emails/valid_reply.eml') }
+ let(:project) { create(:project, :public) }
+ let(:noteable) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) }
+
+ context "when the recipient address doesn't include a mail key" do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") }
+
+ it "raises a UnknownIncomingEmail" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
+ end
+ end
+
+ context "when no sent notification for the mail key could be found" do
+ let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') }
+
+ it "raises a SentNotificationNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError)
+ end
+ end
+
+ context "when the email was auto generated" do
+ let!(:mail_key) { '636ca428858779856c226bb145ef4fad' }
+ let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
+
+ it "raises an AutoGeneratedEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError)
+ end
+ end
+
+ context "when the noteable could not be found" do
+ before do
+ noteable.destroy
+ end
+
+ it "raises a NoteableNotFoundError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError)
+ end
+ end
+
+ context "when the note could not be saved" do
+ before do
+ allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
+ end
+
+ it "raises an InvalidNoteError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
+ end
+ end
+
+ context "when the reply is blank" do
+ let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
+
+ it "raises an EmptyEmailError" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
+ end
+ end
+
+ context "when everything is fine" do
+ before do
+ setup_attachment
+ end
+
+ it "creates a comment" do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ note = noteable.notes.last
+
+ expect(note.author).to eq(sent_notification.recipient)
+ expect(note.note).to include("I could not disagree more.")
+ end
+
+ it "adds all attachments" do
+ receiver.execute
+
+ note = noteable.notes.last
+
+ expect(note.note).to include(markdown)
+ end
+
+ context 'when sub-addressing is not supported' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: nil)
+ end
+
+ shared_examples 'an email that contains a mail key' do |header|
+ it "fetches the mail key from the #{header} header and creates a comment" do
+ expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ note = noteable.notes.last
+
+ expect(note.author).to eq(sent_notification.recipient)
+ expect(note.note).to include('I could not disagree more.')
+ end
+ end
+
+ context 'mail key is in the References header' do
+ let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
+
+ it_behaves_like 'an email that contains a mail key', 'References'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index c19f33e2224..5b966bddb6a 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -16,9 +16,12 @@ describe Gitlab::Email::Message::RepositoryPush do
{ author_id: author.id, ref: 'master', action: :push, compare: compare,
send_from_committer_email: true }
end
- let(:compare) do
+ let(:raw_compare) do
Gitlab::Git::Compare.new(project.repository.raw_repository,
- sample_image_commit.id, sample_commit.id)
+ sample_image_commit.id, sample_commit.id)
+ end
+ let(:compare) do
+ Compare.decorate(raw_compare, project)
end
describe '#project' do
@@ -62,17 +65,17 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#diffs_count' do
subject { message.diffs_count }
- it { is_expected.to eq compare.diffs.count }
+ it { is_expected.to eq raw_compare.diffs.size }
end
describe '#compare' do
subject { message.compare }
- it { is_expected.to be_an_instance_of Gitlab::Git::Compare }
+ it { is_expected.to be_an_instance_of Compare }
end
describe '#compare_timeout' do
subject { message.compare_timeout }
- it { is_expected.to eq compare.diffs.overflow? }
+ it { is_expected.to eq raw_compare.diffs.overflow? }
end
describe '#reverse_compare?' do
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 84d2584a791..2a86b427806 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,34 +1,14 @@
-require "spec_helper"
+require 'spec_helper'
+require_relative 'email_shared_blocks'
describe Gitlab::Email::Receiver, lib: true do
- before do
- stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo")
- stub_config_setting(host: 'localhost')
- end
-
- let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
- let(:email_raw) { fixture_file('emails/valid_reply.eml') }
-
- let(:project) { create(:project, :public) }
- let(:noteable) { create(:issue, project: project) }
- let(:user) { create(:user) }
- let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) }
-
- let(:receiver) { described_class.new(email_raw) }
-
- context "when the recipient address doesn't include a reply key" do
- let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") }
-
- it "raises a SentNotificationNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
- end
- end
+ include_context :email_shared_context
- context "when no sent notificiation for the reply key could be found" do
- let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') }
+ context "when we cannot find a capable handler" do
+ let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") }
- it "raises a SentNotificationNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
+ it "raises a UnknownIncomingEmail" do
+ expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail)
end
end
@@ -36,128 +16,7 @@ describe Gitlab::Email::Receiver, lib: true do
let(:email_raw) { "" }
it "raises an EmptyEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
- end
- end
-
- context "when the email was auto generated" do
- let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
- let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
- it "raises an AutoGeneratedEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
- end
- end
-
- context "when the user could not be found" do
- before do
- user.destroy
- end
-
- it "raises a UserNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError)
- end
- end
-
- context "when the user has been blocked" do
- before do
- user.block
- end
-
- it "raises a UserBlockedError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError)
- end
- end
-
- context "when the user is not authorized to create a note" do
- before do
- project.update_attribute(:visibility_level, Project::PRIVATE)
- end
-
- it "raises a UserNotAuthorizedError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError)
- end
- end
-
- context "when the noteable could not be found" do
- before do
- noteable.destroy
- end
-
- it "raises a NoteableNotFoundError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError)
- end
- end
-
- context "when the reply is blank" do
- let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
- it "raises an EmptyEmailError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
- end
- end
-
- context "when the note could not be saved" do
- before do
- allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
- end
-
- it "raises an InvalidNoteError" do
- expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError)
- end
- end
-
- context "when everything is fine" do
- let(:markdown) { "![image](uploads/image.png)" }
-
- before do
- allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
- [
- {
- url: "uploads/image.png",
- alt: "image",
- markdown: markdown
- }
- ]
- )
- end
-
- it "creates a comment" do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
-
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include("I could not disagree more.")
- end
-
- it "adds all attachments" do
- receiver.execute
-
- note = noteable.notes.last
-
- expect(note.note).to include(markdown)
- end
-
- context 'when sub-addressing is not supported' do
- before do
- stub_incoming_email_setting(enabled: true, address: nil)
- end
-
- shared_examples 'an email that contains a reply key' do |header|
- it "fetches the reply key from the #{header} header and creates a comment" do
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
- note = noteable.notes.last
-
- expect(note.author).to eq(sent_notification.recipient)
- expect(note.note).to include('I could not disagree more.')
- end
- end
-
- context 'reply key is in the References header' do
- let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') }
-
- it_behaves_like 'an email that contains a reply key', 'References'
- end
+ expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
end
end
end
diff --git a/spec/lib/gitlab/git/hook_spec.rb b/spec/lib/gitlab/git/hook_spec.rb
index a15aa173fbd..d1f947b6850 100644
--- a/spec/lib/gitlab/git/hook_spec.rb
+++ b/spec/lib/gitlab/git/hook_spec.rb
@@ -25,7 +25,6 @@ describe Gitlab::Git::Hook, lib: true do
end
['pre-receive', 'post-receive', 'update'].each do |hook_name|
-
context "when triggering a #{hook_name} hook" do
context "when the hook is successful" do
it "returns success with no errors" do
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index ae064a878b0..f12c9a370f7 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -19,11 +19,11 @@ describe Gitlab::GitAccess, lib: true do
end
it 'blocks ssh git push' do
- expect(@acc.check('git-receive-pack').allowed?).to be_falsey
+ expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
end
it 'blocks ssh git pull' do
- expect(@acc.check('git-upload-pack').allowed?).to be_falsey
+ expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
end
end
@@ -34,17 +34,17 @@ describe Gitlab::GitAccess, lib: true do
end
it 'blocks http push' do
- expect(@acc.check('git-receive-pack').allowed?).to be_falsey
+ expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey
end
it 'blocks http git pull' do
- expect(@acc.check('git-upload-pack').allowed?).to be_falsey
+ expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey
end
end
end
describe 'download_access_check' do
- subject { access.check('git-upload-pack') }
+ subject { access.check('git-upload-pack', '_any') }
describe 'master permissions' do
before { project.team << [user, :master] }
@@ -151,7 +151,13 @@ describe Gitlab::GitAccess, lib: true do
def self.run_permission_checks(permissions_matrix)
permissions_matrix.keys.each do |role|
describe "#{role} access" do
- before { project.team << [user, role] }
+ before do
+ if role == :admin
+ user.update_attribute(:admin, true)
+ else
+ project.team << [user, role]
+ end
+ end
permissions_matrix[role].each do |action, allowed|
context action do
@@ -165,6 +171,17 @@ describe Gitlab::GitAccess, lib: true do
end
permissions_matrix = {
+ admin: {
+ push_new_branch: true,
+ push_master: true,
+ push_protected_branch: true,
+ push_remove_protected_branch: false,
+ push_tag: true,
+ push_new_tag: true,
+ push_all: true,
+ merge_into_protected_branch: true
+ },
+
master: {
push_new_branch: true,
push_master: true,
@@ -217,19 +234,20 @@ describe Gitlab::GitAccess, lib: true do
run_permission_checks(permissions_matrix)
end
- context "when 'developers can push' is turned on for the #{protected_branch_type} protected branch" do
- before { create(:protected_branch, name: protected_branch_name, developers_can_push: true, project: project) }
+ context "when developers are allowed to push into the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) }
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end
- context "when 'developers can merge' is turned on for the #{protected_branch_type} protected branch" do
- before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, project: project) }
+ context "developers are allowed to merge into the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) }
context "when a merge request exists for the given source/target branch" do
context "when the merge request is in progress" do
before do
- create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
+ create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature',
+ state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
end
run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true }))
@@ -242,51 +260,59 @@ describe Gitlab::GitAccess, lib: true do
run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
end
- end
- context "when a merge request does not exist for the given source/target branch" do
- run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+ context "when a merge request does not exist for the given source/target branch" do
+ run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+ end
end
end
- context "when 'developers can merge' and 'developers can push' are turned on for the #{protected_branch_type} protected branch" do
- before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, developers_can_push: true, project: project) }
+ context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) }
run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
end
+
+ context "when no one is allowed to push to the #{protected_branch_name} protected branch" do
+ before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) }
+
+ run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
+ master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false },
+ admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }))
+ end
end
+ end
- describe 'deploy key permissions' do
- let(:key) { create(:deploy_key) }
- let(:actor) { key }
+ describe 'deploy key permissions' do
+ let(:key) { create(:deploy_key) }
+ let(:actor) { key }
- context 'push code' do
- subject { access.check('git-receive-pack') }
+ context 'push code' do
+ subject { access.check('git-receive-pack', '_any') }
- context 'when project is authorized' do
- before { key.projects << project }
+ context 'when project is authorized' do
+ before { key.projects << project }
- it { expect(subject).not_to be_allowed }
- end
+ it { expect(subject).not_to be_allowed }
+ end
- context 'when unauthorized' do
- context 'to public project' do
- let(:project) { create(:project, :public) }
+ context 'when unauthorized' do
+ context 'to public project' do
+ let(:project) { create(:project, :public) }
- it { expect(subject).not_to be_allowed }
- end
+ it { expect(subject).not_to be_allowed }
+ end
- context 'to internal project' do
- let(:project) { create(:project, :internal) }
+ context 'to internal project' do
+ let(:project) { create(:project, :internal) }
- it { expect(subject).not_to be_allowed }
- end
+ it { expect(subject).not_to be_allowed }
+ end
- context 'to private project' do
- let(:project) { create(:project, :internal) }
+ context 'to private project' do
+ let(:project) { create(:project, :internal) }
- it { expect(subject).not_to be_allowed }
- end
+ it { expect(subject).not_to be_allowed }
end
end
end
diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
index fc9d5204148..e5300dbba1e 100644
--- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb
@@ -32,20 +32,6 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do
end
end
- describe '#name' do
- it 'returns raw ref when branch exists' do
- branch = described_class.new(project, double(raw))
-
- expect(branch.name).to eq 'feature'
- end
-
- it 'returns formatted ref when branch does not exist' do
- branch = described_class.new(project, double(raw.merge(ref: 'removed-branch', sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b')))
-
- expect(branch.name).to eq 'removed-branch-2e5d3239'
- end
- end
-
describe '#repo' do
it 'returns raw repo' do
branch = described_class.new(project, double(raw))
diff --git a/spec/lib/gitlab/github_import/hook_formatter_spec.rb b/spec/lib/gitlab/github_import/hook_formatter_spec.rb
deleted file mode 100644
index 110ba428258..00000000000
--- a/spec/lib/gitlab/github_import/hook_formatter_spec.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GithubImport::HookFormatter, lib: true do
- describe '#id' do
- it 'returns raw id' do
- raw = double(id: 100000)
- formatter = described_class.new(raw)
- expect(formatter.id).to eq 100000
- end
- end
-
- describe '#name' do
- it 'returns raw id' do
- raw = double(name: 'web')
- formatter = described_class.new(raw)
- expect(formatter.name).to eq 'web'
- end
- end
-
- describe '#config' do
- it 'returns raw config.attrs' do
- raw = double(config: double(attrs: { url: 'http://something.com/webhook' }))
- formatter = described_class.new(raw)
- expect(formatter.config).to eq({ url: 'http://something.com/webhook' })
- end
- end
-
- describe '#valid?' do
- it 'returns true when events contains the wildcard event' do
- raw = double(events: ['*', 'commit_comment'], active: true)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq true
- end
-
- it 'returns true when events contains the create event' do
- raw = double(events: ['create', 'commit_comment'], active: true)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq true
- end
-
- it 'returns true when events contains delete event' do
- raw = double(events: ['delete', 'commit_comment'], active: true)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq true
- end
-
- it 'returns true when events contains pull_request event' do
- raw = double(events: ['pull_request', 'commit_comment'], active: true)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq true
- end
-
- it 'returns false when events does not contains branch related events' do
- raw = double(events: ['member', 'commit_comment'], active: true)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq false
- end
-
- it 'returns false when hook is not active' do
- raw = double(events: ['pull_request', 'commit_comment'], active: false)
- formatter = described_class.new(raw)
- expect(formatter.valid?).to eq false
- end
- end
-end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 79931ecd134..aa28e360993 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -9,6 +9,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:source_branch) { double(ref: 'feature', repo: source_repo, sha: source_sha) }
let(:target_repo) { repository }
let(:target_branch) { double(ref: 'master', repo: target_repo, sha: target_sha) }
+ let(:removed_branch) { double(ref: 'removed-branch', repo: source_repo, sha: '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b') }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
@@ -165,6 +166,42 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
+ describe '#source_branch_name' do
+ context 'when source branch exists' do
+ let(:raw_data) { double(base_data) }
+
+ it 'returns branch ref' do
+ expect(pull_request.source_branch_name).to eq 'feature'
+ end
+ end
+
+ context 'when source branch does not exist' do
+ let(:raw_data) { double(base_data.merge(head: removed_branch)) }
+
+ it 'prefixes branch name with pull request number' do
+ expect(pull_request.source_branch_name).to eq 'pull/1347/removed-branch'
+ end
+ end
+ end
+
+ describe '#target_branch_name' do
+ context 'when source branch exists' do
+ let(:raw_data) { double(base_data) }
+
+ it 'returns branch ref' do
+ expect(pull_request.target_branch_name).to eq 'master'
+ end
+ end
+
+ context 'when target branch does not exist' do
+ let(:raw_data) { double(base_data.merge(base: removed_branch)) }
+
+ it 'prefixes branch name with pull request number' do
+ expect(pull_request.target_branch_name).to eq 'pull/1347/removed-branch'
+ end
+ end
+ end
+
describe '#valid?' do
context 'when source, and target repos are not a fork' do
let(:raw_data) { double(base_data) }
@@ -178,8 +215,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:source_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
- it 'returns false' do
- expect(pull_request.valid?).to eq false
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
end
end
@@ -187,8 +224,8 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
let(:target_repo) { double(id: 2) }
let(:raw_data) { double(base_data) }
- it 'returns false' do
- expect(pull_request.valid?).to eq false
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
end
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 364532e94e3..fc021416d92 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -17,6 +17,18 @@ describe Gitlab::Highlight, lib: true do
expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
end
+
+ describe 'with CRLF' do
+ let(:branch) { 'crlf-diff' }
+ let(:blob) { repository.blob_at_branch(branch, path) }
+ let(:lines) do
+ Gitlab::Highlight.highlight_lines(project.repository, 'crlf-diff', 'files/whitespace')
+ end
+
+ it 'strips extra LFs' do
+ expect(lines[0]).to eq("<span id=\"LC1\" class=\"line\">test </span>")
+ end
+ end
end
describe 'custom highlighting from .gitattributes' do
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 6d5aa0d04a2..770e8b0c2f4 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -26,6 +26,20 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
"email" => user2.email,
"username" => user2.username
}
+ },
+ {
+ "id" => 3,
+ "access_level" => 40,
+ "source_id" => 14,
+ "source_type" => "Project",
+ "user_id" => nil,
+ "notification_level" => 3,
+ "created_at" => "2016-03-11T10:21:44.822Z",
+ "updated_at" => "2016-03-11T10:21:44.822Z",
+ "created_by_id" => 1,
+ "invite_email" => 'invite@test.com',
+ "invite_token" => 'token',
+ "invite_accepted_at" => nil
}]
end
@@ -47,5 +61,11 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
expect(members_mapper.missing_author_ids.first).to eq(-1)
end
+
+ it 'has invited members with no user' do
+ members_mapper.map
+
+ expect(ProjectMember.find_by_invite_email('invite@test.com')).not_to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index b1a5d72c624..cbbf98dca94 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -18,7 +18,6 @@
"position": 0,
"branch_name": null,
"description": "Aliquam enim illo et possimus.",
- "milestone_id": 18,
"state": "opened",
"iid": 10,
"updated_by_id": null,
@@ -27,6 +26,52 @@
"due_date": null,
"moved_to_id": null,
"test_ee_field": "test",
+ "milestone": {
+ "id": 1,
+ "title": "v0.0",
+ "project_id": 8,
+ "description": "test milestone",
+ "due_date": null,
+ "created_at": "2016-06-14T15:02:04.415Z",
+ "updated_at": "2016-06-14T15:02:04.415Z",
+ "state": "active",
+ "iid": 1,
+ "events": [
+ {
+ "id": 487,
+ "target_type": "Milestone",
+ "target_id": 1,
+ "title": null,
+ "data": null,
+ "project_id": 46,
+ "created_at": "2016-06-14T15:02:04.418Z",
+ "updated_at": "2016-06-14T15:02:04.418Z",
+ "action": 1,
+ "author_id": 18
+ }
+ ]
+ },
+ "label_links": [
+ {
+ "id": 2,
+ "label_id": 2,
+ "target_id": 3,
+ "target_type": "Issue",
+ "created_at": "2016-07-22T08:57:02.840Z",
+ "updated_at": "2016-07-22T08:57:02.840Z",
+ "label": {
+ "id": 2,
+ "title": "test2",
+ "color": "#428bca",
+ "project_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "priority": null
+ }
+ }
+ ],
"notes": [
{
"id": 351,
@@ -233,7 +278,6 @@
"position": 0,
"branch_name": null,
"description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.",
- "milestone_id": 16,
"state": "opened",
"iid": 9,
"updated_by_id": null,
@@ -447,7 +491,6 @@
"position": 0,
"branch_name": null,
"description": "Ea recusandae neque autem tempora.",
- "milestone_id": 16,
"state": "closed",
"iid": 8,
"updated_by_id": null,
@@ -661,7 +704,6 @@
"position": 0,
"branch_name": null,
"description": "Maiores architecto quos in dolorem.",
- "milestone_id": 17,
"state": "opened",
"iid": 7,
"updated_by_id": null,
@@ -875,7 +917,6 @@
"position": 0,
"branch_name": null,
"description": "Ut aut ut et tenetur velit aut id modi.",
- "milestone_id": 16,
"state": "opened",
"iid": 6,
"updated_by_id": null,
@@ -1089,7 +1130,6 @@
"position": 0,
"branch_name": null,
"description": "Dicta nisi nihil non ipsa velit.",
- "milestone_id": 20,
"state": "closed",
"iid": 5,
"updated_by_id": null,
@@ -1303,7 +1343,6 @@
"position": 0,
"branch_name": null,
"description": "Ut et explicabo vel voluptatem consequuntur ut sed.",
- "milestone_id": 19,
"state": "closed",
"iid": 4,
"updated_by_id": null,
@@ -1517,7 +1556,6 @@
"position": 0,
"branch_name": null,
"description": "Non asperiores velit accusantium voluptate.",
- "milestone_id": 18,
"state": "closed",
"iid": 3,
"updated_by_id": null,
@@ -1731,7 +1769,6 @@
"position": 0,
"branch_name": null,
"description": "Molestiae corporis magnam et fugit aliquid nulla quia.",
- "milestone_id": 17,
"state": "closed",
"iid": 2,
"updated_by_id": null,
@@ -1945,7 +1982,6 @@
"position": 0,
"branch_name": null,
"description": "Quod ad architecto qui est sed quia.",
- "milestone_id": 20,
"state": "closed",
"iid": 1,
"updated_by_id": null,
@@ -2259,117 +2295,6 @@
"author_id": 25
}
]
- },
- {
- "id": 18,
- "title": "v2.0",
- "project_id": 5,
- "description": "Error dolorem rerum aut nulla.",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.576Z",
- "updated_at": "2016-06-14T15:02:04.576Z",
- "state": "active",
- "iid": 3,
- "events": [
- {
- "id": 242,
- "target_type": "Milestone",
- "target_id": 18,
- "title": null,
- "data": null,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:04.579Z",
- "updated_at": "2016-06-14T15:02:04.579Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 58,
- "target_type": "Milestone",
- "target_id": 18,
- "title": null,
- "data": null,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:04.579Z",
- "updated_at": "2016-06-14T15:02:04.579Z",
- "action": 1,
- "author_id": 22
- }
- ]
- },
- {
- "id": 17,
- "title": "v1.0",
- "project_id": 5,
- "description": "Molestiae perspiciatis voluptates doloremque commodi veniam consequatur.",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.569Z",
- "updated_at": "2016-06-14T15:02:04.569Z",
- "state": "active",
- "iid": 2,
- "events": [
- {
- "id": 243,
- "target_type": "Milestone",
- "target_id": 17,
- "title": null,
- "data": null,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:04.570Z",
- "updated_at": "2016-06-14T15:02:04.570Z",
- "action": 1,
- "author_id": 1
- },
- {
- "id": 57,
- "target_type": "Milestone",
- "target_id": 17,
- "title": null,
- "data": null,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:04.570Z",
- "updated_at": "2016-06-14T15:02:04.570Z",
- "action": 1,
- "author_id": 20
- }
- ]
- },
- {
- "id": 16,
- "title": "v0.0",
- "project_id": 5,
- "description": "Velit numquam et sed sit.",
- "due_date": null,
- "created_at": "2016-06-14T15:02:04.561Z",
- "updated_at": "2016-06-14T15:02:04.561Z",
- "state": "closed",
- "iid": 1,
- "events": [
- {
- "id": 244,
- "target_type": "Milestone",
- "target_id": 16,
- "title": null,
- "data": null,
- "project_id": 36,
- "created_at": "2016-06-14T15:02:04.563Z",
- "updated_at": "2016-06-14T15:02:04.563Z",
- "action": 1,
- "author_id": 26
- },
- {
- "id": 56,
- "target_type": "Milestone",
- "target_id": 16,
- "title": null,
- "data": null,
- "project_id": 5,
- "created_at": "2016-06-14T15:02:04.563Z",
- "updated_at": "2016-06-14T15:02:04.563Z",
- "action": 1,
- "author_id": 26
- }
- ]
}
],
"snippets": [
@@ -2468,10 +2393,9 @@
"source_project_id": 5,
"author_id": 1,
"assignee_id": null,
- "title": "Cannot be automatically merged",
+ "title": "MR1",
"created_at": "2016-06-14T15:02:36.568Z",
"updated_at": "2016-06-14T15:02:56.815Z",
- "milestone_id": null,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -2903,13 +2827,12 @@
"id": 26,
"target_branch": "master",
"source_branch": "feature",
- "source_project_id": 5,
+ "source_project_id": 4,
"author_id": 1,
"assignee_id": null,
- "title": "Can be automatically merged",
+ "title": "MR2",
"created_at": "2016-06-14T15:02:36.418Z",
"updated_at": "2016-06-14T15:02:57.013Z",
- "milestone_id": null,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -3194,7 +3117,6 @@
"title": "Qui accusantium et inventore facilis doloribus occaecati officiis.",
"created_at": "2016-06-14T15:02:25.168Z",
"updated_at": "2016-06-14T15:02:59.521Z",
- "milestone_id": 17,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -3479,7 +3401,6 @@
"title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.",
"created_at": "2016-06-14T15:02:24.760Z",
"updated_at": "2016-06-14T15:02:59.749Z",
- "milestone_id": 20,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -4170,7 +4091,6 @@
"title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.",
"created_at": "2016-06-14T15:02:24.415Z",
"updated_at": "2016-06-14T15:02:59.958Z",
- "milestone_id": 17,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -4719,7 +4639,6 @@
"title": "In a rerum harum nihil accusamus aut quia nobis non.",
"created_at": "2016-06-14T15:02:24.000Z",
"updated_at": "2016-06-14T15:03:00.225Z",
- "milestone_id": 19,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -5219,7 +5138,6 @@
"title": "Corporis provident similique perspiciatis dolores eos animi.",
"created_at": "2016-06-14T15:02:23.767Z",
"updated_at": "2016-06-14T15:03:00.475Z",
- "milestone_id": 18,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -5480,7 +5398,6 @@
"title": "Eligendi reprehenderit doloribus quia et sit id.",
"created_at": "2016-06-14T15:02:23.014Z",
"updated_at": "2016-06-14T15:03:00.685Z",
- "milestone_id": 20,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
@@ -6171,7 +6088,6 @@
"title": "Et ipsam voluptas velit sequi illum ut.",
"created_at": "2016-06-14T15:02:22.825Z",
"updated_at": "2016-06-14T15:03:00.904Z",
- "milestone_id": 16,
"state": "opened",
"merge_status": "unchecked",
"target_project_id": 5,
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 6ae20c943b1..4d857945fde 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
describe 'restore project tree' do
-
let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
@@ -60,6 +59,40 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
end
+
+ it 'has labels associated to label links, associated to issues' do
+ restored_project_json
+
+ expect(Label.first.label_links.first.target).not_to be_nil
+ end
+
+ it 'has milestones associated to issues' do
+ restored_project_json
+
+ expect(Milestone.find_by_description('test milestone').issues).not_to be_empty
+ end
+
+ context 'Merge requests' do
+ before do
+ restored_project_json
+ end
+
+ it 'always has the new project as a target' do
+ expect(MergeRequest.find_by_title('MR1').target_project).to eq(project)
+ end
+
+ it 'has the same source project as originally if source/target are the same' do
+ expect(MergeRequest.find_by_title('MR1').source_project).to eq(project)
+ end
+
+ it 'has the new project as target if source/target differ' do
+ expect(MergeRequest.find_by_title('MR2').target_project).to eq(project)
+ end
+
+ it 'has no source if source/target differ' do
+ expect(MergeRequest.find_by_title('MR2').source_project_id).to eq(-1)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 057ef6e76a0..3a86a4ce07c 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -31,10 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json).to include({ "visibility_level" => 20 })
end
- it 'has events' do
- expect(saved_project_json['milestones'].first['events']).not_to be_empty
- end
-
it 'has milestones' do
expect(saved_project_json['milestones']).not_to be_empty
end
@@ -43,8 +39,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['merge_requests']).not_to be_empty
end
- it 'has labels' do
- expect(saved_project_json['labels']).not_to be_empty
+ it 'has merge request\'s milestones' do
+ expect(saved_project_json['merge_requests'].first['milestone']).not_to be_empty
+ end
+
+ it 'has events' do
+ expect(saved_project_json['merge_requests'].first['milestone']['events']).not_to be_empty
end
it 'has snippets' do
@@ -103,6 +103,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
end
+ it 'has labels with no associations' do
+ expect(saved_project_json['labels']).not_to be_empty
+ end
+
+ it 'has labels associated to records' do
+ expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
+ end
+
it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
@@ -113,19 +121,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
def setup_project
issue = create(:issue, assignee: user)
- label = create(:label)
snippet = create(:project_snippet)
release = create(:release)
project = create(:project,
:public,
issues: [issue],
- labels: [label],
snippets: [snippet],
releases: [release]
)
-
- merge_request = create(:merge_request, source_project: project)
+ label = create(:label, project: project)
+ create(:label_link, label: label, target: issue)
+ milestone = create(:milestone, project: project)
+ merge_request = create(:merge_request, source_project: project, milestone: milestone)
commit_status = create(:commit_status, project: project)
ci_pipeline = create(:ci_pipeline,
@@ -135,7 +143,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
statuses: [commit_status])
create(:ci_build, pipeline: ci_pipeline, project: project)
- milestone = create(:milestone, project: project)
+ create(:milestone, project: project)
create(:note, noteable: issue, project: project)
create(:note, noteable: merge_request, project: project)
create(:note, noteable: snippet, project: project)
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
new file mode 100644
index 00000000000..90c6d1c67f6
--- /dev/null
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::VersionChecker, services: true do
+ describe 'bundle a project Git repo' do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+ let(:version) { Gitlab::ImportExport.version }
+
+ before do
+ allow(File).to receive(:open).and_return(version)
+ end
+
+ it 'returns true if Import/Export have the same version' do
+ expect(described_class.check!(shared: shared)).to be true
+ end
+
+ context 'newer version' do
+ let(:version) { '900.0'}
+
+ it 'returns false if export version is newer' do
+ expect(described_class.check!(shared: shared)).to be false
+ end
+
+ it 'shows the correct error message' do
+ described_class.check!(shared: shared)
+
+ expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}")
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb
index afb3e26f8fb..1dcf2c0668b 100644
--- a/spec/lib/gitlab/incoming_email_spec.rb
+++ b/spec/lib/gitlab/incoming_email_spec.rb
@@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do
end
end
- context 'self.key_from_fallback_reply_message_id' do
+ context 'self.key_from_fallback_message_id' do
it 'returns reply key' do
- expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key')
+ expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key')
end
end
end
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index acd5394382c..534bcbf39fe 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -64,7 +64,7 @@ describe Gitlab::LDAP::Access, lib: true do
user.ldap_block
end
- it 'should unblock user in GitLab' do
+ it 'unblocks user in GitLab' do
access.allowed?
expect(user).not_to be_blocked
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 949f6e2b19a..89790c9e1af 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::LDAP::User, lib: true do
expect(ldap_user.changed?).to be_truthy
end
- it "dont marks existing ldap user as changed" do
+ it "does not mark existing ldap user as changed" do
create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', ldap_email: true)
expect(ldap_user.changed?).to be_falsey
end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 8809b7e3f12..d88bcae41fb 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do
allow(@dummy).to receive(:name).and_return('Dummy')
end
+ describe '.series' do
+ it 'returns a String' do
+ expect(described_class.series).to be_an_instance_of(String)
+ end
+ end
+
describe '.configure' do
it 'yields self' do
described_class.configure do |c|
@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:measure_method).
- with('Dummy.foo')
+ expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
@dummy.foo
end
@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
- expect(transaction).to receive(:measure_method).
- with('Dummy#bar')
+ expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
@dummy.new.bar
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index cf0e282c2fb..9e2ea89a712 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do
end
describe '.cpu_time' do
- it 'returns a Float' do
- expect(described_class.cpu_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end
end
describe '.real_time' do
- it 'returns a Float' do
- expect(described_class.real_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.real_time).to be_an_instance_of(Fixnum)
end
end
describe '.monotonic_time' do
- it 'returns a Float' do
- expect(described_class.monotonic_time).to be_an_instance_of(Float)
+ it 'returns a Fixnum' do
+ expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 3b1c67a2147..f1a191d9410 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do
end
end
- describe '#measure_method' do
- it 'adds a new method if it does not exist already' do
- transaction.measure_method('Foo#bar') { 'foo' }
+ describe '#method_call_for' do
+ it 'returns a MethodCall' do
+ method = transaction.method_call_for('Foo#bar')
- expect(transaction.methods['Foo#bar']).
- to be_an_instance_of(Gitlab::Metrics::MethodCall)
- end
-
- it 'adds timings to an existing method call' do
- transaction.measure_method('Foo#bar') { 'foo' }
- transaction.measure_method('Foo#bar') { 'foo' }
-
- expect(transaction.methods['Foo#bar'].call_count).to eq(2)
+ expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
end
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 96f7eabbca6..84f9475a0f8 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -147,4 +147,10 @@ describe Gitlab::Metrics do
end
end
end
+
+ describe '#series_prefix' do
+ it 'returns a String' do
+ expect(described_class.series_prefix).to be_an_instance_of(String)
+ end
+ end
end
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 1fca8a13037..78c669e8fa5 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::OAuth::User, lib: true do
describe 'signup' do
shared_examples 'to verify compliance with allow_single_sign_on' do
context 'provider is marked as external' do
- it 'should mark user as external' do
+ it 'marks user as external' do
stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['twitter'])
oauth_user.save
expect(gl_user).to be_valid
@@ -51,7 +51,7 @@ describe Gitlab::OAuth::User, lib: true do
end
context 'provider was external, now has been removed' do
- it 'should not mark external user as internal' do
+ it 'does not mark external user as internal' do
create(:omniauth_user, extern_uid: 'my-uid', provider: 'twitter', external: true)
stub_omniauth_config(allow_single_sign_on: ['twitter'], external_providers: ['facebook'])
oauth_user.save
@@ -62,7 +62,7 @@ describe Gitlab::OAuth::User, lib: true do
context 'provider is not external' do
context 'when adding a new OAuth identity' do
- it 'should not promote an external user to internal' do
+ it 'does not promote an external user to internal' do
user = create(:user, email: 'john@mail.com', external: true)
user.identities.create(provider: provider, extern_uid: uid)
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 270b89972d7..29abb4d4d07 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
- it 'should not list project confidential issues for non project members' do
+ it 'does not list project confidential issues for non project members' do
results = described_class.new(non_member, project, query)
issues = results.objects('issues')
@@ -43,7 +43,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 1
end
- it 'should not list project confidential issues for project members with guest role' do
+ it 'does not list project confidential issues for project members with guest role' do
project.team << [member, :guest]
results = described_class.new(member, project, query)
@@ -55,7 +55,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 1
end
- it 'should list project confidential issues for author' do
+ it 'lists project confidential issues for author' do
results = described_class.new(author, project, query)
issues = results.objects('issues')
@@ -65,7 +65,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 2
end
- it 'should list project confidential issues for assignee' do
+ it 'lists project confidential issues for assignee' do
results = described_class.new(assignee, project.id, query)
issues = results.objects('issues')
@@ -75,7 +75,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 2
end
- it 'should list project confidential issues for project members' do
+ it 'lists project confidential issues for project members' do
project.team << [member, :developer]
results = described_class.new(member, project, query)
@@ -87,7 +87,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
expect(results.issues_count).to eq 3
end
- it 'should list all project issues for admin' do
+ it 'lists all project issues for admin' do
results = described_class.new(admin, project, query)
issues = results.objects('issues')
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
new file mode 100644
index 00000000000..e54f5ffb312
--- /dev/null
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -0,0 +1,79 @@
+require 'spec_helper'
+
+describe Gitlab::Redis do
+ let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
+
+ before(:each) { described_class.reset_params! }
+ after(:each) { described_class.reset_params! }
+
+ describe '.params' do
+ subject { described_class.params }
+
+ context 'when url contains unix socket reference' do
+ let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_socket.yml').to_s }
+ let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_socket.yml').to_s }
+
+ context 'with old format' do
+ it 'returns path key instead' do
+ expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+
+ is_expected.to include(path: '/path/to/old/redis.sock')
+ is_expected.not_to have_key(:url)
+ end
+ end
+
+ context 'with new format' do
+ it 'returns path key instead' do
+ expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+
+ is_expected.to include(path: '/path/to/redis.sock')
+ is_expected.not_to have_key(:url)
+ end
+ end
+ end
+
+ context 'when url is host based' do
+ let(:config_old) { Rails.root.join('spec/fixtures/config/redis_old_format_host.yml') }
+ let(:config_new) { Rails.root.join('spec/fixtures/config/redis_new_format_host.yml') }
+
+ context 'with old format' do
+ it 'returns hash with host, port, db, and password' do
+ expect_any_instance_of(described_class).to receive(:config_file) { config_old }
+
+ is_expected.to include(host: 'localhost', password: 'mypassword', port: 6379, db: 99)
+ is_expected.not_to have_key(:url)
+ end
+ end
+
+ context 'with new format' do
+ it 'returns hash with host, port, db, and password' do
+ expect_any_instance_of(described_class).to receive(:config_file) { config_new }
+
+ is_expected.to include(host: 'localhost', password: 'mynewpassword', port: 6379, db: 99)
+ is_expected.not_to have_key(:url)
+ end
+ end
+ end
+ end
+
+ describe '#raw_config_hash' do
+ it 'returns default redis url when no config file is present' do
+ expect(subject).to receive(:fetch_config) { false }
+
+ expect(subject.send(:raw_config_hash)).to eq(url: Gitlab::Redis::DEFAULT_REDIS_URL)
+ end
+
+ it 'returns old-style single url config in a hash' do
+ expect(subject).to receive(:fetch_config) { 'redis://myredis:6379' }
+ expect(subject.send(:raw_config_hash)).to eq(url: 'redis://myredis:6379')
+ end
+ end
+
+ describe '#fetch_config' do
+ it 'returns false when no config file is present' do
+ allow(File).to receive(:exist?).with(redis_config) { false }
+
+ expect(subject.send(:fetch_config)).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/saml/user_spec.rb b/spec/lib/gitlab/saml/user_spec.rb
index 56bf08e7041..02c139f1a0d 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -67,7 +67,7 @@ describe Gitlab::Saml::User, lib: true do
end
context 'user was external, now should not be' do
- it 'should make user internal' do
+ it 'makes user internal' do
existing_user.update_attribute('external', true)
saml_user.save
expect(gl_user).to be_valid
@@ -94,14 +94,14 @@ describe Gitlab::Saml::User, lib: true do
context 'with allow_single_sign_on default (["saml"])' do
before { stub_omniauth_config(allow_single_sign_on: ['saml']) }
- it 'should not throw an error' do
+ it 'does not throw an error' do
expect{ saml_user.save }.not_to raise_error
end
end
context 'with allow_single_sign_on disabled' do
before { stub_omniauth_config(allow_single_sign_on: false) }
- it 'should throw an error' do
+ it 'throws an error' do
expect{ saml_user.save }.to raise_error StandardError
end
end
@@ -223,7 +223,7 @@ describe Gitlab::Saml::User, lib: true do
context 'dont block on create' do
before { stub_omniauth_config(block_auto_created_users: false) }
- it 'should not block the user' do
+ it 'does not block the user' do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
@@ -233,7 +233,7 @@ describe Gitlab::Saml::User, lib: true do
context 'block on create' do
before { stub_omniauth_config(block_auto_created_users: true) }
- it 'should block user' do
+ it 'blocks user' do
saml_user.save
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 1bb444bf34f..8a656ab0ee9 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -73,7 +73,7 @@ describe Gitlab::SearchResults do
let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
- it 'should not list confidential issues for non project members' do
+ it 'does not list confidential issues for non project members' do
results = described_class.new(non_member, limit_projects, query)
issues = results.objects('issues')
@@ -86,7 +86,7 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 1
end
- it 'should not list confidential issues for project members with guest role' do
+ it 'does not list confidential issues for project members with guest role' do
project_1.team << [member, :guest]
project_2.team << [member, :guest]
@@ -102,7 +102,7 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 1
end
- it 'should list confidential issues for author' do
+ it 'lists confidential issues for author' do
results = described_class.new(author, limit_projects, query)
issues = results.objects('issues')
@@ -115,7 +115,7 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 3
end
- it 'should list confidential issues for assignee' do
+ it 'lists confidential issues for assignee' do
results = described_class.new(assignee, limit_projects, query)
issues = results.objects('issues')
@@ -128,7 +128,7 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 3
end
- it 'should list confidential issues for project members' do
+ it 'lists confidential issues for project members' do
project_1.team << [member, :developer]
project_2.team << [member, :developer]
@@ -144,7 +144,7 @@ describe Gitlab::SearchResults do
expect(results.issues_count).to eq 4
end
- it 'should list all issues for admin' do
+ it 'lists all issues for admin' do
results = described_class.new(admin, limit_projects, query)
issues = results.objects('issues')
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index e958e087a80..edadab043d7 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -9,19 +9,19 @@ describe Gitlab::Upgrader, lib: true do
end
describe 'latest_version?' do
- it 'should be true if newest version' do
+ it 'is true if newest version' do
allow(upgrader).to receive(:latest_version_raw).and_return(current_version)
expect(upgrader.latest_version?).to be_truthy
end
end
describe 'latest_version_raw' do
- it 'should be latest version for GitLab 5' do
+ it 'is the latest version for GitLab 5' do
allow(upgrader).to receive(:current_version_raw).and_return("5.3.0")
expect(upgrader.latest_version_raw).to eq("v5.4.2")
end
- it 'should get the latest version from tags' do
+ it 'gets the latest version from tags' do
allow(upgrader).to receive(:fetch_git_tags).and_return([
'6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1',
'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}',
diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index aa9ec243498..d3c3b800b94 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -9,80 +9,130 @@ describe Gitlab::UserAccess, lib: true do
describe 'push to none protected branch' do
it 'returns true if user is a master' do
project.team << [user, :master]
+
expect(access.can_push_to_branch?('random_branch')).to be_truthy
end
it 'returns true if user is a developer' do
project.team << [user, :developer]
+
expect(access.can_push_to_branch?('random_branch')).to be_truthy
end
it 'returns false if user is a reporter' do
project.team << [user, :reporter]
+
expect(access.can_push_to_branch?('random_branch')).to be_falsey
end
end
+ describe 'push to empty project' do
+ let(:empty_project) { create(:project_empty_repo) }
+ let(:project_access) { Gitlab::UserAccess.new(user, project: empty_project) }
+
+ it 'returns true if user is master' do
+ empty_project.team << [user, :master]
+
+ expect(project_access.can_push_to_branch?('master')).to be_truthy
+ end
+
+ it 'returns false if user is developer and project is fully protected' do
+ empty_project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
+
+ expect(project_access.can_push_to_branch?('master')).to be_falsey
+ end
+
+ it 'returns false if user is developer and it is not allowed to push new commits but can merge into branch' do
+ empty_project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(project_access.can_push_to_branch?('master')).to be_falsey
+ end
+
+ it 'returns true if user is developer and project is unprotected' do
+ empty_project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+
+ expect(project_access.can_push_to_branch?('master')).to be_truthy
+ end
+
+ it 'returns true if user is developer and project grants developers permission' do
+ empty_project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(project_access.can_push_to_branch?('master')).to be_truthy
+ end
+ end
+
describe 'push to protected branch' do
let(:branch) { create :protected_branch, project: project }
it 'returns true if user is a master' do
project.team << [user, :master]
+
expect(access.can_push_to_branch?(branch.name)).to be_truthy
end
it 'returns false if user is a developer' do
project.team << [user, :developer]
+
expect(access.can_push_to_branch?(branch.name)).to be_falsey
end
it 'returns false if user is a reporter' do
project.team << [user, :reporter]
+
expect(access.can_push_to_branch?(branch.name)).to be_falsey
end
end
describe 'push to protected branch if allowed for developers' do
before do
- @branch = create :protected_branch, project: project, developers_can_push: true
+ @branch = create :protected_branch, :developers_can_push, project: project
end
it 'returns true if user is a master' do
project.team << [user, :master]
+
expect(access.can_push_to_branch?(@branch.name)).to be_truthy
end
it 'returns true if user is a developer' do
project.team << [user, :developer]
+
expect(access.can_push_to_branch?(@branch.name)).to be_truthy
end
it 'returns false if user is a reporter' do
project.team << [user, :reporter]
+
expect(access.can_push_to_branch?(@branch.name)).to be_falsey
end
end
describe 'merge to protected branch if allowed for developers' do
before do
- @branch = create :protected_branch, project: project, developers_can_merge: true
+ @branch = create :protected_branch, :developers_can_merge, project: project
end
it 'returns true if user is a master' do
project.team << [user, :master]
+
expect(access.can_merge_to_branch?(@branch.name)).to be_truthy
end
it 'returns true if user is a developer' do
project.team << [user, :developer]
+
expect(access.can_merge_to_branch?(@branch.name)).to be_truthy
end
it 'returns false if user is a reporter' do
project.team << [user, :reporter]
+
expect(access.can_merge_to_branch?(@branch.name)).to be_falsey
end
end
-
end
end
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index c6758ccad39..781472d0c00 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -48,7 +48,7 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like 'a user cannot unsubscribe through footer link'
- it 'should not contain the new user\'s password' do
+ it 'does not contain the new user\'s password' do
is_expected.not_to have_body_text /password/
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 3685b2b17b5..fa241867858 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -591,7 +591,7 @@ describe Notify do
is_expected.to have_body_text /#{note.note}/
end
- it 'not contains note author' do
+ it 'does not contain note author' do
is_expected.not_to have_body_text /wrote\:/
end
@@ -944,8 +944,9 @@ describe Notify do
describe 'email on push with multiple commits' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
- let(:commits) { Commit.decorate(compare.commits, nil) }
+ let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) }
+ let(:compare) { Compare.decorate(raw_compare, project) }
+ let(:commits) { compare.commits }
let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
let(:send_from_committer_email) { false }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
@@ -1046,8 +1047,9 @@ describe Notify do
describe 'email on push with a single commit' do
let(:example_site_path) { root_path }
let(:user) { create(:user) }
- let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
- let(:commits) { Commit.decorate(compare.commits, nil) }
+ let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) }
+ let(:compare) { Compare.decorate(raw_compare, project) }
+ let(:commits) { compare.commits }
let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) }
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index 1acb5846fcf..853f6943cef 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,6 +1,62 @@
require 'spec_helper'
describe Ability, lib: true do
+ describe '.can_edit_note?' do
+ let(:project) { create(:empty_project) }
+ let!(:note) { create(:note_on_issue, project: project) }
+
+ context 'using an anonymous user' do
+ it 'returns false' do
+ expect(described_class.can_edit_note?(nil, note)).to be_falsy
+ end
+ end
+
+ context 'using a system note' do
+ it 'returns false' do
+ system_note = create(:note, system: true)
+ user = create(:user)
+
+ expect(described_class.can_edit_note?(user, system_note)).to be_falsy
+ end
+ end
+
+ context 'using users with different access levels' do
+ let(:user) { create(:user) }
+
+ it 'returns true for the author' do
+ expect(described_class.can_edit_note?(note.author, note)).to be_truthy
+ end
+
+ it 'returns false for a guest user' do
+ project.team << [user, :guest]
+
+ expect(described_class.can_edit_note?(user, note)).to be_falsy
+ end
+
+ it 'returns false for a developer' do
+ project.team << [user, :developer]
+
+ expect(described_class.can_edit_note?(user, note)).to be_falsy
+ end
+
+ it 'returns true for a master' do
+ project.team << [user, :master]
+
+ expect(described_class.can_edit_note?(user, note)).to be_truthy
+ end
+
+ it 'returns true for a group owner' do
+ group = create(:group)
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::MASTER)
+ group.add_owner(user)
+
+ expect(described_class.can_edit_note?(user, note)).to be_truthy
+ end
+ end
+ end
+
describe '.users_that_can_read_project' do
context 'using a public project' do
it 'returns all the users' do
@@ -114,4 +170,52 @@ describe Ability, lib: true do
end
end
end
+
+ describe '.issues_readable_by_user' do
+ context 'with an admin user' do
+ it 'returns all given issues' do
+ user = build(:user, admin: true)
+ issue = build(:issue)
+
+ expect(described_class.issues_readable_by_user([issue], user)).
+ to eq([issue])
+ end
+ end
+
+ context 'with a regular user' do
+ it 'returns the issues readable by the user' do
+ user = build(:user)
+ issue = build(:issue)
+
+ expect(issue).to receive(:readable_by?).with(user).and_return(true)
+
+ expect(described_class.issues_readable_by_user([issue], user)).
+ to eq([issue])
+ end
+
+ it 'returns an empty Array when no issues are readable' do
+ user = build(:user)
+ issue = build(:issue)
+
+ expect(issue).to receive(:readable_by?).with(user).and_return(false)
+
+ expect(described_class.issues_readable_by_user([issue], user)).to eq([])
+ end
+ end
+
+ context 'without a regular user' do
+ it 'returns issues that are publicly visible' do
+ hidden_issue = build(:issue)
+ visible_issue = build(:issue)
+
+ expect(hidden_issue).to receive(:publicly_visible?).and_return(false)
+ expect(visible_issue).to receive(:publicly_visible?).and_return(true)
+
+ issues = described_class.
+ issues_readable_by_user([hidden_issue, visible_issue])
+
+ expect(issues).to eq([visible_issue])
+ end
+ end
+ end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index fb040ba82bc..cc215d252f9 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -53,59 +53,59 @@ describe ApplicationSetting, models: true do
end
context 'restricted signup domains' do
- it 'set single domain' do
+ it 'sets single domain' do
setting.domain_whitelist_raw = 'example.com'
expect(setting.domain_whitelist).to eq(['example.com'])
end
- it 'set multiple domains with spaces' do
+ it 'sets multiple domains with spaces' do
setting.domain_whitelist_raw = 'example.com *.example.com'
expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
end
- it 'set multiple domains with newlines and a space' do
+ it 'sets multiple domains with newlines and a space' do
setting.domain_whitelist_raw = "example.com\n *.example.com"
expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
end
- it 'set multiple domains with commas' do
+ it 'sets multiple domains with commas' do
setting.domain_whitelist_raw = "example.com, *.example.com"
expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
end
end
context 'blacklisted signup domains' do
- it 'set single domain' do
+ it 'sets single domain' do
setting.domain_blacklist_raw = 'example.com'
expect(setting.domain_blacklist).to contain_exactly('example.com')
end
- it 'set multiple domains with spaces' do
+ it 'sets multiple domains with spaces' do
setting.domain_blacklist_raw = 'example.com *.example.com'
expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
end
- it 'set multiple domains with newlines and a space' do
+ it 'sets multiple domains with newlines and a space' do
setting.domain_blacklist_raw = "example.com\n *.example.com"
expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
end
- it 'set multiple domains with commas' do
+ it 'sets multiple domains with commas' do
setting.domain_blacklist_raw = "example.com, *.example.com"
expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
end
- it 'set multiple domains with semicolon' do
+ it 'sets multiple domains with semicolon' do
setting.domain_blacklist_raw = "example.com; *.example.com"
expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
end
- it 'set multiple domains with mixture of everything' do
+ it 'sets multiple domains with mixture of everything' do
setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
end
- it 'set multiple domain with file' do
+ it 'sets multiple domain with file' do
setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
end
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 78e95c8fac5..cee20234e1f 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -33,6 +33,22 @@ describe Blob do
end
end
+ describe '#video?' do
+ it 'is falsey with image extension' do
+ git_blob = Gitlab::Git::Blob.new(name: 'image.png')
+
+ expect(described_class.decorate(git_blob)).not_to be_video
+ end
+
+ UploaderHelper::VIDEO_EXT.each do |ext|
+ it "is truthy when extension is .#{ext}" do
+ git_blob = Gitlab::Git::Blob.new(name: "video.#{ext}")
+
+ expect(described_class.decorate(git_blob)).to be_video
+ end
+ end
+ end
+
describe '#to_partial_path' do
def stubbed_blob(overrides = {})
overrides.reverse_merge!(
@@ -78,4 +94,26 @@ describe Blob do
expect(blob.to_partial_path).to eq 'download'
end
end
+
+ describe '#size_within_svg_limits?' do
+ let(:blob) { described_class.decorate(double(:blob)) }
+
+ it 'returns true when the blob size is smaller than the SVG limit' do
+ expect(blob).to receive(:size).and_return(42)
+
+ expect(blob.size_within_svg_limits?).to eq(true)
+ end
+
+ it 'returns true when the blob size is equal to the SVG limit' do
+ expect(blob).to receive(:size).and_return(Blob::MAXIMUM_SVG_SIZE)
+
+ expect(blob.size_within_svg_limits?).to eq(true)
+ end
+
+ it 'returns false when the blob size is larger than the SVG limit' do
+ expect(blob).to receive(:size).and_return(1.terabyte)
+
+ expect(blob.size_within_svg_limits?).to eq(false)
+ end
+ end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 6ad8bfef4f2..72688137f08 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -23,19 +23,19 @@ describe BroadcastMessage, models: true do
end
describe '.current' do
- it "should return last message if time match" do
+ it "returns last message if time match" do
message = create(:broadcast_message)
expect(BroadcastMessage.current).to eq message
end
- it "should return nil if time not come" do
+ it "returns nil if time not come" do
create(:broadcast_message, :future)
expect(BroadcastMessage.current).to be_nil
end
- it "should return nil if time has passed" do
+ it "returns nil if time has passed" do
create(:broadcast_message, :expired)
expect(BroadcastMessage.current).to be_nil
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 978ad9c52d5..5980f6ddc32 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -32,7 +32,7 @@ describe Ci::Build, models: true do
end
let(:create_from_build) { Ci::Build.create_from build }
- it 'there should be a pending task' do
+ it 'exists a pending task' do
expect(Ci::Build.pending.count(:all)).to eq 0
create_from_build
expect(Ci::Build.pending.count(:all)).to be > 0
@@ -259,7 +259,7 @@ describe Ci::Build, models: true do
let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
let(:user_trigger_variable) do
- { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
+ { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
end
let(:predefined_trigger_variable) do
{ key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
@@ -573,19 +573,19 @@ describe Ci::Build, models: true do
let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
- it 'to have no dependents if this is first build' do
+ it 'expects to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
end
- it 'to have one dependent if this is test' do
+ it 'expects to have one dependent if this is test' do
expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
end
- it 'to have all builds from build and test stage if this is last' do
+ it 'expects to have all builds from build and test stage if this is last' do
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
end
- it 'to have retried builds instead the original ones' do
+ it 'expects to have retried builds instead the original ones' do
retried_rspec = Ci::Build.retry(rspec_test)
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
@@ -655,23 +655,23 @@ describe Ci::Build, models: true do
describe 'build erasable' do
shared_examples 'erasable' do
- it 'should remove artifact file' do
+ it 'removes artifact file' do
expect(build.artifacts_file.exists?).to be_falsy
end
- it 'should remove artifact metadata file' do
+ it 'removes artifact metadata file' do
expect(build.artifacts_metadata.exists?).to be_falsy
end
- it 'should erase build trace in trace file' do
+ it 'erases build trace in trace file' do
expect(build.trace).to be_empty
end
- it 'should set erased to true' do
+ it 'sets erased to true' do
expect(build.erased?).to be true
end
- it 'should set erase date' do
+ it 'sets erase date' do
expect(build.erased_at).not_to be_falsy
end
end
@@ -704,7 +704,7 @@ describe Ci::Build, models: true do
include_examples 'erasable'
- it 'should record user who erased a build' do
+ it 'records user who erased a build' do
expect(build.erased_by).to eq user
end
end
@@ -714,7 +714,7 @@ describe Ci::Build, models: true do
include_examples 'erasable'
- it 'should not set user who erased a build' do
+ it 'does not set user who erased a build' do
expect(build.erased_by).to be_nil
end
end
@@ -750,7 +750,7 @@ describe Ci::Build, models: true do
end
describe '#erase' do
- it 'should not raise error' do
+ it 'does not raise error' do
expect { build.erase }.not_to raise_error
end
end
@@ -764,6 +764,53 @@ describe Ci::Build, models: true do
end
end
+ describe '#when' do
+ subject { build.when }
+
+ context 'if is undefined' do
+ before do
+ build.when = nil
+ end
+
+ context 'use from gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ context 'if config is not found' do
+ let(:config) { nil }
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'if config does not have a questioned job' do
+ let(:config) do
+ YAML.dump({
+ test_other: {
+ script: 'Hello World'
+ }
+ })
+ end
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'if config has when' do
+ let(:config) do
+ YAML.dump({
+ test: {
+ script: 'Hello World',
+ when: 'always'
+ }
+ })
+ end
+
+ it { is_expected.to eq('always') }
+ end
+ end
+ end
+ end
+
describe '#retryable?' do
context 'when build is running' do
before do
@@ -839,8 +886,10 @@ describe Ci::Build, models: true do
is_expected.to eq(build)
end
- context 'for success build' do
- before { build.queue }
+ context 'for successful build' do
+ before do
+ build.update(status: 'success')
+ end
it 'creates a new build' do
is_expected.to be_pending
@@ -900,7 +949,7 @@ describe Ci::Build, models: true do
context 'when build is running' do
before { build.run! }
- it 'should return false' do
+ it 'returns false' do
expect(build.retryable?).to be false
end
end
@@ -908,7 +957,7 @@ describe Ci::Build, models: true do
context 'when build is finished' do
before { build.success! }
- it 'should return true' do
+ it 'returns true' do
expect(build.retryable?).to be true
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 0d4c86955ce..950833cb219 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Ci::Pipeline, models: true do
let(:project) { FactoryGirl.create :empty_project }
- let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
+ let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project }
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
@@ -38,9 +38,6 @@ describe Ci::Pipeline, models: true do
it { expect(pipeline.sha).to start_with(subject) }
end
- describe '#create_next_builds' do
- end
-
describe '#retried' do
subject { pipeline.retried }
@@ -54,312 +51,9 @@ describe Ci::Pipeline, models: true do
end
end
- describe '#create_builds' do
- let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project, ref: 'master', tag: false }
-
- def create_builds(trigger_request = nil)
- pipeline.create_builds(nil, trigger_request)
- end
-
- def create_next_builds
- pipeline.create_next_builds(pipeline.builds.order(:id).last)
- end
-
- it 'creates builds' do
- expect(create_builds).to be_truthy
- pipeline.builds.update_all(status: "success")
- expect(pipeline.builds.count(:all)).to eq(2)
-
- expect(create_next_builds).to be_truthy
- pipeline.builds.update_all(status: "success")
- expect(pipeline.builds.count(:all)).to eq(4)
-
- expect(create_next_builds).to be_truthy
- pipeline.builds.update_all(status: "success")
- expect(pipeline.builds.count(:all)).to eq(5)
-
- expect(create_next_builds).to be_falsey
- end
-
- context 'custom stage with first job allowed to fail' do
- let(:yaml) do
- {
- stages: ['clean', 'test'],
- clean_job: {
- stage: 'clean',
- allow_failure: true,
- script: 'BUILD',
- },
- test_job: {
- stage: 'test',
- script: 'TEST',
- },
- }
- end
-
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(yaml))
- create_builds
- end
-
- it 'properly schedules builds' do
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:drop)
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending', 'failed')
- end
- end
-
- context 'properly creates builds when "when" is defined' do
- let(:yaml) do
- {
- stages: ["build", "test", "test_failure", "deploy", "cleanup"],
- build: {
- stage: "build",
- script: "BUILD",
- },
- test: {
- stage: "test",
- script: "TEST",
- },
- test_failure: {
- stage: "test_failure",
- script: "ON test failure",
- when: "on_failure",
- },
- deploy: {
- stage: "deploy",
- script: "PUBLISH",
- },
- cleanup: {
- stage: "cleanup",
- script: "TIDY UP",
- when: "always",
- }
- }
- end
-
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(yaml))
- end
-
- context 'when builds are successful' do
- it 'properly creates builds' do
- expect(create_builds).to be_truthy
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
- pipeline.reload
- expect(pipeline.status).to eq('success')
- end
- end
-
- context 'when test job fails' do
- it 'properly creates builds' do
- expect(create_builds).to be_truthy
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
- pipeline.builds.running_or_pending.each(&:drop)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
- pipeline.reload
- expect(pipeline.status).to eq('failed')
- end
- end
-
- context 'when test and test_failure jobs fail' do
- it 'properly creates builds' do
- expect(create_builds).to be_truthy
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
- pipeline.builds.running_or_pending.each(&:drop)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
- pipeline.builds.running_or_pending.each(&:drop)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
- pipeline.reload
- expect(pipeline.status).to eq('failed')
- end
- end
-
- context 'when deploy job fails' do
- it 'properly creates builds' do
- expect(create_builds).to be_truthy
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
- pipeline.builds.running_or_pending.each(&:drop)
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
- pipeline.reload
- expect(pipeline.status).to eq('failed')
- end
- end
-
- context 'when build is canceled in the second stage' do
- it 'does not schedule builds after build has been canceled' do
- expect(create_builds).to be_truthy
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
- pipeline.builds.running_or_pending.each(&:success)
-
- expect(pipeline.builds.running_or_pending).not_to be_empty
-
- expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
- expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
- pipeline.builds.running_or_pending.each(&:cancel)
-
- expect(pipeline.builds.running_or_pending).to be_empty
- expect(pipeline.reload.status).to eq('canceled')
- end
- end
-
- context 'when listing manual actions' do
- let(:yaml) do
- {
- stages: ["build", "test", "staging", "production", "cleanup"],
- build: {
- stage: "build",
- script: "BUILD",
- },
- test: {
- stage: "test",
- script: "TEST",
- },
- staging: {
- stage: "staging",
- script: "PUBLISH",
- },
- production: {
- stage: "production",
- script: "PUBLISH",
- when: "manual",
- },
- cleanup: {
- stage: "cleanup",
- script: "TIDY UP",
- when: "always",
- },
- clear_cache: {
- stage: "cleanup",
- script: "CLEAR CACHE",
- when: "manual",
- }
- }
- end
-
- it 'returns only for skipped builds' do
- # currently all builds are created
- expect(create_builds).to be_truthy
- expect(manual_actions).to be_empty
-
- # succeed stage build
- pipeline.builds.running_or_pending.each(&:success)
- expect(manual_actions).to be_empty
-
- # succeed stage test
- pipeline.builds.running_or_pending.each(&:success)
- expect(manual_actions).to be_empty
-
- # succeed stage staging and skip stage production
- pipeline.builds.running_or_pending.each(&:success)
- expect(manual_actions).to be_many # production and clear cache
-
- # succeed stage cleanup
- pipeline.builds.running_or_pending.each(&:success)
-
- # after processing a pipeline we should have 6 builds, 5 succeeded
- expect(pipeline.builds.count).to eq(6)
- expect(pipeline.builds.success.count).to eq(4)
- end
-
- def manual_actions
- pipeline.manual_actions
- end
- end
- end
-
- context 'when no builds created' do
- let(:pipeline) { build(:ci_pipeline) }
-
- before do
- stub_ci_pipeline_yaml_file(YAML.dump(before_script: ['ls']))
- end
-
- it 'returns false' do
- expect(pipeline.create_builds(nil)).to be_falsey
- expect(pipeline).not_to be_persisted
- end
- end
- end
-
- describe "#finished_at" do
- let(:pipeline) { FactoryGirl.create :ci_pipeline }
-
- it "returns finished_at of latest build" do
- build = FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 60
- FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 120
-
- expect(pipeline.finished_at.to_i).to eq(build.finished_at.to_i)
- end
-
- it "returns nil if there is no finished build" do
- FactoryGirl.create :ci_not_started_build, pipeline: pipeline
-
- expect(pipeline.finished_at).to be_nil
- end
- end
-
describe "coverage" do
let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
- let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
+ let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project }
it "calculates average when there are two builds with coverage" do
FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
@@ -426,33 +120,47 @@ describe Ci::Pipeline, models: true do
end
end
- describe '#update_state' do
- it 'execute update_state after touching object' do
- expect(pipeline).to receive(:update_state).and_return(true)
- pipeline.touch
+ describe 'state machine' do
+ let(:current) { Time.now.change(usec: 0) }
+ let(:build) { create :ci_build, name: 'build1', pipeline: pipeline, started_at: current - 60, finished_at: current }
+ let(:build2) { create :ci_build, name: 'build2', pipeline: pipeline, started_at: current - 60, finished_at: current }
+
+ describe '#duration' do
+ before do
+ build.skip
+ build2.skip
+ end
+
+ it 'matches sum of builds duration' do
+ expect(pipeline.reload.duration).to eq(build.duration + build2.duration)
+ end
end
- context 'dependent objects' do
- let(:commit_status) { build :commit_status, pipeline: pipeline }
+ describe '#started_at' do
+ it 'updates on transitioning to running' do
+ build.run
+
+ expect(pipeline.reload.started_at).not_to be_nil
+ end
+
+ it 'does not update on transitioning to success' do
+ build.success
- it 'execute update_state after saving dependent object' do
- expect(pipeline).to receive(:update_state).and_return(true)
- commit_status.save
+ expect(pipeline.reload.started_at).to be_nil
end
end
- context 'update state' do
- let(:current) { Time.now.change(usec: 0) }
- let(:build) { FactoryGirl.create :ci_build, :success, pipeline: pipeline, started_at: current - 120, finished_at: current - 60 }
+ describe '#finished_at' do
+ it 'updates on transitioning to success' do
+ build.success
- before do
- build
+ expect(pipeline.reload.finished_at).not_to be_nil
end
- [:status, :started_at, :finished_at, :duration].each do |param|
- it "update #{param}" do
- expect(pipeline.send(param)).to eq(build.send(param))
- end
+ it 'does not update on transitioning to running' do
+ build.run
+
+ expect(pipeline.reload.finished_at).to be_nil
end
end
end
@@ -513,7 +221,7 @@ describe Ci::Pipeline, models: true do
create :ci_build, :success, pipeline: pipeline, name: 'rspec'
create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
end
-
+
it 'returns true' do
is_expected.to be_truthy
end
@@ -524,7 +232,7 @@ describe Ci::Pipeline, models: true do
create :ci_build, :success, pipeline: pipeline, name: 'rspec'
create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
end
-
+
it 'returns false' do
is_expected.to be_falsey
end
@@ -542,4 +250,64 @@ describe Ci::Pipeline, models: true do
end
end
end
+
+ describe '#status' do
+ let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }
+
+ subject { pipeline.reload.status }
+
+ context 'on queuing' do
+ before do
+ build.enqueue
+ end
+
+ it { is_expected.to eq('pending') }
+ end
+
+ context 'on run' do
+ before do
+ build.enqueue
+ build.run
+ end
+
+ it { is_expected.to eq('running') }
+ end
+
+ context 'on drop' do
+ before do
+ build.drop
+ end
+
+ it { is_expected.to eq('failed') }
+ end
+
+ context 'on success' do
+ before do
+ build.success
+ end
+
+ it { is_expected.to eq('success') }
+ end
+
+ context 'on cancel' do
+ before do
+ build.cancel
+ end
+
+ it { is_expected.to eq('canceled') }
+ end
+
+ context 'on failure and build retry' do
+ before do
+ build.drop
+ Ci::Build.retry(build)
+ end
+
+ # We are changing a state: created > failed > running
+ # Instead of: created > failed > pending
+ # Since the pipeline already run, so it should not be pending anymore
+
+ it { is_expected.to eq('running') }
+ end
+ end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 474b0b1621d..3ca9231f58e 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -4,12 +4,12 @@ describe Ci::Trigger, models: true do
let(:project) { FactoryGirl.create :empty_project }
describe 'before_validation' do
- it 'should set an random token if none provided' do
+ it 'sets an random token if none provided' do
trigger = FactoryGirl.create :ci_trigger_without_token, project: project
expect(trigger.token).not_to be_nil
end
- it 'should not set an random token if one provided' do
+ it 'does not set an random token if one provided' do
trigger = FactoryGirl.create :ci_trigger, project: project
expect(trigger.token).to eq('token')
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ba02d5fe977..d3e6a6648cc 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -13,6 +13,26 @@ describe Commit, models: true do
it { is_expected.to include_module(StaticModel) }
end
+ describe '#author' do
+ it 'looks up the author in a case-insensitive way' do
+ user = create(:user, email: commit.author_email.upcase)
+ expect(commit.author).to eq(user)
+ end
+
+ it 'caches the author' do
+ user = create(:user, email: commit.author_email)
+ expect(RequestStore).to receive(:active?).twice.and_return(true)
+ expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original
+
+ expect(commit.author).to eq(user)
+ key = "commit_author:#{commit.author_email}"
+ expect(RequestStore.store[key]).to eq(user)
+
+ expect(commit.author).to eq(user)
+ RequestStore.store.clear
+ end
+ end
+
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id
@@ -66,6 +86,27 @@ eos
end
end
+ describe '#full_title' do
+ it "returns no_commit_message when safe_message is blank" do
+ allow(commit).to receive(:safe_message).and_return('')
+ expect(commit.full_title).to eq("--no commit message")
+ end
+
+ it "returns entire message if there is no newline" do
+ message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.'
+
+ allow(commit).to receive(:safe_message).and_return(message)
+ expect(commit.full_title).to eq(message)
+ end
+
+ it "returns first line of message if there is a newLine" do
+ message = commit.safe_message.split(" ").first
+
+ allow(commit).to receive(:safe_message).and_return(message + "\n" + message)
+ expect(commit.full_title).to eq(message)
+ end
+ end
+
describe "delegation" do
subject { commit }
@@ -212,6 +253,7 @@ eos
it 'returns the URI type at the given path' do
expect(commit.uri_type('files/html')).to be(:tree)
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
expect(commit.uri_type('files/js/application.js')).to be(:blob)
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index ff6371ad685..fcfa3138ce5 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -133,7 +133,7 @@ describe CommitStatus, models: true do
@commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'aa', ref: 'bb', status: 'success'
end
- it 'return unique statuses' do
+ it 'returns unique statuses' do
is_expected.to eq([@commit4, @commit5])
end
end
@@ -149,7 +149,7 @@ describe CommitStatus, models: true do
@commit5 = FactoryGirl.create :commit_status, pipeline: pipeline, name: 'ee', ref: nil, status: 'canceled'
end
- it 'return statuses that are running or pending' do
+ it 'returns statuses that are running or pending' do
is_expected.to eq([@commit1, @commit2])
end
end
@@ -160,7 +160,7 @@ describe CommitStatus, models: true do
context 'when no before_sha is set for pipeline' do
before { pipeline.before_sha = nil }
- it 'return blank sha' do
+ it 'returns blank sha' do
is_expected.to eq(Gitlab::Git::BLANK_SHA)
end
end
@@ -169,7 +169,7 @@ describe CommitStatus, models: true do
let(:value) { '1234' }
before { pipeline.before_sha = value }
- it 'return the set value' do
+ it 'returns the set value' do
is_expected.to eq(value)
end
end
@@ -186,7 +186,7 @@ describe CommitStatus, models: true do
context 'stages list' do
subject { CommitStatus.where(pipeline: pipeline).stages }
- it 'return ordered list of stages' do
+ it 'returns ordered list of stages' do
is_expected.to eq(%w(build test deploy))
end
end
@@ -194,7 +194,7 @@ describe CommitStatus, models: true do
context 'stages with statuses' do
subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
- it 'return list of stages with statuses' do
+ it 'returns list of stages with statuses' do
is_expected.to eq({
'build' => 'failed',
'test' => 'success',
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
new file mode 100644
index 00000000000..49ab3c4b6e9
--- /dev/null
+++ b/spec/models/compare_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Compare, models: true do
+ include RepoHelpers
+
+ let(:project) { create(:project, :public) }
+ let(:commit) { project.commit }
+
+ let(:start_commit) { sample_image_commit }
+ let(:head_commit) { sample_commit }
+
+ let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, start_commit.id, head_commit.id) }
+
+ subject { described_class.new(raw_compare, project) }
+
+ describe '#start_commit' do
+ it 'returns raw compare base commit' do
+ expect(subject.start_commit.id).to eq(start_commit.id)
+ end
+
+ it 'returns nil if compare base commit is nil' do
+ expect(raw_compare).to receive(:base).and_return(nil)
+
+ expect(subject.start_commit).to eq(nil)
+ end
+ end
+
+ describe '#commit' do
+ it 'returns raw compare head commit' do
+ expect(subject.commit.id).to eq(head_commit.id)
+ end
+
+ it 'returns nil if compare head commit is nil' do
+ expect(raw_compare).to receive(:head).and_return(nil)
+
+ expect(subject.commit).to eq(nil)
+ end
+ end
+
+ describe '#base_commit' do
+ let(:base_commit) { Commit.new(another_sample_commit, project) }
+
+ it 'returns project merge base commit' do
+ expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit)
+
+ expect(subject.base_commit).to eq(base_commit)
+ end
+
+ it 'returns nil if there is no start_commit' do
+ expect(subject).to receive(:start_commit).and_return(nil)
+
+ expect(subject.base_commit).to eq(nil)
+ end
+
+ it 'returns nil if there is no head commit' do
+ expect(subject).to receive(:head_commit).and_return(nil)
+
+ expect(subject.base_commit).to eq(nil)
+ end
+ end
+
+ describe '#diff_refs' do
+ it 'uses base_commit sha as base_sha' do
+ expect(subject).to receive(:base_commit).at_least(:once).and_call_original
+
+ expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id)
+ end
+
+ it 'uses start_commit sha as start_sha' do
+ expect(subject.diff_refs.start_sha).to eq(start_commit.id)
+ end
+
+ it 'uses commit sha as head sha' do
+ expect(subject.diff_refs.head_sha).to eq(head_commit.id)
+ end
+ end
+end
diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb
new file mode 100644
index 00000000000..8d3f94267fa
--- /dev/null
+++ b/spec/models/concerns/faster_cache_keys_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe FasterCacheKeys do
+ describe '#cache_key' do
+ it 'returns a String' do
+ # We're using a fixed string here so it's easier to set an expectation for
+ # the resulting cache key.
+ time = '2016-08-08 16:39:00+02'
+ issue = build(:issue, updated_at: time)
+ issue.extend(described_class)
+
+ expect(issue).to receive(:id).and_return(1)
+
+ expect(issue.cache_key).to eq("issues/1-#{time}")
+ end
+ end
+end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 5e652660e2c..549b0042038 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -68,7 +68,7 @@ describe Issue, "Mentionable" do
describe '#create_cross_references!' do
let(:project) { create(:project) }
- let(:author) { double('author') }
+ let(:author) { build(:user) }
let(:commit) { project.commit }
let(:commit2) { project.commit }
@@ -88,6 +88,10 @@ describe Issue, "Mentionable" do
let(:author) { create(:author) }
let(:issues) { create_list(:issue, 2, project: project, author: author) }
+ before do
+ project.team << [author, Gitlab::Access::DEVELOPER]
+ end
+
context 'before changes are persisted' do
it 'ignores pre-existing references' do
issue = create_issue(description: issues[0].to_reference)
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 7e9ab8940cf..b7e973798a3 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -26,53 +26,53 @@ describe Milestone, 'Milestoneish' do
end
describe '#closed_items_count' do
- it 'should not count confidential issues for non project members' do
+ it 'does not count confidential issues for non project members' do
expect(milestone.closed_items_count(non_member)).to eq 2
end
- it 'should not count confidential issues for project members with guest role' do
+ it 'does not count confidential issues for project members with guest role' do
expect(milestone.closed_items_count(guest)).to eq 2
end
- it 'should count confidential issues for author' do
+ it 'counts confidential issues for author' do
expect(milestone.closed_items_count(author)).to eq 4
end
- it 'should count confidential issues for assignee' do
+ it 'counts confidential issues for assignee' do
expect(milestone.closed_items_count(assignee)).to eq 4
end
- it 'should count confidential issues for project members' do
+ it 'counts confidential issues for project members' do
expect(milestone.closed_items_count(member)).to eq 6
end
- it 'should count all issues for admin' do
+ it 'counts all issues for admin' do
expect(milestone.closed_items_count(admin)).to eq 6
end
end
describe '#total_items_count' do
- it 'should not count confidential issues for non project members' do
+ it 'does not count confidential issues for non project members' do
expect(milestone.total_items_count(non_member)).to eq 4
end
- it 'should not count confidential issues for project members with guest role' do
+ it 'does not count confidential issues for project members with guest role' do
expect(milestone.total_items_count(guest)).to eq 4
end
- it 'should count confidential issues for author' do
+ it 'counts confidential issues for author' do
expect(milestone.total_items_count(author)).to eq 7
end
- it 'should count confidential issues for assignee' do
+ it 'counts confidential issues for assignee' do
expect(milestone.total_items_count(assignee)).to eq 7
end
- it 'should count confidential issues for project members' do
+ it 'counts confidential issues for project members' do
expect(milestone.total_items_count(member)).to eq 10
end
- it 'should count all issues for admin' do
+ it 'counts all issues for admin' do
expect(milestone.total_items_count(admin)).to eq 10
end
end
@@ -91,27 +91,27 @@ describe Milestone, 'Milestoneish' do
end
describe '#percent_complete' do
- it 'should not count confidential issues for non project members' do
+ it 'does not count confidential issues for non project members' do
expect(milestone.percent_complete(non_member)).to eq 50
end
- it 'should not count confidential issues for project members with guest role' do
+ it 'does not count confidential issues for project members with guest role' do
expect(milestone.percent_complete(guest)).to eq 50
end
- it 'should count confidential issues for author' do
+ it 'counts confidential issues for author' do
expect(milestone.percent_complete(author)).to eq 57
end
- it 'should count confidential issues for assignee' do
+ it 'counts confidential issues for assignee' do
expect(milestone.percent_complete(assignee)).to eq 57
end
- it 'should count confidential issues for project members' do
+ it 'counts confidential issues for project members' do
expect(milestone.percent_complete(member)).to eq 60
end
- it 'should count confidential issues for admin' do
+ it 'counts confidential issues for admin' do
expect(milestone.percent_complete(admin)).to eq 60
end
end
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
new file mode 100644
index 00000000000..32935bc0b09
--- /dev/null
+++ b/spec/models/concerns/spammable_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Issue, 'Spammable' do
+ let(:issue) { create(:issue, description: 'Test Desc.') }
+
+ describe 'Associations' do
+ it { is_expected.to have_one(:user_agent_detail).dependent(:destroy) }
+ end
+
+ describe 'ClassMethods' do
+ it 'should return correct attr_spammable' do
+ expect(issue.spammable_text).to eq("#{issue.title}\n#{issue.description}")
+ end
+ end
+
+ describe 'InstanceMethods' do
+ it 'should be invalid if spam' do
+ issue = build(:issue, spam: true)
+ expect(issue.valid?).to be_falsey
+ end
+
+ describe '#check_for_spam?' do
+ it 'returns true for public project' do
+ issue.project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ expect(issue.check_for_spam?).to eq(true)
+ end
+
+ it 'returns false for other visibility levels' do
+ expect(issue.check_for_spam?).to eq(false)
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 9e8ebc56a31..eb64f3d0c83 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -41,7 +41,7 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
describe 'ensured! token' do
subject { described_class.new.send("ensure_#{token_field}!") }
- it 'should persist new token' do
+ it 'persists new token' do
expect(subject).to eq described_class.current[token_field]
end
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 7df3df4bb9e..bfff639ad78 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -15,4 +15,28 @@ describe Deployment, models: true do
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
+
+ describe '#includes_commit?' do
+ let(:project) { create(:project) }
+ let(:environment) { create(:environment, project: project) }
+ let(:deployment) do
+ create(:deployment, environment: environment, sha: project.commit.id)
+ end
+
+ context 'when there is no project commit' do
+ it 'returns false' do
+ commit = project.commit('feature')
+
+ expect(deployment.includes_commit?(commit)).to be false
+ end
+ end
+
+ context 'when they share the same tree branch' do
+ it 'returns true' do
+ commit = project.commit
+
+ expect(deployment.includes_commit?(commit)).to be true
+ end
+ end
+ end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index af8e890ca95..1fa96eb1f15 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -119,7 +119,7 @@ describe DiffNote, models: true do
context "when the merge request's diff refs don't match that of the diff note" do
before do
- allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs)
+ allow(subject.noteable).to receive(:diff_sha_refs).and_return(commit.diff_refs)
end
it "returns false" do
@@ -168,7 +168,7 @@ describe DiffNote, models: true do
context "when the note is outdated" do
before do
- allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs)
+ allow(merge_request).to receive(:diff_sha_refs).and_return(commit.diff_refs)
end
it "uses the DiffPositionUpdateService" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 7629af6a570..c881897926e 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -11,4 +11,56 @@ describe Environment, models: true do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) }
it { is_expected.to validate_length_of(:name).is_within(0..255) }
+
+ it { is_expected.to validate_length_of(:external_url).is_within(0..255) }
+
+ # To circumvent a not null violation of the name column:
+ # https://github.com/thoughtbot/shoulda-matchers/issues/336
+ it 'validates uniqueness of :external_url' do
+ create(:environment)
+
+ is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id)
+ end
+
+ describe '#nullify_external_url' do
+ it 'replaces a blank url with nil' do
+ env = build(:environment, external_url: "")
+
+ expect(env.save).to be true
+ expect(env.external_url).to be_nil
+ end
+ end
+
+ describe '#includes_commit?' do
+ context 'without a last deployment' do
+ it "returns false" do
+ expect(environment.includes_commit?('HEAD')).to be false
+ end
+ end
+
+ context 'with a last deployment' do
+ let(:project) { create(:project) }
+ let(:environment) { create(:environment, project: project) }
+
+ let!(:deployment) do
+ create(:deployment, environment: environment, sha: project.commit('master').id)
+ end
+
+ context 'in the same branch' do
+ it 'returns true' do
+ expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be true
+ end
+ end
+
+ context 'not in the same branch' do
+ before do
+ deployment.update(sha: project.commit('feature').id)
+ end
+
+ it 'returns false' do
+ expect(environment.includes_commit?(RepoHelpers.sample_commit)).to be false
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index f94987dcaff..9c81d159cdf 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -9,11 +9,11 @@ describe ForkedProjectLink, "add link on fork" do
@project_to = fork_project(project_from, user)
end
- it "project_to should know it is forked" do
+ it "project_to knows it is forked" do
expect(@project_to.forked?).to be_truthy
end
- it "project should know who it is forked from" do
+ it "project knows who it is forked from" do
expect(@project_to.forked_from_project).to eq(project_from)
end
end
@@ -29,15 +29,15 @@ describe '#forked?' do
forked_project_link.save!
end
- it "project_to should know it is forked" do
+ it "project_to knows it is forked" do
expect(project_to.forked?).to be_truthy
end
- it "project_from should not be forked" do
+ it "project_from is not forked" do
expect(project_from.forked?).to be_falsey
end
- it "project_to.destroy should destroy fork_link" do
+ it "project_to.destroy destroys fork_link" do
expect(forked_project_link).to receive(:destroy)
project_to.destroy
end
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index ae77ec5b348..92e0f7f27ce 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -29,15 +29,15 @@ describe GlobalMilestone, models: true do
@global_milestones = GlobalMilestone.build_collection(milestones)
end
- it 'should have all project milestones' do
+ it 'has all project milestones' do
expect(@global_milestones.count).to eq(2)
end
- it 'should have all project milestones titles' do
+ it 'has all project milestones titles' do
expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123'])
end
- it 'should have all project milestones' do
+ it 'has all project milestones' do
expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6)
end
end
@@ -54,11 +54,11 @@ describe GlobalMilestone, models: true do
@global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones)
end
- it 'should have exactly one group milestone' do
+ it 'has exactly one group milestone' do
expect(@global_milestone.title).to eq('Milestone v1.2')
end
- it 'should have all project milestones with the same title' do
+ it 'has all project milestones with the same title' do
expect(@global_milestone.milestones.count).to eq(3)
end
end
@@ -66,7 +66,7 @@ describe GlobalMilestone, models: true do
describe '#safe_title' do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
- it 'should strip out slashes and spaces' do
+ it 'strips out slashes and spaces' do
global_milestone = GlobalMilestone.new(milestone.title, [milestone])
expect(global_milestone.safe_title).to eq('git-test')
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 266c46213a6..ea4b59c26b1 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -116,7 +116,7 @@ describe Group, models: true do
let(:user) { create(:user) }
before { group.add_users([user.id], GroupMember::GUEST) }
- it "should update the group permission" do
+ it "updates the group permission" do
expect(group.group_members.guests.map(&:user)).to include(user)
group.add_users([user.id], GroupMember::DEVELOPER)
expect(group.group_members.developers.map(&:user)).to include(user)
@@ -128,12 +128,12 @@ describe Group, models: true do
let(:user) { create(:user) }
before { group.add_user(user, GroupMember::MASTER) }
- it "should be true if avatar is image" do
+ it "is true if avatar is image" do
group.update_attribute(:avatar, 'uploads/avatar.png')
expect(group.avatar_type).to be_truthy
end
- it "should be false if avatar is html page" do
+ it "is false if avatar is html page" do
group.update_attribute(:avatar, 'uploads/avatar.html')
expect(group.avatar_type).to eq(["only images allowed"])
end
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 983848392b7..4a457997a4f 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -24,7 +24,7 @@ describe ProjectHook, models: true do
end
describe '.push_hooks' do
- it 'should return hooks for push events only' do
+ it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true)
create(:project_hook, push_events: false)
expect(ProjectHook.push_hooks).to eq([hook])
@@ -32,7 +32,7 @@ describe ProjectHook, models: true do
end
describe '.tag_push_hooks' do
- it 'should return hooks for tag push events only' do
+ it 'returns hooks for tag push events only' do
hook = create(:project_hook, tag_push_events: true)
create(:project_hook, tag_push_events: false)
expect(ProjectHook.tag_push_hooks).to eq([hook])
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 4078b9e4ff5..cbdf7eec082 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -38,7 +38,7 @@ describe SystemHook, models: true do
end
it "project_destroy hook" do
- Projects::DestroyService.new(project, user, {}).pending_delete!
+ Projects::DestroyService.new(project, user, {}).async_execute
expect(WebMock).to have_requested(:post, system_hook.url).with(
body: /project_destroy/,
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6a897c96690..3259f795296 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -306,4 +306,257 @@ describe Issue, models: true do
expect(user2.assigned_open_issues_count).to eq(1)
end
end
+
+ describe '#visible_to_user?' do
+ context 'with a user' do
+ let(:user) { build(:user) }
+ let(:issue) { build(:issue) }
+
+ it 'returns true when the issue is readable' do
+ expect(issue).to receive(:readable_by?).with(user).and_return(true)
+
+ expect(issue.visible_to_user?(user)).to eq(true)
+ end
+
+ it 'returns false when the issue is not readable' do
+ expect(issue).to receive(:readable_by?).with(user).and_return(false)
+
+ expect(issue.visible_to_user?(user)).to eq(false)
+ end
+ end
+
+ context 'without a user' do
+ let(:issue) { build(:issue) }
+
+ it 'returns true when the issue is publicly visible' do
+ expect(issue).to receive(:publicly_visible?).and_return(true)
+
+ expect(issue.visible_to_user?).to eq(true)
+ end
+
+ it 'returns false when the issue is not publicly visible' do
+ expect(issue).to receive(:publicly_visible?).and_return(false)
+
+ expect(issue.visible_to_user?).to eq(false)
+ end
+ end
+ end
+
+ describe '#readable_by?' do
+ describe 'with a regular user that is not a team member' do
+ let(:user) { create(:user) }
+
+ context 'using a public project' do
+ let(:project) { create(:empty_project, :public) }
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, project: project, confidential: true)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+ end
+
+ context 'using an internal project' do
+ let(:project) { create(:empty_project, :internal) }
+
+ context 'using an internal user' do
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+ end
+
+ context 'using an external user' do
+ before do
+ allow(user).to receive(:external?).and_return(true)
+ end
+
+ it 'returns false for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+ end
+ end
+
+ context 'using a private project' do
+ let(:project) { create(:empty_project, :private) }
+
+ it 'returns false for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+
+ context 'when the user is the project owner' do
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+
+ it 'returns true for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_readable_by(user)
+ end
+ end
+ end
+ end
+
+ context 'with a regular user that is a team member' do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :public) }
+
+ context 'using a public project' do
+ before do
+ project.team << [user, Gitlab::Access::DEVELOPER]
+ end
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns true for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+ end
+
+ context 'using an internal project' do
+ let(:project) { create(:empty_project, :internal) }
+
+ before do
+ project.team << [user, Gitlab::Access::DEVELOPER]
+ end
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns true for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+ end
+
+ context 'using a private project' do
+ let(:project) { create(:empty_project, :private) }
+
+ before do
+ project.team << [user, Gitlab::Access::DEVELOPER]
+ end
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns true for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+ end
+ end
+
+ context 'with an admin user' do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user, admin: true) }
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+
+ it 'returns true for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).to be_readable_by(user)
+ end
+ end
+ end
+
+ describe '#publicly_visible?' do
+ context 'using a public project' do
+ let(:project) { create(:empty_project, :public) }
+
+ it 'returns true for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).to be_publicly_visible
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_publicly_visible
+ end
+ end
+
+ context 'using an internal project' do
+ let(:project) { create(:empty_project, :internal) }
+
+ it 'returns false for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).not_to be_publicly_visible
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_publicly_visible
+ end
+ end
+
+ context 'using a private project' do
+ let(:project) { create(:empty_project, :private) }
+
+ it 'returns false for a regular issue' do
+ issue = build(:issue, project: project)
+
+ expect(issue).not_to be_publicly_visible
+ end
+
+ it 'returns false for a confidential issue' do
+ issue = build(:issue, :confidential, project: project)
+
+ expect(issue).not_to be_publicly_visible
+ end
+ end
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 49cf3d8633a..fd4a2beff58 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -16,12 +16,13 @@ describe Key, models: true do
end
describe "Methods" do
+ let(:user) { create(:user) }
it { is_expected.to respond_to :projects }
it { is_expected.to respond_to :publishable_key }
describe "#publishable_keys" do
- it 'strips all personal information' do
- expect(build(:key).publishable_key).not_to match(/dummy@gitlab/)
+ it 'replaces SSH key comment with simple identifier of username + hostname' do
+ expect(build(:key, user: user).publishable_key).to include("#{user.name} (localhost)")
end
end
end
@@ -72,13 +73,13 @@ describe Key, models: true do
end
context 'callbacks' do
- it 'should add new key to authorized_file' do
+ it 'adds new key to authorized_file' do
@key = build(:personal_key, id: 7)
expect(GitlabShellWorker).to receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@key.save
end
- it 'should remove key from authorized_file' do
+ it 'removes key from authorized_file' do
@key = create(:personal_key)
expect(GitlabShellWorker).to receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@key.destroy
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index f37f44a608e..2a09063f857 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -18,7 +18,7 @@ describe Label, models: true do
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
- it 'should validate color code' do
+ it 'validates color code' do
expect(label).not_to allow_value('G-ITLAB').for(:color)
expect(label).not_to allow_value('AABBCC').for(:color)
expect(label).not_to allow_value('#AABBCCEE').for(:color)
@@ -30,7 +30,7 @@ describe Label, models: true do
expect(label).to allow_value('#abcdef').for(:color)
end
- it 'should validate title' do
+ it 'validates title' do
expect(label).not_to allow_value('G,ITLAB').for(:title)
expect(label).not_to allow_value('').for(:title)
diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb
index d23fc06c3ad..2cfd26419ca 100644
--- a/spec/models/legacy_diff_note_spec.rb
+++ b/spec/models/legacy_diff_note_spec.rb
@@ -5,12 +5,12 @@ describe LegacyDiffNote, models: true do
let!(:note) { create(:legacy_diff_note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
- it "should save a valid note" do
+ it "saves a valid note" do
expect(note.commit_id).to eq(commit.id)
expect(note.noteable.id).to eq(commit.id)
end
- it "should be recognized by #legacy_diff_note?" do
+ it "is recognized by #legacy_diff_note?" do
expect(note).to be_legacy_diff_note
end
end
@@ -58,7 +58,7 @@ describe LegacyDiffNote, models: true do
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
- diff = merge.diffs.to_a.sample
+ diff = merge.raw_diffs.to_a.sample
line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 40181a8b906..2277f4e13bf 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -10,7 +10,7 @@ describe Member, models: true do
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:source) }
- it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
+ it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.all_values) }
it_behaves_like 'an object with email-formated attributes', :invite_email do
subject { build(:project_member) }
@@ -79,6 +79,18 @@ describe Member, models: true do
@accepted_request_member = project.requesters.find_by(user_id: accepted_request_user.id).tap { |m| m.accept_request }
end
+ describe '.access_for_user_ids' do
+ it 'returns the right access levels' do
+ users = [@owner_user.id, @master_user.id]
+ expected = {
+ @owner_user.id => Gitlab::Access::OWNER,
+ @master_user.id => Gitlab::Access::MASTER
+ }
+
+ expect(described_class.access_for_user_ids(users)).to eq(expected)
+ end
+ end
+
describe '.invite' do
it { expect(described_class.invite).not_to include @master }
it { expect(described_class.invite).to include @invited_member }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 18439cac2a4..4f875fd257a 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -22,7 +22,7 @@ require 'spec_helper'
describe GroupMember, models: true do
describe 'notifications' do
describe "#after_create" do
- it "should send email to user" do
+ it "sends email to user" do
membership = build(:group_member)
allow(membership).to receive(:notification_service).
@@ -40,7 +40,7 @@ describe GroupMember, models: true do
and_return(double('NotificationService').as_null_object)
end
- it "should send email to user" do
+ it "sends email to user" do
expect(@group_member).to receive(:notification_service)
@group_member.update_attribute(:access_level, GroupMember::MASTER)
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index ba622dfb9be..913d74645a7 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -27,6 +27,7 @@ describe ProjectMember, models: true do
describe 'validations' do
it { is_expected.to allow_value('Project').for(:source_type) }
it { is_expected.not_to allow_value('project').for(:source_type) }
+ it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
end
describe 'modules' do
@@ -40,7 +41,7 @@ describe ProjectMember, models: true do
end
describe "#destroy" do
- let(:owner) { create(:project_member, access_level: ProjectMember::OWNER) }
+ let(:owner) { create(:project_member, access_level: ProjectMember::MASTER) }
let(:project) { owner.project }
let(:master) { create(:project_member, project: project) }
@@ -52,7 +53,7 @@ describe ProjectMember, models: true do
master_todos
end
- it "destroy itself and delete associated todos" do
+ it "destroys itself and delete associated todos" do
expect(owner.user.todos.size).to eq(2)
expect(master.user.todos.size).to eq(3)
expect(Todo.count).to eq(5)
@@ -101,7 +102,7 @@ describe ProjectMember, models: true do
end
end
- describe '.add_users_into_projects' do
+ describe '.add_users_to_projects' do
before do
@project_1 = create :project
@project_2 = create :project
@@ -109,7 +110,7 @@ describe ProjectMember, models: true do
@user_1 = create :user
@user_2 = create :user
- ProjectMember.add_users_into_projects(
+ ProjectMember.add_users_to_projects(
[@project_1.id, @project_2.id],
[@user_1.id, @user_2.id],
ProjectMember::MASTER
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 9a637c94fbe..29f7396f862 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -10,7 +10,7 @@ describe MergeRequestDiff, models: true do
expect(mr_diff).not_to receive(:load_diffs)
expect(Gitlab::Git::Compare).to receive(:new).and_call_original
- mr_diff.diffs(ignore_whitespace_change: true)
+ mr_diff.raw_diffs(ignore_whitespace_change: true)
end
end
@@ -18,19 +18,19 @@ describe MergeRequestDiff, models: true do
before { mr_diff.update_attributes(st_diffs: '') }
it 'returns an empty DiffCollection' do
- expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
- expect(mr_diff.diffs).to be_empty
+ expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.raw_diffs).to be_empty
end
end
context 'when the raw diffs exist' do
it 'returns the diffs' do
- expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
- expect(mr_diff.diffs).not_to be_empty
+ expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.raw_diffs).not_to be_empty
end
context 'when the :paths option is set' do
- let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) }
+ let(:diffs) { mr_diff.raw_diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) }
it 'only returns diffs that match the (old path, new path) given' do
expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb')
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 43a87e07435..acb75ec21a9 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -65,11 +65,11 @@ describe MergeRequest, models: true do
end
describe '#target_branch_sha' do
- context 'when the target branch does not exist anymore' do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
- subject { create(:merge_request, source_project: project, target_project: project) }
+ subject { create(:merge_request, source_project: project, target_project: project) }
+ context 'when the target branch does not exist' do
before do
project.repository.raw_repository.delete_branch(subject.target_branch)
end
@@ -78,6 +78,12 @@ describe MergeRequest, models: true do
expect(subject.target_branch_sha).to be_nil
end
end
+
+ it 'returns memoized value' do
+ subject.target_branch_sha = '8ffb3c15a5475e59ae909384297fede4badcb4c7'
+
+ expect(subject.target_branch_sha).to eq '8ffb3c15a5475e59ae909384297fede4badcb4c7'
+ end
end
describe '#source_branch_sha' do
@@ -103,6 +109,12 @@ describe MergeRequest, models: true do
expect(subject.source_branch_sha).to be_nil
end
end
+
+ it 'returns memoized value' do
+ subject.source_branch_sha = '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+
+ expect(subject.source_branch_sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b'
+ end
end
describe '#to_reference' do
@@ -116,6 +128,31 @@ describe MergeRequest, models: true do
end
end
+ describe '#raw_diffs' do
+ let(:merge_request) { build(:merge_request) }
+ let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
+
+ context 'when there are MR diffs' do
+ it 'delegates to the MR diffs' do
+ merge_request.merge_request_diff = MergeRequestDiff.new
+
+ expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(options)
+
+ merge_request.raw_diffs(options)
+ end
+ end
+
+ context 'when there are no MR diffs' do
+ it 'delegates to the compare object' do
+ merge_request.compare = double(:compare)
+
+ expect(merge_request.compare).to receive(:raw_diffs).with(options)
+
+ merge_request.raw_diffs(options)
+ end
+ end
+ end
+
describe '#diffs' do
let(:merge_request) { build(:merge_request) }
let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
@@ -124,7 +161,7 @@ describe MergeRequest, models: true do
it 'delegates to the MR diffs' do
merge_request.merge_request_diff = MergeRequestDiff.new
- expect(merge_request.merge_request_diff).to receive(:diffs).with(options)
+ expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options))
merge_request.diffs(options)
end
@@ -151,12 +188,12 @@ describe MergeRequest, models: true do
create(:note, noteable: merge_request, project: merge_request.project)
end
- it "should include notes for commits" do
+ it "includes notes for commits" do
expect(merge_request.commits).not_to be_empty
expect(merge_request.mr_and_commit_notes.count).to eq(2)
end
- it "should include notes for commits from target project as well" do
+ it "includes notes for commits from target project as well" do
create(:note_on_commit, commit_id: merge_request.commits.first.id,
project: merge_request.target_project)
@@ -267,7 +304,7 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
- it "cant remove a root ref" do
+ it "can't remove a root ref" do
subject.source_branch = "master"
subject.target_branch = "feature"
@@ -664,6 +701,21 @@ describe MergeRequest, models: true do
end
end
+ describe "#environments" do
+ let(:project) { create(:project) }
+ let!(:environment) { create(:environment, project: project) }
+ let!(:environment1) { create(:environment, project: project) }
+ let!(:environment2) { create(:environment, project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'selects deployed environments' do
+ create(:deployment, environment: environment, sha: project.commit('master').id)
+ create(:deployment, environment: environment1, sha: project.commit('feature').id)
+
+ expect(merge_request.environments).to eq [environment]
+ end
+ end
+
describe "#reload_diff" do
let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
@@ -675,6 +727,12 @@ describe MergeRequest, models: true do
subject.reload_diff
end
+ it "executs diff cache service" do
+ expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
+
+ subject.reload_diff
+ end
+
it "updates diff note positions" do
old_diff_refs = subject.diff_refs
@@ -701,4 +759,28 @@ describe MergeRequest, models: true do
subject.reload_diff
end
end
+
+ describe "#diff_sha_refs" do
+ context "with diffs" do
+ subject { create(:merge_request, :with_diffs) }
+
+ it "does not touch the repository" do
+ subject # Instantiate the object
+
+ expect_any_instance_of(Repository).not_to receive(:commit)
+
+ subject.diff_sha_refs
+ end
+
+ it "returns expected diff_refs" do
+ expected_diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: subject.merge_request_diff.base_commit_sha,
+ start_sha: subject.merge_request_diff.start_commit_sha,
+ head_sha: subject.merge_request_diff.head_commit_sha
+ )
+
+ expect(subject.diff_sha_refs).to eq(expected_diff_refs)
+ end
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index d661dc0e59a..d64d6cde2b5 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -28,12 +28,12 @@ describe Milestone, models: true do
end
describe "unique milestone title per project" do
- it "shouldn't accept the same title in a project twice" do
+ it "does not accept the same title in a project twice" do
new_milestone = Milestone.new(project: milestone.project, title: milestone.title)
expect(new_milestone).not_to be_valid
end
- it "should accept the same title in another project" do
+ it "accepts the same title in another project" do
project = build(:project)
new_milestone = Milestone.new(project: project, title: milestone.title)
@@ -42,29 +42,29 @@ describe Milestone, models: true do
end
describe "#percent_complete" do
- it "should not count open issues" do
+ it "does not count open issues" do
milestone.issues << issue
expect(milestone.percent_complete(user)).to eq(0)
end
- it "should count closed issues" do
+ it "counts closed issues" do
issue.close
milestone.issues << issue
expect(milestone.percent_complete(user)).to eq(100)
end
- it "should recover from dividing by zero" do
+ it "recovers from dividing by zero" do
expect(milestone.percent_complete(user)).to eq(0)
end
end
describe "#expires_at" do
- it "should be nil when due_date is unset" do
+ it "is nil when due_date is unset" do
milestone.update_attributes(due_date: nil)
expect(milestone.expires_at).to be_nil
end
- it "should not be nil when due_date is set" do
+ it "is not nil when due_date is set" do
milestone.update_attributes(due_date: Date.tomorrow)
expect(milestone.expires_at).to be_present
end
@@ -121,7 +121,7 @@ describe Milestone, models: true do
create :merge_request, milestone: milestone
end
- it 'Should return total count of issues and merge requests assigned to milestone' do
+ it 'returns total count of issues and merge requests assigned to milestone' do
expect(milestone.total_items_count(user)).to eq 2
end
end
@@ -134,11 +134,11 @@ describe Milestone, models: true do
create :issue
end
- it 'should be true if milestone active and all nested issues closed' do
+ it 'returns true if milestone active and all nested issues closed' do
expect(milestone.can_be_closed?).to be_truthy
end
- it 'should be false if milestone active and not all nested issues closed' do
+ it 'returns false if milestone active and not all nested issues closed' do
issue.milestone = milestone
issue.save
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index a162da0208e..544920d1824 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -61,11 +61,11 @@ describe Namespace, models: true do
allow(@namespace).to receive(:path_changed?).and_return(true)
end
- it "should raise error when directory exists" do
+ it "raises error when directory exists" do
expect { @namespace.move_dir }.to raise_error("namespace directory cannot be moved")
end
- it "should move dir if path changed" do
+ it "moves dir if path changed" do
new_path = @namespace.path + "_new"
allow(@namespace).to receive(:path_was).and_return(@namespace.path)
allow(@namespace).to receive(:path).and_return(new_path)
@@ -93,7 +93,7 @@ describe Namespace, models: true do
before { namespace.destroy }
- it "should remove its dirs when deleted" do
+ it "removes its dirs when deleted" do
expect(File.exist?(path)).to be(false)
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 1243f5420a7..53733d253f7 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -56,18 +56,18 @@ describe Note, models: true do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
- it "should be accessible through #noteable" do
+ it "is accessible through #noteable" do
expect(note.commit_id).to eq(commit.id)
expect(note.noteable).to be_a(Commit)
expect(note.noteable).to eq(commit)
end
- it "should save a valid note" do
+ it "saves a valid note" do
expect(note.commit_id).to eq(commit.id)
note.noteable == commit
end
- it "should be recognized by #for_commit?" do
+ it "is recognized by #for_commit?" do
expect(note).to be_for_commit
end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index 2142c7c13ef..36379074ea0 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -21,7 +21,7 @@ describe Project, models: true do
let(:owner_actions) { Ability.project_owner_rules }
describe "Non member rules" do
- it "should deny for non-project users any actions" do
+ it "denies for non-project users any actions" do
owner_actions.each do |action|
expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey
end
@@ -33,7 +33,7 @@ describe Project, models: true do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST)
end
- it "should allow for project user any guest actions" do
+ it "allows for project user any guest actions" do
guest_actions.each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
@@ -45,7 +45,7 @@ describe Project, models: true do
@p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER)
end
- it "should allow for project user any report actions" do
+ it "allows for project user any report actions" do
report_actions.each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy
end
@@ -58,13 +58,13 @@ describe Project, models: true do
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER)
end
- it "should deny for developer master-specific actions" do
+ it "denies for developer master-specific actions" do
[dev_actions - report_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
- it "should allow for project user any dev actions" do
+ it "allows for project user any dev actions" do
dev_actions.each do |action|
expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
@@ -77,13 +77,13 @@ describe Project, models: true do
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
end
- it "should deny for developer master-specific actions" do
+ it "denies for developer master-specific actions" do
[master_actions - dev_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
- it "should allow for project user any master actions" do
+ it "allows for project user any master actions" do
master_actions.each do |action|
expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy
end
@@ -96,13 +96,13 @@ describe Project, models: true do
@p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER)
end
- it "should deny for masters admin-specific actions" do
+ it "denies for masters admin-specific actions" do
[owner_actions - master_actions].each do |action|
expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey
end
end
- it "should allow for project owner any admin actions" do
+ it "allows for project owner any admin actions" do
owner_actions.each do |action|
expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy
end
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index f3d15f3c1ea..dc702cfc42c 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -65,7 +65,7 @@ describe AsanaService, models: true do
)
end
- it 'should call Asana service to create a story' do
+ it 'calls Asana service to create a story' do
data = create_data_for_commits('Message from commit. related to #123456')
expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
@@ -76,7 +76,7 @@ describe AsanaService, models: true do
@asana.execute(data)
end
- it 'should call Asana service to create a story and close a task' do
+ it 'calls Asana service to create a story and close a task' do
data = create_data_for_commits('fix #456789')
d1 = double('Asana::Task')
expect(d1).to receive(:add_comment)
@@ -86,7 +86,7 @@ describe AsanaService, models: true do
@asana.execute(data)
end
- it 'should be able to close via url' do
+ it 'is able to close via url' do
data = create_data_for_commits('closes https://app.asana.com/19292/956299/42')
d1 = double('Asana::Task')
expect(d1).to receive(:add_comment)
@@ -96,7 +96,7 @@ describe AsanaService, models: true do
@asana.execute(data)
end
- it 'should allow multiple matches per line' do
+ it 'allows multiple matches per line' do
message = <<-EOF
minor bigfix, refactoring, fixed #123 and Closes #456 work on #789
ref https://app.asana.com/19292/956299/42 and closing https://app.asana.com/19292/956299/12
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 17e9361dd5c..00c4e0fb64c 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -44,7 +44,7 @@ describe AssemblaService, models: true do
WebMock.stub_request(:post, @api_url)
end
- it "should call Assembla API" do
+ it "calls Assembla API" do
@assembla_service.execute(@sample_data)
expect(WebMock).to have_requested(:post, @api_url).with(
body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index 3e6da42803b..1adf93258f3 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -39,4 +39,62 @@ describe CampfireService, models: true do
it { is_expected.not_to validate_presence_of(:token) }
end
end
+
+ describe "#execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ @campfire_service = CampfireService.new
+ allow(@campfire_service).to receive_messages(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ token: 'verySecret',
+ subdomain: 'project-name',
+ room: 'test-room'
+ )
+ @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+ @rooms_url = 'https://verySecret:X@project-name.campfirenow.com/rooms.json'
+ @headers = { 'Content-Type' => 'application/json; charset=utf-8' }
+ end
+
+ it "calls Campfire API to get a list of rooms and speak in a room" do
+ # make sure a valid list of rooms is returned
+ body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
+ WebMock.stub_request(:get, @rooms_url).to_return(
+ body: body,
+ status: 200,
+ headers: @headers
+ )
+ # stub the speak request with the room id found in the previous request's response
+ speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/123/speak.json'
+ WebMock.stub_request(:post, speak_url)
+
+ @campfire_service.execute(@sample_data)
+
+ expect(WebMock).to have_requested(:get, @rooms_url).once
+ expect(WebMock).to have_requested(:post, speak_url).with(
+ body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/
+ ).once
+ end
+
+ it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
+ # return a list of rooms that do not contain a room named 'test-room'
+ body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
+ WebMock.stub_request(:get, @rooms_url).to_return(
+ body: body,
+ status: 200,
+ headers: @headers
+ )
+ # we want to make sure no request is sent to the /speak endpoint, here is a basic
+ # regexp that matches this endpoint
+ speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/.*/speak.json'
+
+ @campfire_service.execute(@sample_data)
+
+ expect(WebMock).to have_requested(:get, @rooms_url).once
+ expect(WebMock).not_to have_requested(:post, /#{speak_url}/)
+ end
+ end
end
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 5fe5ea7d2df..d7c5ea95d71 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -56,7 +56,7 @@ describe ExternalWikiService, models: true do
@service.destroy!
end
- it 'should replace the wiki url' do
+ it 'replaces the wiki url' do
wiki_path = get_project_wiki_path(project)
expect(wiki_path).to match('https://gitlab.com')
end
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index b7e627e6518..6518098ceea 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -57,7 +57,7 @@ describe FlowdockService, models: true do
WebMock.stub_request(:post, @api_url)
end
- it "should call FlowDock API" do
+ it "calls FlowDock API" do
@flowdock_service.execute(@sample_data)
@sample_data[:commits].each do |commit|
# One request to Flowdock per new commit
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index a08f1ac229f..2c5583bdaa2 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -57,7 +57,7 @@ describe GemnasiumService, models: true do
)
@sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
end
- it "should call Gemnasium service" do
+ it "calls Gemnasium service" do
expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once
@gemnasium_service.execute(@sample_data)
end
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 7a1f106d6e3..8ef79a17d50 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -54,7 +54,7 @@ describe GitlabIssueTrackerService, models: true do
@service.destroy!
end
- it 'should give the correct path' do
+ it 'gives the correct path' do
expect(@service.project_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues")
expect(@service.new_issue_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/new")
expect(@service.issue_url(432)).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/432")
@@ -71,7 +71,7 @@ describe GitlabIssueTrackerService, models: true do
@service.destroy!
end
- it 'should give the correct path' do
+ it 'gives the correct path' do
expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues")
expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new")
expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432")
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 5f618322aab..1b383219eb9 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -61,7 +61,7 @@ describe HipchatService, models: true do
WebMock.stub_request(:post, api_url)
end
- it 'should test and return errors' do
+ it 'tests and return errors' do
allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room')
result = hipchat.test(push_sample_data)
@@ -69,7 +69,7 @@ describe HipchatService, models: true do
expect(result[:result].to_s).to eq('no such room')
end
- it 'should use v1 if version is provided' do
+ it 'uses v1 if version is provided' do
allow(hipchat).to receive(:api_version).and_return('v1')
expect(HipChat::Client).to receive(:new).with(
token,
@@ -79,7 +79,7 @@ describe HipchatService, models: true do
hipchat.execute(push_sample_data)
end
- it 'should use v2 as the version when nothing is provided' do
+ it 'uses v2 as the version when nothing is provided' do
allow(hipchat).to receive(:api_version).and_return('')
expect(HipChat::Client).to receive(:new).with(
token,
@@ -90,13 +90,13 @@ describe HipchatService, models: true do
end
context 'push events' do
- it "should call Hipchat API for push events" do
+ it "calls Hipchat API for push events" do
hipchat.execute(push_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should create a push message" do
+ it "creates a push message" do
message = hipchat.send(:create_push_message, push_sample_data)
push_sample_data[:object_attributes]
@@ -110,13 +110,13 @@ describe HipchatService, models: true do
context 'tag_push events' do
let(:push_sample_data) { Gitlab::PushDataBuilder.build(project, user, Gitlab::Git::BLANK_SHA, '1' * 40, 'refs/tags/test', []) }
- it "should call Hipchat API for tag push events" do
+ it "calls Hipchat API for tag push events" do
hipchat.execute(push_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should create a tag push message" do
+ it "creates a tag push message" do
message = hipchat.send(:create_push_message, push_sample_data)
push_sample_data[:object_attributes]
@@ -131,13 +131,13 @@ describe HipchatService, models: true do
let(:issue_service) { Issues::CreateService.new(project, user) }
let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
- it "should call Hipchat API for issue events" do
+ it "calls Hipchat API for issue events" do
hipchat.execute(issues_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should create an issue message" do
+ it "creates an issue message" do
message = hipchat.send(:create_issue_message, issues_sample_data)
obj_attr = issues_sample_data[:object_attributes]
@@ -154,13 +154,13 @@ describe HipchatService, models: true do
let(:merge_service) { MergeRequests::CreateService.new(project, user) }
let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
- it "should call Hipchat API for merge requests events" do
+ it "calls Hipchat API for merge requests events" do
hipchat.execute(merge_sample_data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should create a merge request message" do
+ it "creates a merge request message" do
message = hipchat.send(:create_merge_request_message,
merge_sample_data)
@@ -184,7 +184,7 @@ describe HipchatService, models: true do
note: 'a comment on a commit')
end
- it "should call Hipchat API for commit comment events" do
+ it "calls Hipchat API for commit comment events" do
data = Gitlab::NoteDataBuilder.build(commit_note, user)
hipchat.execute(data)
@@ -216,7 +216,7 @@ describe HipchatService, models: true do
note: "merge request note")
end
- it "should call Hipchat API for merge request comment events" do
+ it "calls Hipchat API for merge request comment events" do
data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
hipchat.execute(data)
@@ -243,7 +243,7 @@ describe HipchatService, models: true do
note: "issue note")
end
- it "should call Hipchat API for issue comment events" do
+ it "calls Hipchat API for issue comment events" do
data = Gitlab::NoteDataBuilder.build(issue_note, user)
hipchat.execute(data)
@@ -269,7 +269,7 @@ describe HipchatService, models: true do
note: "snippet note")
end
- it "should call Hipchat API for snippet comment events" do
+ it "calls Hipchat API for snippet comment events" do
data = Gitlab::NoteDataBuilder.build(snippet_note, user)
hipchat.execute(data)
@@ -291,19 +291,20 @@ describe HipchatService, models: true do
end
context 'build events' do
- let(:build) { create(:ci_build) }
+ let(:pipeline) { create(:ci_empty_pipeline) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
let(:data) { Gitlab::BuildDataBuilder.build(build) }
context 'for failed' do
before { build.drop }
- it "should call Hipchat API" do
+ it "calls Hipchat API" do
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should create a build message" do
+ it "creates a build message" do
message = hipchat.send(:create_build_message, data)
project_url = project.web_url
@@ -325,13 +326,13 @@ describe HipchatService, models: true do
build.success
end
- it "should call Hipchat API" do
+ it "calls Hipchat API" do
hipchat.notify_only_broken_builds = false
hipchat.execute(data)
expect(WebMock).to have_requested(:post, api_url).once
end
- it "should notify only broken" do
+ it "notifies only broken" do
hipchat.notify_only_broken_builds = true
hipchat.execute(data)
expect(WebMock).not_to have_requested(:post, api_url).once
@@ -340,18 +341,36 @@ describe HipchatService, models: true do
end
context "#message_options" do
- it "should be set to the defaults" do
- expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
+ it "is set to the defaults" do
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
end
- it "should set notfiy to true" do
+ it "sets notify to true" do
allow(hipchat).to receive(:notify).and_return('1')
- expect(hipchat.send(:message_options)).to eq({ notify: true, color: 'yellow' })
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
end
- it "should set the color" do
+ it "sets the color" do
allow(hipchat).to receive(:color).and_return('red')
- expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'red' })
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+ end
+
+ context 'with a successful build' do
+ it 'uses the green color' do
+ build_data = { object_kind: 'build', commit: { status: 'success' } }
+
+ expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'green' })
+ end
+ end
+
+ context 'with a failed build' do
+ it 'uses the red color' do
+ build_data = { object_kind: 'build', commit: { status: 'failed' } }
+
+ expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' })
+ end
end
end
end
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index 4ee022a5171..ea718232255 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -52,26 +52,27 @@ describe IrkerService, models: true do
let(:colorize_messages) { '1' }
before do
+ @irker_server = TCPServer.new 'localhost', 0
+
allow(irker).to receive_messages(
active: true,
project: project,
project_id: project.id,
service_hook: true,
- server_host: 'localhost',
- server_port: 6659,
+ server_host: @irker_server.addr[2],
+ server_port: @irker_server.addr[1],
default_irc_uri: 'irc://chat.freenode.net/',
recipients: recipients,
colorize_messages: colorize_messages)
irker.valid?
- @irker_server = TCPServer.new 'localhost', 6659
end
after do
@irker_server.close
end
- it 'should send valid JSON messages to an Irker listener' do
+ it 'sends valid JSON messages to an Irker listener' do
irker.execute(sample_data)
conn = @irker_server.accept
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 5a97cf370da..342403f6354 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -75,7 +75,7 @@ describe JiraService, models: true do
WebMock.stub_request(:post, @comment_url)
end
- it "should call JIRA API" do
+ it "calls JIRA API" do
@jira_service.execute(merge_request,
ExternalIssue.new("JIRA-123", project))
expect(WebMock).to have_requested(:post, @comment_url).with(
@@ -128,7 +128,7 @@ describe JiraService, models: true do
expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
end
- it "should reset password if url changed, even if setter called multiple times" do
+ it "resets password if url changed, even if setter called multiple times" do
@jira_service.api_url = 'http://jira1.example.com/rest/api/2'
@jira_service.api_url = 'http://jira1.example.com/rest/api/2'
@jira_service.save
@@ -181,7 +181,7 @@ describe JiraService, models: true do
@service.destroy!
end
- it 'should be initialized' do
+ it 'is initialized' do
expect(@service.title).to eq('JIRA')
expect(@service.description).to eq("Jira issue tracker")
end
@@ -197,7 +197,7 @@ describe JiraService, models: true do
@service.destroy!
end
- it "should be correct" do
+ it "is correct" do
expect(@service.title).to eq('Jira One')
expect(@service.description).to eq('Jira One issue tracker')
end
@@ -225,7 +225,7 @@ describe JiraService, models: true do
@service.destroy!
end
- it 'should be prepopulated with the settings' do
+ it 'is prepopulated with the settings' do
expect(@service.properties["project_url"]).to eq('http://jira.sample/projects/project_a')
expect(@service.properties["issues_url"]).to eq("http://jira.sample/issues/:id")
expect(@service.properties["new_issue_url"]).to eq("http://jira.sample/projects/project_a/issues/new")
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index f37edd4d970..d098d988521 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -39,4 +39,75 @@ describe PivotaltrackerService, models: true do
it { is_expected.not_to validate_presence_of(:token) }
end
end
+
+ describe 'Execute' do
+ let(:service) do
+ PivotaltrackerService.new.tap do |service|
+ service.token = 'secret_api_token'
+ end
+ end
+
+ let(:url) { PivotaltrackerService::API_ENDPOINT }
+
+ def push_data(branch: 'master')
+ {
+ object_kind: 'push',
+ ref: "refs/heads/#{branch}",
+ commits: [
+ {
+ id: '21c12ea',
+ author: {
+ name: 'Some User'
+ },
+ url: 'https://example.com/commit',
+ message: 'commit message',
+ }
+ ]
+ }
+ end
+
+ before do
+ WebMock.stub_request(:post, url)
+ end
+
+ it 'should post correct message' do
+ service.execute(push_data)
+ expect(WebMock).to have_requested(:post, url).with(
+ body: {
+ 'source_commit' => {
+ 'commit_id' => '21c12ea',
+ 'author' => 'Some User',
+ 'url' => 'https://example.com/commit',
+ 'message' => 'commit message'
+ }
+ },
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'X-TrackerToken' => 'secret_api_token'
+ }
+ ).once
+ end
+
+ context 'when allowed branches is specified' do
+ let(:service) do
+ super().tap do |service|
+ service.restrict_to_branch = 'master,v10'
+ end
+ end
+
+ it 'should post message if branch is in the list' do
+ service.execute(push_data(branch: 'master'))
+ service.execute(push_data(branch: 'v10'))
+
+ expect(WebMock).to have_requested(:post, url).twice
+ end
+
+ it 'should not post message if branch is not in the list' do
+ service.execute(push_data(branch: 'mas'))
+ service.execute(push_data(branch: 'v11'))
+
+ expect(WebMock).not_to have_requested(:post, url)
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 555d9757b47..19c0270a493 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -72,7 +72,7 @@ describe PushoverService, models: true do
WebMock.stub_request(:post, api_url)
end
- it 'should call Pushover API' do
+ it 'calls Pushover API' do
pushover.execute(sample_data)
expect(WebMock).to have_requested(:post, api_url).once
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
index 379c3e1219c..41b93f08050 100644
--- a/spec/models/project_services/slack_service/note_message_spec.rb
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -60,6 +60,7 @@ describe SlackService::NoteMessage, models: true do
title: "merge request title\ndetails\n"
}
end
+
it 'returns a message regarding notes on a merge request' do
message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq("Test User commented on " \
diff --git a/spec/models/project_services/slack_service/wiki_page_message_spec.rb b/spec/models/project_services/slack_service/wiki_page_message_spec.rb
index 46dedb66c7c..13aea0b0600 100644
--- a/spec/models/project_services/slack_service/wiki_page_message_spec.rb
+++ b/spec/models/project_services/slack_service/wiki_page_message_spec.rb
@@ -47,7 +47,7 @@ describe SlackService::WikiPageMessage, models: true do
context 'when :action == "create"' do
before { args[:object_attributes][:action] = 'create' }
- it 'it returns the attachment for a new wiki page' do
+ it 'returns the attachment for a new wiki page' do
expect(subject.attachments).to eq([
{
text: "Wiki page description",
@@ -60,7 +60,7 @@ describe SlackService::WikiPageMessage, models: true do
context 'when :action == "update"' do
before { args[:object_attributes][:action] = 'update' }
- it 'it returns the attachment for an updated wiki page' do
+ it 'returns the attachment for an updated wiki page' do
expect(subject.attachments).to eq([
{
text: "Wiki page description",
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index df511b1bc4c..45a5f4ef12a 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -93,31 +93,31 @@ describe SlackService, models: true do
@wiki_page_sample_data = wiki_page_service.hook_data(@wiki_page, 'create')
end
- it "should call Slack API for push events" do
+ it "calls Slack API for push events" do
slack.execute(push_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
- it "should call Slack API for issue events" do
+ it "calls Slack API for issue events" do
slack.execute(@issues_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
- it "should call Slack API for merge requests events" do
+ it "calls Slack API for merge requests events" do
slack.execute(@merge_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
- it "should call Slack API for wiki page events" do
+ it "calls Slack API for wiki page events" do
slack.execute(@wiki_page_sample_data)
expect(WebMock).to have_requested(:post, webhook_url).once
end
- it 'should use the username as an option for slack when configured' do
+ it 'uses the username as an option for slack when configured' do
allow(slack).to receive(:username).and_return(username)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, username: username).
@@ -128,7 +128,7 @@ describe SlackService, models: true do
slack.execute(push_sample_data)
end
- it 'should use the channel as an option when it is configured' do
+ it 'uses the channel as an option when it is configured' do
allow(slack).to receive(:channel).and_return(channel)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: channel).
@@ -234,7 +234,7 @@ describe SlackService, models: true do
note: 'a comment on a commit')
end
- it "should call Slack API for commit comment events" do
+ it "calls Slack API for commit comment events" do
data = Gitlab::NoteDataBuilder.build(commit_note, user)
slack.execute(data)
@@ -248,7 +248,7 @@ describe SlackService, models: true do
note: "merge request note")
end
- it "should call Slack API for merge request comment events" do
+ it "calls Slack API for merge request comment events" do
data = Gitlab::NoteDataBuilder.build(merge_request_note, user)
slack.execute(data)
@@ -261,7 +261,7 @@ describe SlackService, models: true do
create(:note_on_issue, project: project, note: "issue note")
end
- it "should call Slack API for issue comment events" do
+ it "calls Slack API for issue comment events" do
data = Gitlab::NoteDataBuilder.build(issue_note, user)
slack.execute(data)
@@ -275,7 +275,7 @@ describe SlackService, models: true do
note: "snippet note")
end
- it "should call Slack API for snippet comment events" do
+ it "calls Slack API for snippet comment events" do
data = Gitlab::NoteDataBuilder.build(snippet_note, user)
slack.execute(data)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 9b017288488..9c3b4712cab 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -69,6 +69,7 @@ describe Project, models: true do
it { is_expected.to include_module(Gitlab::ConfigHelper) }
it { is_expected.to include_module(Gitlab::ShellAdapter) }
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
+ it { is_expected.to include_module(Gitlab::CurrentSettings) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
end
@@ -88,7 +89,7 @@ describe Project, models: true do
it { is_expected.to validate_presence_of(:namespace) }
it { is_expected.to validate_presence_of(:repository_storage) }
- it 'should not allow new projects beyond user limits' do
+ it 'does not allow new projects beyond user limits' do
project2 = build(:project)
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
expect(project2).not_to be_valid
@@ -97,7 +98,7 @@ describe Project, models: true do
describe 'wiki path conflict' do
context "when the new path has been used by the wiki of other Project" do
- it 'should have an error on the name attribute' do
+ it 'has an error on the name attribute' do
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
expect(new_project).not_to be_valid
@@ -106,7 +107,7 @@ describe Project, models: true do
end
context "when the new wiki path has been used by the path of other Project" do
- it 'should have an error on the name attribute' do
+ it 'has an error on the name attribute' do
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
@@ -124,7 +125,7 @@ describe Project, models: true do
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
- it "should not allow repository storages that don't match a label in the configuration" do
+ it "does not allow repository storages that don't match a label in the configuration" do
expect(project2).not_to be_valid
expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
end
@@ -171,12 +172,12 @@ describe Project, models: true do
end
describe 'project token' do
- it 'should set an random token if none provided' do
+ it 'sets an random token if none provided' do
project = FactoryGirl.create :empty_project, runners_token: ''
expect(project.runners_token).not_to eq('')
end
- it 'should not set an random toke if one provided' do
+ it 'does not set an random toke if one provided' do
project = FactoryGirl.create :empty_project, runners_token: 'my-token'
expect(project.runners_token).to eq('my-token')
end
@@ -224,7 +225,7 @@ describe Project, models: true do
end
end
- it 'should return valid url to repo' do
+ it 'returns valid url to repo' do
project = Project.new(path: 'somewhere')
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
@@ -245,12 +246,40 @@ describe Project, models: true do
end
end
+ describe "#new_issue_address" do
+ let(:project) { create(:empty_project, path: "somewhere") }
+ let(:user) { create(:user) }
+
+ context 'incoming email enabled' do
+ before do
+ stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
+ end
+
+ it 'returns the address to create a new issue' do
+ token = user.authentication_token
+ address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab"
+
+ expect(project.new_issue_address(user)).to eq(address)
+ end
+ end
+
+ context 'incoming email disabled' do
+ before do
+ stub_incoming_email_setting(enabled: false)
+ end
+
+ it 'returns nil' do
+ expect(project.new_issue_address(user)).to be_nil
+ end
+ end
+ end
+
describe 'last_activity methods' do
let(:project) { create(:project) }
let(:last_event) { double(created_at: Time.now) }
describe 'last_activity' do
- it 'should alias last_activity to last_event' do
+ it 'alias last_activity to last_event' do
allow(project).to receive(:last_event).and_return(last_event)
expect(project.last_activity).to eq(last_event)
end
@@ -321,13 +350,13 @@ describe Project, models: true do
let(:prev_commit_id) { merge_request.commits.last.id }
let(:commit_id) { merge_request.commits.first.id }
- it 'should close merge request if last commit from source branch was pushed to target branch' do
+ it 'closes merge request if last commit from source branch was pushed to target branch' do
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user)
merge_request.reload
expect(merge_request.merged?).to be_truthy
end
- it 'should update merge request commits with new one if pushed to source branch' do
+ it 'updates merge request commits with new one if pushed to source branch' do
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user)
merge_request.reload
expect(merge_request.diff_head_sha).to eq(commit_id)
@@ -372,6 +401,24 @@ describe Project, models: true do
it { expect(@project.to_param).to eq('gitlabhq') }
end
+
+ context 'with invalid path' do
+ it 'returns previous path to keep project suitable for use in URLs when persisted' do
+ project = create(:empty_project, path: 'gitlab')
+ project.path = 'foo&bar'
+
+ expect(project).not_to be_valid
+ expect(project.to_param).to eq 'gitlab'
+ end
+
+ it 'returns current path when new record' do
+ project = build(:empty_project, path: 'gitlab')
+ project.path = 'foo&bar'
+
+ expect(project).not_to be_valid
+ expect(project.to_param).to eq 'foo&bar'
+ end
+ end
end
describe '#repository' do
@@ -386,11 +433,11 @@ describe Project, models: true do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
- it "should be true if used internal tracker" do
+ it "is true if used internal tracker" do
expect(project.default_issues_tracker?).to be_truthy
end
- it "should be false if used other tracker" do
+ it "is false if used other tracker" do
expect(ext_project.default_issues_tracker?).to be_falsey
end
end
@@ -589,12 +636,12 @@ describe Project, models: true do
describe '#avatar_type' do
let(:project) { create(:project) }
- it 'should be true if avatar is image' do
+ it 'is true if avatar is image' do
project.update_attribute(:avatar, 'uploads/avatar.png')
expect(project.avatar_type).to be_truthy
end
- it 'should be false if avatar is html page' do
+ it 'is false if avatar is html page' do
project.update_attribute(:avatar, 'uploads/avatar.html')
expect(project.avatar_type).to eq(['only images allowed'])
end
@@ -667,6 +714,20 @@ describe Project, models: true do
it { expect(project.builds_enabled?).to be_truthy }
end
+ describe '.cached_count', caching: true do
+ let(:group) { create(:group, :public) }
+ let!(:project1) { create(:empty_project, :public, group: group) }
+ let!(:project2) { create(:empty_project, :public, group: group) }
+
+ it 'returns total project count' do
+ expect(Project).to receive(:count).once.and_call_original
+
+ 3.times do
+ expect(Project.cached_count).to eq(2)
+ end
+ end
+ end
+
describe '.trending' do
let(:group) { create(:group, :public) }
let(:project1) { create(:empty_project, :public, group: group) }
@@ -767,16 +828,16 @@ describe Project, models: true do
context 'for shared runners disabled' do
let(:shared_runners_enabled) { false }
- it 'there are no runners available' do
+ it 'has no runners available' do
expect(project.any_runners?).to be_falsey
end
- it 'there is a specific runner' do
+ it 'has a specific runner' do
project.runners << specific_runner
expect(project.any_runners?).to be_truthy
end
- it 'there is a shared runner, but they are prohibited to use' do
+ it 'has a shared runner, but they are prohibited to use' do
shared_runner
expect(project.any_runners?).to be_falsey
end
@@ -790,7 +851,7 @@ describe Project, models: true do
context 'for shared runners enabled' do
let(:shared_runners_enabled) { true }
- it 'there is a shared runner' do
+ it 'has a shared runner' do
shared_runner
expect(project.any_runners?).to be_truthy
end
@@ -1024,68 +1085,97 @@ describe Project, models: true do
end
describe '#protected_branch?' do
- let(:project) { create(:empty_project) }
+ context 'existing project' do
+ let(:project) { create(:project) }
- it 'returns true when the branch matches a protected branch via direct match' do
- project.protected_branches.create!(name: 'foo')
+ it 'returns true when the branch matches a protected branch via direct match' do
+ project.protected_branches.create!(name: 'foo')
- expect(project.protected_branch?('foo')).to eq(true)
- end
+ expect(project.protected_branch?('foo')).to eq(true)
+ end
- it 'returns true when the branch matches a protected branch via wildcard match' do
- project.protected_branches.create!(name: 'production/*')
+ it 'returns true when the branch matches a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
- expect(project.protected_branch?('production/some-branch')).to eq(true)
- end
+ expect(project.protected_branch?('production/some-branch')).to eq(true)
+ end
- it 'returns false when the branch does not match a protected branch via direct match' do
- expect(project.protected_branch?('foo')).to eq(false)
- end
+ it 'returns false when the branch does not match a protected branch via direct match' do
+ expect(project.protected_branch?('foo')).to eq(false)
+ end
- it 'returns false when the branch does not match a protected branch via wildcard match' do
- project.protected_branches.create!(name: 'production/*')
+ it 'returns false when the branch does not match a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
- expect(project.protected_branch?('staging/some-branch')).to eq(false)
+ expect(project.protected_branch?('staging/some-branch')).to eq(false)
+ end
end
- end
- describe "#developers_can_push_to_protected_branch?" do
- let(:project) { create(:empty_project) }
+ context "new project" do
+ let(:project) { create(:empty_project) }
- context "when the branch matches a protected branch via direct match" do
- it "returns true if 'Developers can Push' is turned on" do
- create(:protected_branch, name: "production", project: project, developers_can_push: true)
+ it 'returns false when default_protected_branch is unprotected' do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
- expect(project.developers_can_push_to_protected_branch?('production')).to be true
+ expect(project.protected_branch?('master')).to be false
end
- it "returns false if 'Developers can Push' is turned off" do
- create(:protected_branch, name: "production", project: project, developers_can_push: false)
+ it 'returns false when default_protected_branch lets developers push' do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
- expect(project.developers_can_push_to_protected_branch?('production')).to be false
+ expect(project.protected_branch?('master')).to be false
end
- end
- context "when the branch matches a protected branch via wilcard match" do
- it "returns true if 'Developers can Push' is turned on" do
- create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+ it 'returns true when default_branch_protection does not let developers push but let developer merge branches' do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
- expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true
+ expect(project.protected_branch?('master')).to be true
end
- it "returns false if 'Developers can Push' is turned off" do
- create(:protected_branch, name: "production/*", project: project, developers_can_push: false)
+ it 'returns true when default_branch_protection is in full protection' do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
- expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false
+ expect(project.protected_branch?('master')).to be true
end
end
+ end
+
+ describe '#user_can_push_to_empty_repo?' do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ it 'returns false when default_branch_protection is in full protection and user is developer' do
+ project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
+
+ expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
+ end
- context "when the branch does not match a protected branch" do
- it "returns false" do
- create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+ it 'returns false when default_branch_protection only lets devs merge and user is dev' do
+ project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
- expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false
- end
+ expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
+ end
+
+ it 'returns true when default_branch_protection lets devs push and user is developer' do
+ project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
+ end
+
+ it 'returns true when default_branch_protection is unprotected and user is developer' do
+ project.team << [user, :developer]
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
+
+ expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
+ end
+
+ it 'returns true when user is master' do
+ project.team << [user, :master]
+
+ expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
end
end
@@ -1244,6 +1334,32 @@ describe Project, models: true do
end
end
+ describe '#add_import_job' do
+ context 'forked' do
+ let(:forked_project_link) { create(:forked_project_link) }
+ let(:forked_from_project) { forked_project_link.forked_from_project }
+ let(:project) { forked_project_link.forked_to_project }
+
+ it 'schedules a RepositoryForkWorker job' do
+ expect(RepositoryForkWorker).to receive(:perform_async).
+ with(project.id, forked_from_project.repository_storage_path,
+ forked_from_project.path_with_namespace, project.namespace.path)
+
+ project.add_import_job
+ end
+ end
+
+ context 'not forked' do
+ let(:project) { create(:project) }
+
+ it 'schedules a RepositoryImportWorker job' do
+ expect(RepositoryImportWorker).to receive(:perform_async).with(project.id)
+
+ project.add_import_job
+ end
+ end
+ end
+
describe '.where_paths_in' do
context 'without any paths' do
it 'returns an empty relation' do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 9262aeb6ed8..5eaf0d3b7a6 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -151,8 +151,8 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
end
context 'when project is shared with group' do
@@ -168,14 +168,14 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::DEVELOPER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
context 'but share_with_group_lock is true' do
before { project.namespace.update(share_with_group_lock: true) }
- it { expect(project.team.max_member_access(master.id)).to be_nil }
- it { expect(project.team.max_member_access(reporter.id)).to be_nil }
+ it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::NO_ACCESS) }
end
end
end
@@ -194,8 +194,74 @@ describe ProjectTeam, models: true do
it { expect(project.team.max_member_access(master.id)).to eq(Gitlab::Access::MASTER) }
it { expect(project.team.max_member_access(reporter.id)).to eq(Gitlab::Access::REPORTER) }
it { expect(project.team.max_member_access(guest.id)).to eq(Gitlab::Access::GUEST) }
- it { expect(project.team.max_member_access(nonmember.id)).to be_nil }
- it { expect(project.team.max_member_access(requester.id)).to be_nil }
+ it { expect(project.team.max_member_access(nonmember.id)).to eq(Gitlab::Access::NO_ACCESS) }
+ it { expect(project.team.max_member_access(requester.id)).to eq(Gitlab::Access::NO_ACCESS) }
end
end
+
+ shared_examples_for "#max_member_access_for_users" do |enable_request_store|
+ describe "#max_member_access_for_users" do
+ before do
+ RequestStore.begin! if enable_request_store
+ end
+
+ after do
+ if enable_request_store
+ RequestStore.end!
+ RequestStore.clear!
+ end
+ end
+
+ it 'returns correct roles for different users' do
+ master = create(:user)
+ reporter = create(:user)
+ promoted_guest = create(:user)
+ guest = create(:user)
+ project = create(:project)
+
+ project.team << [master, :master]
+ project.team << [reporter, :reporter]
+ project.team << [promoted_guest, :guest]
+ project.team << [guest, :guest]
+
+ group = create(:group)
+ group_developer = create(:user)
+ second_developer = create(:user)
+ project.project_group_links.create(
+ group: group,
+ group_access: Gitlab::Access::DEVELOPER)
+
+ group.add_master(promoted_guest)
+ group.add_developer(group_developer)
+ group.add_developer(second_developer)
+
+ second_group = create(:group)
+ project.project_group_links.create(
+ group: second_group,
+ group_access: Gitlab::Access::MASTER)
+ second_group.add_master(second_developer)
+
+ users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id)
+
+ expected = {
+ master.id => Gitlab::Access::MASTER,
+ reporter.id => Gitlab::Access::REPORTER,
+ promoted_guest.id => Gitlab::Access::DEVELOPER,
+ guest.id => Gitlab::Access::GUEST,
+ group_developer.id => Gitlab::Access::DEVELOPER,
+ second_developer.id => Gitlab::Access::MASTER
+ }
+
+ expect(project.team.max_member_access_for_user_ids(users)).to eq(expected)
+ end
+ end
+ end
+
+ describe '#max_member_access_for_users with RequestStore' do
+ it_behaves_like "#max_member_access_for_users", true
+ end
+
+ describe '#max_member_access_for_users without RequestStore' do
+ it_behaves_like "#max_member_access_for_users", false
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 881ab5ff8dc..f7dbfd712cc 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -50,8 +50,9 @@ describe Repository, models: true do
double_first = double(committed_date: Time.now)
double_last = double(committed_date: Time.now - 1.second)
- allow(repository).to receive(:commit).with(tag_a.target).and_return(double_first)
- allow(repository).to receive(:commit).with(tag_b.target).and_return(double_last)
+ allow(tag_a).to receive(:target).and_return(double_first)
+ allow(tag_b).to receive(:target).and_return(double_last)
+ allow(repository).to receive(:tags).and_return([tag_a, tag_b])
end
it { is_expected.to eq(['v1.0.0', 'v1.1.0']) }
@@ -64,8 +65,9 @@ describe Repository, models: true do
double_first = double(committed_date: Time.now - 1.second)
double_last = double(committed_date: Time.now)
- allow(repository).to receive(:commit).with(tag_a.target).and_return(double_last)
- allow(repository).to receive(:commit).with(tag_b.target).and_return(double_first)
+ allow(tag_a).to receive(:target).and_return(double_last)
+ allow(tag_b).to receive(:target).and_return(double_first)
+ allow(repository).to receive(:tags).and_return([tag_a, tag_b])
end
it { is_expected.to eq(['v1.1.0', 'v1.0.0']) }
@@ -338,14 +340,14 @@ describe Repository, models: true do
describe '#add_branch' do
context 'when pre hooks were successful' do
- it 'should run without errors' do
+ it 'runs without errors' do
hook = double(trigger: [true, nil])
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error
end
- it 'should create the branch' do
+ it 'creates the branch' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
branch = repository.add_branch(user, 'new_feature', 'master')
@@ -361,7 +363,7 @@ describe Repository, models: true do
end
context 'when pre hooks failed' do
- it 'should get an error' do
+ it 'gets an error' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
@@ -369,7 +371,7 @@ describe Repository, models: true do
end.to raise_error(GitHooksService::PreReceiveError)
end
- it 'should not create the branch' do
+ it 'does not create the branch' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
@@ -381,14 +383,18 @@ describe Repository, models: true do
end
describe '#rm_branch' do
+ let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+ let(:blank_sha) { '0000000000000000000000000000000000000000' }
+
context 'when pre hooks were successful' do
- it 'should run without errors' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
+ it 'runs without errors' do
+ expect_any_instance_of(GitHooksService).to receive(:execute).
+ with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature')
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
end
- it 'should delete the branch' do
+ it 'deletes the branch' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil])
expect { repository.rm_branch(user, 'feature') }.not_to raise_error
@@ -398,7 +404,7 @@ describe Repository, models: true do
end
context 'when pre hooks failed' do
- it 'should get an error' do
+ it 'gets an error' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
@@ -406,7 +412,7 @@ describe Repository, models: true do
end.to raise_error(GitHooksService::PreReceiveError)
end
- it 'should not delete the branch' do
+ it 'does not delete the branch' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
@@ -418,27 +424,38 @@ describe Repository, models: true do
end
describe '#commit_with_hooks' do
+ let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
+
context 'when pre hooks were successful' do
before do
expect_any_instance_of(GitHooksService).to receive(:execute).
- and_return(true)
+ with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature').
+ and_yield.and_return(true)
end
- it 'should run without errors' do
+ it 'runs without errors' do
expect do
repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end.not_to raise_error
end
- it 'should ensure the autocrlf Git option is set to :input' do
+ it 'ensures the autocrlf Git option is set to :input' do
expect(repository).to receive(:update_autocrlf_option)
repository.commit_with_hooks(user, 'feature') { sample_commit.id }
end
+
+ context "when the branch wasn't empty" do
+ it 'updates the head' do
+ expect(repository.find_branch('feature').target.id).to eq(old_rev)
+ repository.commit_with_hooks(user, 'feature') { sample_commit.id }
+ expect(repository.find_branch('feature').target.id).to eq(sample_commit.id)
+ end
+ end
end
context 'when pre hooks failed' do
- it 'should get an error' do
+ it 'gets an error' do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
@@ -446,6 +463,43 @@ describe Repository, models: true do
end.to raise_error(GitHooksService::PreReceiveError)
end
end
+
+ context 'when target branch is different from source branch' do
+ before do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+ end
+
+ it 'expires branch cache' do
+ expect(repository).not_to receive(:expire_exists_cache)
+ expect(repository).not_to receive(:expire_root_ref_cache)
+ expect(repository).not_to receive(:expire_emptiness_caches)
+ expect(repository).to receive(:expire_branches_cache)
+ expect(repository).to receive(:expire_has_visible_content_cache)
+ expect(repository).to receive(:expire_branch_count_cache)
+
+ repository.commit_with_hooks(user, 'new-feature') { sample_commit.id }
+ end
+ end
+
+ context 'when repository is empty' do
+ before do
+ allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, ''])
+ end
+
+ it 'expires creation and branch cache' do
+ empty_repository = create(:empty_project, :empty_repo).repository
+
+ expect(empty_repository).to receive(:expire_exists_cache)
+ expect(empty_repository).to receive(:expire_root_ref_cache)
+ expect(empty_repository).to receive(:expire_emptiness_caches)
+ expect(empty_repository).to receive(:expire_branches_cache)
+ expect(empty_repository).to receive(:expire_has_visible_content_cache)
+ expect(empty_repository).to receive(:expire_branch_count_cache)
+
+ empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+ 'Updates file content', 'master', false)
+ end
+ end
end
describe '#exists?' do
@@ -661,7 +715,7 @@ describe Repository, models: true do
end
describe '#merge' do
- it 'should merge the code and return the commit id' do
+ it 'merges the code and return the commit id' do
expect(merge_commit).to be_present
expect(repository.blob_at(merge_commit.id, 'files/ruby/feature.rb')).to be_present
end
@@ -672,13 +726,13 @@ describe Repository, models: true do
let(:update_image_commit) { repository.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
context 'when there is a conflict' do
- it 'should abort the operation' do
+ it 'aborts the operation' do
expect(repository.revert(user, new_image_commit, 'master')).to eq(false)
end
end
context 'when commit was already reverted' do
- it 'should abort the operation' do
+ it 'aborts the operation' do
repository.revert(user, update_image_commit, 'master')
expect(repository.revert(user, update_image_commit, 'master')).to eq(false)
@@ -686,13 +740,13 @@ describe Repository, models: true do
end
context 'when commit can be reverted' do
- it 'should revert the changes' do
+ it 'reverts the changes' do
expect(repository.revert(user, update_image_commit, 'master')).to be_truthy
end
end
context 'reverting a merge commit' do
- it 'should revert the changes' do
+ it 'reverts the changes' do
merge_commit
expect(repository.blob_at_branch('master', 'files/ruby/feature.rb')).to be_present
@@ -708,13 +762,13 @@ describe Repository, models: true do
let(:pickable_merge) { repository.commit('e56497bb5f03a90a51293fc6d516788730953899') }
context 'when there is a conflict' do
- it 'should abort the operation' do
+ it 'aborts the operation' do
expect(repository.cherry_pick(user, conflict_commit, 'master')).to eq(false)
end
end
context 'when commit was already cherry-picked' do
- it 'should abort the operation' do
+ it 'aborts the operation' do
repository.cherry_pick(user, pickable_commit, 'master')
expect(repository.cherry_pick(user, pickable_commit, 'master')).to eq(false)
@@ -722,13 +776,13 @@ describe Repository, models: true do
end
context 'when commit can be cherry-picked' do
- it 'should cherry-pick the changes' do
+ it 'cherry-picks the changes' do
expect(repository.cherry_pick(user, pickable_commit, 'master')).to be_truthy
end
end
context 'cherry-picking a merge commit' do
- it 'should cherry-pick the changes' do
+ it 'cherry-picks the changes' do
expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).to be_nil
repository.cherry_pick(user, pickable_merge, 'master')
@@ -1100,7 +1154,7 @@ describe Repository, models: true do
it 'does not flush the cache if the commit does not change any logos' do
diff = double(:diff, new_path: 'test.txt')
- expect(commit).to receive(:diffs).and_return([diff])
+ expect(commit).to receive(:raw_diffs).and_return([diff])
expect(cache).not_to receive(:expire)
repository.expire_avatar_cache(repository.root_ref, '123')
@@ -1109,7 +1163,7 @@ describe Repository, models: true do
it 'flushes the cache if the commit changes any of the logos' do
diff = double(:diff, new_path: Repository::AVATAR_FILES[0])
- expect(commit).to receive(:diffs).and_return([diff])
+ expect(commit).to receive(:raw_diffs).and_return([diff])
expect(cache).to receive(:expire).with(:avatar)
repository.expire_avatar_cache(repository.root_ref, '123')
@@ -1161,17 +1215,6 @@ describe Repository, models: true do
end
end
- describe '#local_branches' do
- it 'returns the local branches' do
- masterrev = repository.find_branch('master').target
- create_remote_branch('joe', 'remote_branch', masterrev)
- repository.add_branch(user, 'local_branch', masterrev)
-
- expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
- expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
- end
- end
-
describe "#keep_around" do
it "does not fail if we attempt to reference bad commit" do
expect(repository.kept_around?('abc1234')).to be_falsey
@@ -1199,9 +1242,4 @@ describe Repository, models: true do
File.delete(path)
end
end
-
- def create_remote_branch(remote_name, branch_name, target)
- rugged = repository.rugged
- rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target)
- end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 67b3783d514..05056a4bb47 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -65,13 +65,13 @@ describe Service, models: true do
end
let(:project) { create(:project) }
- describe 'should be prefilled for projects pushover service' do
+ describe 'is prefilled for projects pushover service' do
before do
service_template
project.build_missing_services
end
- it "should have all fields prefilled" do
+ it "has all fields prefilled" do
service = project.pushover_service
expect(service.template).to eq(false)
expect(service.device).to eq('MyDevice')
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
new file mode 100644
index 00000000000..a8c25766e73
--- /dev/null
+++ b/spec/models/user_agent_detail_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe UserAgentDetail, type: :model do
+ describe '.submittable?' do
+ it 'is submittable when not already submitted' do
+ detail = build(:user_agent_detail)
+
+ expect(detail.submittable?).to be_truthy
+ end
+
+ it 'is not submittable when already submitted' do
+ detail = build(:user_agent_detail, submitted: true)
+
+ expect(detail.submittable?).to be_falsey
+ end
+ end
+
+ describe '.valid?' do
+ it 'is valid with a subject' do
+ detail = build(:user_agent_detail)
+
+ expect(detail).to be_valid
+ end
+
+ it 'is invalid without a subject' do
+ detail = build(:user_agent_detail, subject: nil)
+
+ expect(detail).not_to be_valid
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2a5a7fb2fc6..f67acbbef37 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -166,7 +166,7 @@ describe User, models: true do
allow_any_instance_of(ApplicationSetting).to receive(:domain_whitelist).and_return(['*.example.com'])
end
- it 'should give priority to whitelist and allow info@test.example.com' do
+ it 'gives priority to whitelist and allow info@test.example.com' do
user = build(:user, email: 'info@test.example.com')
expect(user).to be_valid
end
@@ -304,18 +304,18 @@ describe User, models: true do
end
describe '#generate_password' do
- it "should execute callback when force_random_password specified" do
+ it "executes callback when force_random_password specified" do
user = build(:user, force_random_password: true)
expect(user).to receive(:generate_password)
user.save
end
- it "should not generate password by default" do
+ it "does not generate password by default" do
user = create(:user, password: 'abcdefghe')
expect(user.password).to eq('abcdefghe')
end
- it "should generate password when forcing random password" do
+ it "generates password when forcing random password" do
allow(Devise).to receive(:friendly_token).and_return('123456789')
user = create(:user, password: 'abcdefg', force_random_password: true)
expect(user.password).to eq('12345678')
@@ -323,7 +323,7 @@ describe User, models: true do
end
describe 'authentication token' do
- it "should have authentication token" do
+ it "has authentication token" do
user = create(:user)
expect(user.authentication_token).not_to be_blank
end
@@ -430,7 +430,7 @@ describe User, models: true do
describe 'blocking user' do
let(:user) { create(:user, name: 'John Smith') }
- it "should block user" do
+ it "blocks user" do
user.block
expect(user.blocked?).to be_truthy
end
@@ -501,7 +501,7 @@ describe User, models: true do
describe 'with defaults' do
let(:user) { User.new }
- it "should apply defaults to user" do
+ it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
@@ -512,7 +512,7 @@ describe User, models: true do
describe 'with default overrides' do
let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
- it "should apply defaults to user" do
+ it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
expect(user.theme_id).to eq(1)
@@ -602,7 +602,7 @@ describe User, models: true do
describe 'by_username_or_id' do
let(:user1) { create(:user, username: 'foo') }
- it "should get the correct user" do
+ it "gets the correct user" do
expect(User.by_username_or_id(user1.id)).to eq(user1)
expect(User.by_username_or_id('foo')).to eq(user1)
expect(User.by_username_or_id(-1)).to be_nil
@@ -614,7 +614,7 @@ describe User, models: true do
let(:username) { 'John' }
let!(:user) { create(:user, username: username) }
- it 'should get the correct user' do
+ it 'gets the correct user' do
expect(User.by_login(user.email.upcase)).to eq user
expect(User.by_login(user.email)).to eq user
expect(User.by_login(username.downcase)).to eq user
@@ -639,23 +639,23 @@ describe User, models: true do
describe 'all_ssh_keys' do
it { is_expected.to have_many(:keys).dependent(:destroy) }
- it "should have all ssh keys" do
+ it "has all ssh keys" do
user = create :user
key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id
- expect(user.all_ssh_keys).to include(key.key)
+ expect(user.all_ssh_keys).to include(a_string_starting_with(key.key))
end
end
describe '#avatar_type' do
let(:user) { create(:user) }
- it "should be true if avatar is image" do
+ it "is true if avatar is image" do
user.update_attribute(:avatar, 'uploads/avatar.png')
expect(user.avatar_type).to be_truthy
end
- it "should be false if avatar is html page" do
+ it "is false if avatar is html page" do
user.update_attribute(:avatar, 'uploads/avatar.html')
expect(user.avatar_type).to eq(["only images allowed"])
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index ddc49495eda..5c34b1b0a30 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -147,12 +147,12 @@ describe WikiPage, models: true do
@page = wiki.find_page("Delete Page")
end
- it "should delete the page" do
+ it "deletes the page" do
@page.delete
expect(wiki.pages).to be_empty
end
- it "should return true" do
+ it "returns true" do
expect(@page.delete).to eq(true)
end
end
@@ -183,7 +183,7 @@ describe WikiPage, models: true do
destroy_page("Title")
end
- it "should be replace a hyphen to a space" do
+ it "replaces a hyphen to a space" do
@page.title = "Import-existing-repositories-into-GitLab"
expect(@page.title).to eq("Import existing repositories into GitLab")
end
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
new file mode 100644
index 00000000000..d78494b76fa
--- /dev/null
+++ b/spec/requests/api/access_requests_spec.rb
@@ -0,0 +1,246 @@
+require 'spec_helper'
+
+describe API::AccessRequests, api: true do
+ include ApiHelpers
+
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let(:project) do
+ project = create(:project, :public, creator_id: master.id, namespace: master.namespace)
+ project.team << [developer, :developer]
+ project.team << [master, :master]
+ project.request_access(access_requester)
+ project
+ end
+
+ let(:group) do
+ group = create(:group, :public)
+ group.add_developer(developer)
+ group.add_owner(master)
+ group.request_access(access_requester)
+ group
+ end
+
+ shared_examples 'GET /:sources/:id/access_requests' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) }
+ end
+
+ context 'when authenticated as a non-master/owner' do
+ %i[developer access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ get api("/#{source_type.pluralize}/#{source.id}/access_requests", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'returns access requesters' do
+ get api("/#{source_type.pluralize}/#{source.id}/access_requests", master)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+ end
+ end
+ end
+
+ shared_examples 'POST /:sources/:id/access_requests' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger) }
+ end
+
+ context 'when authenticated as a member' do
+ %i[developer master].each do |type|
+ context "as a #{type}" do
+ it 'returns 400' do
+ expect do
+ user = public_send(type)
+ post api("/#{source_type.pluralize}/#{source.id}/access_requests", user)
+
+ expect(response).to have_http_status(400)
+ end.not_to change { source.requesters.count }
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as an access requester' do
+ it 'returns 400' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/access_requests", access_requester)
+
+ expect(response).to have_http_status(400)
+ end.not_to change { source.requesters.count }
+ end
+ end
+
+ context 'when authenticated as a stranger' do
+ it 'returns 201' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/access_requests", stranger)
+
+ expect(response).to have_http_status(201)
+ end.to change { source.requesters.count }.by(1)
+
+ # User attributes
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['name']).to eq(stranger.name)
+ expect(json_response['username']).to eq(stranger.username)
+ expect(json_response['state']).to eq(stranger.state)
+ expect(json_response['avatar_url']).to eq(stranger.avatar_url)
+ expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(stranger))
+
+ # Member attributes
+ expect(json_response['requested_at']).to be_present
+ end
+ end
+ end
+ end
+
+ shared_examples 'PUT /:sources/:id/access_requests/:user_id/approve' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", stranger) }
+ end
+
+ context 'when authenticated as a non-master/owner' do
+ %i[developer access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'returns 201' do
+ expect do
+ put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ # User attributes
+ expect(json_response['id']).to eq(access_requester.id)
+ expect(json_response['name']).to eq(access_requester.name)
+ expect(json_response['username']).to eq(access_requester.username)
+ expect(json_response['state']).to eq(access_requester.state)
+ expect(json_response['avatar_url']).to eq(access_requester.avatar_url)
+ expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(access_requester))
+
+ # Member attributes
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ end
+
+ context 'user_id does not match an existing access requester' do
+ it 'returns 404' do
+ expect do
+ put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{stranger.id}/approve", master)
+
+ expect(response).to have_http_status(404)
+ end.not_to change { source.members.count }
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples 'DELETE /:sources/:id/access_requests/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-master/owner' do
+ %i[developer stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as the access requester' do
+ it 'returns 200' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", access_requester)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.requesters.count }.by(-1)
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'returns 200' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}", master)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.requesters.count }.by(-1)
+ end
+
+ context 'user_id does not match an existing access requester' do
+ it 'returns 404' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/access_requests/#{stranger.id}", master)
+
+ expect(response).to have_http_status(404)
+ end.not_to change { source.requesters.count }
+ end
+ end
+ end
+ end
+ end
+
+ it_behaves_like 'GET /:sources/:id/access_requests', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/access_requests', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'POST /:sources/:id/access_requests', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'POST /:sources/:id/access_requests', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/access_requests/:user_id/approve', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/access_requests/:user_id/approve', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/access_requests/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/access_requests/:user_id', 'group' do
+ let(:source) { group }
+ end
+end
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index 831889afb6c..c65510fadec 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -41,19 +41,19 @@ describe API::Helpers, api: true do
describe ".current_user" do
describe "when authenticating using a user's private token" do
- it "should return nil for an invalid token" do
+ it "returns nil for an invalid token" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
expect(current_user).to be_nil
end
- it "should return nil for a user without access" do
+ it "returns nil for a user without access" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil
end
- it "should leave user as is when sudo not specified" do
+ it "leaves user as is when sudo not specified" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token
expect(current_user).to eq(user)
clear_env
@@ -65,19 +65,19 @@ describe API::Helpers, api: true do
describe "when authenticating using a user's personal access tokens" do
let(:personal_access_token) { create(:personal_access_token, user: user) }
- it "should return nil for an invalid token" do
+ it "returns nil for an invalid token" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token'
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
expect(current_user).to be_nil
end
- it "should return nil for a user without access" do
+ it "returns nil for a user without access" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil
end
- it "should leave user as is when sudo not specified" do
+ it "leaves user as is when sudo not specified" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
expect(current_user).to eq(user)
clear_env
@@ -100,7 +100,7 @@ describe API::Helpers, api: true do
end
end
- it "should change current user to sudo when admin" do
+ it "changes current user to sudo when admin" do
set_env(admin, user.id)
expect(current_user).to eq(user)
set_param(admin, user.id)
@@ -111,7 +111,7 @@ describe API::Helpers, api: true do
expect(current_user).to eq(user)
end
- it "should throw an error when the current user is not an admin and attempting to sudo" do
+ it "throws an error when the current user is not an admin and attempting to sudo" do
set_env(user, admin.id)
expect { current_user }.to raise_error(Exception)
set_param(user, admin.id)
@@ -122,7 +122,7 @@ describe API::Helpers, api: true do
expect { current_user }.to raise_error(Exception)
end
- it "should throw an error when the user cannot be found for a given id" do
+ it "throws an error when the user cannot be found for a given id" do
id = user.id + admin.id
expect(user.id).not_to eq(id)
expect(admin.id).not_to eq(id)
@@ -133,7 +133,7 @@ describe API::Helpers, api: true do
expect { current_user }.to raise_error(Exception)
end
- it "should throw an error when the user cannot be found for a given username" do
+ it "throws an error when the user cannot be found for a given username" do
username = "#{user.username}#{admin.username}"
expect(user.username).not_to eq(username)
expect(admin.username).not_to eq(username)
@@ -144,7 +144,7 @@ describe API::Helpers, api: true do
expect { current_user }.to raise_error(Exception)
end
- it "should handle sudo's to oneself" do
+ it "handles sudo's to oneself" do
set_env(admin, admin.id)
expect(current_user).to eq(admin)
set_param(admin, admin.id)
@@ -155,7 +155,7 @@ describe API::Helpers, api: true do
expect(current_user).to eq(admin)
end
- it "should handle multiple sudo's to oneself" do
+ it "handles multiple sudo's to oneself" do
set_env(admin, user.id)
expect(current_user).to eq(user)
expect(current_user).to eq(user)
@@ -171,7 +171,7 @@ describe API::Helpers, api: true do
expect(current_user).to eq(user)
end
- it "should handle multiple sudo's to oneself using string ids" do
+ it "handles multiple sudo's to oneself using string ids" do
set_env(admin, user.id.to_s)
expect(current_user).to eq(user)
expect(current_user).to eq(user)
@@ -183,7 +183,7 @@ describe API::Helpers, api: true do
end
describe '.sudo_identifier' do
- it "should return integers when input is an int" do
+ it "returns integers when input is an int" do
set_env(admin, '123')
expect(sudo_identifier).to eq(123)
set_env(admin, '0001234567890')
@@ -195,7 +195,7 @@ describe API::Helpers, api: true do
expect(sudo_identifier).to eq(1234567890)
end
- it "should return string when input is an is not an int" do
+ it "returns string when input is an is not an int" do
set_env(admin, '12.30')
expect(sudo_identifier).to eq("12.30")
set_env(admin, 'hello')
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 2b74dd4bbb0..73c268c0d1e 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -22,7 +22,7 @@ describe API::API, api: true do
expect(json_response.first['name']).to eq(award_emoji.name)
end
- it "should return a 404 error when issue id not found" do
+ it "returns a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/12345/award_emoji", user)
expect(response).to have_http_status(404)
@@ -124,13 +124,13 @@ describe API::API, api: true do
expect(json_response['user']['username']).to eq(user.username)
end
- it "should return a 400 bad request error if the name is not given" do
+ it "returns a 400 bad request error if the name is not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user)
expect(response).to have_http_status(400)
end
- it "should return a 401 unauthorized error if the user is not authenticated" do
+ it "returns a 401 unauthorized error if the user is not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji"), name: 'thumbsup'
expect(response).to have_http_status(401)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 719da27f919..9444138f93d 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -13,7 +13,7 @@ describe API::API, api: true do
let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
describe "GET /projects/:id/repository/branches" do
- it "should return an array of project branches" do
+ it "returns an array of project branches" do
project.repository.expire_cache
get api("/projects/#{project.id}/repository/branches", user)
@@ -25,7 +25,7 @@ describe API::API, api: true do
end
describe "GET /projects/:id/repository/branches/:branch" do
- it "should return the branch information for a single branch" do
+ it "returns the branch information for a single branch" do
get api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(200)
@@ -36,12 +36,12 @@ describe API::API, api: true do
expect(json_response['developers_can_merge']).to eq(false)
end
- it "should return a 403 error if guest" do
+ it "returns a 403 error if guest" do
get api("/projects/#{project.id}/repository/branches", user2)
expect(response).to have_http_status(403)
end
- it "should return a 404 error if branch is not available" do
+ it "returns a 404 error if branch is not available" do
get api("/projects/#{project.id}/repository/branches/unknown", user)
expect(response).to have_http_status(404)
end
@@ -112,7 +112,7 @@ describe API::API, api: true do
before do
project.repository.add_branch(user, protected_branch, 'master')
- create(:protected_branch, project: project, name: protected_branch, developers_can_push: true, developers_can_merge: true)
+ create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: protected_branch)
end
it 'updates that a developer can push' do
@@ -138,17 +138,17 @@ describe API::API, api: true do
end
end
- it "should return a 404 error if branch not found" do
+ it "returns a 404 error if branch not found" do
put api("/projects/#{project.id}/repository/branches/unknown/protect", user)
expect(response).to have_http_status(404)
end
- it "should return a 403 error if guest" do
+ it "returns a 403 error if guest" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2)
expect(response).to have_http_status(403)
end
- it "should return success when protect branch again" do
+ it "returns success when protect branch again" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user)
expect(response).to have_http_status(200)
@@ -156,7 +156,7 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/repository/branches/:branch/unprotect" do
- it "should unprotect a single branch" do
+ it "unprotects a single branch" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
expect(response).to have_http_status(200)
@@ -165,12 +165,12 @@ describe API::API, api: true do
expect(json_response['protected']).to eq(false)
end
- it "should return success when unprotect branch" do
+ it "returns success when unprotect branch" do
put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user)
expect(response).to have_http_status(404)
end
- it "should return success when unprotect branch again" do
+ it "returns success when unprotect branch again" do
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user)
expect(response).to have_http_status(200)
@@ -178,7 +178,7 @@ describe API::API, api: true do
end
describe "POST /projects/:id/repository/branches" do
- it "should create a new branch" do
+ it "creates a new branch" do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'feature1',
ref: branch_sha
@@ -189,14 +189,14 @@ describe API::API, api: true do
expect(json_response['commit']['id']).to eq(branch_sha)
end
- it "should deny for user without push access" do
+ it "denies for user without push access" do
post api("/projects/#{project.id}/repository/branches", user2),
branch_name: branch_name,
ref: branch_sha
expect(response).to have_http_status(403)
end
- it 'should return 400 if branch name is invalid' do
+ it 'returns 400 if branch name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new design',
ref: branch_sha
@@ -204,7 +204,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Branch name is invalid')
end
- it 'should return 400 if branch already exists' do
+ it 'returns 400 if branch already exists' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new_design1',
ref: branch_sha
@@ -217,7 +217,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Branch already exists')
end
- it 'should return 400 if ref name is invalid' do
+ it 'returns 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/branches", user),
branch_name: 'new_design3',
ref: 'foo'
@@ -231,25 +231,25 @@ describe API::API, api: true do
allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
end
- it "should remove branch" do
+ it "removes branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(200)
expect(json_response['branch_name']).to eq(branch_name)
end
- it 'should return 404 if branch not exists' do
+ it 'returns 404 if branch not exists' do
delete api("/projects/#{project.id}/repository/branches/foobar", user)
expect(response).to have_http_status(404)
end
- it "should remove protected branch" do
+ it "removes protected branch" do
project.protected_branches.create(name: branch_name)
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('Protected branch cant be removed')
end
- it "should not remove HEAD branch" do
+ it "does not remove HEAD branch" do
delete api("/projects/#{project.id}/repository/branches/master", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('Cannot remove HEAD branch')
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 86a7b242fbe..41503885dd9 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -9,7 +9,7 @@ describe API::API, api: true do
let!(:developer) { create(:project_member, :developer, user: user, project: project) }
let(:reporter) { create(:project_member, :reporter, project: project) }
let(:guest) { create(:project_member, :guest, project: project) }
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
let!(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
@@ -18,7 +18,7 @@ describe API::API, api: true do
before { get api("/projects/#{project.id}/builds?#{query}", api_user) }
context 'authorized user' do
- it 'should return project builds' do
+ it 'returns project builds' do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
end
@@ -84,7 +84,7 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
end
- it 'should return project builds for specific commit' do
+ it 'returns project builds for specific commit' do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq 2
@@ -113,7 +113,7 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
end
- it 'should not return project builds' do
+ it 'does not return project builds' do
expect(response).to have_http_status(401)
expect(json_response.except('message')).to be_empty
end
@@ -125,7 +125,7 @@ describe API::API, api: true do
before { get api("/projects/#{project.id}/builds/#{build.id}", api_user) }
context 'authorized user' do
- it 'should return specific build data' do
+ it 'returns specific build data' do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq('test')
end
@@ -134,7 +134,7 @@ describe API::API, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not return specific build data' do
+ it 'does not return specific build data' do
expect(response).to have_http_status(401)
end
end
@@ -152,7 +152,7 @@ describe API::API, api: true do
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
- it 'should return specific build artifacts' do
+ it 'returns specific build artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include(download_headers)
end
@@ -161,20 +161,24 @@ describe API::API, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not return specific build artifacts' do
+ it 'does not return specific build artifacts' do
expect(response).to have_http_status(401)
end
end
end
- it 'should not return build artifacts if not uploaded' do
+ it 'does not return build artifacts if not uploaded' do
expect(response).to have_http_status(404)
end
end
describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
let(:api_user) { reporter.user }
- let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+ let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ before do
+ build.success
+ end
def path_for_ref(ref = pipeline.ref, job = build.name)
api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
@@ -272,7 +276,7 @@ describe API::API, api: true do
end
context 'authorized user' do
- it 'should return specific build trace' do
+ it 'returns specific build trace' do
expect(response).to have_http_status(200)
expect(response.body).to eq(build.trace)
end
@@ -281,7 +285,7 @@ describe API::API, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not return specific build trace' do
+ it 'does not return specific build trace' do
expect(response).to have_http_status(401)
end
end
@@ -292,7 +296,7 @@ describe API::API, api: true do
context 'authorized user' do
context 'user with :update_build persmission' do
- it 'should cancel running or pending build' do
+ it 'cancels running or pending build' do
expect(response).to have_http_status(201)
expect(project.builds.first.status).to eq('canceled')
end
@@ -301,7 +305,7 @@ describe API::API, api: true do
context 'user without :update_build permission' do
let(:api_user) { reporter.user }
- it 'should not cancel build' do
+ it 'does not cancel build' do
expect(response).to have_http_status(403)
end
end
@@ -310,7 +314,7 @@ describe API::API, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not cancel build' do
+ it 'does not cancel build' do
expect(response).to have_http_status(401)
end
end
@@ -323,7 +327,7 @@ describe API::API, api: true do
context 'authorized user' do
context 'user with :update_build permission' do
- it 'should retry non-running build' do
+ it 'retries non-running build' do
expect(response).to have_http_status(201)
expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
@@ -333,7 +337,7 @@ describe API::API, api: true do
context 'user without :update_build permission' do
let(:api_user) { reporter.user }
- it 'should not retry build' do
+ it 'does not retry build' do
expect(response).to have_http_status(403)
end
end
@@ -342,7 +346,7 @@ describe API::API, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not retry build' do
+ it 'does not retry build' do
expect(response).to have_http_status(401)
end
end
@@ -356,14 +360,14 @@ describe API::API, api: true do
context 'build is erasable' do
let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
- it 'should erase build content' do
+ it 'erases build content' do
expect(response.status).to eq 201
expect(build.trace).to be_empty
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
end
- it 'should update build' do
+ it 'updates build' do
expect(build.reload.erased_at).to be_truthy
expect(build.reload.erased_by).to eq user
end
@@ -372,7 +376,7 @@ describe API::API, api: true do
context 'build is not erasable' do
let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
- it 'should respond with forbidden' do
+ it 'responds with forbidden' do
expect(response.status).to eq 403
end
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 2da01da7fa1..2d6093fec7a 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -99,7 +99,7 @@ describe API::CommitStatuses, api: true do
context "guest user" do
before { get api(get_url, guest) }
- it "should not return project commits" do
+ it "does not return project commits" do
expect(response).to have_http_status(403)
end
end
@@ -107,7 +107,7 @@ describe API::CommitStatuses, api: true do
context "unauthorized user" do
before { get api(get_url) }
- it "should not return project commits" do
+ it "does not return project commits" do
expect(response).to have_http_status(401)
end
end
@@ -179,7 +179,7 @@ describe API::CommitStatuses, api: true do
context 'reporter user' do
before { post api(post_url, reporter) }
- it 'should not create commit status' do
+ it 'does not create commit status' do
expect(response).to have_http_status(403)
end
end
@@ -187,7 +187,7 @@ describe API::CommitStatuses, api: true do
context 'guest user' do
before { post api(post_url, guest) }
- it 'should not create commit status' do
+ it 'does not create commit status' do
expect(response).to have_http_status(403)
end
end
@@ -195,7 +195,7 @@ describe API::CommitStatuses, api: true do
context 'unauthorized user' do
before { post api(post_url) }
- it 'should not create commit status' do
+ it 'does not create commit status' do
expect(response).to have_http_status(401)
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 5219c808791..7ca75d77673 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -17,7 +17,7 @@ describe API::API, api: true do
context "authorized user" do
before { project.team << [user2, :reporter] }
- it "should return project commits" do
+ it "returns project commits" do
get api("/projects/#{project.id}/repository/commits", user)
expect(response).to have_http_status(200)
@@ -27,14 +27,14 @@ describe API::API, api: true do
end
context "unauthorized user" do
- it "should not return project commits" do
+ it "does not return project commits" do
get api("/projects/#{project.id}/repository/commits")
expect(response).to have_http_status(401)
end
end
context "since optional parameter" do
- it "should return project commits since provided parameter" do
+ it "returns project commits since provided parameter" do
commits = project.repository.commits("master")
since = commits.second.created_at
@@ -47,7 +47,7 @@ describe API::API, api: true do
end
context "until optional parameter" do
- it "should return project commits until provided parameter" do
+ it "returns project commits until provided parameter" do
commits = project.repository.commits("master")
before = commits.second.created_at
@@ -60,7 +60,7 @@ describe API::API, api: true do
end
context "invalid xmlschema date parameters" do
- it "should return an invalid parameter error message" 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)
@@ -71,34 +71,51 @@ describe API::API, api: true do
describe "GET /projects:id/repository/commits/:sha" do
context "authorized user" do
- it "should return a commit by sha" do
+ it "returns a commit by sha" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['id']).to eq(project.repository.commit.id)
expect(json_response['title']).to eq(project.repository.commit.title)
+ expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions)
+ expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions)
+ expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total)
end
- it "should return a 404 error if not found" do
+ it "returns a 404 error if not found" do
get api("/projects/#{project.id}/repository/commits/invalid_sha", user)
expect(response).to have_http_status(404)
end
- it "should return nil for commit without CI" do
+ it "returns nil for commit without CI" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['status']).to be_nil
end
- it "should return status for CI" do
+ it "returns status for CI" do
pipeline = project.ensure_pipeline(project.repository.commit.sha, 'master')
+ pipeline.update(status: 'success')
+
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
expect(response).to have_http_status(200)
expect(json_response['status']).to eq(pipeline.status)
end
+
+ it "returns status for CI when pipeline is created" do
+ project.ensure_pipeline(project.repository.commit.sha, 'master')
+
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['status']).to be_nil
+ end
end
context "unauthorized user" do
- it "should not return the selected commit" do
+ it "does not return the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}")
expect(response).to have_http_status(401)
end
@@ -109,7 +126,7 @@ describe API::API, api: true do
context "authorized user" do
before { project.team << [user2, :reporter] }
- it "should return the diff of the selected commit" do
+ it "returns the diff of the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user)
expect(response).to have_http_status(200)
@@ -118,14 +135,14 @@ describe API::API, api: true do
expect(json_response.first.keys).to include "diff"
end
- it "should return a 404 error if invalid commit" do
+ it "returns a 404 error if invalid commit" do
get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user)
expect(response).to have_http_status(404)
end
end
context "unauthorized user" do
- it "should not return the diff of the selected commit" do
+ it "does not return the diff of the selected commit" do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff")
expect(response).to have_http_status(401)
end
@@ -134,7 +151,7 @@ describe API::API, api: true do
describe 'GET /projects:id/repository/commits/:sha/comments' do
context 'authorized user' do
- it 'should return merge_request comments' do
+ it 'returns merge_request comments' do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -143,14 +160,14 @@ describe API::API, api: true do
expect(json_response.first['author']['id']).to eq(user.id)
end
- it 'should return a 404 error if merge_request_id not found' do
+ it 'returns a 404 error if merge_request_id not found' do
get api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
expect(response).to have_http_status(404)
end
end
context 'unauthorized user' do
- it 'should not return the diff of the selected commit' do
+ it 'does not return the diff of the selected commit' do
get api("/projects/#{project.id}/repository/commits/1234ab/comments")
expect(response).to have_http_status(401)
end
@@ -159,7 +176,7 @@ describe API::API, api: true do
describe 'POST /projects:id/repository/commits/:sha/comments' do
context 'authorized user' do
- it 'should return comment' do
+ it 'returns comment' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
expect(response).to have_http_status(201)
expect(json_response['note']).to eq('My comment')
@@ -168,28 +185,28 @@ describe API::API, api: true do
expect(json_response['line_type']).to be_nil
end
- it 'should return the inline comment' do
- post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new'
+ it 'returns the inline comment' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 7, line_type: 'new'
expect(response).to have_http_status(201)
expect(json_response['note']).to eq('My comment')
- expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path)
+ expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path)
expect(json_response['line']).to eq(7)
expect(json_response['line_type']).to eq('new')
end
- it 'should return 400 if note is missing' do
+ it 'returns 400 if note is missing' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response).to have_http_status(400)
end
- it 'should return 404 if note is attached to non existent commit' do
+ it 'returns 404 if note is attached to non existent commit' do
post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
expect(response).to have_http_status(404)
end
end
context 'unauthorized user' do
- it 'should not return the diff of the selected commit' do
+ it 'does not return the diff of the selected commit' do
post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
expect(response).to have_http_status(401)
end
diff --git a/spec/requests/api/deploy_keys.rb b/spec/requests/api/deploy_keys.rb
deleted file mode 100644
index ac42288bc34..00000000000
--- a/spec/requests/api/deploy_keys.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-
-describe API::API, api: true do
- include ApiHelpers
-
- let(:user) { create(:user) }
- let(:project) { create(:project, creator_id: user.id) }
- let!(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
- let(:admin) { create(:admin) }
-
- describe 'GET /deploy_keys' do
- before { admin }
-
- context 'when unauthenticated' do
- it 'should return authentication error' do
- get api('/deploy_keys')
- expect(response.status).to eq(401)
- end
- end
-
- context 'when authenticated as non-admin user' do
- it 'should return a 403 error' do
- get api('/deploy_keys', user)
- expect(response.status).to eq(403)
- end
- end
-
- context 'when authenticated as admin' do
- it 'should return all deploy keys' do
- get api('/deploy_keys', admin)
- expect(response.status).to eq(200)
-
- expect(json_response).to be_an Array
- expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
- end
- end
- end
-end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
new file mode 100644
index 00000000000..7d8cc45327c
--- /dev/null
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -0,0 +1,160 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:project) { create(:project, creator_id: user.id) }
+ let(:deploy_key) { create(:deploy_key, public: true) }
+
+ let!(:deploy_keys_project) do
+ create(:deploy_keys_project, project: project, deploy_key: deploy_key)
+ end
+
+ describe 'GET /deploy_keys' do
+ context 'when unauthenticated' do
+ it 'should return authentication error' do
+ get api('/deploy_keys')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated as non-admin user' do
+ it 'should return a 403 error' do
+ get api('/deploy_keys', user)
+
+ expect(response.status).to eq(403)
+ end
+ end
+
+ context 'when authenticated as admin' do
+ it 'should return all deploy keys' do
+ get api('/deploy_keys', admin)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(deploy_keys_project.deploy_key.id)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/deploy_keys' do
+ before { deploy_key }
+
+ it 'should return array of ssh keys' do
+ get api("/projects/#{project.id}/deploy_keys", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['title']).to eq(deploy_key.title)
+ end
+ end
+
+ describe 'GET /projects/:id/deploy_keys/:key_id' do
+ it 'should return a single key' do
+ get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['title']).to eq(deploy_key.title)
+ end
+
+ it 'should return 404 Not Found with invalid ID' do
+ get api("/projects/#{project.id}/deploy_keys/404", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'POST /projects/:id/deploy_keys' do
+ it 'should not create an invalid ssh key' do
+ post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' }
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['key']).to eq([
+ 'can\'t be blank',
+ 'is too short (minimum is 0 characters)',
+ 'is invalid'
+ ])
+ end
+
+ it 'should not create a key without title' do
+ post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key'
+
+ expect(response).to have_http_status(400)
+ expect(json_response['message']['title']).to eq([
+ 'can\'t be blank',
+ 'is too short (minimum is 0 characters)'
+ ])
+ end
+
+ it 'should create new ssh key' do
+ key_attrs = attributes_for :another_key
+
+ expect do
+ post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
+ end.to change{ project.deploy_keys.count }.by(1)
+ end
+ end
+
+ describe 'DELETE /projects/:id/deploy_keys/:key_id' do
+ before { deploy_key }
+
+ it 'should delete existing key' do
+ expect do
+ delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
+ end.to change{ project.deploy_keys.count }.by(-1)
+ end
+
+ it 'should return 404 Not Found with invalid ID' do
+ delete api("/projects/#{project.id}/deploy_keys/404", admin)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'POST /projects/:id/deploy_keys/:key_id/enable' do
+ let(:project2) { create(:empty_project) }
+
+ context 'when the user can admin the project' do
+ it 'enables the key' do
+ expect do
+ post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", admin)
+ end.to change { project2.deploy_keys.count }.from(0).to(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['id']).to eq(deploy_key.id)
+ end
+ end
+
+ context 'when authenticated as non-admin user' do
+ it 'should return a 404 error' do
+ post api("/projects/#{project2.id}/deploy_keys/#{deploy_key.id}/enable", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/deploy_keys/:key_id/disable' do
+ context 'when the user can admin the project' do
+ it 'disables the key' do
+ expect do
+ delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", admin)
+ end.to change { project.deploy_keys.count }.from(1).to(0)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(deploy_key.id)
+ end
+ end
+
+ context 'when authenticated as non-admin user' do
+ it 'should return a 404 error' do
+ delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}/disable", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
new file mode 100644
index 00000000000..05e57905343
--- /dev/null
+++ b/spec/requests/api/environments_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:project) { create(:project, :private, namespace: user.namespace) }
+ let!(:environment) { create(:environment, project: project) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe 'GET /projects/:id/environments' do
+ context 'as member of the project' do
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api("/projects/#{project.id}/environments", user) }
+ end
+
+ it 'returns project environments' do
+ get api("/projects/#{project.id}/environments", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['name']).to eq(environment.name)
+ expect(json_response.first['external_url']).to eq(environment.external_url)
+ end
+ end
+
+ context 'as non member' do
+ it 'returns a 404 status code' do
+ get api("/projects/#{project.id}/environments", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/environments' do
+ context 'as a member' do
+ it 'creates a environment with valid params' do
+ post api("/projects/#{project.id}/environments", user), name: "mepmep"
+
+ expect(response).to have_http_status(201)
+ expect(json_response['name']).to eq('mepmep')
+ expect(json_response['external']).to be nil
+ end
+
+ it 'requires name to be passed' do
+ post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com'
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns a 400 if environment already exists' do
+ post api("/projects/#{project.id}/environments", user), name: environment.name
+
+ expect(response).to have_http_status(400)
+ end
+ end
+
+ context 'a non member' do
+ it 'rejects the request' do
+ post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com'
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns a 400 when the required params are missing' do
+ post api("/projects/12345/environments", non_member), external_url: 'http://env.git.com'
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/environments/:environment_id' do
+ it 'returns a 200 if name and external_url are changed' do
+ url = 'https://mepmep.whatever.ninja'
+ put api("/projects/#{project.id}/environments/#{environment.id}", user),
+ name: 'Mepmep', external_url: url
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq('Mepmep')
+ expect(json_response['external_url']).to eq(url)
+ end
+
+ it "won't update the external_url if only the name is passed" do
+ url = environment.external_url
+ put api("/projects/#{project.id}/environments/#{environment.id}", user),
+ name: 'Mepmep'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq('Mepmep')
+ expect(json_response['external_url']).to eq(url)
+ end
+
+ it 'returns a 404 if the environment does not exist' do
+ put api("/projects/#{project.id}/environments/12345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'DELETE /projects/:id/environments/:environment_id' do
+ context 'as a master' do
+ it 'returns a 200 for an existing environment' do
+ delete api("/projects/#{project.id}/environments/#{environment.id}", user)
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns a 404 for non existing id' do
+ delete api("/projects/#{project.id}/environments/12345", user)
+
+ expect(response).to have_http_status(404)
+ expect(json_response['message']).to eq('404 Not found')
+ end
+ end
+
+ context 'a non member' do
+ it 'rejects the request' do
+ delete api("/projects/#{project.id}/environments/#{environment.id}", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 2e5448143d5..2d1213df8a7 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -9,7 +9,7 @@ describe API::API, api: true do
before { project.team << [user, :developer] }
describe "GET /projects/:id/repository/files" do
- it "should return file info" do
+ it "returns file info" do
params = {
file_path: file_path,
ref: 'master',
@@ -23,12 +23,12 @@ describe API::API, api: true do
expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
end
- it "should return a 400 bad request if no params given" do
+ it "returns a 400 bad request if no params given" do
get api("/projects/#{project.id}/repository/files", user)
expect(response).to have_http_status(400)
end
- it "should return a 404 if such file does not exist" do
+ it "returns a 404 if such file does not exist" do
params = {
file_path: 'app/models/application.rb',
ref: 'master',
@@ -49,18 +49,18 @@ describe API::API, api: true do
}
end
- it "should create a new file in project repo" do
+ it "creates a new file in project repo" do
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response).to have_http_status(201)
expect(json_response['file_path']).to eq('newfile.rb')
end
- it "should return a 400 bad request if no params given" do
+ it "returns a 400 bad request if no params given" do
post api("/projects/#{project.id}/repository/files", user)
expect(response).to have_http_status(400)
end
- it "should return a 400 if editor fails to create file" do
+ it "returns a 400 if editor fails to create file" do
allow_any_instance_of(Repository).to receive(:commit_file).
and_return(false)
@@ -79,13 +79,13 @@ describe API::API, api: true do
}
end
- it "should update existing file in project repo" do
+ it "updates existing file in project repo" do
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response).to have_http_status(200)
expect(json_response['file_path']).to eq(file_path)
end
- it "should return a 400 bad request if no params given" do
+ it "returns a 400 bad request if no params given" do
put api("/projects/#{project.id}/repository/files", user)
expect(response).to have_http_status(400)
end
@@ -100,18 +100,18 @@ describe API::API, api: true do
}
end
- it "should delete existing file in project repo" do
+ it "deletes existing file in project repo" do
delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response).to have_http_status(200)
expect(json_response['file_path']).to eq(file_path)
end
- it "should return a 400 bad request if no params given" do
+ it "returns a 400 bad request if no params given" do
delete api("/projects/#{project.id}/repository/files", user)
expect(response).to have_http_status(400)
end
- it "should return a 400 if fails to create file" do
+ it "returns a 400 if fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index a9f5aa924b7..f802fcd2d2e 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -20,7 +20,7 @@ describe API::API, api: true do
before { user3 }
context 'when authenticated' do
- it 'should fork if user has sufficient access to project' do
+ it 'forks if user has sufficient access to project' do
post api("/projects/fork/#{project.id}", user2)
expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name)
@@ -30,7 +30,7 @@ describe API::API, api: true do
expect(json_response['forked_from_project']['id']).to eq(project.id)
end
- it 'should fork if user is admin' do
+ it 'forks if user is admin' do
post api("/projects/fork/#{project.id}", admin)
expect(response).to have_http_status(201)
expect(json_response['name']).to eq(project.name)
@@ -40,20 +40,20 @@ describe API::API, api: true do
expect(json_response['forked_from_project']['id']).to eq(project.id)
end
- it 'should fail on missing project access for the project to fork' do
+ it 'fails on missing project access for the project to fork' do
post api("/projects/fork/#{project.id}", user3)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
end
- it 'should fail if forked project exists in the user namespace' do
+ it 'fails if forked project exists in the user namespace' do
post api("/projects/fork/#{project.id}", user)
expect(response).to have_http_status(409)
expect(json_response['message']['name']).to eq(['has already been taken'])
expect(json_response['message']['path']).to eq(['has already been taken'])
end
- it 'should fail if project to fork from does not exist' do
+ it 'fails if project to fork from does not exist' do
post api('/projects/fork/424242', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
@@ -61,7 +61,7 @@ describe API::API, api: true do
end
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
post api("/projects/fork/#{project.id}")
expect(response).to have_http_status(401)
expect(json_response['message']).to eq('401 Unauthorized')
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
deleted file mode 100644
index 52f9e7d4681..00000000000
--- a/spec/requests/api/group_members_spec.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'spec_helper'
-
-describe API::API, api: true do
- include ApiHelpers
-
- let(:owner) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let(:stranger) { create(:user) }
-
- let!(:group_with_members) do
- group = create(:group, :private)
- group.add_users([reporter.id], GroupMember::REPORTER)
- group.add_users([developer.id], GroupMember::DEVELOPER)
- group.add_users([master.id], GroupMember::MASTER)
- group.add_users([guest.id], GroupMember::GUEST)
- group
- end
-
- let!(:group_no_members) { create(:group) }
-
- before do
- group_with_members.add_owner owner
- group_no_members.add_owner owner
- end
-
- describe "GET /groups/:id/members" do
- context "when authenticated as user that is part or the group" do
- it "each user: should return an array of members groups of group3" do
- [owner, master, developer, reporter, guest].each do |user|
- get api("/groups/#{group_with_members.id}/members", user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.size).to eq(5)
- expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER)
- expect(json_response.find { |e| e['id'] == reporter.id }['access_level']).to eq(GroupMember::REPORTER)
- expect(json_response.find { |e| e['id'] == developer.id }['access_level']).to eq(GroupMember::DEVELOPER)
- expect(json_response.find { |e| e['id'] == master.id }['access_level']).to eq(GroupMember::MASTER)
- expect(json_response.find { |e| e['id'] == guest.id }['access_level']).to eq(GroupMember::GUEST)
- end
- end
-
- it 'users not part of the group should get access error' do
- get api("/groups/#{group_with_members.id}/members", stranger)
-
- expect(response).to have_http_status(404)
- end
- end
- end
-
- describe "POST /groups/:id/members" do
- context "when not a member of the group" do
- it "should not add guest as member of group_no_members when adding being done by person outside the group" do
- post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
- expect(response).to have_http_status(403)
- end
- end
-
- context "when a member of the group" do
- it "should return ok and add new member" do
- new_user = create(:user)
-
- expect do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
- end.to change { group_no_members.members.count }.by(1)
-
- expect(response).to have_http_status(201)
- expect(json_response['name']).to eq(new_user.name)
- expect(json_response['access_level']).to eq(GroupMember::MASTER)
- end
-
- it "should not allow guest to modify group members" do
- new_user = create(:user)
-
- expect do
- post api("/groups/#{group_with_members.id}/members", guest), user_id: new_user.id, access_level: GroupMember::MASTER
- end.not_to change { group_with_members.members.count }
-
- expect(response).to have_http_status(403)
- end
-
- it "should return error if member already exists" do
- post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
- expect(response).to have_http_status(409)
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
- expect(response).to have_http_status(400)
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
- expect(response).to have_http_status(400)
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
- expect(response).to have_http_status(422)
- end
- end
- end
-
- describe 'PUT /groups/:id/members/:user_id' do
- context 'when not a member of the group' do
- it 'should return a 409 error if the user is not a group member' do
- put(
- api("/groups/#{group_no_members.id}/members/#{developer.id}",
- owner), access_level: GroupMember::MASTER
- )
- expect(response).to have_http_status(404)
- end
- end
-
- context 'when a member of the group' do
- it 'should return ok and update member access level' do
- put(
- api("/groups/#{group_with_members.id}/members/#{reporter.id}",
- owner),
- access_level: GroupMember::MASTER
- )
-
- expect(response).to have_http_status(200)
-
- get api("/groups/#{group_with_members.id}/members", owner)
- json_reporter = json_response.find do |e|
- e['id'] == reporter.id
- end
-
- expect(json_reporter['access_level']).to eq(GroupMember::MASTER)
- end
-
- it 'should not allow guest to modify group members' do
- put(
- api("/groups/#{group_with_members.id}/members/#{developer.id}",
- guest),
- access_level: GroupMember::MASTER
- )
-
- expect(response).to have_http_status(403)
-
- get api("/groups/#{group_with_members.id}/members", owner)
- json_developer = json_response.find do |e|
- e['id'] == developer.id
- end
-
- expect(json_developer['access_level']).to eq(GroupMember::DEVELOPER)
- end
-
- it 'should return a 400 error when access level is not given' do
- put(
- api("/groups/#{group_with_members.id}/members/#{master.id}", owner)
- )
- expect(response).to have_http_status(400)
- end
-
- it 'should return a 422 error when access level is not known' do
- put(
- api("/groups/#{group_with_members.id}/members/#{master.id}", owner),
- access_level: 1234
- )
- expect(response).to have_http_status(422)
- end
- end
- end
-
- describe 'DELETE /groups/:id/members/:user_id' do
- context 'when not a member of the group' do
- it "should not delete guest's membership of group_with_members" do
- random_user = create(:user)
- delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
-
- expect(response).to have_http_status(404)
- end
- end
-
- context "when a member of the group" do
- it "should delete guest's membership of group" do
- expect do
- delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
- end.to change { group_with_members.members.count }.by(-1)
-
- expect(response).to have_http_status(200)
- end
-
- it "should return a 404 error when user id is not known" do
- delete api("/groups/#{group_with_members.id}/members/1328", owner)
- expect(response).to have_http_status(404)
- end
-
- it "should not allow guest to modify group members" do
- delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest)
- expect(response).to have_http_status(403)
- end
- end
- end
-end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c2c94040ece..4860b23c2ed 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -21,14 +21,14 @@ describe API::API, api: true do
describe "GET /groups" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/groups")
expect(response).to have_http_status(401)
end
end
context "when authenticated as user" do
- it "normal user: should return an array of groups of user1" do
+ it "normal user: returns an array of groups of user1" do
get api("/groups", user1)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -38,7 +38,7 @@ describe API::API, api: true do
end
context "when authenticated as admin" do
- it "admin: should return an array of all groups" do
+ it "admin: returns an array of all groups" do
get api("/groups", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -70,12 +70,12 @@ describe API::API, api: true do
expect(json_response['shared_projects'][0]['id']).to eq(project.id)
end
- it "should not return a non existing group" do
+ it "does not return a non existing group" do
get api("/groups/1328", user1)
expect(response).to have_http_status(404)
end
- it "should not return a group not attached to user1" do
+ it "does not return a group not attached to user1" do
get api("/groups/#{group2.id}", user1)
expect(response).to have_http_status(404)
@@ -83,31 +83,31 @@ describe API::API, api: true do
end
context "when authenticated as admin" do
- it "should return any existing group" do
+ it "returns any existing group" do
get api("/groups/#{group2.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(group2.name)
end
- it "should not return a non existing group" do
+ it "does not return a non existing group" do
get api("/groups/1328", admin)
expect(response).to have_http_status(404)
end
end
context 'when using group path in URL' do
- it 'should return any existing group' do
+ it 'returns any existing group' do
get api("/groups/#{group1.path}", admin)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(group1.name)
end
- it 'should not return a non existing group' do
+ it 'does not return a non existing group' do
get api('/groups/unknown', admin)
expect(response).to have_http_status(404)
end
- it 'should not return a group not attached to user1' do
+ it 'does not return a group not attached to user1' do
get api("/groups/#{group2.path}", user1)
expect(response).to have_http_status(404)
@@ -161,7 +161,7 @@ describe API::API, api: true do
describe "GET /groups/:id/projects" do
context "when authenticated as user" do
- it "should return the group's projects" do
+ it "returns the group's projects" do
get api("/groups/#{group1.id}/projects", user1)
expect(response).to have_http_status(200)
@@ -170,12 +170,12 @@ describe API::API, api: true do
expect(project_names).to match_array([project1.name, project3.name])
end
- it "should not return a non existing group" do
+ it "does not return a non existing group" do
get api("/groups/1328/projects", user1)
expect(response).to have_http_status(404)
end
- it "should not return a group not attached to user1" do
+ it "does not return a group not attached to user1" do
get api("/groups/#{group2.id}/projects", user1)
expect(response).to have_http_status(404)
@@ -215,12 +215,12 @@ describe API::API, api: true do
expect(project_names).to match_array([project1.name, project3.name])
end
- it 'should not return a non existing group' do
+ it 'does not return a non existing group' do
get api('/groups/unknown/projects', admin)
expect(response).to have_http_status(404)
end
- it 'should not return a group not attached to user1' do
+ it 'does not return a group not attached to user1' do
get api("/groups/#{group2.path}/projects", user1)
expect(response).to have_http_status(404)
@@ -230,30 +230,30 @@ describe API::API, api: true do
describe "POST /groups" do
context "when authenticated as user without group permissions" do
- it "should not create group" do
+ it "does not create group" do
post api("/groups", user1), attributes_for(:group)
expect(response).to have_http_status(403)
end
end
context "when authenticated as user with group permissions" do
- it "should create group" do
+ it "creates group" do
post api("/groups", user3), attributes_for(:group)
expect(response).to have_http_status(201)
end
- it "should not create group, duplicate" do
+ it "does not create group, duplicate" do
post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
expect(response).to have_http_status(400)
expect(response.message).to eq("Bad Request")
end
- it "should return 400 bad request error if name not given" do
+ it "returns 400 bad request error if name not given" do
post api("/groups", user3), { path: group2.path }
expect(response).to have_http_status(400)
end
- it "should return 400 bad request error if path not given" do
+ it "returns 400 bad request error if path not given" do
post api("/groups", user3), { name: 'test' }
expect(response).to have_http_status(400)
end
@@ -262,24 +262,24 @@ describe API::API, api: true do
describe "DELETE /groups/:id" do
context "when authenticated as user" do
- it "should remove group" do
+ it "removes group" do
delete api("/groups/#{group1.id}", user1)
expect(response).to have_http_status(200)
end
- it "should not remove a group if not an owner" do
+ it "does not remove a group if not an owner" do
user4 = create(:user)
group1.add_master(user4)
delete api("/groups/#{group1.id}", user3)
expect(response).to have_http_status(403)
end
- it "should not remove a non existing group" do
+ it "does not remove a non existing group" do
delete api("/groups/1328", user1)
expect(response).to have_http_status(404)
end
- it "should not remove a group not attached to user1" do
+ it "does not remove a group not attached to user1" do
delete api("/groups/#{group2.id}", user1)
expect(response).to have_http_status(404)
@@ -287,12 +287,12 @@ describe API::API, api: true do
end
context "when authenticated as admin" do
- it "should remove any existing group" do
+ it "removes any existing group" do
delete api("/groups/#{group2.id}", admin)
expect(response).to have_http_status(200)
end
- it "should not remove a non existing group" do
+ it "does not remove a non existing group" do
delete api("/groups/1328", admin)
expect(response).to have_http_status(404)
end
@@ -308,14 +308,14 @@ describe API::API, api: true do
end
context "when authenticated as user" do
- it "should not transfer project to group" do
+ it "does not transfer project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", user2)
expect(response).to have_http_status(403)
end
end
context "when authenticated as admin" do
- it "should transfer project to group" do
+ it "transfers project to group" do
post api("/groups/#{group1.id}/projects/#{project.id}", admin)
expect(response).to have_http_status(201)
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index f6f85d6e95e..be52f88831f 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -275,6 +275,24 @@ describe API::API, api: true do
end
end
+ describe 'GET /internal/merge_request_urls' do
+ let(:repo_name) { "#{project.namespace.name}/#{project.path}" }
+ let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") }
+
+ before do
+ project.team << [user, :developer]
+ get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token
+ end
+
+ it 'returns link to create new merge request' do
+ expect(json_response).to match [{
+ "branch_name" => "new_branch",
+ "url" => "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "new_merge_request" => true
+ }]
+ end
+ end
+
def pull(key, project, protocol = 'ssh')
post(
api("/internal/allowed"),
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 12f2cfa6942..a40e1a93b71 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -49,28 +49,28 @@ describe API::API, api: true do
describe "GET /issues" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/issues")
expect(response).to have_http_status(401)
end
end
context "when authenticated" do
- it "should return an array of issues" do
+ it "returns an array of issues" do
get api("/issues", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(issue.title)
end
- it "should add pagination headers and keep query params" do
+ it "adds pagination headers and keep query params" do
get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq(
'<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token]
)
end
- it 'should return an array of closed issues' do
+ it 'returns an array of closed issues' do
get api('/issues?state=closed', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -78,7 +78,7 @@ describe API::API, api: true do
expect(json_response.first['id']).to eq(closed_issue.id)
end
- it 'should return an array of opened issues' do
+ it 'returns an array of opened issues' do
get api('/issues?state=opened', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -86,7 +86,7 @@ describe API::API, api: true do
expect(json_response.first['id']).to eq(issue.id)
end
- it 'should return an array of all issues' do
+ it 'returns an array of all issues' do
get api('/issues?state=all', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -95,7 +95,7 @@ describe API::API, api: true do
expect(json_response.second['id']).to eq(closed_issue.id)
end
- it 'should return an array of labeled issues' do
+ it 'returns an array of labeled issues' do
get api("/issues?labels=#{label.title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -103,7 +103,7 @@ describe API::API, api: true do
expect(json_response.first['labels']).to eq([label.title])
end
- it 'should return an array of labeled issues when at least one label matches' do
+ it 'returns an array of labeled issues when at least one label matches' do
get api("/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -111,14 +111,14 @@ describe API::API, api: true do
expect(json_response.first['labels']).to eq([label.title])
end
- it 'should return an empty array if no issue matches labels' do
+ it 'returns an empty array if no issue matches labels' do
get api('/issues?labels=foo,bar', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- it 'should return an array of labeled issues matching given state' do
+ it 'returns an array of labeled issues matching given state' do
get api("/issues?labels=#{label.title}&state=opened", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -127,7 +127,7 @@ describe API::API, api: true do
expect(json_response.first['state']).to eq('opened')
end
- it 'should return an empty array if no issue matches labels and state filters' do
+ it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues?labels=#{label.title}&state=closed", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -282,7 +282,7 @@ describe API::API, api: true do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
- it 'should return project issues without confidential issues for non project members' do
+ it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -290,7 +290,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return project issues without confidential issues for project members with guest role' do
+ it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -298,7 +298,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return project confidential issues for author' do
+ it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -306,7 +306,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return project confidential issues for assignee' do
+ it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -314,7 +314,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return project issues with confidential issues for project members' do
+ it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -322,7 +322,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return project confidential issues for admin' do
+ it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -330,7 +330,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title)
end
- it 'should return an array of labeled project issues' do
+ it 'returns an array of labeled project issues' do
get api("#{base_url}/issues?labels=#{label.title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -338,7 +338,7 @@ describe API::API, api: true do
expect(json_response.first['labels']).to eq([label.title])
end
- it 'should return an array of labeled project issues when at least one label matches' do
+ it 'returns an array of labeled project issues when at least one label matches' do
get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -346,28 +346,28 @@ describe API::API, api: true do
expect(json_response.first['labels']).to eq([label.title])
end
- it 'should return an empty array if no project issue matches labels' do
+ it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues?labels=foo,bar", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- it 'should return an empty array if no issue matches milestone' do
+ it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- it 'should return an empty array if milestone does not exist' do
+ it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues?milestone=foo", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(0)
end
- it 'should return an array of issues in given milestone' do
+ it 'returns an array of issues in given milestone' do
get api("#{base_url}/issues?milestone=#{title}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -376,7 +376,7 @@ describe API::API, api: true do
expect(json_response.second['id']).to eq(closed_issue.id)
end
- it 'should return an array of issues matching state in milestone' do
+ it 'returns an array of issues matching state in milestone' do
get api("#{base_url}/issues?milestone=#{milestone.title}"\
'&state=closed', user)
expect(response).to have_http_status(200)
@@ -405,7 +405,7 @@ describe API::API, api: true do
expect(json_response['author']).to be_a Hash
end
- it "should return a project issue by id" do
+ it "returns a project issue by id" do
get api("/projects/#{project.id}/issues/#{issue.id}", user)
expect(response).to have_http_status(200)
@@ -413,7 +413,7 @@ describe API::API, api: true do
expect(json_response['iid']).to eq(issue.iid)
end
- it 'should return a project issue by iid' do
+ it 'returns a project issue by iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user)
expect(response.status).to eq 200
expect(json_response.first['title']).to eq issue.title
@@ -421,44 +421,44 @@ describe API::API, api: true do
expect(json_response.first['iid']).to eq issue.iid
end
- it "should return 404 if issue id not found" do
+ it "returns 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user)
expect(response).to have_http_status(404)
end
context 'confidential issues' do
- it "should return 404 for non project members" do
+ it "returns 404 for non project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
expect(response).to have_http_status(404)
end
- it "should return 404 for project members with guest role" do
+ it "returns 404 for project members with guest role" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest)
expect(response).to have_http_status(404)
end
- it "should return confidential issue for project members" do
+ it "returns confidential issue for project members" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
- it "should return confidential issue for author" do
+ it "returns confidential issue for author" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
- it "should return confidential issue for assignee" do
+ it "returns confidential issue for assignee" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
expect(json_response['iid']).to eq(confidential_issue.iid)
end
- it "should return confidential issue for admin" do
+ it "returns confidential issue for admin" do
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(confidential_issue.title)
@@ -468,7 +468,7 @@ describe API::API, api: true do
end
describe "POST /projects/:id/issues" do
- it "should create a new project issue" do
+ it "creates a new project issue" do
post api("/projects/#{project.id}/issues", user),
title: 'new issue', labels: 'label, label2'
expect(response).to have_http_status(201)
@@ -477,12 +477,12 @@ describe API::API, api: true do
expect(json_response['labels']).to eq(['label', 'label2'])
end
- it "should return a 400 bad request if title not given" do
+ it "returns a 400 bad request if title not given" do
post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
expect(response).to have_http_status(400)
end
- it 'should allow special label names' do
+ it 'allows special label names' do
post api("/projects/#{project.id}/issues", user),
title: 'new issue',
labels: 'label, label?, label&foo, ?, &'
@@ -494,7 +494,7 @@ describe API::API, api: true do
expect(json_response['labels']).to include '&'
end
- it 'should return 400 if title is too long' do
+ it 'returns 400 if title is too long' do
post api("/projects/#{project.id}/issues", user),
title: 'g' * 256
expect(response).to have_http_status(400)
@@ -531,10 +531,8 @@ describe API::API, api: true do
describe 'POST /projects/:id/issues with spam filtering' do
before do
- Grape::Endpoint.before_each do |endpoint|
- allow(endpoint).to receive(:check_for_spam?).and_return(true)
- allow(endpoint).to receive(:is_spam?).and_return(true)
- end
+ allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
+ allow_any_instance_of(AkismetService).to receive_messages(is_spam?: true)
end
let(:params) do
@@ -545,7 +543,7 @@ describe API::API, api: true do
}
end
- it "should not create a new project issue" do
+ it "does not create a new project issue" do
expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
expect(response).to have_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
@@ -556,12 +554,11 @@ describe API::API, api: true do
expect(spam_logs[0].description).to eq('content here')
expect(spam_logs[0].user).to eq(user)
expect(spam_logs[0].noteable_type).to eq('Issue')
- expect(spam_logs[0].project_id).to eq(project.id)
end
end
describe "PUT /projects/:id/issues/:issue_id to update only title" do
- it "should update a project issue" do
+ it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title'
expect(response).to have_http_status(200)
@@ -569,13 +566,13 @@ describe API::API, api: true do
expect(json_response['title']).to eq('updated title')
end
- it "should return 404 error if issue id not found" do
+ it "returns 404 error if issue id not found" do
put api("/projects/#{project.id}/issues/44444", user),
title: 'updated title'
expect(response).to have_http_status(404)
end
- it 'should allow special label names' do
+ it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title',
labels: 'label, label?, label&foo, ?, &'
@@ -589,33 +586,33 @@ describe API::API, api: true do
end
context 'confidential issues' do
- it "should return 403 for non project members" do
+ it "returns 403 for non project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
title: 'updated title'
expect(response).to have_http_status(403)
end
- it "should return 403 for project members with guest role" do
+ it "returns 403 for project members with guest role" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", guest),
title: 'updated title'
expect(response).to have_http_status(403)
end
- it "should update a confidential issue for project members" do
+ it "updates a confidential issue for project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
- it "should update a confidential issue for author" do
+ it "updates a confidential issue for author" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
- it "should update a confidential issue for admin" do
+ it "updates a confidential issue for admin" do
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
title: 'updated title'
expect(response).to have_http_status(200)
@@ -628,21 +625,21 @@ describe API::API, api: true do
let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) }
- it 'should not update labels if not present' do
+ it 'does not update labels if not present' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([label.title])
end
- it 'should remove all labels' do
+ it 'removes all labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: ''
expect(response).to have_http_status(200)
expect(json_response['labels']).to eq([])
end
- it 'should update labels' do
+ it 'updates labels' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'foo,bar'
expect(response).to have_http_status(200)
@@ -650,7 +647,7 @@ describe API::API, api: true do
expect(json_response['labels']).to include 'bar'
end
- it 'should allow special label names' do
+ it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&'
expect(response.status).to eq(200)
@@ -664,7 +661,7 @@ describe API::API, api: true do
expect(json_response['labels']).to include '&'
end
- it 'should return 400 if title is too long' do
+ it 'returns 400 if title is too long' do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
title: 'g' * 256
expect(response).to have_http_status(400)
@@ -675,7 +672,7 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/issues/:issue_id to update state and label" do
- it "should update a project issue" do
+ it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
labels: 'label2', state_event: "close"
expect(response).to have_http_status(200)
diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb
index 1861882d59e..893ed5c2b10 100644
--- a/spec/requests/api/keys_spec.rb
+++ b/spec/requests/api/keys_spec.rb
@@ -12,20 +12,20 @@ describe API::API, api: true do
before { admin }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api("/keys/#{key.id}")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should return 404 for non-existing key' do
+ it 'returns 404 for non-existing key' do
get api('/keys/999999', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
- it 'should return single ssh key with user information' do
+ it 'returns single ssh key with user information' do
user.keys << key
user.save
get api("/keys/#{key.id}", admin)
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 63636b4a1b6..83789223019 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/labels' do
- it 'should return project labels' do
+ it 'returns project labels' do
get api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -22,7 +22,7 @@ describe API::API, api: true do
end
describe 'POST /projects/:id/labels' do
- it 'should return created label when all params' do
+ it 'returns created label when all params' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAABB',
@@ -33,7 +33,7 @@ describe API::API, api: true do
expect(json_response['description']).to eq('test')
end
- it 'should return created label when only required params' do
+ it 'returns created label when only required params' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo & Bar',
color: '#FFAABB'
@@ -43,17 +43,17 @@ describe API::API, api: true do
expect(json_response['description']).to be_nil
end
- it 'should return a 400 bad request if name not given' do
+ it 'returns a 400 bad request if name not given' do
post api("/projects/#{project.id}/labels", user), color: '#FFAABB'
expect(response).to have_http_status(400)
end
- it 'should return a 400 bad request if color not given' do
+ it 'returns a 400 bad request if color not given' do
post api("/projects/#{project.id}/labels", user), name: 'Foobar'
expect(response).to have_http_status(400)
end
- it 'should return 400 for invalid color' do
+ it 'returns 400 for invalid color' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAA'
@@ -61,7 +61,7 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
- it 'should return 400 for too long color code' do
+ it 'returns 400 for too long color code' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAAFFFF'
@@ -69,7 +69,7 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
- it 'should return 400 for invalid name' do
+ it 'returns 400 for invalid name' do
post api("/projects/#{project.id}/labels", user),
name: ',',
color: '#FFAABB'
@@ -77,7 +77,7 @@ describe API::API, api: true do
expect(json_response['message']['title']).to eq(['is invalid'])
end
- it 'should return 409 if label already exists' do
+ it 'returns 409 if label already exists' do
post api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FFAABB'
@@ -87,25 +87,25 @@ describe API::API, api: true do
end
describe 'DELETE /projects/:id/labels' do
- it 'should return 200 for existing label' do
+ it 'returns 200 for existing label' do
delete api("/projects/#{project.id}/labels", user), name: 'label1'
expect(response).to have_http_status(200)
end
- it 'should return 404 for non existing label' do
+ it 'returns 404 for non existing label' do
delete api("/projects/#{project.id}/labels", user), name: 'label2'
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
- it 'should return 400 for wrong parameters' do
+ it 'returns 400 for wrong parameters' do
delete api("/projects/#{project.id}/labels", user)
expect(response).to have_http_status(400)
end
end
describe 'PUT /projects/:id/labels' do
- it 'should return 200 if name and colors and description are changed' do
+ it 'returns 200 if name and colors and description are changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
new_name: 'New Label',
@@ -117,7 +117,7 @@ describe API::API, api: true do
expect(json_response['description']).to eq('test')
end
- it 'should return 200 if name is changed' do
+ it 'returns 200 if name is changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
new_name: 'New Label'
@@ -126,7 +126,7 @@ describe API::API, api: true do
expect(json_response['color']).to eq(label1.color)
end
- it 'should return 200 if colors is changed' do
+ it 'returns 200 if colors is changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FFFFFF'
@@ -135,7 +135,7 @@ describe API::API, api: true do
expect(json_response['color']).to eq('#FFFFFF')
end
- it 'should return 200 if description is changed' do
+ it 'returns 200 if description is changed' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
description: 'test'
@@ -144,27 +144,27 @@ describe API::API, api: true do
expect(json_response['description']).to eq('test')
end
- it 'should return 404 if label does not exist' do
+ it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
name: 'label2',
new_name: 'label3'
expect(response).to have_http_status(404)
end
- it 'should return 400 if no label name given' do
+ it 'returns 400 if no label name given' do
put api("/projects/#{project.id}/labels", user), new_name: 'label2'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "name" not given')
end
- it 'should return 400 if no new parameters given' do
+ it 'returns 400 if no new parameters given' do
put api("/projects/#{project.id}/labels", user), name: 'label1'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('Required parameters '\
'"new_name" or "color" missing')
end
- it 'should return 400 for invalid name' do
+ it 'returns 400 for invalid name' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
new_name: ',',
@@ -173,7 +173,7 @@ describe API::API, api: true do
expect(json_response['message']['title']).to eq(['is invalid'])
end
- it 'should return 400 when color code is too short' do
+ it 'returns 400 when color code is too short' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FF'
@@ -181,7 +181,7 @@ describe API::API, api: true do
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
- it 'should return 400 for too long color code' do
+ it 'returns 400 for too long color code' do
post api("/projects/#{project.id}/labels", user),
name: 'Foo',
color: '#FFAAFFFF'
@@ -192,7 +192,7 @@ describe API::API, api: true do
describe "POST /projects/:id/labels/:label_id/subscription" do
context "when label_id is a label title" do
- it "should subscribe to the label" do
+ it "subscribes to the label" do
post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
expect(response).to have_http_status(201)
@@ -202,7 +202,7 @@ describe API::API, api: true do
end
context "when label_id is a label ID" do
- it "should subscribe to the label" do
+ it "subscribes to the label" do
post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response).to have_http_status(201)
@@ -214,7 +214,7 @@ describe API::API, api: true do
context "when user is already subscribed to label" do
before { label1.subscribe(user) }
- it "should return 304" do
+ it "returns 304" do
post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response).to have_http_status(304)
@@ -222,7 +222,7 @@ describe API::API, api: true do
end
context "when label ID is not found" do
- it "should a return 404 error" do
+ it "returns 404 error" do
post api("/projects/#{project.id}/labels/1234/subscription", user)
expect(response).to have_http_status(404)
@@ -234,7 +234,7 @@ describe API::API, api: true do
before { label1.subscribe(user) }
context "when label_id is a label title" do
- it "should unsubscribe from the label" do
+ it "unsubscribes from the label" do
delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user)
expect(response).to have_http_status(200)
@@ -244,7 +244,7 @@ describe API::API, api: true do
end
context "when label_id is a label ID" do
- it "should unsubscribe from the label" do
+ it "unsubscribes from the label" do
delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response).to have_http_status(200)
@@ -256,7 +256,7 @@ describe API::API, api: true do
context "when user is already unsubscribed from label" do
before { label1.unsubscribe(user) }
- it "should return 304" do
+ it "returns 304" do
delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user)
expect(response).to have_http_status(304)
@@ -264,7 +264,7 @@ describe API::API, api: true do
end
context "when label ID is not found" do
- it "should a return 404 error" do
+ it "returns 404 error" do
delete api("/projects/#{project.id}/labels/1234/subscription", user)
expect(response).to have_http_status(404)
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
new file mode 100644
index 00000000000..a56ee30f7b1
--- /dev/null
+++ b/spec/requests/api/members_spec.rb
@@ -0,0 +1,312 @@
+require 'spec_helper'
+
+describe API::Members, api: true do
+ include ApiHelpers
+
+ let(:master) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let(:project) do
+ project = create(:project, :public, creator_id: master.id, namespace: master.namespace)
+ project.team << [developer, :developer]
+ project.team << [master, :master]
+ project.request_access(access_requester)
+ project
+ end
+
+ let!(:group) do
+ group = create(:group, :public)
+ group.add_developer(developer)
+ group.add_owner(master)
+ group.request_access(access_requester)
+ group
+ end
+
+ shared_examples 'GET /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
+ end
+
+ context 'when authenticated as a non-member' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get api("/#{source_type.pluralize}/#{source.id}/members", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response.size).to eq(2)
+ end
+ end
+ end
+ end
+
+ it 'finds members with query string' do
+ get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
+
+ expect(response).to have_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['username']).to eq(master.username)
+ end
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(200)
+ # User attributes
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['name']).to eq(developer.name)
+ expect(json_response['username']).to eq(developer.username)
+ expect(json_response['state']).to eq(developer.state)
+ expect(json_response['avatar_url']).to eq(developer.avatar_url)
+ expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(developer))
+
+ # Member attributes
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples 'POST /:sources/:id/members' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { post api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ post api("/#{source_type.pluralize}/#{source.id}/members", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and new member is already a requester' do
+ it 'transforms the requester into a proper member' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: access_requester.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(source.requesters.count).to eq(0)
+ expect(json_response['id']).to eq(access_requester.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ end
+ end
+
+ it 'creates a new member' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: Member::DEVELOPER
+
+ expect(response).to have_http_status(201)
+ end.to change { source.members.count }.by(1)
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['access_level']).to eq(Member::DEVELOPER)
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: master.id, access_level: Member::MASTER
+
+ expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
+ end
+
+ it 'returns 400 when user_id is not given' do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access_level is not valid' do
+ post api("/#{source_type.pluralize}/#{source.id}/members", master),
+ user_id: stranger.id, access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'updates the member' do
+ put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(200)
+ expect(json_response['id']).to eq(developer.id)
+ expect(json_response['access_level']).to eq(Member::MASTER)
+ end
+ end
+
+ it 'returns 409 if member does not exist' do
+ put api("/#{source_type.pluralize}/#{source.id}/members/123", master),
+ access_level: Member::MASTER
+
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns 400 when access_level is not given' do
+ put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns 422 when access level is not valid' do
+ put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
+ access_level: 1234
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ shared_examples 'DELETE /:sources/:id/members/:user_id' do |source_type|
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+ delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a member and deleting themself' do
+ it 'deletes the member' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ context 'and member is a requester' do
+ it "returns #{source_type == 'project' ? 200 : 404}" do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end.not_to change { source.requesters.count }
+ end
+ end
+
+ it 'deletes the member' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
+
+ expect(response).to have_http_status(200)
+ end.to change { source.members.count }.by(-1)
+ end
+ end
+
+ it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do
+ delete api("/#{source_type.pluralize}/#{source.id}/members/123", master)
+
+ expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
+ end
+ end
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'GET /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'POST /:sources/:id/members', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'PUT /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'project' do
+ let(:source) { project }
+ end
+
+ it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do
+ let(:source) { group }
+ end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 651b91e9f68..617600d6173 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -20,14 +20,14 @@ describe API::API, api: true do
describe "GET /projects/:id/merge_requests" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/projects/#{project.id}/merge_requests")
expect(response).to have_http_status(401)
end
end
context "when authenticated" do
- it "should return an array of all merge_requests" 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(json_response).to be_an Array
@@ -35,7 +35,7 @@ describe API::API, api: true do
expect(json_response.last['title']).to eq(merge_request.title)
end
- it "should return an array of all merge_requests" 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(json_response).to be_an Array
@@ -43,7 +43,7 @@ describe API::API, api: true do
expect(json_response.last['title']).to eq(merge_request.title)
end
- it "should return an array of open merge_requests" 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(json_response).to be_an Array
@@ -51,7 +51,7 @@ describe API::API, api: true do
expect(json_response.last['title']).to eq(merge_request.title)
end
- it "should return an array of closed merge_requests" 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(json_response).to be_an Array
@@ -59,7 +59,7 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(merge_request_closed.title)
end
- it "should return an array of merged merge_requests" 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(json_response).to be_an Array
@@ -73,7 +73,7 @@ describe API::API, api: true do
@mr_earlier = mr_with_earlier_created_and_updated_at_time
end
- it "should return an array of merge_requests in ascending order" 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(json_response).to be_an Array
@@ -82,7 +82,7 @@ describe API::API, api: true do
expect(response_dates).to eq(response_dates.sort)
end
- it "should return an array of merge_requests in descending order" 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(json_response).to be_an Array
@@ -91,7 +91,7 @@ describe API::API, api: true do
expect(response_dates).to eq(response_dates.sort.reverse)
end
- it "should return an array of merge_requests ordered by updated_at" 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(json_response).to be_an Array
@@ -100,7 +100,7 @@ describe API::API, api: true do
expect(response_dates).to eq(response_dates.sort.reverse)
end
- it "should return an array of merge_requests ordered by created_at" 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(json_response).to be_an Array
@@ -142,7 +142,7 @@ describe API::API, api: true do
expect(json_response['force_close_merge_request']).to be_falsy
end
- it "should return merge_request" do
+ it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(merge_request.title)
@@ -153,7 +153,7 @@ describe API::API, api: true do
expect(json_response['force_close_merge_request']).to be_falsy
end
- it 'should return merge_request by iid' do
+ it 'returns merge_request by iid' do
url = "/projects/#{project.id}/merge_requests?iid=#{merge_request.iid}"
get api(url, user)
expect(response.status).to eq 200
@@ -161,7 +161,7 @@ describe API::API, api: true do
expect(json_response.first['id']).to eq merge_request.id
end
- it "should return a 404 error if merge_request_id not found" do
+ it "returns a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_requests/999", user)
expect(response).to have_http_status(404)
end
@@ -169,7 +169,7 @@ describe API::API, api: true do
context 'Work in Progress' do
let!(:merge_request_wip) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "WIP: Test", created_at: base_time + 1.second) }
- it "should return merge_request" do
+ it "returns merge_request" do
get api("/projects/#{project.id}/merge_requests/#{merge_request_wip.id}", user)
expect(response).to have_http_status(200)
expect(json_response['work_in_progress']).to eq(true)
@@ -195,7 +195,7 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/merge_requests/:merge_request_id/changes' do
- it 'should return the change information of the merge_request' do
+ it 'returns the change information of the merge_request' do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/changes", user)
expect(response.status).to eq 200
expect(json_response['changes'].size).to eq(merge_request.diffs.size)
@@ -209,7 +209,7 @@ describe API::API, api: true do
describe "POST /projects/:id/merge_requests" do
context 'between branches projects' do
- it "should return merge_request" do
+ it "returns merge_request" do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'feature_conflict',
@@ -223,31 +223,31 @@ describe API::API, api: true do
expect(json_response['milestone']['id']).to eq(milestone.id)
end
- it "should return 422 when source_branch equals target_branch" 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)
end
- it "should return 400 when source_branch is missing" do
+ 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)
end
- it "should return 400 when target_branch is missing" do
+ 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)
end
- it "should return 400 when title is missing" do
+ 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)
end
- it 'should allow special label names' do
+ it 'allows special label names' do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
source_branch: 'markdown',
@@ -272,7 +272,7 @@ describe API::API, api: true do
@mr = MergeRequest.all.last
end
- it 'should return 409 when MR already exists for source/target' do
+ it 'returns 409 when MR already exists for source/target' do
expect do
post api("/projects/#{project.id}/merge_requests", user),
title: 'New test merge_request',
@@ -294,7 +294,7 @@ describe API::API, api: true do
fork_project.team << [user2, :reporters]
end
- it "should return merge_request" do
+ 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'
@@ -303,7 +303,7 @@ describe API::API, api: true do
expect(json_response['description']).to eq('Test description for Test merge_request')
end
- it "should not return 422 when source_branch equals target_branch" do
+ it "does not return 422 when source_branch equals target_branch" do
expect(project.id).not_to eq(fork_project.id)
expect(fork_project.forked?).to be_truthy
expect(fork_project.forked_from_project).to eq(project)
@@ -313,26 +313,26 @@ describe API::API, api: true do
expect(json_response['title']).to eq('Test merge_request')
end
- it "should return 400 when source_branch is missing" do
+ 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)
end
- it "should return 400 when target_branch is missing" do
+ 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)
end
- it "should return 400 when title is missing" do
+ 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)
end
context 'when target_branch is specified' do
- it 'should return 422 if not a forked project' do
+ it 'returns 422 if not a forked project' do
post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request',
target_branch: 'master',
@@ -342,7 +342,7 @@ describe API::API, api: true do
expect(response).to have_http_status(422)
end
- it 'should return 422 if targeting a different fork' do
+ it 'returns 422 if targeting a different fork' do
post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request',
target_branch: 'master',
@@ -353,7 +353,7 @@ describe API::API, api: true do
end
end
- it "should return 201 when target_branch is specified and for the same project" do
+ 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)
@@ -385,7 +385,7 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/merge_requests/:merge_request_id to close MR" do
- it "should return merge_request" do
+ it "returns merge_request" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
expect(response).to have_http_status(200)
expect(json_response['state']).to eq('closed')
@@ -395,13 +395,13 @@ describe API::API, api: true do
describe "PUT /projects/:id/merge_requests/:merge_request_id/merge" do
let(:pipeline) { create(:ci_pipeline_without_jobs) }
- it "should return merge_request in case of success" do
+ it "returns merge_request in case of success" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response).to have_http_status(200)
end
- it "should return 406 if branch can't be merged" do
+ it "returns 406 if branch can't be merged" do
allow_any_instance_of(MergeRequest).
to receive(:can_be_merged?).and_return(false)
@@ -411,14 +411,14 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Branch cannot be merged')
end
- it "should return 405 if merge_request is not open" do
+ it "returns 405 if merge_request is not open" do
merge_request.close
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user)
expect(response).to have_http_status(405)
expect(json_response['message']).to eq('405 Method Not Allowed')
end
- it "should return 405 if merge_request is a work in progress" do
+ 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.id}/merge", user)
expect(response).to have_http_status(405)
@@ -434,7 +434,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('405 Method Not Allowed')
end
- it "should return 401 if user has no permissions to merge" do
+ it "returns 401 if user has no permissions to merge" do
user2 = create(:user)
project.team << [user2, :reporter]
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user2)
@@ -486,19 +486,19 @@ describe API::API, api: true do
expect(json_response['milestone']['id']).to eq(milestone.id)
end
- it "should return 400 when source_branch is specified" do
+ it "returns 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
expect(response).to have_http_status(400)
end
- it "should return merge_request with renamed target_branch" do
+ it "returns merge_request with renamed target_branch" do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), target_branch: "wiki"
expect(response).to have_http_status(200)
expect(json_response['target_branch']).to eq('wiki')
end
- it 'should allow special label names' do
+ it 'allows special label names' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}",
user),
title: 'new issue',
@@ -513,7 +513,7 @@ describe API::API, api: true do
end
describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do
- it "should return comment" do
+ it "returns comment" do
original_count = merge_request.notes.size
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment"
@@ -524,12 +524,12 @@ describe API::API, api: true do
expect(merge_request.notes.size).to eq(original_count + 1)
end
- it "should return 400 if note is missing" do
+ it "returns 400 if note is missing" do
post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response).to have_http_status(400)
end
- it "should return 404 if note is attached to non existent merge request" do
+ it "returns 404 if note is attached to non existent merge request" do
post api("/projects/#{project.id}/merge_requests/404/comments", user),
note: 'My comment'
expect(response).to have_http_status(404)
@@ -537,7 +537,7 @@ describe API::API, api: true do
end
describe "GET :id/merge_requests/:merge_request_id/comments" do
- it "should return merge_request comments ordered by created_at" do
+ it "returns merge_request comments ordered by created_at" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -547,7 +547,7 @@ describe API::API, api: true do
expect(json_response.last['note']).to eq("another comment on a MR")
end
- it "should return a 404 error if merge_request_id not found" do
+ it "returns a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_requests/999/comments", user)
expect(response).to have_http_status(404)
end
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 0f4e38b2475..d6a0c656e74 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -10,14 +10,14 @@ describe API::API, api: true do
before { project.team << [user, :developer] }
describe 'GET /projects/:id/milestones' do
- it 'should return project milestones' do
+ it 'returns project milestones' do
get api("/projects/#{project.id}/milestones", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(milestone.title)
end
- it 'should return a 401 error if user not authenticated' do
+ it 'returns a 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones")
expect(response).to have_http_status(401)
end
@@ -42,14 +42,14 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/milestones/:milestone_id' do
- it 'should return a project milestone by id' do
+ it 'returns a project milestone by id' do
get api("/projects/#{project.id}/milestones/#{milestone.id}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(milestone.title)
expect(json_response['iid']).to eq(milestone.iid)
end
- it 'should return a project milestone by iid' do
+ it 'returns a project milestone by iid' do
get api("/projects/#{project.id}/milestones?iid=#{closed_milestone.iid}", user)
expect(response.status).to eq 200
@@ -58,26 +58,26 @@ describe API::API, api: true do
expect(json_response.first['id']).to eq closed_milestone.id
end
- it 'should return 401 error if user not authenticated' do
+ it 'returns 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}")
expect(response).to have_http_status(401)
end
- it 'should return a 404 error if milestone id not found' do
+ it 'returns a 404 error if milestone id not found' do
get api("/projects/#{project.id}/milestones/1234", user)
expect(response).to have_http_status(404)
end
end
describe 'POST /projects/:id/milestones' do
- it 'should create a new project milestone' do
+ it 'creates a new project milestone' do
post api("/projects/#{project.id}/milestones", user), title: 'new milestone'
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new milestone')
expect(json_response['description']).to be_nil
end
- it 'should create a new project milestone with description and due date' do
+ it 'creates a new project milestone with description and due date' do
post api("/projects/#{project.id}/milestones", user),
title: 'new milestone', description: 'release', due_date: '2013-03-02'
expect(response).to have_http_status(201)
@@ -85,21 +85,21 @@ describe API::API, api: true do
expect(json_response['due_date']).to eq('2013-03-02')
end
- it 'should return a 400 error if title is missing' do
+ it 'returns a 400 error if title is missing' do
post api("/projects/#{project.id}/milestones", user)
expect(response).to have_http_status(400)
end
end
describe 'PUT /projects/:id/milestones/:milestone_id' do
- it 'should update a project milestone' do
+ it 'updates a project milestone' do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
title: 'updated title'
expect(response).to have_http_status(200)
expect(json_response['title']).to eq('updated title')
end
- it 'should return a 404 error if milestone id not found' do
+ it 'returns a 404 error if milestone id not found' do
put api("/projects/#{project.id}/milestones/1234", user),
title: 'updated title'
expect(response).to have_http_status(404)
@@ -107,7 +107,7 @@ describe API::API, api: true do
end
describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do
- it 'should update a project milestone' do
+ it 'updates a project milestone' do
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
state_event: 'close'
expect(response).to have_http_status(200)
@@ -117,7 +117,7 @@ describe API::API, api: true do
end
describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
- it 'should create an activity event when an milestone is closed' do
+ it 'creates an activity event when an milestone is closed' do
expect(Event).to receive(:create)
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
@@ -129,14 +129,14 @@ describe API::API, api: true do
before do
milestone.issues << create(:issue, project: project)
end
- it 'should return project issues for a particular milestone' do
+ it 'returns project issues for a particular milestone' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
- it 'should return a 401 error if user not authenticated' do
+ it 'returns a 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
expect(response).to have_http_status(401)
end
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 237b4b17eb5..5347cf4f7bc 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -9,14 +9,14 @@ describe API::API, api: true do
describe "GET /namespaces" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/namespaces")
expect(response).to have_http_status(401)
end
end
context "when authenticated as admin" do
- it "admin: should return an array of all namespaces" do
+ it "admin: returns an array of all namespaces" do
get api("/namespaces", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -24,7 +24,7 @@ describe API::API, api: true do
expect(json_response.length).to eq(Namespace.count)
end
- it "admin: should return an array of matched namespaces" do
+ it "admin: returns an array of matched namespaces" do
get api("/namespaces?search=#{group1.name}", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -34,7 +34,7 @@ describe API::API, api: true do
end
context "when authenticated as a regular user" do
- it "user: should return an array of namespaces" do
+ it "user: returns an array of namespaces" do
get api("/namespaces", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -42,7 +42,7 @@ describe API::API, api: true do
expect(json_response.length).to eq(1)
end
- it "admin: should return an array of matched namespaces" do
+ it "admin: returns an array of matched namespaces" do
get api("/namespaces?search=#{user.username}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 65c53211dd3..737fa14cbb0 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -37,7 +37,7 @@ describe API::API, api: true do
end
context "when noteable is an Issue" do
- it "should return an array of issue notes" do
+ it "returns an array of issue notes" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
expect(response).to have_http_status(200)
@@ -45,14 +45,14 @@ describe API::API, api: true do
expect(json_response.first['body']).to eq(issue_note.note)
end
- it "should return a 404 error when issue id not found" do
+ it "returns a 404 error when issue id not found" do
get api("/projects/#{project.id}/issues/12345/notes", user)
expect(response).to have_http_status(404)
end
context "and current user cannot view the notes" do
- it "should return an empty array" do
+ it "returns an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", user)
expect(response).to have_http_status(200)
@@ -71,7 +71,7 @@ describe API::API, api: true do
end
context "and current user can view the note" do
- it "should return an empty array" do
+ it "returns an empty array" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes", private_user)
expect(response).to have_http_status(200)
@@ -83,7 +83,7 @@ describe API::API, api: true do
end
context "when noteable is a Snippet" do
- it "should return an array of snippet notes" do
+ it "returns an array of snippet notes" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
expect(response).to have_http_status(200)
@@ -91,7 +91,7 @@ describe API::API, api: true do
expect(json_response.first['body']).to eq(snippet_note.note)
end
- it "should return a 404 error when snippet id not found" do
+ it "returns a 404 error when snippet id not found" do
get api("/projects/#{project.id}/snippets/42/notes", user)
expect(response).to have_http_status(404)
@@ -105,7 +105,7 @@ describe API::API, api: true do
end
context "when noteable is a Merge Request" do
- it "should return an array of merge_requests notes" do
+ it "returns an array of merge_requests notes" do
get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/notes", user)
expect(response).to have_http_status(200)
@@ -113,7 +113,7 @@ describe API::API, api: true do
expect(json_response.first['body']).to eq(merge_request_note.note)
end
- it "should return a 404 error if merge request id not found" do
+ it "returns a 404 error if merge request id not found" do
get api("/projects/#{project.id}/merge_requests/4444/notes", user)
expect(response).to have_http_status(404)
@@ -129,21 +129,21 @@ describe API::API, api: true do
describe "GET /projects/:id/noteable/:noteable_id/notes/:note_id" do
context "when noteable is an Issue" do
- it "should return an issue note by id" do
+ it "returns an issue note by id" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/#{issue_note.id}", user)
expect(response).to have_http_status(200)
expect(json_response['body']).to eq(issue_note.note)
end
- it "should return a 404 error if issue note not found" do
+ it "returns a 404 error if issue note not found" do
get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
expect(response).to have_http_status(404)
end
context "and current user cannot view the note" do
- it "should return a 404 error" do
+ it "returns a 404 error" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", user)
expect(response).to have_http_status(404)
@@ -160,7 +160,7 @@ describe API::API, api: true do
end
context "and current user can view the note" do
- it "should return an issue note by id" do
+ it "returns an issue note by id" do
get api("/projects/#{ext_proj.id}/issues/#{ext_issue.id}/notes/#{cross_reference_note.id}", private_user)
expect(response).to have_http_status(200)
@@ -171,14 +171,14 @@ describe API::API, api: true do
end
context "when noteable is a Snippet" do
- it "should return a snippet note by id" do
+ it "returns a snippet note by id" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/#{snippet_note.id}", user)
expect(response).to have_http_status(200)
expect(json_response['body']).to eq(snippet_note.note)
end
- it "should return a 404 error if snippet note not found" do
+ it "returns a 404 error if snippet note not found" do
get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
expect(response).to have_http_status(404)
@@ -188,7 +188,7 @@ describe API::API, api: true do
describe "POST /projects/:id/noteable/:noteable_id/notes" do
context "when noteable is an Issue" do
- it "should create a new issue note" do
+ it "creates a new issue note" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
expect(response).to have_http_status(201)
@@ -196,13 +196,13 @@ describe API::API, api: true do
expect(json_response['author']['username']).to eq(user.username)
end
- it "should return a 400 bad request error if body not given" do
+ it "returns a 400 bad request error if body not given" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user)
expect(response).to have_http_status(400)
end
- it "should return a 401 unauthorized error if user not authenticated" do
+ it "returns a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes"), body: 'hi!'
expect(response).to have_http_status(401)
@@ -223,7 +223,7 @@ describe API::API, api: true do
end
context "when noteable is a Snippet" do
- it "should create a new snippet note" do
+ it "creates a new snippet note" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user), body: 'hi!'
expect(response).to have_http_status(201)
@@ -231,13 +231,13 @@ describe API::API, api: true do
expect(json_response['author']['username']).to eq(user.username)
end
- it "should return a 400 bad request error if body not given" do
+ it "returns a 400 bad request error if body not given" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user)
expect(response).to have_http_status(400)
end
- it "should return a 401 unauthorized error if user not authenticated" do
+ it "returns a 401 unauthorized error if user not authenticated" do
post api("/projects/#{project.id}/snippets/#{snippet.id}/notes"), body: 'hi!'
expect(response).to have_http_status(401)
@@ -267,7 +267,7 @@ describe API::API, api: true do
end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
- it "should create an activity event when an issue note is created" do
+ it "creates an activity event when an issue note is created" do
expect(Event).to receive(:create)
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
@@ -276,7 +276,7 @@ describe API::API, api: true do
describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do
context 'when noteable is an Issue' do
- it 'should return modified note' do
+ it 'returns modified note' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user), body: 'Hello!'
@@ -284,14 +284,14 @@ describe API::API, api: true do
expect(json_response['body']).to eq('Hello!')
end
- it 'should return a 404 error when note id not found' do
+ it 'returns a 404 error when note id not found' do
put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
body: 'Hello!'
expect(response).to have_http_status(404)
end
- it 'should return a 400 bad request error if body not given' do
+ it 'returns a 400 bad request error if body not given' do
put api("/projects/#{project.id}/issues/#{issue.id}/"\
"notes/#{issue_note.id}", user)
@@ -300,7 +300,7 @@ describe API::API, api: true do
end
context 'when noteable is a Snippet' do
- it 'should return modified note' do
+ it 'returns modified note' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/#{snippet_note.id}", user), body: 'Hello!'
@@ -308,7 +308,7 @@ describe API::API, api: true do
expect(json_response['body']).to eq('Hello!')
end
- it 'should return a 404 error when note id not found' do
+ it 'returns a 404 error when note id not found' do
put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
"notes/12345", user), body: "Hello!"
@@ -317,7 +317,7 @@ describe API::API, api: true do
end
context 'when noteable is a Merge Request' do
- it 'should return modified note' do
+ it 'returns modified note' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/#{merge_request_note.id}", user), body: 'Hello!'
@@ -325,7 +325,7 @@ describe API::API, api: true do
expect(json_response['body']).to eq('Hello!')
end
- it 'should return a 404 error when note id not found' do
+ it 'returns a 404 error when note id not found' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
"notes/12345", user), body: "Hello!"
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index fd1fffa6223..34fac297923 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -20,7 +20,7 @@ describe API::API, 'ProjectHooks', api: true do
describe "GET /projects/:id/hooks" do
context "authorized user" do
- it "should return project hooks" do
+ it "returns project hooks" do
get api("/projects/#{project.id}/hooks", user)
expect(response).to have_http_status(200)
@@ -38,7 +38,7 @@ describe API::API, 'ProjectHooks', api: true do
end
context "unauthorized user" do
- it "should not access project hooks" do
+ it "does not access project hooks" do
get api("/projects/#{project.id}/hooks", user3)
expect(response).to have_http_status(403)
end
@@ -47,7 +47,7 @@ describe API::API, 'ProjectHooks', api: true do
describe "GET /projects/:id/hooks/:hook_id" do
context "authorized user" do
- it "should return a project hook" do
+ it "returns a project hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_http_status(200)
expect(json_response['url']).to eq(hook.url)
@@ -59,27 +59,27 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end
- it "should return a 404 error if hook id is not available" do
+ it "returns a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
expect(response).to have_http_status(404)
end
end
context "unauthorized user" do
- it "should not access an existing hook" do
+ it "does not access an existing hook" do
get api("/projects/#{project.id}/hooks/#{hook.id}", user3)
expect(response).to have_http_status(403)
end
end
- it "should return a 404 error if hook id is not available" do
+ it "returns a 404 error if hook id is not available" do
get api("/projects/#{project.id}/hooks/1234", user)
expect(response).to have_http_status(404)
end
end
describe "POST /projects/:id/hooks" do
- it "should add hook to project" do
+ it "adds hook to project" do
expect do
post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true
end.to change {project.hooks.count}.by(1)
@@ -94,19 +94,19 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['enable_ssl_verification']).to eq(true)
end
- it "should return a 400 error if url not given" do
+ it "returns a 400 error if url not given" do
post api("/projects/#{project.id}/hooks", user)
expect(response).to have_http_status(400)
end
- it "should return a 422 error if url not valid" do
+ it "returns a 422 error if url not valid" do
post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
expect(response).to have_http_status(422)
end
end
describe "PUT /projects/:id/hooks/:hook_id" do
- it "should update an existing project hook" do
+ it "updates an existing project hook" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
url: 'http://example.org', push_events: false
expect(response).to have_http_status(200)
@@ -119,46 +119,46 @@ describe API::API, 'ProjectHooks', api: true do
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
end
- it "should return 404 error if hook id not found" do
+ it "returns 404 error if hook id not found" do
put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
expect(response).to have_http_status(404)
end
- it "should return 400 error if url is not given" do
+ it "returns 400 error if url is not given" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_http_status(400)
end
- it "should return a 422 error if url is not valid" do
+ it "returns a 422 error if url is not valid" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
expect(response).to have_http_status(422)
end
end
describe "DELETE /projects/:id/hooks/:hook_id" do
- it "should delete hook from project" do
+ it "deletes hook from project" do
expect do
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
end.to change {project.hooks.count}.by(-1)
expect(response).to have_http_status(200)
end
- it "should return success when deleting hook" do
+ it "returns success when deleting hook" do
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
expect(response).to have_http_status(200)
end
- it "should return a 404 error when deleting non existent hook" do
+ it "returns a 404 error when deleting non existent hook" do
delete api("/projects/#{project.id}/hooks/42", user)
expect(response).to have_http_status(404)
end
- it "should return a 405 error if hook id not given" do
+ it "returns a 405 error if hook id not given" do
delete api("/projects/#{project.id}/hooks", user)
expect(response).to have_http_status(405)
end
- it "shold return a 404 if a user attempts to delete project hooks he/she does not own" do
+ it "returns a 404 if a user attempts to delete project hooks he/she does not own" do
test_user = create(:user)
other_project = create(:project)
other_project.team << [test_user, :master]
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
deleted file mode 100644
index 9a7c1da4401..00000000000
--- a/spec/requests/api/project_members_spec.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-require 'spec_helper'
-
-describe API::API, api: true do
- include ApiHelpers
- let(:user) { create(:user) }
- let(:user2) { create(:user) }
- let(:user3) { create(:user) }
- let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
- let(:project_member) { create(:project_member, :master, user: user, project: project) }
- let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
-
- describe "GET /projects/:id/members" do
- before { project_member }
- before { project_member2 }
-
- it "should return project team members" do
- get api("/projects/#{project.id}/members", user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.count).to eq(2)
- expect(json_response.map { |u| u['username'] }).to include user.username
- end
-
- it "finds team members with query string" do
- get api("/projects/#{project.id}/members", user), query: user.username
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.count).to eq(1)
- expect(json_response.first['username']).to eq(user.username)
- end
-
- it "should return a 404 error if id not found" do
- get api("/projects/9999/members", user)
- expect(response).to have_http_status(404)
- end
- end
-
- describe "GET /projects/:id/members/:user_id" do
- before { project_member }
-
- it "should return project team member" do
- get api("/projects/#{project.id}/members/#{user.id}", user)
- expect(response).to have_http_status(200)
- expect(json_response['username']).to eq(user.username)
- expect(json_response['access_level']).to eq(ProjectMember::MASTER)
- end
-
- it "should return a 404 error if user id not found" do
- get api("/projects/#{project.id}/members/1234", user)
- expect(response).to have_http_status(404)
- end
- end
-
- describe "POST /projects/:id/members" do
- it "should add user to project team" do
- expect do
- post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER
- end.to change { ProjectMember.count }.by(1)
-
- expect(response).to have_http_status(201)
- expect(json_response['username']).to eq(user2.username)
- expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER)
- end
-
- it "should return a 201 status if user is already project member" do
- post api("/projects/#{project.id}/members", user),
- user_id: user2.id,
- access_level: ProjectMember::DEVELOPER
- expect do
- post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER
- end.not_to change { ProjectMember.count }
-
- expect(response).to have_http_status(201)
- expect(json_response['username']).to eq(user2.username)
- expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER)
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/projects/#{project.id}/members", user), access_level: ProjectMember::MASTER
- expect(response).to have_http_status(400)
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/projects/#{project.id}/members", user), user_id: user2.id
- expect(response).to have_http_status(400)
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234
- expect(response).to have_http_status(422)
- end
- end
-
- describe "PUT /projects/:id/members/:user_id" do
- before { project_member2 }
-
- it "should update project team member" do
- put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: ProjectMember::MASTER
- expect(response).to have_http_status(200)
- expect(json_response['username']).to eq(user3.username)
- expect(json_response['access_level']).to eq(ProjectMember::MASTER)
- end
-
- it "should return a 404 error if user_id is not found" do
- put api("/projects/#{project.id}/members/1234", user), access_level: ProjectMember::MASTER
- expect(response).to have_http_status(404)
- end
-
- it "should return a 400 error when access level is not given" do
- put api("/projects/#{project.id}/members/#{user3.id}", user)
- expect(response).to have_http_status(400)
- end
-
- it "should return a 422 error when access level is not known" do
- put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123
- expect(response).to have_http_status(422)
- end
- end
-
- describe "DELETE /projects/:id/members/:user_id" do
- before do
- project_member
- project_member2
- end
-
- it "should remove user from project team" do
- expect do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- end.to change { ProjectMember.count }.by(-1)
- end
-
- it "should return 200 if team member is not part of a project" do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- expect do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- end.not_to change { ProjectMember.count }
- expect(response).to have_http_status(200)
- end
-
- it "should return 200 if team member already removed" do
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- delete api("/projects/#{project.id}/members/#{user3.id}", user)
- expect(response).to have_http_status(200)
- end
-
- it "should return 200 OK when the user was not member" do
- expect do
- delete api("/projects/#{project.id}/members/1000000", user)
- end.to change { ProjectMember.count }.by(0)
- expect(response).to have_http_status(200)
- expect(json_response['id']).to eq(1000000)
- expect(json_response['message']).to eq('Access revoked')
- end
-
- context 'when the user is not an admin or owner' do
- it 'can leave the project' do
- expect do
- delete api("/projects/#{project.id}/members/#{user3.id}", user3)
- end.to change { ProjectMember.count }.by(-1)
-
- expect(response).to have_http_status(200)
- expect(json_response['id']).to eq(project_member2.id)
- end
- end
- end
-end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 4ebde201941..42757ff21b0 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -17,7 +17,7 @@ describe API::API, api: true do
end
describe 'GET /projects/:project_id/snippets/' do
- it 'all snippets available to team member' do
+ it 'returns all snippets available to team member' do
project = create(:project, :public)
user = create(:user)
project.team << [user, :developer]
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 8c6a7e6529d..4742b3d0e37 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -43,14 +43,14 @@ describe API::API, api: true do
before { project }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api('/projects')
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should return an array of projects' do
+ it 'returns an array of projects' do
get api('/projects', user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -58,21 +58,21 @@ describe API::API, api: true do
expect(json_response.first['owner']['username']).to eq(user.username)
end
- it 'should include the project labels as the tag_list' do
+ it 'includes the project labels as the tag_list' do
get api('/projects', user)
expect(response.status).to eq 200
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('tag_list')
end
- it 'should include open_issues_count' do
+ it 'includes open_issues_count' do
get api('/projects', user)
expect(response.status).to eq 200
expect(json_response).to be_an Array
expect(json_response.first.keys).to include('open_issues_count')
end
- it 'should not include open_issues_count' do
+ it 'does not include open_issues_count' do
project.update_attributes( { issues_enabled: false } )
get api('/projects', user)
@@ -94,7 +94,7 @@ describe API::API, api: true do
end
context 'and using search' do
- it 'should return searched project' do
+ it 'returns searched project' do
get api('/projects', user), { search: project.name }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -103,21 +103,21 @@ describe API::API, api: true do
end
context 'and using the visibility filter' do
- it 'should filter based on private visibility param' do
+ it 'filters based on private visibility param' do
get api('/projects', user), { visibility: 'private' }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
end
- it 'should filter based on internal visibility param' do
+ it 'filters based on internal visibility param' do
get api('/projects', user), { visibility: 'internal' }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
end
- it 'should filter based on public visibility param' do
+ it 'filters based on public visibility param' do
get api('/projects', user), { visibility: 'public' }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -131,7 +131,7 @@ describe API::API, api: true do
project3
end
- it 'should return the correct order when sorted by id' do
+ it 'returns the correct order when sorted by id' do
get api('/projects', user), { order_by: 'id', sort: 'desc' }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -145,21 +145,21 @@ describe API::API, api: true do
before { project }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api('/projects/all')
expect(response).to have_http_status(401)
end
end
context 'when authenticated as regular user' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api('/projects/all', user)
expect(response).to have_http_status(403)
end
end
context 'when authenticated as admin' do
- it 'should return an array of all projects' do
+ it 'returns an array of all projects' do
get api('/projects/all', admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -183,7 +183,7 @@ describe API::API, api: true do
user3.update_attributes(starred_projects: [project, project2, project3, public_project])
end
- it 'should return the starred projects viewable by the user' do
+ it 'returns the starred projects viewable by the user' do
get api('/projects/starred', user3)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -193,7 +193,7 @@ describe API::API, api: true do
describe 'POST /projects' do
context 'maximum number of projects reached' do
- it 'should not create new project and respond with 403' do
+ it 'does not create new project and respond with 403' do
allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
expect { post api('/projects', user2), name: 'foo' }.
to change {Project.count}.by(0)
@@ -201,24 +201,24 @@ describe API::API, api: true do
end
end
- it 'should create new project without path and return 201' do
+ it 'creates new project without path and return 201' do
expect { post api('/projects', user), name: 'foo' }.
to change { Project.count }.by(1)
expect(response).to have_http_status(201)
end
- it 'should create last project before reaching project limit' do
+ it 'creates last project before reaching project limit' do
allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
post api('/projects', user2), name: 'foo'
expect(response).to have_http_status(201)
end
- it 'should not create new project without name and return 400' do
+ it 'does not create new project without name and return 400' do
expect { post api('/projects', user) }.not_to change { Project.count }
expect(response).to have_http_status(400)
end
- it "should assign attributes to project" do
+ it "assigns attributes to project" do
project = attributes_for(:project, {
path: 'camelCasePath',
description: FFaker::Lorem.sentence,
@@ -234,42 +234,42 @@ describe API::API, api: true do
end
end
- it 'should set a project as public' do
+ it 'sets a project as public' do
project = attributes_for(:project, :public)
post api('/projects', user), project
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'should set a project as public using :public' do
+ it 'sets a project as public using :public' do
project = attributes_for(:project, { public: true })
post api('/projects', user), project
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'should set a project as internal' do
+ it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api('/projects', user), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'should set a project as internal overriding :public' do
+ it 'sets a project as internal overriding :public' do
project = attributes_for(:project, :internal, { public: true })
post api('/projects', user), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'should set a project as private' do
+ it 'sets a project as private' do
project = attributes_for(:project, :private)
post api('/projects', user), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'should set a project as private using :public' do
+ it 'sets a project as private using :public' do
project = attributes_for(:project, { public: false })
post api('/projects', user), project
expect(json_response['public']).to be_falsey
@@ -282,7 +282,7 @@ describe API::API, api: true do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
- it 'should not allow a non-admin to use a restricted visibility level' do
+ it 'does not allow a non-admin to use a restricted visibility level' do
post api('/projects', user), @project
expect(response).to have_http_status(400)
@@ -291,7 +291,7 @@ describe API::API, api: true do
)
end
- it 'should allow an admin to override restricted visibility settings' do
+ it 'allows an admin to override restricted visibility settings' do
post api('/projects', admin), @project
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to(
@@ -310,7 +310,7 @@ describe API::API, api: true do
expect(response).to have_http_status(201)
end
- it 'should respond with 400 on failure and not project' do
+ it 'responds with 400 on failure and not project' do
expect { post api("/projects/user/#{user.id}", admin) }.
not_to change { Project.count }
@@ -327,7 +327,7 @@ describe API::API, api: true do
])
end
- it 'should assign attributes to project' do
+ it 'assigns attributes to project' do
project = attributes_for(:project, {
description: FFaker::Lorem.sentence,
issues_enabled: false,
@@ -343,42 +343,42 @@ describe API::API, api: true do
end
end
- it 'should set a project as public' do
+ it 'sets a project as public' do
project = attributes_for(:project, :public)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'should set a project as public using :public' do
+ it 'sets a project as public using :public' do
project = attributes_for(:project, { public: true })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_truthy
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
- it 'should set a project as internal' do
+ it 'sets a project as internal' do
project = attributes_for(:project, :internal)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'should set a project as internal overriding :public' do
+ it 'sets a project as internal overriding :public' do
project = attributes_for(:project, :internal, { public: true })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL)
end
- it 'should set a project as private' do
+ it 'sets a project as private' do
project = attributes_for(:project, :private)
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_falsey
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'should set a project as private using :public' do
+ it 'sets a project as private using :public' do
project = attributes_for(:project, { public: false })
post api("/projects/user/#{user.id}", admin), project
expect(json_response['public']).to be_falsey
@@ -446,25 +446,25 @@ describe API::API, api: true do
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
end
- it 'should return a project by path name' do
+ it 'returns a project by path name' do
get api("/projects/#{project.id}", user)
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(project.name)
end
- it 'should return a 404 error if not found' do
+ it 'returns a 404 error if not found' do
get api('/projects/42', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
end
- it 'should return a 404 error if user is not a member' do
+ it 'returns a 404 error if user is not a member' do
other_user = create(:user)
get api("/projects/#{project.id}", other_user)
expect(response).to have_http_status(404)
end
- it 'should handle users with dots' do
+ it 'handles users with dots' do
dot_user = create(:user, username: 'dot.user')
project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
@@ -504,7 +504,7 @@ describe API::API, api: true do
before { project2.group.add_owner(user) }
- it 'should set the owner and return 200' do
+ it 'sets the owner and return 200' do
get api("/projects/#{project2.id}", user)
expect(response).to have_http_status(200)
@@ -545,13 +545,13 @@ describe API::API, api: true do
end
end
- it 'should return a 404 error if not found' do
+ it 'returns a 404 error if not found' do
get api('/projects/42/events', user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
end
- it 'should return a 404 error if user is not a member' do
+ it 'returns a 404 error if user is not a member' do
other_user = create(:user)
get api("/projects/#{project.id}/events", other_user)
expect(response).to have_http_status(404)
@@ -561,7 +561,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/snippets' do
before { snippet }
- it 'should return an array of project snippets' do
+ it 'returns an array of project snippets' do
get api("/projects/#{project.id}/snippets", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -570,20 +570,20 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/snippets/:snippet_id' do
- it 'should return a project snippet' do
+ it 'returns a project snippet' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
expect(response).to have_http_status(200)
expect(json_response['title']).to eq(snippet.title)
end
- it 'should return a 404 error if snippet id not found' do
+ it 'returns a 404 error if snippet id not found' do
get api("/projects/#{project.id}/snippets/1234", user)
expect(response).to have_http_status(404)
end
end
describe 'POST /projects/:id/snippets' do
- it 'should create a new project snippet' do
+ it 'creates a new project snippet' do
post api("/projects/#{project.id}/snippets", user),
title: 'api test', file_name: 'sample.rb', code: 'test',
visibility_level: '0'
@@ -591,14 +591,14 @@ describe API::API, api: true do
expect(json_response['title']).to eq('api test')
end
- it 'should return a 400 error if invalid snippet is given' do
+ it 'returns a 400 error if invalid snippet is given' do
post api("/projects/#{project.id}/snippets", user)
expect(status).to eq(400)
end
end
describe 'PUT /projects/:id/snippets/:snippet_id' do
- it 'should update an existing project snippet' do
+ it 'updates an existing project snippet' do
put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
code: 'updated code'
expect(response).to have_http_status(200)
@@ -606,7 +606,7 @@ describe API::API, api: true do
expect(snippet.reload.content).to eq('updated code')
end
- it 'should update an existing project snippet with new title' do
+ it 'updates an existing project snippet with new title' do
put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
title: 'other api test'
expect(response).to have_http_status(200)
@@ -617,103 +617,31 @@ describe API::API, api: true do
describe 'DELETE /projects/:id/snippets/:snippet_id' do
before { snippet }
- it 'should delete existing project snippet' do
+ it 'deletes existing project snippet' do
expect do
delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
end.to change { Snippet.count }.by(-1)
expect(response).to have_http_status(200)
end
- it 'should return 404 when deleting unknown snippet id' do
+ it 'returns 404 when deleting unknown snippet id' do
delete api("/projects/#{project.id}/snippets/1234", user)
expect(response).to have_http_status(404)
end
end
describe 'GET /projects/:id/snippets/:snippet_id/raw' do
- it 'should get a raw project snippet' do
+ it 'gets a raw project snippet' do
get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
expect(response).to have_http_status(200)
end
- it 'should return a 404 error if raw project snippet not found' do
+ it 'returns a 404 error if raw project snippet not found' do
get api("/projects/#{project.id}/snippets/5555/raw", user)
expect(response).to have_http_status(404)
end
end
- describe :deploy_keys do
- let(:deploy_keys_project) { create(:deploy_keys_project, project: project) }
- let(:deploy_key) { deploy_keys_project.deploy_key }
-
- describe 'GET /projects/:id/deploy_keys' do
- before { deploy_key }
-
- it 'should return array of ssh keys' do
- get api("/projects/#{project.id}/deploy_keys", user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.first['title']).to eq(deploy_key.title)
- end
- end
-
- describe 'GET /projects/:id/deploy_keys/:key_id' do
- it 'should return a single key' do
- get api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
- expect(response).to have_http_status(200)
- expect(json_response['title']).to eq(deploy_key.title)
- end
-
- it 'should return 404 Not Found with invalid ID' do
- get api("/projects/#{project.id}/deploy_keys/404", user)
- expect(response).to have_http_status(404)
- end
- end
-
- describe 'POST /projects/:id/deploy_keys' do
- it 'should not create an invalid ssh key' do
- post api("/projects/#{project.id}/deploy_keys", user), { title: 'invalid key' }
- expect(response).to have_http_status(400)
- expect(json_response['message']['key']).to eq([
- 'can\'t be blank',
- 'is too short (minimum is 0 characters)',
- 'is invalid'
- ])
- end
-
- it 'should not create a key without title' do
- post api("/projects/#{project.id}/deploy_keys", user), key: 'some key'
- expect(response).to have_http_status(400)
- expect(json_response['message']['title']).to eq([
- 'can\'t be blank',
- 'is too short (minimum is 0 characters)'
- ])
- end
-
- it 'should create new ssh key' do
- key_attrs = attributes_for :key
- expect do
- post api("/projects/#{project.id}/deploy_keys", user), key_attrs
- end.to change{ project.deploy_keys.count }.by(1)
- end
- end
-
- describe 'DELETE /projects/:id/deploy_keys/:key_id' do
- before { deploy_key }
-
- it 'should delete existing key' do
- expect do
- delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user)
- end.to change{ project.deploy_keys.count }.by(-1)
- end
-
- it 'should return 404 Not Found with invalid ID' do
- delete api("/projects/#{project.id}/deploy_keys/404", user)
- expect(response).to have_http_status(404)
- end
- end
- end
-
describe :fork_admin do
let(:project_fork_target) { create(:project) }
let(:project_fork_source) { create(:project, :public) }
@@ -721,12 +649,12 @@ describe API::API, api: true do
describe 'POST /projects/:id/fork/:forked_from_id' do
let(:new_project_fork_source) { create(:project, :public) }
- it "shouldn't available for non admin users" do
+ it "is not available for non admin users" do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
expect(response).to have_http_status(403)
end
- it 'should allow project to be forked from an existing project' do
+ it 'allows project to be forked from an existing project' do
expect(project_fork_target.forked?).not_to be_truthy
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
expect(response).to have_http_status(201)
@@ -736,12 +664,12 @@ describe API::API, api: true do
expect(project_fork_target.forked?).to be_truthy
end
- it 'should fail if forked_from project which does not exist' do
+ 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)
end
- it 'should fail with 409 if already forked' do
+ it 'fails with 409 if already forked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload
expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
@@ -754,7 +682,7 @@ describe API::API, api: true do
end
describe 'DELETE /projects/:id/fork' do
- it "shouldn't be visible to users outside group" do
+ it "is not visible to users outside group" do
delete api("/projects/#{project_fork_target.id}/fork", user)
expect(response).to have_http_status(404)
end
@@ -767,12 +695,12 @@ describe API::API, api: true do
project_fork_target.group.add_developer user2
end
- it 'should be forbidden to non-owner users' do
+ it 'is forbidden to non-owner users' do
delete api("/projects/#{project_fork_target.id}/fork", user2)
expect(response).to have_http_status(403)
end
- it 'should make forked project unforked' do
+ it 'makes forked project unforked' do
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
project_fork_target.reload
expect(project_fork_target.forked_from_project).not_to be_nil
@@ -784,7 +712,7 @@ describe API::API, api: true do
expect(project_fork_target.forked?).not_to be_truthy
end
- it 'should be idempotent if not forked' do
+ it 'is idempotent if not forked' do
expect(project_fork_target.forked_from_project).to be_nil
delete api("/projects/#{project_fork_target.id}/fork", admin)
expect(response).to have_http_status(200)
@@ -797,7 +725,7 @@ describe API::API, api: true do
describe "POST /projects/:id/share" do
let(:group) { create(:group) }
- it "should share project with group" do
+ it "shares project with group" do
expect do
post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
end.to change { ProjectGroupLink.count }.by(1)
@@ -807,23 +735,23 @@ describe API::API, api: true do
expect(json_response['group_access']).to eq Gitlab::Access::DEVELOPER
end
- it "should return a 400 error when group id is not given" do
+ it "returns a 400 error when group id is not given" do
post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
expect(response.status).to eq 400
end
- it "should return a 400 error when access level is not given" do
+ it "returns a 400 error when access level is not given" do
post api("/projects/#{project.id}/share", user), group_id: group.id
expect(response.status).to eq 400
end
- it "should return a 400 error when sharing is disabled" do
+ it "returns a 400 error when sharing is disabled" do
project.namespace.update(share_with_group_lock: true)
post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
expect(response.status).to eq 400
end
- it "should return a 409 error when wrong params passed" do
+ it "returns a 409 error when wrong params passed" do
post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
expect(response.status).to eq 409
expect(json_response['message']).to eq 'Group access is not included in the list'
@@ -843,14 +771,14 @@ describe API::API, api: true do
let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api("/projects/search/#{query}")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should return an array of projects' do
+ it 'returns an array of projects' do
get api("/projects/search/#{query}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -860,7 +788,7 @@ describe API::API, api: true do
end
context 'when authenticated as a different user' do
- it 'should return matching public projects' do
+ it 'returns matching public projects' do
get api("/projects/search/#{query}", user2)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -881,7 +809,7 @@ describe API::API, api: true do
before { project_member2 }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
project_param = { name: 'bar' }
put api("/projects/#{project.id}"), project_param
expect(response).to have_http_status(401)
@@ -889,7 +817,7 @@ describe API::API, api: true do
end
context 'when authenticated as project owner' do
- it 'should update name' do
+ it 'updates name' do
project_param = { name: 'bar' }
put api("/projects/#{project.id}", user), project_param
expect(response).to have_http_status(200)
@@ -898,7 +826,7 @@ describe API::API, api: true do
end
end
- it 'should update visibility_level' do
+ it 'updates visibility_level' do
project_param = { visibility_level: 20 }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
@@ -907,7 +835,7 @@ describe API::API, api: true do
end
end
- it 'should update visibility_level from public to private' do
+ it 'updates visibility_level from public to private' do
project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
project_param = { public: false }
@@ -919,14 +847,14 @@ describe API::API, api: true do
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
- it 'should not update name to existing name' do
+ it 'does not update name to existing name' do
project_param = { name: project3.name }
put api("/projects/#{project.id}", user), project_param
expect(response).to have_http_status(400)
expect(json_response['message']['name']).to eq(['has already been taken'])
end
- it 'should update path & name to existing path & name in different namespace' do
+ it 'updates path & name to existing path & name in different namespace' do
project_param = { path: project4.path, name: project4.name }
put api("/projects/#{project3.id}", user), project_param
expect(response).to have_http_status(200)
@@ -937,7 +865,7 @@ describe API::API, api: true do
end
context 'when authenticated as project master' do
- it 'should update path' do
+ it 'updates path' do
project_param = { path: 'bar' }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(200)
@@ -946,7 +874,7 @@ describe API::API, api: true do
end
end
- it 'should update other attributes' do
+ it 'updates other attributes' do
project_param = { issues_enabled: true,
wiki_enabled: true,
snippets_enabled: true,
@@ -960,20 +888,20 @@ describe API::API, api: true do
end
end
- it 'should not update path to existing path' do
+ it 'does not update path to existing path' do
project_param = { path: project.path }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(400)
expect(json_response['message']['path']).to eq(['has already been taken'])
end
- it 'should not update name' do
+ it 'does not update name' do
project_param = { name: 'bar' }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(403)
end
- it 'should not update visibility_level' do
+ it 'does not update visibility_level' do
project_param = { visibility_level: 20 }
put api("/projects/#{project3.id}", user4), project_param
expect(response).to have_http_status(403)
@@ -981,7 +909,7 @@ describe API::API, api: true do
end
context 'when authenticated as project developer' do
- it 'should not update other attributes' do
+ it 'does not update other attributes' do
project_param = { path: 'bar',
issues_enabled: true,
wiki_enabled: true,
@@ -1116,36 +1044,36 @@ describe API::API, api: true do
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
- it 'should remove project' do
+ it 'removes project' do
delete api("/projects/#{project.id}", user)
expect(response).to have_http_status(200)
end
- it 'should not remove a project if not an owner' do
+ it 'does not remove a project if not an owner' do
user3 = create(:user)
project.team << [user3, :developer]
delete api("/projects/#{project.id}", user3)
expect(response).to have_http_status(403)
end
- it 'should not remove a non existing project' do
+ it 'does not remove a non existing project' do
delete api('/projects/1328', user)
expect(response).to have_http_status(404)
end
- it 'should not remove a project not attached to user' do
+ it 'does not remove a project not attached to user' do
delete api("/projects/#{project.id}", user2)
expect(response).to have_http_status(404)
end
end
context 'when authenticated as admin' do
- it 'should remove any existing project' do
+ it 'removes any existing project' do
delete api("/projects/#{project.id}", admin)
expect(response).to have_http_status(200)
end
- it 'should not remove a non existing project' do
+ it 'does not remove a non existing project' do
delete api('/projects/1328', admin)
expect(response).to have_http_status(404)
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 5890e9c9d3d..80a856a6e90 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -16,7 +16,7 @@ describe API::API, api: true do
context "authorized user" do
before { project.team << [user2, :reporter] }
- it "should return project commits" do
+ it "returns project commits" do
get api("/projects/#{project.id}/repository/tree", user)
expect(response).to have_http_status(200)
@@ -26,7 +26,7 @@ describe API::API, api: true do
expect(json_response.first['mode']).to eq('040000')
end
- it 'should return a 404 for unknown ref' do
+ it 'returns a 404 for unknown ref' do
get api("/projects/#{project.id}/repository/tree?ref_name=foo", user)
expect(response).to have_http_status(404)
@@ -36,7 +36,7 @@ describe API::API, api: true do
end
context "unauthorized user" do
- it "should not return project commits" do
+ it "does not return project commits" do
get api("/projects/#{project.id}/repository/tree")
expect(response).to have_http_status(401)
end
@@ -44,41 +44,41 @@ describe API::API, api: true do
end
describe "GET /projects/:id/repository/blobs/:sha" do
- it "should get the raw file contents" do
+ it "gets the raw file contents" do
get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user)
expect(response).to have_http_status(200)
end
- it "should return 404 for invalid branch_name" do
+ it "returns 404 for invalid branch_name" do
get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user)
expect(response).to have_http_status(404)
end
- it "should return 404 for invalid file" do
+ it "returns 404 for invalid file" do
get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user)
expect(response).to have_http_status(404)
end
- it "should return a 400 error if filepath is missing" do
+ it "returns a 400 error if filepath is missing" do
get api("/projects/#{project.id}/repository/blobs/master", user)
expect(response).to have_http_status(400)
end
end
describe "GET /projects/:id/repository/commits/:sha/blob" do
- it "should get the raw file contents" do
+ it "gets the raw file contents" do
get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
expect(response).to have_http_status(200)
end
end
describe "GET /projects/:id/repository/raw_blobs/:sha" do
- it "should get the raw file contents" do
+ it "gets the raw file contents" do
get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user)
expect(response).to have_http_status(200)
end
- it 'should return a 404 for unknown blob' do
+ it 'returns a 404 for unknown blob' do
get api("/projects/#{project.id}/repository/raw_blobs/123456", user)
expect(response).to have_http_status(404)
@@ -88,7 +88,7 @@ describe API::API, api: true do
end
describe "GET /projects/:id/repository/archive(.:format)?:sha" do
- it "should get the archive" do
+ it "gets the archive" do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response).to have_http_status(200)
@@ -97,7 +97,7 @@ describe API::API, api: true do
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
- it "should get the archive.zip" do
+ it "gets the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response).to have_http_status(200)
@@ -106,7 +106,7 @@ describe API::API, api: true do
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
- it "should get the archive.tar.bz2" do
+ it "gets the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response).to have_http_status(200)
@@ -115,28 +115,28 @@ describe API::API, api: true do
expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
- it "should return 404 for invalid sha" do
+ it "returns 404 for invalid sha" do
get api("/projects/#{project.id}/repository/archive/?sha=xxx", user)
expect(response).to have_http_status(404)
end
end
describe 'GET /projects/:id/repository/compare' do
- it "should compare branches" do
+ it "compares branches" do
get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature'
expect(response).to have_http_status(200)
expect(json_response['commits']).to be_present
expect(json_response['diffs']).to be_present
end
- it "should compare tags" do
+ it "compares tags" do
get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0'
expect(response).to have_http_status(200)
expect(json_response['commits']).to be_present
expect(json_response['diffs']).to be_present
end
- it "should compare commits" do
+ it "compares commits" do
get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id
expect(response).to have_http_status(200)
expect(json_response['commits']).to be_empty
@@ -144,14 +144,14 @@ describe API::API, api: true do
expect(json_response['compare_same_ref']).to be_falsey
end
- it "should compare commits in reverse order" do
+ it "compares commits in reverse order" do
get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id
expect(response).to have_http_status(200)
expect(json_response['commits']).to be_present
expect(json_response['diffs']).to be_present
end
- it "should compare same refs" do
+ it "compares same refs" do
get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master'
expect(response).to have_http_status(200)
expect(json_response['commits']).to be_empty
@@ -161,7 +161,7 @@ describe API::API, api: true do
end
describe 'GET /projects/:id/repository/contributors' do
- it 'should return valid data' do
+ it 'returns valid data' do
get api("/projects/#{project.id}/repository/contributors", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 00a3c917b6a..f46f016135e 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -35,7 +35,7 @@ describe API::Runners, api: true do
describe 'GET /runners' do
context 'authorized user' do
- it 'should return user available runners' do
+ it 'returns user available runners' do
get api('/runners', user)
shared = json_response.any?{ |r| r['is_shared'] }
@@ -44,7 +44,7 @@ describe API::Runners, api: true do
expect(shared).to be_falsey
end
- it 'should filter runners by scope' do
+ it 'filters runners by scope' do
get api('/runners?scope=active', user)
shared = json_response.any?{ |r| r['is_shared'] }
@@ -53,14 +53,14 @@ describe API::Runners, api: true do
expect(shared).to be_falsey
end
- it 'should avoid filtering if scope is invalid' do
+ it 'avoids filtering if scope is invalid' do
get api('/runners?scope=unknown', user)
expect(response).to have_http_status(400)
end
end
context 'unauthorized user' do
- it 'should not return runners' do
+ it 'does not return runners' do
get api('/runners')
expect(response).to have_http_status(401)
@@ -71,7 +71,7 @@ describe API::Runners, api: true do
describe 'GET /runners/all' do
context 'authorized user' do
context 'with admin privileges' do
- it 'should return all runners' do
+ it 'returns all runners' do
get api('/runners/all', admin)
shared = json_response.any?{ |r| r['is_shared'] }
@@ -82,14 +82,14 @@ describe API::Runners, api: true do
end
context 'without admin privileges' do
- it 'should not return runners list' do
+ it 'does not return runners list' do
get api('/runners/all', user)
expect(response).to have_http_status(403)
end
end
- it 'should filter runners by scope' do
+ it 'filters runners by scope' do
get api('/runners/all?scope=specific', admin)
shared = json_response.any?{ |r| r['is_shared'] }
@@ -98,14 +98,14 @@ describe API::Runners, api: true do
expect(shared).to be_falsey
end
- it 'should avoid filtering if scope is invalid' do
+ it 'avoids filtering if scope is invalid' do
get api('/runners?scope=unknown', admin)
expect(response).to have_http_status(400)
end
end
context 'unauthorized user' do
- it 'should not return runners' do
+ it 'does not return runners' do
get api('/runners')
expect(response).to have_http_status(401)
@@ -116,7 +116,7 @@ describe API::Runners, api: true do
describe 'GET /runners/:id' do
context 'admin user' do
context 'when runner is shared' do
- it "should return runner's details" do
+ it "returns runner's details" do
get api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(200)
@@ -125,7 +125,7 @@ describe API::Runners, api: true do
end
context 'when runner is not shared' do
- it "should return runner's details" do
+ it "returns runner's details" do
get api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(200)
@@ -133,7 +133,7 @@ describe API::Runners, api: true do
end
end
- it 'should return 404 if runner does not exists' do
+ it 'returns 404 if runner does not exists' do
get api('/runners/9999', admin)
expect(response).to have_http_status(404)
@@ -142,7 +142,7 @@ describe API::Runners, api: true do
context "runner project's administrative user" do
context 'when runner is not shared' do
- it "should return runner's details" do
+ it "returns runner's details" do
get api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(200)
@@ -151,7 +151,7 @@ describe API::Runners, api: true do
end
context 'when runner is shared' do
- it "should return runner's details" do
+ it "returns runner's details" do
get api("/runners/#{shared_runner.id}", user)
expect(response).to have_http_status(200)
@@ -161,7 +161,7 @@ describe API::Runners, api: true do
end
context 'other authorized user' do
- it "should not return runner's details" do
+ it "does not return runner's details" do
get api("/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
@@ -169,7 +169,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it "should not return runner's details" do
+ it "does not return runner's details" do
get api("/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
@@ -180,7 +180,7 @@ describe API::Runners, api: true do
describe 'PUT /runners/:id' do
context 'admin user' do
context 'when runner is shared' do
- it 'should update runner' do
+ it 'updates runner' do
description = shared_runner.description
active = shared_runner.active
@@ -201,7 +201,7 @@ describe API::Runners, api: true do
end
context 'when runner is not shared' do
- it 'should update runner' do
+ it 'updates runner' do
description = specific_runner.description
update_runner(specific_runner.id, admin, description: 'test')
specific_runner.reload
@@ -212,7 +212,7 @@ describe API::Runners, api: true do
end
end
- it 'should return 404 if runner does not exists' do
+ it 'returns 404 if runner does not exists' do
update_runner(9999, admin, description: 'test')
expect(response).to have_http_status(404)
@@ -225,7 +225,7 @@ describe API::Runners, api: true do
context 'authorized user' do
context 'when runner is shared' do
- it 'should not update runner' do
+ it 'does not update runner' do
put api("/runners/#{shared_runner.id}", user)
expect(response).to have_http_status(403)
@@ -233,13 +233,13 @@ describe API::Runners, api: true do
end
context 'when runner is not shared' do
- it 'should not update runner without access to it' do
+ it 'does not update runner without access to it' do
put api("/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
end
- it 'should update runner with access to it' do
+ it 'updates runner with access to it' do
description = specific_runner.description
put api("/runners/#{specific_runner.id}", admin), description: 'test'
specific_runner.reload
@@ -252,7 +252,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it 'should not delete runner' do
+ it 'does not delete runner' do
put api("/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
@@ -263,7 +263,7 @@ describe API::Runners, api: true do
describe 'DELETE /runners/:id' do
context 'admin user' do
context 'when runner is shared' do
- it 'should delete runner' do
+ it 'deletes runner' do
expect do
delete api("/runners/#{shared_runner.id}", admin)
end.to change{ Ci::Runner.shared.count }.by(-1)
@@ -272,14 +272,14 @@ describe API::Runners, api: true do
end
context 'when runner is not shared' do
- it 'should delete unused runner' do
+ it 'deletes unused runner' do
expect do
delete api("/runners/#{unused_specific_runner.id}", admin)
end.to change{ Ci::Runner.specific.count }.by(-1)
expect(response).to have_http_status(200)
end
- it 'should delete used runner' do
+ it 'deletes used runner' do
expect do
delete api("/runners/#{specific_runner.id}", admin)
end.to change{ Ci::Runner.specific.count }.by(-1)
@@ -287,7 +287,7 @@ describe API::Runners, api: true do
end
end
- it 'should return 404 if runner does not exists' do
+ it 'returns 404 if runner does not exists' do
delete api('/runners/9999', admin)
expect(response).to have_http_status(404)
@@ -296,24 +296,24 @@ describe API::Runners, api: true do
context 'authorized user' do
context 'when runner is shared' do
- it 'should not delete runner' do
+ it 'does not delete runner' do
delete api("/runners/#{shared_runner.id}", user)
expect(response).to have_http_status(403)
end
end
context 'when runner is not shared' do
- it 'should not delete runner without access to it' do
+ it 'does not delete runner without access to it' do
delete api("/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
end
- it 'should not delete runner with more than one associated project' do
+ it 'does not delete runner with more than one associated project' do
delete api("/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(403)
end
- it 'should delete runner for one owned project' do
+ it 'deletes runner for one owned project' do
expect do
delete api("/runners/#{specific_runner.id}", user)
end.to change{ Ci::Runner.specific.count }.by(-1)
@@ -323,7 +323,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it 'should not delete runner' do
+ it 'does not delete runner' do
delete api("/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
@@ -333,7 +333,7 @@ describe API::Runners, api: true do
describe 'GET /projects/:id/runners' do
context 'authorized user with master privileges' do
- it "should return project's runners" do
+ it "returns project's runners" do
get api("/projects/#{project.id}/runners", user)
shared = json_response.any?{ |r| r['is_shared'] }
@@ -344,7 +344,7 @@ describe API::Runners, api: true do
end
context 'authorized user without master privileges' do
- it "should not return project's runners" do
+ it "does not return project's runners" do
get api("/projects/#{project.id}/runners", user2)
expect(response).to have_http_status(403)
@@ -352,7 +352,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it "should not return project's runners" do
+ it "does not return project's runners" do
get api("/projects/#{project.id}/runners")
expect(response).to have_http_status(401)
@@ -368,21 +368,21 @@ describe API::Runners, api: true do
end
end
- it 'should enable specific runner' do
+ it 'enables specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(+1)
expect(response).to have_http_status(201)
end
- it 'should avoid changes when enabling already enabled runner' do
+ it 'avoids changes when enabling already enabled runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
end.to change{ project.runners.count }.by(0)
expect(response).to have_http_status(409)
end
- it 'should not enable locked runner' do
+ it 'does not enable locked runner' do
specific_runner2.update(locked: true)
expect do
@@ -392,14 +392,14 @@ describe API::Runners, api: true do
expect(response).to have_http_status(403)
end
- it 'should not enable shared runner' do
+ it 'does not enable shared runner' do
post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id
expect(response).to have_http_status(403)
end
context 'user is admin' do
- it 'should enable any specific runner' do
+ it 'enables any specific runner' do
expect do
post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
end.to change{ project.runners.count }.by(+1)
@@ -408,14 +408,14 @@ describe API::Runners, api: true do
end
context 'user is not admin' do
- it 'should not enable runner without access to' do
+ it 'does not enable runner without access to' do
post api("/projects/#{project.id}/runners", user), runner_id: unused_specific_runner.id
expect(response).to have_http_status(403)
end
end
- it 'should raise an error when no runner_id param is provided' do
+ it 'raises an error when no runner_id param is provided' do
post api("/projects/#{project.id}/runners", admin)
expect(response).to have_http_status(400)
@@ -423,7 +423,7 @@ describe API::Runners, api: true do
end
context 'authorized user without permissions' do
- it 'should not enable runner' do
+ it 'does not enable runner' do
post api("/projects/#{project.id}/runners", user2)
expect(response).to have_http_status(403)
@@ -431,7 +431,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it 'should not enable runner' do
+ it 'does not enable runner' do
post api("/projects/#{project.id}/runners")
expect(response).to have_http_status(401)
@@ -442,7 +442,7 @@ describe API::Runners, api: true do
describe 'DELETE /projects/:id/runners/:runner_id' do
context 'authorized user' do
context 'when runner have more than one associated projects' do
- it "should disable project's runner" do
+ it "disables project's runner" do
expect do
delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
end.to change{ project.runners.count }.by(-1)
@@ -451,7 +451,7 @@ describe API::Runners, api: true do
end
context 'when runner have one associated projects' do
- it "should not disable project's runner" do
+ it "does not disable project's runner" do
expect do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
end.to change{ project.runners.count }.by(0)
@@ -459,7 +459,7 @@ describe API::Runners, api: true do
end
end
- it 'should return 404 is runner is not found' do
+ it 'returns 404 is runner is not found' do
delete api("/projects/#{project.id}/runners/9999", user)
expect(response).to have_http_status(404)
@@ -467,7 +467,7 @@ describe API::Runners, api: true do
end
context 'authorized user without permissions' do
- it "should not disable project's runner" do
+ it "does not disable project's runner" do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user2)
expect(response).to have_http_status(403)
@@ -475,7 +475,7 @@ describe API::Runners, api: true do
end
context 'unauthorized user' do
- it "should not disable project's runner" do
+ it "does not disable project's runner" do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}")
expect(response).to have_http_status(401)
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index a2446e12804..375671bca4c 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -11,13 +11,13 @@ describe API::API, api: true do
describe "PUT /projects/:id/services/#{service.dasherize}" do
include_context service
- it "should update #{service} settings" do
+ it "updates #{service} settings" do
put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
expect(response).to have_http_status(200)
end
- it "should return if required fields missing" do
+ it "returns if required fields missing" do
attrs = service_attrs
required_attributes = service_attrs_list.select do |attr|
@@ -32,7 +32,7 @@ describe API::API, api: true do
attrs.delete(required_attributes.sample)
expected_code = 400
end
-
+
put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs
expect(response.status).to eq(expected_code)
@@ -42,7 +42,7 @@ describe API::API, api: true do
describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
- it "should delete #{service}" do
+ it "deletes #{service}" do
delete api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_http_status(200)
@@ -62,29 +62,29 @@ describe API::API, api: true do
service_object.save
end
- it 'should return authentication error when unauthenticated' do
+ it 'returns authentication error when unauthenticated' do
get api("/projects/#{project.id}/services/#{dashed_service}")
expect(response).to have_http_status(401)
end
-
- it "should return all properties of service #{service} when authenticated as admin" do
+
+ it "returns all properties of service #{service} when authenticated as admin" do
get api("/projects/#{project.id}/services/#{dashed_service}", admin)
-
+
expect(response).to have_http_status(200)
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
end
- it "should return properties of service #{service} other than passwords when authenticated as project owner" do
+ it "returns properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_http_status(200)
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
end
- it "should return error when authenticated but not a project owner" do
+ it "returns error when authenticated but not a project owner" do
project.team << [user2, :developer]
get api("/projects/#{project.id}/services/#{dashed_service}", user2)
-
+
expect(response).to have_http_status(403)
end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index c15b7ff9792..519e7ce12ad 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -7,7 +7,7 @@ describe API::API, api: true do
describe "POST /session" do
context "when valid password" do
- it "should return private token" do
+ it "returns private token" do
post api("/session"), email: user.email, password: '12345678'
expect(response).to have_http_status(201)
@@ -20,7 +20,7 @@ describe API::API, api: true do
end
context 'when email has case-typo and password is valid' do
- it 'should return private token' do
+ it 'returns private token' do
post api('/session'), email: user.email.upcase, password: '12345678'
expect(response.status).to eq 201
@@ -33,7 +33,7 @@ describe API::API, api: true do
end
context 'when login has case-typo and password is valid' do
- it 'should return private token' do
+ it 'returns private token' do
post api('/session'), login: user.username.upcase, password: '12345678'
expect(response.status).to eq 201
@@ -46,7 +46,7 @@ describe API::API, api: true do
end
context "when invalid password" do
- it "should return authentication error" do
+ it "returns authentication error" do
post api("/session"), email: user.email, password: '123'
expect(response).to have_http_status(401)
@@ -56,7 +56,7 @@ describe API::API, api: true do
end
context "when empty password" do
- it "should return authentication error" do
+ it "returns authentication error" do
post api("/session"), email: user.email
expect(response).to have_http_status(401)
@@ -66,7 +66,7 @@ describe API::API, api: true do
end
context "when empty name" do
- it "should return authentication error" do
+ it "returns authentication error" do
post api("/session"), password: user.password
expect(response).to have_http_status(401)
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 684c2cd8e24..54d096e8b7f 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -7,7 +7,7 @@ describe API::API, 'Settings', api: true do
let(:admin) { create(:admin) }
describe "GET /application/settings" do
- it "should return application settings" do
+ it "returns application settings" do
get api("/application/settings", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Hash
@@ -23,7 +23,7 @@ describe API::API, 'Settings', api: true do
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
end
- it "should update application settings" do
+ it "updates application settings" do
put api("/application/settings", admin),
default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom'
expect(response).to have_http_status(200)
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index cf66f261ade..1ce2658569e 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -11,21 +11,21 @@ describe API::API, api: true do
describe "GET /hooks" do
context "when no user" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/hooks")
expect(response).to have_http_status(401)
end
end
context "when not an admin" do
- it "should return forbidden error" do
+ it "returns forbidden error" do
get api("/hooks", user)
expect(response).to have_http_status(403)
end
end
context "when authenticated as admin" do
- it "should return an array of hooks" do
+ it "returns an array of hooks" do
get api("/hooks", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -35,18 +35,18 @@ describe API::API, api: true do
end
describe "POST /hooks" do
- it "should create new hook" do
+ it "creates new hook" do
expect do
post api("/hooks", admin), url: 'http://example.com'
end.to change { SystemHook.count }.by(1)
end
- it "should respond with 400 if url not given" do
+ it "responds with 400 if url not given" do
post api("/hooks", admin)
expect(response).to have_http_status(400)
end
- it "should not create new hook without url" do
+ it "does not create new hook without url" do
expect do
post api("/hooks", admin)
end.not_to change { SystemHook.count }
@@ -54,26 +54,26 @@ describe API::API, api: true do
end
describe "GET /hooks/:id" do
- it "should return hook by id" do
+ it "returns hook by id" do
get api("/hooks/#{hook.id}", admin)
expect(response).to have_http_status(200)
expect(json_response['event_name']).to eq('project_create')
end
- it "should return 404 on failure" do
+ it "returns 404 on failure" do
get api("/hooks/404", admin)
expect(response).to have_http_status(404)
end
end
describe "DELETE /hooks/:id" do
- it "should delete a hook" do
+ it "deletes a hook" do
expect do
delete api("/hooks/#{hook.id}", admin)
end.to change { SystemHook.count }.by(-1)
end
- it "should return success if hook id not found" do
+ it "returns success if hook id not found" do
delete api("/hooks/12345", admin)
expect(response).to have_http_status(200)
end
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index fa700ab7343..d563883cd47 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -16,7 +16,7 @@ describe API::API, api: true do
let(:description) { 'Awesome release!' }
context 'without releases' do
- it "should return an array of project tags" do
+ it "returns an array of project tags" do
get api("/projects/#{project.id}/repository/tags", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -30,7 +30,7 @@ describe API::API, api: true do
release.update_attributes(description: description)
end
- it "should return an array of project tags with release info" do
+ it "returns an array of project tags with release info" do
get api("/projects/#{project.id}/repository/tags", user)
expect(response).to have_http_status(200)
@@ -61,7 +61,7 @@ describe API::API, api: true do
describe 'POST /projects/:id/repository/tags' do
context 'lightweight tags' do
- it 'should create a new tag' do
+ it 'creates a new tag' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.0.1',
ref: 'master'
@@ -72,7 +72,7 @@ describe API::API, api: true do
end
context 'lightweight tags with release notes' do
- it 'should create a new tag' do
+ it 'creates a new tag' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v7.0.1',
ref: 'master',
@@ -92,13 +92,13 @@ describe API::API, api: true do
end
context 'delete tag' do
- it 'should delete an existing tag' do
+ it 'deletes an existing tag' do
delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
expect(response).to have_http_status(200)
expect(json_response['tag_name']).to eq(tag_name)
end
- it 'should raise 404 if the tag does not exist' do
+ it 'raises 404 if the tag does not exist' do
delete api("/projects/#{project.id}/repository/tags/foobar", user)
expect(response).to have_http_status(404)
end
@@ -106,7 +106,7 @@ describe API::API, api: true do
end
context 'annotated tag' do
- it 'should create a new annotated tag' do
+ it 'creates a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
repo_path = project.repository.path_to_repo
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
@@ -123,14 +123,14 @@ describe API::API, api: true do
end
end
- it 'should deny for user without push access' do
+ it 'denies for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
tag_name: 'v1.9.0',
ref: '621491c677087aa243f165eab467bfdfbee00be1'
expect(response).to have_http_status(403)
end
- it 'should return 400 if tag name is invalid' do
+ it 'returns 400 if tag name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v 1.0.0',
ref: 'master'
@@ -138,7 +138,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Tag name invalid')
end
- it 'should return 400 if tag already exists' do
+ it 'returns 400 if tag already exists' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'v8.0.0',
ref: 'master'
@@ -150,7 +150,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Tag v8.0.0 already exists')
end
- it 'should return 400 if ref name is invalid' do
+ it 'returns 400 if ref name is invalid' do
post api("/projects/#{project.id}/repository/tags", user),
tag_name: 'mytag',
ref: 'foo'
@@ -163,7 +163,7 @@ describe API::API, api: true do
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
- it 'should create description for existing git tag' do
+ it 'creates description for existing git tag' do
post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
description: description
@@ -172,7 +172,7 @@ describe API::API, api: true do
expect(json_response['description']).to eq(description)
end
- it 'should return 404 if the tag does not exist' do
+ it 'returns 404 if the tag does not exist' do
post api("/projects/#{project.id}/repository/tags/foobar/release", user),
description: description
@@ -186,7 +186,7 @@ describe API::API, api: true do
release.update_attributes(description: description)
end
- it 'should return 409 if there is already a release' do
+ it 'returns 409 if there is already a release' do
post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
description: description
@@ -207,7 +207,7 @@ describe API::API, api: true do
release.update_attributes(description: description)
end
- it 'should update the release description' do
+ it 'updates the release description' do
put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
description: new_description
@@ -217,7 +217,7 @@ describe API::API, api: true do
end
end
- it 'should return 404 if the tag does not exist' do
+ it 'returns 404 if the tag does not exist' do
put api("/projects/#{project.id}/repository/tags/foobar/release", user),
description: new_description
@@ -225,7 +225,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Tag does not exist')
end
- it 'should return 404 if the release does not exist' do
+ it 'returns 404 if the release does not exist' do
put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user),
description: new_description
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index 3ccd0af652f..887a2ba5b84 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -117,6 +117,12 @@ describe API::Todos, api: true do
expect(response.status).to eq(200)
expect(pending_1.reload).to be_done
end
+
+ it 'updates todos cache' do
+ expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
+
+ delete api("/todos/#{pending_1.id}", john_doe)
+ end
end
end
@@ -139,6 +145,12 @@ describe API::Todos, api: true do
expect(pending_2.reload).to be_done
expect(pending_3.reload).to be_done
end
+
+ it 'updates todos cache' do
+ expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original
+
+ delete api("/todos", john_doe)
+ end
end
end
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 8992996c30a..82bba1ce8a4 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -27,17 +27,17 @@ describe API::API do
end
context 'Handles errors' do
- it 'should return bad request if token is missing' do
+ it 'returns bad request if token is missing' do
post api("/projects/#{project.id}/trigger/builds"), ref: 'master'
expect(response).to have_http_status(400)
end
- it 'should return not found if project is not found' do
+ it 'returns not found if project is not found' do
post api('/projects/0/trigger/builds'), options.merge(ref: 'master')
expect(response).to have_http_status(404)
end
- it 'should return unauthorized if token is for different project' do
+ it 'returns unauthorized if token is for different project' do
post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(401)
end
@@ -46,14 +46,15 @@ describe API::API do
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
- it 'should create builds' do
+ it 'creates builds' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
- expect(pipeline.builds.size).to eq(2)
+ expect(pipeline.builds.pending.size).to eq(2)
+ expect(pipeline.builds.size).to eq(5)
end
- it 'should return bad request with no builds created if there\'s no commit for that ref' do
+ it 'returns bad request with no builds created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created')
@@ -64,19 +65,19 @@ describe API::API do
{ 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
end
- it 'should validate variables to be a hash' do
+ it 'validates variables to be a hash' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a hash')
end
- it 'should validate variables needs to be a map of key-valued strings' do
+ it 'validates variables needs to be a map of key-valued strings' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
- it 'create trigger request with variables' do
+ it 'creates trigger request with variables' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
@@ -88,7 +89,7 @@ describe API::API do
describe 'GET /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
- it 'should return list of triggers' do
+ it 'returns list of triggers' do
get api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(200)
@@ -98,7 +99,7 @@ describe API::API do
end
context 'authenticated user with invalid permissions' do
- it 'should not return triggers list' do
+ it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
@@ -106,7 +107,7 @@ describe API::API do
end
context 'unauthenticated user' do
- it 'should not return triggers list' do
+ it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
@@ -116,14 +117,14 @@ describe API::API do
describe 'GET /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
- it 'should return trigger details' do
+ it 'returns trigger details' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash)
end
- it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ it 'responds with 404 Not Found if requesting non-existing trigger' do
get api("/projects/#{project.id}/triggers/abcdef012345", user)
expect(response).to have_http_status(404)
@@ -131,7 +132,7 @@ describe API::API do
end
context 'authenticated user with invalid permissions' do
- it 'should not return triggers list' do
+ it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
expect(response).to have_http_status(403)
@@ -139,7 +140,7 @@ describe API::API do
end
context 'unauthenticated user' do
- it 'should not return triggers list' do
+ it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}")
expect(response).to have_http_status(401)
@@ -149,7 +150,7 @@ describe API::API do
describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
- it 'should create trigger' do
+ it 'creates trigger' do
expect do
post api("/projects/#{project.id}/triggers", user)
end.to change{project.triggers.count}.by(1)
@@ -160,7 +161,7 @@ describe API::API do
end
context 'authenticated user with invalid permissions' do
- it 'should not create trigger' do
+ it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
@@ -168,7 +169,7 @@ describe API::API do
end
context 'unauthenticated user' do
- it 'should not create trigger' do
+ it 'does not create trigger' do
post api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
@@ -178,14 +179,14 @@ describe API::API do
describe 'DELETE /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
- it 'should delete trigger' do
+ it 'deletes trigger' do
expect do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
end.to change{project.triggers.count}.by(-1)
expect(response).to have_http_status(200)
end
- it 'should respond with 404 Not Found if requesting non-existing trigger' do
+ it 'responds with 404 Not Found if requesting non-existing trigger' do
delete api("/projects/#{project.id}/triggers/abcdef012345", user)
expect(response).to have_http_status(404)
@@ -193,7 +194,7 @@ describe API::API do
end
context 'authenticated user with invalid permissions' do
- it 'should not delete trigger' do
+ it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
expect(response).to have_http_status(403)
@@ -201,7 +202,7 @@ describe API::API do
end
context 'unauthenticated user' do
- it 'should not delete trigger' do
+ it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}")
expect(response).to have_http_status(401)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e43e3e269bf..0bbba64a6d5 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -13,7 +13,7 @@ describe API::API, api: true do
describe "GET /users" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/users")
expect(response).to have_http_status(401)
end
@@ -38,7 +38,7 @@ describe API::API, api: true do
end
end
- it "should return an array of users" do
+ it "returns an array of users" do
get api("/users", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -48,7 +48,7 @@ describe API::API, api: true do
end['username']).to eq(username)
end
- it "should return one user" do
+ it "returns one user" do
get api("/users?username=#{omniauth_user.username}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -57,7 +57,7 @@ describe API::API, api: true do
end
context "when admin" do
- it "should return an array of users" do
+ it "returns an array of users" do
get api("/users", admin)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -72,24 +72,24 @@ describe API::API, api: true do
end
describe "GET /users/:id" do
- it "should return a user by id" do
+ it "returns a user by id" do
get api("/users/#{user.id}", user)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
- it "should return a 401 if unauthenticated" do
+ it "returns a 401 if unauthenticated" do
get api("/users/9998")
expect(response).to have_http_status(401)
end
- it "should return a 404 error if user id not found" do
+ it "returns a 404 error if user id not found" do
get api("/users/9999", user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
- it "should return a 404 if invalid ID" do
+ it "returns a 404 if invalid ID" do
get api("/users/1ASDF", user)
expect(response).to have_http_status(404)
end
@@ -98,13 +98,13 @@ describe API::API, api: true do
describe "POST /users" do
before{ admin }
- it "should create user" do
+ it "creates user" do
expect do
post api("/users", admin), attributes_for(:user, projects_limit: 3)
end.to change { User.count }.by(1)
end
- it "should create user with correct attributes" do
+ it "creates user with correct attributes" do
post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true)
expect(response).to have_http_status(201)
user_id = json_response['id']
@@ -114,7 +114,7 @@ describe API::API, api: true do
expect(new_user.can_create_group).to eq(true)
end
- it "should create non-admin user" do
+ it "creates non-admin user" do
post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false)
expect(response).to have_http_status(201)
user_id = json_response['id']
@@ -124,7 +124,7 @@ describe API::API, api: true do
expect(new_user.can_create_group).to eq(false)
end
- it "should create non-admin users by default" do
+ it "creates non-admin users by default" do
post api('/users', admin), attributes_for(:user)
expect(response).to have_http_status(201)
user_id = json_response['id']
@@ -133,7 +133,7 @@ describe API::API, api: true do
expect(new_user.admin).to eq(false)
end
- it "should return 201 Created on success" do
+ it "returns 201 Created on success" do
post api("/users", admin), attributes_for(:user, projects_limit: 3)
expect(response).to have_http_status(201)
end
@@ -148,7 +148,7 @@ describe API::API, api: true do
expect(new_user.external).to be_falsy
end
- it 'should allow an external user to be created' do
+ it 'allows an external user to be created' do
post api("/users", admin), attributes_for(:user, external: true)
expect(response).to have_http_status(201)
@@ -158,7 +158,7 @@ describe API::API, api: true do
expect(new_user.external).to be_truthy
end
- it "should not create user with invalid email" do
+ it "does not create user with invalid email" do
post api('/users', admin),
email: 'invalid email',
password: 'password',
@@ -166,27 +166,27 @@ describe API::API, api: true do
expect(response).to have_http_status(400)
end
- it 'should return 400 error if name not given' do
+ it 'returns 400 error if name not given' do
post api('/users', admin), attributes_for(:user).except(:name)
expect(response).to have_http_status(400)
end
- it 'should return 400 error if password not given' do
+ it 'returns 400 error if password not given' do
post api('/users', admin), attributes_for(:user).except(:password)
expect(response).to have_http_status(400)
end
- it 'should return 400 error if email not given' do
+ it 'returns 400 error if email not given' do
post api('/users', admin), attributes_for(:user).except(:email)
expect(response).to have_http_status(400)
end
- it 'should return 400 error if username not given' do
+ it 'returns 400 error if username not given' do
post api('/users', admin), attributes_for(:user).except(:username)
expect(response).to have_http_status(400)
end
- it 'should return 400 error if user does not validate' do
+ it 'returns 400 error if user does not validate' do
post api('/users', admin),
password: 'pass',
email: 'test@example.com',
@@ -205,7 +205,7 @@ describe API::API, api: true do
to eq([Gitlab::Regex.namespace_regex_message])
end
- it "shouldn't available for non admin users" do
+ it "is not available for non admin users" do
post api("/users", user), attributes_for(:user)
expect(response).to have_http_status(403)
end
@@ -219,7 +219,7 @@ describe API::API, api: true do
name: 'foo'
end
- it 'should return 409 conflict error if user with same email exists' do
+ it 'returns 409 conflict error if user with same email exists' do
expect do
post api('/users', admin),
name: 'foo',
@@ -231,7 +231,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('Email has already been taken')
end
- it 'should return 409 conflict error if same username exists' do
+ it 'returns 409 conflict error if same username exists' do
expect do
post api('/users', admin),
name: 'foo',
@@ -246,7 +246,7 @@ describe API::API, api: true do
end
describe "GET /users/sign_up" do
- it "should redirect to sign in page" do
+ it "redirects to sign in page" do
get "/users/sign_up"
expect(response).to have_http_status(302)
expect(response).to redirect_to(new_user_session_path)
@@ -258,55 +258,55 @@ describe API::API, api: true do
before { admin }
- it "should update user with new bio" do
+ it "updates user with new bio" do
put api("/users/#{user.id}", admin), { bio: 'new test bio' }
expect(response).to have_http_status(200)
expect(json_response['bio']).to eq('new test bio')
expect(user.reload.bio).to eq('new test bio')
end
- it 'should update user with his own email' do
+ it 'updates user with his own email' do
put api("/users/#{user.id}", admin), email: user.email
expect(response).to have_http_status(200)
expect(json_response['email']).to eq(user.email)
expect(user.reload.email).to eq(user.email)
end
- it 'should update user with his own username' do
+ it 'updates user with his own username' do
put api("/users/#{user.id}", admin), username: user.username
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
expect(user.reload.username).to eq(user.username)
end
- it "should update user's existing identity" do
+ it "updates user's existing identity" do
put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321'
expect(response).to have_http_status(200)
expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321')
end
- it 'should update user with new identity' do
+ it 'updates user with new identity' do
put api("/users/#{user.id}", admin), provider: 'github', extern_uid: '67890'
expect(response).to have_http_status(200)
expect(user.reload.identities.first.extern_uid).to eq('67890')
expect(user.reload.identities.first.provider).to eq('github')
end
- it "should update admin status" do
+ it "updates admin status" do
put api("/users/#{user.id}", admin), { admin: true }
expect(response).to have_http_status(200)
expect(json_response['is_admin']).to eq(true)
expect(user.reload.admin).to eq(true)
end
- it "should update external status" do
+ it "updates external status" do
put api("/users/#{user.id}", admin), { external: true }
expect(response.status).to eq 200
expect(json_response['external']).to eq(true)
expect(user.reload.external?).to be_truthy
end
- it "should not update admin status" do
+ it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response).to have_http_status(200)
expect(json_response['is_admin']).to eq(true)
@@ -314,28 +314,28 @@ describe API::API, api: true do
expect(admin_user.can_create_group).to eq(false)
end
- it "should not allow invalid update" do
+ it "does not allow invalid update" do
put api("/users/#{user.id}", admin), { email: 'invalid email' }
expect(response).to have_http_status(400)
expect(user.reload.email).not_to eq('invalid email')
end
- it "shouldn't available for non admin users" do
+ it "is not available for non admin users" do
put api("/users/#{user.id}", user), attributes_for(:user)
expect(response).to have_http_status(403)
end
- it "should return 404 for non-existing user" do
+ it "returns 404 for non-existing user" do
put api("/users/999999", admin), { bio: 'update should fail' }
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{put api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError)
end
- it 'should return 400 error if user does not validate' do
+ it 'returns 400 error if user does not validate' do
put api("/users/#{user.id}", admin),
password: 'pass',
email: 'test@example.com',
@@ -361,13 +361,13 @@ describe API::API, api: true do
@user = User.all.last
end
- it 'should return 409 conflict error if email address exists' do
+ it 'returns 409 conflict error if email address exists' do
put api("/users/#{@user.id}", admin), email: 'test@example.com'
expect(response).to have_http_status(409)
expect(@user.reload.email).to eq(@user.email)
end
- it 'should return 409 conflict error if username taken' do
+ it 'returns 409 conflict error if username taken' do
@user_id = User.all.last.id
put api("/users/#{@user.id}", admin), username: 'test'
expect(response).to have_http_status(409)
@@ -379,28 +379,28 @@ describe API::API, api: true do
describe "POST /users/:id/keys" do
before { admin }
- it "should not create invalid ssh key" do
+ it "does not create invalid ssh key" do
post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "key" not given')
end
- it 'should not create key without title' do
+ it 'does not create key without title' do
post api("/users/#{user.id}/keys", admin), key: 'some key'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "title" not given')
end
- it "should create ssh key" do
+ it "creates ssh key" do
key_attrs = attributes_for :key
expect do
post api("/users/#{user.id}/keys", admin), key_attrs
end.to change{ user.keys.count }.by(1)
end
- it "should return 405 for invalid ID" do
- post api("/users/ASDF/keys", admin)
- expect(response).to have_http_status(405)
+ it "returns 400 for invalid ID" do
+ post api("/users/999999/keys", admin)
+ expect(response).to have_http_status(400)
end
end
@@ -408,20 +408,20 @@ describe API::API, api: true do
before { admin }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api("/users/#{user.id}/keys")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should return 404 for non-existing user' do
+ it 'returns 404 for non-existing user' do
get api('/users/999999/keys', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
- it 'should return array of ssh keys' do
+ it 'returns array of ssh keys' do
user.keys << key
user.save
get api("/users/#{user.id}/keys", admin)
@@ -429,11 +429,6 @@ describe API::API, api: true do
expect(json_response).to be_an Array
expect(json_response.first['title']).to eq(key.title)
end
-
- it "should return 405 for invalid ID" do
- get api("/users/ASDF/keys", admin)
- expect(response).to have_http_status(405)
- end
end
end
@@ -441,14 +436,14 @@ describe API::API, api: true do
before { admin }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
delete api("/users/#{user.id}/keys/42")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should delete existing key' do
+ it 'deletes existing key' do
user.keys << key
user.save
expect do
@@ -457,7 +452,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it 'should return 404 error if user not found' do
+ it 'returns 404 error if user not found' do
user.keys << key
user.save
delete api("/users/999999/keys/#{key.id}", admin)
@@ -465,7 +460,7 @@ describe API::API, api: true do
expect(json_response['message']).to eq('404 User Not Found')
end
- it 'should return 404 error if key not foud' do
+ it 'returns 404 error if key not foud' do
delete api("/users/#{user.id}/keys/42", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Key Not Found')
@@ -476,22 +471,22 @@ describe API::API, api: true do
describe "POST /users/:id/emails" do
before { admin }
- it "should not create invalid email" do
+ it "does not create invalid email" do
post api("/users/#{user.id}/emails", admin), {}
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "email" not given')
end
- it "should create email" do
+ it "creates email" do
email_attrs = attributes_for :email
expect do
post api("/users/#{user.id}/emails", admin), email_attrs
end.to change{ user.emails.count }.by(1)
end
- it "should raise error for invalid ID" do
- post api("/users/ASDF/emails", admin)
- expect(response).to have_http_status(405)
+ it "raises error for invalid ID" do
+ post api("/users/999999/emails", admin)
+ expect(response).to have_http_status(400)
end
end
@@ -499,20 +494,20 @@ describe API::API, api: true do
before { admin }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
get api("/users/#{user.id}/emails")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should return 404 for non-existing user' do
+ it 'returns 404 for non-existing user' do
get api('/users/999999/emails', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
- it 'should return array of emails' do
+ it 'returns array of emails' do
user.emails << email
user.save
get api("/users/#{user.id}/emails", admin)
@@ -521,7 +516,7 @@ describe API::API, api: true do
expect(json_response.first['email']).to eq(email.email)
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
put api("/users/ASDF/emails", admin)
expect(response).to have_http_status(405)
end
@@ -532,14 +527,14 @@ describe API::API, api: true do
before { admin }
context 'when unauthenticated' do
- it 'should return authentication error' do
+ it 'returns authentication error' do
delete api("/users/#{user.id}/emails/42")
expect(response).to have_http_status(401)
end
end
context 'when authenticated' do
- it 'should delete existing email' do
+ it 'deletes existing email' do
user.emails << email
user.save
expect do
@@ -548,7 +543,7 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it 'should return 404 error if user not found' do
+ it 'returns 404 error if user not found' do
user.emails << email
user.save
delete api("/users/999999/emails/#{email.id}", admin)
@@ -556,51 +551,53 @@ describe API::API, api: true do
expect(json_response['message']).to eq('404 User Not Found')
end
- it 'should return 404 error if email not foud' do
+ it 'returns 404 error if email not foud' do
delete api("/users/#{user.id}/emails/42", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Email Not Found')
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{delete api("/users/ASDF/emails/bar", admin) }.to raise_error(ActionController::RoutingError)
end
end
end
describe "DELETE /users/:id" do
+ let!(:namespace) { user.namespace }
before { admin }
- it "should delete user" do
+ it "deletes user" do
delete api("/users/#{user.id}", admin)
expect(response).to have_http_status(200)
expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound
+ expect { Namespace.find(namespace.id) }.to raise_error ActiveRecord::RecordNotFound
expect(json_response['email']).to eq(user.email)
end
- it "should not delete for unauthenticated user" do
+ it "does not delete for unauthenticated user" do
delete api("/users/#{user.id}")
expect(response).to have_http_status(401)
end
- it "shouldn't available for non admin users" do
+ it "is not available for non admin users" do
delete api("/users/#{user.id}", user)
expect(response).to have_http_status(403)
end
- it "should return 404 for non-existing user" do
+ it "returns 404 for non-existing user" do
delete api("/users/999999", admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{delete api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError)
end
end
describe "GET /user" do
- it "should return current user" do
+ it "returns current user" do
get api("/user", user)
expect(response).to have_http_status(200)
expect(json_response['email']).to eq(user.email)
@@ -610,7 +607,7 @@ describe API::API, api: true do
expect(json_response['projects_limit']).to eq(user.projects_limit)
end
- it "should return 401 error if user is unauthenticated" do
+ it "returns 401 error if user is unauthenticated" do
get api("/user")
expect(response).to have_http_status(401)
end
@@ -618,14 +615,14 @@ describe API::API, api: true do
describe "GET /user/keys" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/user/keys")
expect(response).to have_http_status(401)
end
end
context "when authenticated" do
- it "should return array of ssh keys" do
+ it "returns array of ssh keys" do
user.keys << key
user.save
get api("/user/keys", user)
@@ -637,7 +634,7 @@ describe API::API, api: true do
end
describe "GET /user/keys/:id" do
- it "should return single key" do
+ it "returns single key" do
user.keys << key
user.save
get api("/user/keys/#{key.id}", user)
@@ -645,13 +642,13 @@ describe API::API, api: true do
expect(json_response["title"]).to eq(key.title)
end
- it "should return 404 Not Found within invalid ID" do
+ it "returns 404 Not Found within invalid ID" do
get api("/user/keys/42", user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
- it "should return 404 error if admin accesses user's ssh key" do
+ it "returns 404 error if admin accesses user's ssh key" do
user.keys << key
user.save
admin
@@ -660,14 +657,14 @@ describe API::API, api: true do
expect(json_response['message']).to eq('404 Not found')
end
- it "should return 404 for invalid ID" do
+ it "returns 404 for invalid ID" do
get api("/users/keys/ASDF", admin)
expect(response).to have_http_status(404)
end
end
describe "POST /user/keys" do
- it "should create ssh key" do
+ it "creates ssh key" do
key_attrs = attributes_for :key
expect do
post api("/user/keys", user), key_attrs
@@ -675,31 +672,31 @@ describe API::API, api: true do
expect(response).to have_http_status(201)
end
- it "should return a 401 error if unauthorized" do
+ it "returns a 401 error if unauthorized" do
post api("/user/keys"), title: 'some title', key: 'some key'
expect(response).to have_http_status(401)
end
- it "should not create ssh key without key" do
+ it "does not create ssh key without key" do
post api("/user/keys", user), title: 'title'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "key" not given')
end
- it 'should not create ssh key without title' do
+ it 'does not create ssh key without title' do
post api('/user/keys', user), key: 'some key'
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "title" not given')
end
- it "should not create ssh key without title" do
+ it "does not create ssh key without title" do
post api("/user/keys", user), key: "somekey"
expect(response).to have_http_status(400)
end
end
describe "DELETE /user/keys/:id" do
- it "should delete existed key" do
+ it "deletes existed key" do
user.keys << key
user.save
expect do
@@ -708,33 +705,33 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it "should return success if key ID not found" do
+ it "returns success if key ID not found" do
delete api("/user/keys/42", user)
expect(response).to have_http_status(200)
end
- it "should return 401 error if unauthorized" do
+ it "returns 401 error if unauthorized" do
user.keys << key
user.save
delete api("/user/keys/#{key.id}")
expect(response).to have_http_status(401)
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{delete api("/users/keys/ASDF", admin) }.to raise_error(ActionController::RoutingError)
end
end
describe "GET /user/emails" do
context "when unauthenticated" do
- it "should return authentication error" do
+ it "returns authentication error" do
get api("/user/emails")
expect(response).to have_http_status(401)
end
end
context "when authenticated" do
- it "should return array of emails" do
+ it "returns array of emails" do
user.emails << email
user.save
get api("/user/emails", user)
@@ -746,7 +743,7 @@ describe API::API, api: true do
end
describe "GET /user/emails/:id" do
- it "should return single email" do
+ it "returns single email" do
user.emails << email
user.save
get api("/user/emails/#{email.id}", user)
@@ -754,13 +751,13 @@ describe API::API, api: true do
expect(json_response["email"]).to eq(email.email)
end
- it "should return 404 Not Found within invalid ID" do
+ it "returns 404 Not Found within invalid ID" do
get api("/user/emails/42", user)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 Not found')
end
- it "should return 404 error if admin accesses user's email" do
+ it "returns 404 error if admin accesses user's email" do
user.emails << email
user.save
admin
@@ -769,14 +766,14 @@ describe API::API, api: true do
expect(json_response['message']).to eq('404 Not found')
end
- it "should return 404 for invalid ID" do
+ it "returns 404 for invalid ID" do
get api("/users/emails/ASDF", admin)
expect(response).to have_http_status(404)
end
end
describe "POST /user/emails" do
- it "should create email" do
+ it "creates email" do
email_attrs = attributes_for :email
expect do
post api("/user/emails", user), email_attrs
@@ -784,12 +781,12 @@ describe API::API, api: true do
expect(response).to have_http_status(201)
end
- it "should return a 401 error if unauthorized" do
+ it "returns a 401 error if unauthorized" do
post api("/user/emails"), email: 'some email'
expect(response).to have_http_status(401)
end
- it "should not create email with invalid email" do
+ it "does not create email with invalid email" do
post api("/user/emails", user), {}
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('400 (Bad request) "email" not given')
@@ -797,7 +794,7 @@ describe API::API, api: true do
end
describe "DELETE /user/emails/:id" do
- it "should delete existed email" do
+ it "deletes existed email" do
user.emails << email
user.save
expect do
@@ -806,44 +803,44 @@ describe API::API, api: true do
expect(response).to have_http_status(200)
end
- it "should return success if email ID not found" do
+ it "returns success if email ID not found" do
delete api("/user/emails/42", user)
expect(response).to have_http_status(200)
end
- it "should return 401 error if unauthorized" do
+ it "returns 401 error if unauthorized" do
user.emails << email
user.save
delete api("/user/emails/#{email.id}")
expect(response).to have_http_status(401)
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{delete api("/users/emails/ASDF", admin) }.to raise_error(ActionController::RoutingError)
end
end
describe 'PUT /user/:id/block' do
before { admin }
- it 'should block existing user' do
+ it 'blocks existing user' do
put api("/users/#{user.id}/block", admin)
expect(response).to have_http_status(200)
expect(user.reload.state).to eq('blocked')
end
- it 'should not re-block ldap blocked users' do
+ it 'does not re-block ldap blocked users' do
put api("/users/#{ldap_blocked_user.id}/block", admin)
expect(response).to have_http_status(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
- it 'should not be available for non admin users' do
+ it 'does not be available for non admin users' do
put api("/users/#{user.id}/block", user)
expect(response).to have_http_status(403)
expect(user.reload.state).to eq('active')
end
- it 'should return a 404 error if user id not found' do
+ it 'returns a 404 error if user id not found' do
put api('/users/9999/block', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -854,37 +851,37 @@ describe API::API, api: true do
let(:blocked_user) { create(:user, state: 'blocked') }
before { admin }
- it 'should unblock existing user' do
+ it 'unblocks existing user' do
put api("/users/#{user.id}/unblock", admin)
expect(response).to have_http_status(200)
expect(user.reload.state).to eq('active')
end
- it 'should unblock a blocked user' do
+ it 'unblocks a blocked user' do
put api("/users/#{blocked_user.id}/unblock", admin)
expect(response).to have_http_status(200)
expect(blocked_user.reload.state).to eq('active')
end
- it 'should not unblock ldap blocked users' do
+ it 'does not unblock ldap blocked users' do
put api("/users/#{ldap_blocked_user.id}/unblock", admin)
expect(response).to have_http_status(403)
expect(ldap_blocked_user.reload.state).to eq('ldap_blocked')
end
- it 'should not be available for non admin users' do
+ it 'does not be available for non admin users' do
put api("/users/#{user.id}/unblock", user)
expect(response).to have_http_status(403)
expect(user.reload.state).to eq('active')
end
- it 'should return a 404 error if user id not found' do
+ it 'returns a 404 error if user id not found' do
put api('/users/9999/block', admin)
expect(response).to have_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
end
- it "should raise error for invalid ID" do
+ it "raises error for invalid ID" do
expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError)
end
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index ddba18245f8..05fbdb909dc 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -12,7 +12,7 @@ describe API::API, api: true do
describe 'GET /projects/:id/variables' do
context 'authorized user with proper permissions' do
- it 'should return project variables' do
+ it 'returns project variables' do
get api("/projects/#{project.id}/variables", user)
expect(response).to have_http_status(200)
@@ -21,7 +21,7 @@ describe API::API, api: true do
end
context 'authorized user with invalid permissions' do
- it 'should not return project variables' do
+ it 'does not return project variables' do
get api("/projects/#{project.id}/variables", user2)
expect(response).to have_http_status(403)
@@ -29,7 +29,7 @@ describe API::API, api: true do
end
context 'unauthorized user' do
- it 'should not return project variables' do
+ it 'does not return project variables' do
get api("/projects/#{project.id}/variables")
expect(response).to have_http_status(401)
@@ -39,14 +39,14 @@ describe API::API, api: true do
describe 'GET /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
- it 'should return project variable details' do
+ it 'returns project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}", user)
expect(response).to have_http_status(200)
expect(json_response['value']).to eq(variable.value)
end
- it 'should respond with 404 Not Found if requesting non-existing variable' do
+ it 'responds with 404 Not Found if requesting non-existing variable' do
get api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response).to have_http_status(404)
@@ -54,7 +54,7 @@ describe API::API, api: true do
end
context 'authorized user with invalid permissions' do
- it 'should not return project variable details' do
+ it 'does not return project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response).to have_http_status(403)
@@ -62,7 +62,7 @@ describe API::API, api: true do
end
context 'unauthorized user' do
- it 'should not return project variable details' do
+ it 'does not return project variable details' do
get api("/projects/#{project.id}/variables/#{variable.key}")
expect(response).to have_http_status(401)
@@ -72,7 +72,7 @@ describe API::API, api: true do
describe 'POST /projects/:id/variables' do
context 'authorized user with proper permissions' do
- it 'should create variable' do
+ it 'creates variable' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
end.to change{project.variables.count}.by(1)
@@ -82,7 +82,7 @@ describe API::API, api: true do
expect(json_response['value']).to eq('VALUE_2')
end
- it 'should not allow to duplicate variable key' do
+ it 'does not allow to duplicate variable key' do
expect do
post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
end.to change{project.variables.count}.by(0)
@@ -92,7 +92,7 @@ describe API::API, api: true do
end
context 'authorized user with invalid permissions' do
- it 'should not create variable' do
+ it 'does not create variable' do
post api("/projects/#{project.id}/variables", user2)
expect(response).to have_http_status(403)
@@ -100,7 +100,7 @@ describe API::API, api: true do
end
context 'unauthorized user' do
- it 'should not create variable' do
+ it 'does not create variable' do
post api("/projects/#{project.id}/variables")
expect(response).to have_http_status(401)
@@ -110,7 +110,7 @@ describe API::API, api: true do
describe 'PUT /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
- it 'should update variable data' do
+ it 'updates variable data' do
initial_variable = project.variables.first
value_before = initial_variable.value
@@ -123,7 +123,7 @@ describe API::API, api: true do
expect(updated_variable.value).to eq('VALUE_1_UP')
end
- it 'should responde with 404 Not Found if requesting non-existing variable' do
+ it 'responds with 404 Not Found if requesting non-existing variable' do
put api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response).to have_http_status(404)
@@ -131,7 +131,7 @@ describe API::API, api: true do
end
context 'authorized user with invalid permissions' do
- it 'should not update variable' do
+ it 'does not update variable' do
put api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response).to have_http_status(403)
@@ -139,7 +139,7 @@ describe API::API, api: true do
end
context 'unauthorized user' do
- it 'should not update variable' do
+ it 'does not update variable' do
put api("/projects/#{project.id}/variables/#{variable.key}")
expect(response).to have_http_status(401)
@@ -149,14 +149,14 @@ describe API::API, api: true do
describe 'DELETE /projects/:id/variables/:key' do
context 'authorized user with proper permissions' do
- it 'should delete variable' do
+ it 'deletes variable' do
expect do
delete api("/projects/#{project.id}/variables/#{variable.key}", user)
end.to change{project.variables.count}.by(-1)
expect(response).to have_http_status(200)
end
- it 'should responde with 404 Not Found if requesting non-existing variable' do
+ it 'responds with 404 Not Found if requesting non-existing variable' do
delete api("/projects/#{project.id}/variables/non_existing_variable", user)
expect(response).to have_http_status(404)
@@ -164,7 +164,7 @@ describe API::API, api: true do
end
context 'authorized user with invalid permissions' do
- it 'should not delete variable' do
+ it 'does not delete variable' do
delete api("/projects/#{project.id}/variables/#{variable.key}", user2)
expect(response).to have_http_status(403)
@@ -172,7 +172,7 @@ describe API::API, api: true do
end
context 'unauthorized user' do
- it 'should not delete variable' do
+ it 'does not delete variable' do
delete api("/projects/#{project.id}/variables/#{variable.key}")
expect(response).to have_http_status(401)
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 1c7c60ec644..ca7932dc5da 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -6,112 +6,102 @@ describe Ci::API::API do
let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) }
let(:project) { FactoryGirl.create(:empty_project) }
- before do
- stub_ci_pipeline_to_return_yaml_file
- end
-
describe "Builds API for runners" do
- let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") }
- let(:shared_project) { FactoryGirl.create(:empty_project, name: "SharedProject") }
+ let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') }
before do
- FactoryGirl.create :ci_runner_project, project: project, runner: runner
+ project.runners << runner
end
describe "POST /builds/register" do
- it "should start a build" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
- pipeline.create_builds(nil)
- build = pipeline.builds.first
+ let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) }
- post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+ it "starts a build" do
+ register_builds info: { platform: :darwin }
expect(response).to have_http_status(201)
expect(json_response['sha']).to eq(build.sha)
expect(runner.reload.platform).to eq("darwin")
+ expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
+ expect(json_response["variables"]).to include(
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "DB_NAME", "value" => "postgres", "public" => true }
+ )
end
- it "should return 404 error if no pending build found" do
- post ci_api("/builds/register"), token: runner.token
-
- expect(response).to have_http_status(404)
- end
-
- it "should return 404 error if no builds for specific runner" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: shared_project)
- FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending')
+ context 'when builds are finished' do
+ before do
+ build.success
+ end
- post ci_api("/builds/register"), token: runner.token
+ it "returns 404 error if no builds for specific runner" do
+ register_builds
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(404)
+ end
end
- it "should return 404 error if no builds for shared runner" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: project)
- FactoryGirl.create(:ci_build, pipeline: pipeline, status: 'pending')
+ context 'for other project with builds' do
+ before do
+ build.success
+ create(:ci_build, :pending)
+ end
- post ci_api("/builds/register"), token: shared_runner.token
+ it "returns 404 error if no builds for shared runner" do
+ register_builds
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(404)
+ end
end
- it "returns options" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
- pipeline.create_builds(nil)
+ context 'for shared runner' do
+ let(:shared_runner) { create(:ci_runner, token: "SharedRunner") }
- post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+ it "should return 404 error if no builds for shared runner" do
+ register_builds shared_runner.token
- expect(response).to have_http_status(201)
- expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] })
+ expect(response).to have_http_status(404)
+ end
end
- it "returns variables" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
- pipeline.create_builds(nil)
- project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
-
- post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+ context 'for triggered build' do
+ before do
+ trigger = create(:ci_trigger, project: project)
+ create(:ci_trigger_request_with_variables, pipeline: pipeline, builds: [build], trigger: trigger)
+ project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
+ end
- expect(response).to have_http_status(201)
- expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
- { "key" => "DB_NAME", "value" => "postgres", "public" => true },
- { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }
- )
+ it "returns variables for triggers" do
+ register_builds info: { platform: :darwin }
+
+ expect(response).to have_http_status(201)
+ expect(json_response["variables"]).to include(
+ { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
+ { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
+ { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
+ { "key" => "DB_NAME", "value" => "postgres", "public" => true },
+ { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
+ { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false },
+ )
+ end
end
- it "returns variables for triggers" do
- trigger = FactoryGirl.create(:ci_trigger, project: project)
- pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
-
- trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger)
- pipeline.create_builds(nil, trigger_request)
- project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value")
-
- post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
-
- expect(response).to have_http_status(201)
- expect(json_response["variables"]).to include(
- { "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
- { "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
- { "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
- { "key" => "DB_NAME", "value" => "postgres", "public" => true },
- { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
- { "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false }
- )
- end
+ context 'with multiple builds' do
+ before do
+ build.success
+ end
- it "returns dependent builds" do
- pipeline = FactoryGirl.create(:ci_pipeline, project: project, ref: 'master')
- pipeline.create_builds(nil, nil)
- pipeline.builds.where(stage: 'test').each(&:success)
+ let!(:test_build) { create(:ci_build, pipeline: pipeline, name: 'deploy', stage: 'deploy', stage_idx: 1) }
- post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
+ it "returns dependent builds" do
+ register_builds info: { platform: :darwin }
- expect(response).to have_http_status(201)
- expect(json_response["depends_on_builds"].count).to eq(2)
- expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
+ expect(response).to have_http_status(201)
+ expect(json_response["id"]).to eq(test_build.id)
+ expect(json_response["depends_on_builds"].count).to eq(1)
+ expect(json_response["depends_on_builds"][0]).to include('id' => build.id, 'name' => 'spinach')
+ end
end
%w(name version revision platform architecture).each do |param|
@@ -121,8 +111,9 @@ describe Ci::API::API do
subject { runner.read_attribute(param.to_sym) }
it do
- post ci_api("/builds/register"), token: runner.token, info: { param => value }
- expect(response).to have_http_status(404)
+ register_builds info: { param => value }
+
+ expect(response).to have_http_status(201)
runner.reload
is_expected.to eq(value)
end
@@ -131,8 +122,7 @@ describe Ci::API::API do
context 'when build has no tags' do
before do
- pipeline = create(:ci_pipeline, project: project)
- create(:ci_build, pipeline: pipeline, tags: [])
+ build.update(tags: [])
end
context 'when runner is allowed to pick untagged builds' do
@@ -154,42 +144,40 @@ describe Ci::API::API do
expect(response).to have_http_status 404
end
end
+ end
- def register_builds
- post ci_api("/builds/register"), token: runner.token,
- info: { platform: :darwin }
- end
+ def register_builds(token = runner.token, **params)
+ post ci_api("/builds/register"), params.merge(token: token)
end
end
describe "PUT /builds/:id" do
- let(:pipeline) {create(:ci_pipeline, project: project)}
- let(:build) { create(:ci_build, :trace, pipeline: pipeline, runner_id: runner.id) }
+ let(:build) { create(:ci_build, :pending, :trace, pipeline: pipeline, runner_id: runner.id) }
before do
build.run!
put ci_api("/builds/#{build.id}"), token: runner.token
end
- it "should update a running build" do
+ it "updates a running build" do
expect(response).to have_http_status(200)
end
- it 'should not override trace information when no trace is given' do
+ it 'does not override trace information when no trace is given' do
expect(build.reload.trace).to eq 'BUILD TRACE'
end
context 'build has been erased' do
let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
- it 'should respond with forbidden' do
+ it 'responds with forbidden' do
expect(response.status).to eq 403
end
end
end
describe 'PATCH /builds/:id/trace.txt' do
- let(:build) { create(:ci_build, :trace, runner_id: runner.id) }
+ let(:build) { create(:ci_build, :pending, :trace, runner_id: runner.id) }
let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } }
let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
@@ -237,8 +225,7 @@ describe Ci::API::API do
context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline, runner_id: runner.id) }
+ let(:build) { create(:ci_build, :pending, pipeline: pipeline, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
@@ -280,7 +267,7 @@ describe Ci::API::API do
context 'authorization token is invalid' do
before { post authorize_url, { token: 'invalid', filesize: 100 } }
- it 'should respond with forbidden' do
+ it 'responds with forbidden' do
expect(response).to have_http_status(403)
end
end
@@ -300,7 +287,7 @@ describe Ci::API::API do
upload_artifacts(file_upload, headers_with_token)
end
- it 'should respond with forbidden' do
+ it 'responds with forbidden' do
expect(response.status).to eq 403
end
end
@@ -342,7 +329,7 @@ describe Ci::API::API do
end
end
- context 'should post artifacts file and metadata file' do
+ context 'posts artifacts file and metadata file' do
let!(:artifacts) { file_upload }
let!(:metadata) { file_upload2 }
@@ -354,7 +341,7 @@ describe Ci::API::API do
post(post_url, post_data, headers_with_token)
end
- context 'post data accelerated by workhorse is correct' do
+ context 'posts data accelerated by workhorse is correct' do
let(:post_data) do
{ 'file.path' => artifacts.path,
'file.name' => artifacts.original_filename,
@@ -422,7 +409,7 @@ describe Ci::API::API do
end
context "artifacts file is too large" do
- it "should fail to post too large artifact" do
+ it "fails to post too large artifact" do
stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token)
expect(response).to have_http_status(413)
@@ -430,14 +417,14 @@ describe Ci::API::API do
end
context "artifacts post request does not contain file" do
- it "should fail to post artifacts without file" do
+ it "fails to post artifacts without file" do
post post_url, {}, headers_with_token
expect(response).to have_http_status(400)
end
end
context 'GitLab Workhorse is not configured' do
- it "should fail to post artifacts without GitLab-Workhorse" do
+ it "fails to post artifacts without GitLab-Workhorse" do
post post_url, { token: build.token }, {}
expect(response).to have_http_status(403)
end
@@ -456,7 +443,7 @@ describe Ci::API::API do
FileUtils.remove_entry @tmpdir
end
- it "should fail to post artifacts for outside of tmp path" do
+ it "fails to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token)
expect(response).to have_http_status(400)
end
@@ -482,7 +469,7 @@ describe Ci::API::API do
build.reload
end
- it 'should remove build artifacts' do
+ it 'removes build artifacts' do
expect(response).to have_http_status(200)
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
@@ -500,14 +487,14 @@ describe Ci::API::API do
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
- it 'should download artifact' do
+ it 'downloads artifact' do
expect(response).to have_http_status(200)
expect(response.headers).to include download_headers
end
end
context 'build does not has artifacts' do
- it 'should respond with not found' do
+ it 'responds with not found' do
expect(response).to have_http_status(404)
end
end
diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb
index f12678e5a8e..0a0f979f57d 100644
--- a/spec/requests/ci/api/triggers_spec.rb
+++ b/spec/requests/ci/api/triggers_spec.rb
@@ -19,17 +19,17 @@ describe Ci::API::API do
end
context 'Handles errors' do
- it 'should return bad request if token is missing' do
+ it 'returns bad request if token is missing' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger")
expect(response).to have_http_status(400)
end
- it 'should return not found if project is not found' do
+ it 'returns not found if project is not found' do
post ci_api('/projects/0/refs/master/trigger'), options
expect(response).to have_http_status(404)
end
- it 'should return unauthorized if token is for different project' do
+ it 'returns unauthorized if token is for different project' do
post ci_api("/projects/#{project2.ci_id}/refs/master/trigger"), options
expect(response).to have_http_status(401)
end
@@ -38,14 +38,15 @@ describe Ci::API::API do
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
- it 'should create builds' do
+ it 'creates builds' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options
expect(response).to have_http_status(201)
pipeline.builds.reload
- expect(pipeline.builds.size).to eq(2)
+ expect(pipeline.builds.pending.size).to eq(2)
+ expect(pipeline.builds.size).to eq(5)
end
- it 'should return bad request with no builds created if there\'s no commit for that ref' do
+ it 'returns bad request with no builds created if there\'s no commit for that ref' do
post ci_api("/projects/#{project.ci_id}/refs/other-branch/trigger"), options
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created')
@@ -56,19 +57,19 @@ describe Ci::API::API do
{ 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
end
- it 'should validate variables to be a hash' do
+ it 'validates variables to be a hash' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: 'value')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a hash')
end
- it 'should validate variables needs to be a map of key-valued strings' do
+ it 'validates variables needs to be a map of key-valued strings' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: { key: %w(1 2) })
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
- it 'create trigger request with variables' do
+ it 'creates trigger request with variables' do
post ci_api("/projects/#{project.ci_id}/refs/master/trigger"), options.merge(variables: variables)
expect(response).to have_http_status(201)
pipeline.builds.reload
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 82ab582beac..8537c252b58 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -75,9 +75,9 @@ describe 'Git HTTP requests', lib: true do
context "with correct credentials" do
let(:env) { { user: user.username, password: user.password } }
- it "uploads get status 200 (because Git hooks do the real check)" do
+ it "uploads get status 403" do
upload(path, env) do |response|
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(403)
end
end
@@ -86,7 +86,7 @@ describe 'Git HTTP requests', lib: true do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
upload(path, env) do |response|
- expect(response).to have_http_status(404)
+ expect(response).to have_http_status(403)
end
end
end
@@ -236,9 +236,9 @@ describe 'Git HTTP requests', lib: true do
end
end
- it "uploads get status 200 (because Git hooks do the real check)" do
+ it "uploads get status 404" do
upload(path, user: user.username, password: user.password) do |response|
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(404)
end
end
end
@@ -349,19 +349,19 @@ describe 'Git HTTP requests', lib: true do
end
end
- def clone_get(project, options={})
+ def clone_get(project, options = {})
get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def clone_post(project, options={})
+ def clone_post(project, options = {})
post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def push_get(project, options={})
+ def push_get(project, options = {})
get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
- def push_post(project, options={})
+ def push_post(project, options = {})
post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 93d2bc160cc..4c9b4a8ba42 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Lfs::Router do
+describe 'Git LFS API and storage' do
let(:user) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) }
@@ -31,10 +31,11 @@ describe Gitlab::Lfs::Router do
'operation' => 'upload'
}
end
+ let(:authorization) { authorize_user }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
- post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end
it 'responds with 501' do
@@ -71,8 +72,9 @@ describe Gitlab::Lfs::Router do
end
context 'when handling lfs request using deprecated API' do
+ let(:authorization) { authorize_user }
before do
- post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
+ post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
end
it_behaves_like 'a deprecated'
@@ -118,8 +120,8 @@ describe Gitlab::Lfs::Router do
project.lfs_objects << lfs_object
end
- it 'responds with status 403' do
- expect(response).to have_http_status(403)
+ it 'responds with status 404' do
+ expect(response).to have_http_status(404)
end
end
@@ -147,8 +149,8 @@ describe Gitlab::Lfs::Router do
context 'without required headers' do
let(:authorization) { authorize_user }
- it 'responds with status 403' do
- expect(response).to have_http_status(403)
+ it 'responds with status 404' do
+ expect(response).to have_http_status(404)
end
end
end
@@ -162,7 +164,7 @@ describe Gitlab::Lfs::Router do
enable_lfs
update_lfs_permissions
update_user_permissions
- post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ post_lfs_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end
describe 'download' do
@@ -304,10 +306,10 @@ describe Gitlab::Lfs::Router do
end
context 'when user does is not member of the project' do
- let(:role) { :guest }
+ let(:update_user_permissions) { nil }
- it 'responds with 403' do
- expect(response).to have_http_status(403)
+ it 'responds with 404' do
+ expect(response).to have_http_status(404)
end
end
@@ -510,6 +512,7 @@ describe Gitlab::Lfs::Router do
describe 'unsupported' do
let(:project) { create(:empty_project) }
+ let(:authorization) { authorize_user }
let(:body) do
{ 'operation' => 'other',
'objects' => [
@@ -553,11 +556,11 @@ describe Gitlab::Lfs::Router do
context 'and request is sent with a malformed headers' do
before do
- put_finalize('cat /etc/passwd')
+ put_finalize('/etc/passwd')
end
it 'does not recognize it as a valid lfs command' do
- expect(response).to have_http_status(403)
+ expect(response).to have_http_status(401)
end
end
end
@@ -582,6 +585,16 @@ describe Gitlab::Lfs::Router do
expect(response).to have_http_status(403)
end
end
+
+ context 'and request is sent with a malformed headers' do
+ before do
+ put_finalize('/etc/passwd')
+ end
+
+ it 'does not recognize it as a valid lfs command' do
+ expect(response).to have_http_status(403)
+ end
+ end
end
describe 'to one project' do
@@ -624,9 +637,25 @@ describe Gitlab::Lfs::Router do
expect(lfs_object.projects.pluck(:id)).to include(project.id)
end
end
+
+ context 'invalid tempfiles' do
+ it 'rejects slashes in the tempfile name (path traversal' do
+ put_finalize('foo/bar')
+ expect(response).to have_http_status(403)
+ end
+
+ it 'rejects tempfile names that do not start with the oid' do
+ put_finalize("foo#{sample_oid}")
+ expect(response).to have_http_status(403)
+ end
+ end
end
describe 'and user does not have push access' do
+ before do
+ project.team << [user, :reporter]
+ end
+
it_behaves_like 'forbidden'
end
end
@@ -758,8 +787,8 @@ describe Gitlab::Lfs::Router do
Projects::ForkService.new(project, user, {}).execute
end
- def post_json(url, body = nil, headers = nil)
- post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
+ def post_lfs_json(url, body = nil, headers = nil)
+ post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/vnd.git-lfs+json'))
end
def json_response
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index 8b19936ae6d..69eeb45ed71 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -1,6 +1,5 @@
require 'spec_helper'
-# team_update_admin_user PUT /admin/users/:id/team_update(.:format) admin/users#team_update
# block_admin_user PUT /admin/users/:id/block(.:format) admin/users#block
# unblock_admin_user PUT /admin/users/:id/unblock(.:format) admin/users#unblock
# admin_users GET /admin/users(.:format) admin/users#index
@@ -11,10 +10,6 @@ require 'spec_helper'
# PUT /admin/users/:id(.:format) admin/users#update
# DELETE /admin/users/:id(.:format) admin/users#destroy
describe Admin::UsersController, "routing" do
- it "to #team_update" do
- expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1')
- end
-
it "to #block" do
expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1')
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 620f328a114..77842057a10 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -60,7 +60,7 @@ end
# project GET /:id(.:format) projects#show
# PUT /:id(.:format) projects#update
# DELETE /:id(.:format) projects#destroy
-# markdown_preview_project POST /:id/markdown_preview(.:format) projects#markdown_preview
+# preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown
describe ProjectsController, 'routing' do
it 'to #create' do
expect(post('/projects')).to route_to('projects#create')
@@ -91,9 +91,9 @@ describe ProjectsController, 'routing' do
expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq')
end
- it 'to #markdown_preview' do
- expect(post('/gitlab/gitlabhq/markdown_preview')).to(
- route_to('projects#markdown_preview', namespace_id: 'gitlab', id: 'gitlabhq')
+ it 'to #preview_markdown' do
+ expect(post('/gitlab/gitlabhq/preview_markdown')).to(
+ route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq')
)
end
end
@@ -135,10 +135,6 @@ describe Projects::RepositoriesController, 'routing' do
it 'to #archive format:tar.bz2' do
expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2')
end
-
- it 'to #show' do
- expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
- end
end
describe Projects::BranchesController, 'routing' do
@@ -483,13 +479,16 @@ end
describe Projects::NetworkController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
- expect(get('/gitlab/gitlabhq/network/master.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+ expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
end
end
describe Projects::GraphsController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json')
+ expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 0a52c1ab933..1d4df9197f6 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -176,18 +176,10 @@ describe Profiles::KeysController, "routing" do
expect(post("/profile/keys")).to route_to('profiles/keys#create')
end
- it "to #edit" do
- expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1')
- end
-
it "to #show" do
expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1')
end
- it "to #update" do
- expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1')
- end
-
it "to #destroy" do
expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1')
end
diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb
deleted file mode 100644
index 8b0becd83d3..00000000000
--- a/spec/services/ci/create_builds_service_spec.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateBuildsService, services: true do
- let(:pipeline) { create(:ci_pipeline, ref: 'master') }
- let(:user) { create(:user) }
-
- describe '#execute' do
- # Using stubbed .gitlab-ci.yml created in commit factory
- #
-
- subject do
- described_class.new(pipeline).execute('test', user, status, nil)
- end
-
- context 'next builds available' do
- let(:status) { 'success' }
-
- it { is_expected.to be_an_instance_of Array }
- it { is_expected.to all(be_an_instance_of Ci::Build) }
-
- it 'does not persist created builds' do
- expect(subject.first).not_to be_persisted
- end
- end
-
- context 'builds skipped' do
- let(:status) { 'skipped' }
-
- it { is_expected.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
new file mode 100644
index 00000000000..4aadd009f3e
--- /dev/null
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -0,0 +1,214 @@
+require 'spec_helper'
+
+describe Ci::CreatePipelineService, services: true do
+ let(:project) { FactoryGirl.create(:project) }
+ let(:user) { create(:admin) }
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ describe '#execute' do
+ def execute(params)
+ described_class.new(project, user, params).execute
+ end
+
+ context 'valid params' do
+ let(:pipeline) do
+ execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: "Message" }])
+ end
+
+ it { expect(pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(pipeline).to be_valid }
+ it { expect(pipeline).to be_persisted }
+ it { expect(pipeline).to eq(project.pipelines.last) }
+ it { expect(pipeline).to have_attributes(user: user) }
+ it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
+ end
+
+ context "skip tag if there is no build for it" do
+ it "creates commit if there is appropriate job" do
+ result = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: "Message" }])
+ expect(result).to be_persisted
+ end
+
+ it "creates commit if there is no appropriate job but deploy job has right ref setting" do
+ config = YAML.dump({ deploy: { script: "ls", only: ["master"] } })
+ stub_ci_pipeline_yaml_file(config)
+ result = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: "Message" }])
+
+ expect(result).to be_persisted
+ end
+ end
+
+ it 'skips creating pipeline for refs without .gitlab-ci.yml' do
+ stub_ci_pipeline_yaml_file(nil)
+ result = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: 'Message' }])
+
+ expect(result).not_to be_persisted
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+
+ it 'fails commits if yaml is invalid' do
+ message = 'message'
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
+ stub_ci_pipeline_yaml_file('invalid: file: file')
+ commits = [{ message: message }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq('failed')
+ expect(pipeline.yaml_errors).not_to be_nil
+ end
+
+ context 'when commit contains a [ci skip] directive' do
+ let(:message) { "some message[ci skip]" }
+ let(:messageFlip) { "some message[skip ci]" }
+ let(:capMessage) { "some message[CI SKIP]" }
+ let(:capMessageFlip) { "some message[SKIP CI]" }
+
+ before do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
+ end
+
+ it "skips builds creation if there is [ci skip] tag in commit message" do
+ commits = [{ message: message }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ end
+
+ it "skips builds creation if there is [skip ci] tag in commit message" do
+ commits = [{ message: messageFlip }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ end
+
+ it "skips builds creation if there is [CI SKIP] tag in commit message" do
+ commits = [{ message: capMessage }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ end
+
+ it "skips builds creation if there is [SKIP CI] tag in commit message" do
+ commits = [{ message: capMessageFlip }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("skipped")
+ end
+
+ it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do
+ allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
+
+ commits = [{ message: "some message" }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.first.name).to eq("rspec")
+ end
+
+ it "fails builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
+ stub_ci_pipeline_yaml_file('invalid: file: fiile')
+ commits = [{ message: message }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.any?).to be false
+ expect(pipeline.status).to eq("failed")
+ expect(pipeline.yaml_errors).not_to be_nil
+ end
+ end
+
+ it "creates commit with failed status if yaml is invalid" do
+ stub_ci_pipeline_yaml_file('invalid: file')
+ commits = [{ message: "some message" }]
+ pipeline = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: commits)
+
+ expect(pipeline).to be_persisted
+ expect(pipeline.status).to eq("failed")
+ expect(pipeline.builds.any?).to be false
+ end
+
+ context 'when there are no jobs for this pipeline' do
+ before do
+ config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'does not create a new pipeline' do
+ result = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: 'some msg' }])
+
+ expect(result).not_to be_persisted
+ expect(Ci::Build.all).to be_empty
+ expect(Ci::Pipeline.count).to eq(0)
+ end
+ end
+
+ context 'with manual actions' do
+ before do
+ config = YAML.dump({ deploy: { script: 'ls', when: 'manual' } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'does not create a new pipeline' do
+ result = execute(ref: 'refs/heads/master',
+ before: '00000000',
+ after: project.commit.id,
+ commits: [{ message: 'some msg' }])
+
+ expect(result).to be_persisted
+ expect(result.manual_actions).not_to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index b72e0bd3dbe..d8c443d29d5 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Ci::CreateTriggerRequestService, services: true do
- let(:service) { Ci::CreateTriggerRequestService.new }
+ let(:service) { described_class.new }
let(:project) { create(:project) }
let(:trigger) { create(:ci_trigger, project: project) }
@@ -27,8 +27,7 @@ describe Ci::CreateTriggerRequestService, services: true do
subject { service.execute(project, trigger, 'master') }
before do
- stub_ci_pipeline_yaml_file('{}')
- FactoryGirl.create :ci_pipeline, project: project
+ stub_ci_pipeline_yaml_file('script: { only: [develop], script: hello World }')
end
it { expect(subject).to be_nil }
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 3a3e3efe709..c931c3e4829 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -5,8 +5,8 @@ module Ci
let(:service) { ImageForBuildService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:commit_sha) { '01234567890123456789' }
- let(:commit) { project.ensure_pipeline(commit_sha, 'master') }
- let(:build) { FactoryGirl.create(:ci_build, pipeline: commit) }
+ let(:pipeline) { project.ensure_pipeline(commit_sha, 'master') }
+ let(:build) { FactoryGirl.create(:ci_build, pipeline: pipeline) }
describe '#execute' do
before { build }
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
new file mode 100644
index 00000000000..ad8c2485888
--- /dev/null
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -0,0 +1,288 @@
+require 'spec_helper'
+
+describe Ci::ProcessPipelineService, services: true do
+ let(:pipeline) { create(:ci_pipeline, ref: 'master') }
+ let(:user) { create(:user) }
+ let(:all_builds) { pipeline.builds }
+ let(:builds) { all_builds.where.not(status: [:created, :skipped]) }
+ let(:config) { nil }
+
+ before do
+ allow(pipeline).to receive(:ci_yaml_file).and_return(config)
+ end
+
+ describe '#execute' do
+ def create_builds
+ described_class.new(pipeline.project, user).execute(pipeline)
+ end
+
+ def succeed_pending
+ builds.pending.update_all(status: 'success')
+ end
+
+ context 'start queuing next builds' do
+ before do
+ create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage_idx: 0)
+ create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage_idx: 0)
+ create(:ci_build, :created, pipeline: pipeline, name: 'rspec', stage_idx: 1)
+ create(:ci_build, :created, pipeline: pipeline, name: 'rubocop', stage_idx: 1)
+ create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 2)
+ end
+
+ it 'processes a pipeline' do
+ expect(create_builds).to be_truthy
+ succeed_pending
+ expect(builds.success.count).to eq(2)
+
+ expect(create_builds).to be_truthy
+ succeed_pending
+ expect(builds.success.count).to eq(4)
+
+ expect(create_builds).to be_truthy
+ succeed_pending
+ expect(builds.success.count).to eq(5)
+
+ expect(create_builds).to be_falsey
+ end
+
+ it 'does not process pipeline if existing stage is running' do
+ expect(create_builds).to be_truthy
+ expect(builds.pending.count).to eq(2)
+
+ expect(create_builds).to be_falsey
+ expect(builds.pending.count).to eq(2)
+ end
+ end
+
+ context 'custom stage with first job allowed to fail' do
+ before do
+ create(:ci_build, :created, pipeline: pipeline, name: 'clean_job', stage_idx: 0, allow_failure: true)
+ create(:ci_build, :created, pipeline: pipeline, name: 'test_job', stage_idx: 1, allow_failure: true)
+ end
+
+ it 'automatically triggers a next stage when build finishes' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+
+ pipeline.builds.running_or_pending.each(&:drop)
+ expect(builds.pluck(:status)).to contain_exactly('failed', 'pending')
+ end
+ end
+
+ context 'properly creates builds when "when" is defined' do
+ before do
+ create(:ci_build, :created, pipeline: pipeline, name: 'build', stage_idx: 0)
+ create(:ci_build, :created, pipeline: pipeline, name: 'test', stage_idx: 1)
+ create(:ci_build, :created, pipeline: pipeline, name: 'test_failure', stage_idx: 2, when: 'on_failure')
+ create(:ci_build, :created, pipeline: pipeline, name: 'deploy', stage_idx: 3)
+ create(:ci_build, :created, pipeline: pipeline, name: 'production', stage_idx: 3, when: 'manual')
+ create(:ci_build, :created, pipeline: pipeline, name: 'cleanup', stage_idx: 4, when: 'always')
+ create(:ci_build, :created, pipeline: pipeline, name: 'clear cache', stage_idx: 4, when: 'manual')
+ end
+
+ context 'when builds are successful' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:name)).to contain_exactly('build')
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('success')
+ end
+ end
+
+ context 'when test job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:name)).to contain_exactly('build')
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ context 'when test and test_failure jobs fail' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:name)).to contain_exactly('build')
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ context 'when deploy job fails' do
+ it 'properly creates builds' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:name)).to contain_exactly('build')
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
+ pipeline.builds.running_or_pending.each(&:drop)
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
+ pipeline.reload
+ expect(pipeline.status).to eq('failed')
+ end
+ end
+
+ context 'when build is canceled in the second stage' do
+ it 'does not schedule builds after build has been canceled' do
+ expect(create_builds).to be_truthy
+ expect(builds.pluck(:name)).to contain_exactly('build')
+ expect(builds.pluck(:status)).to contain_exactly('pending')
+ pipeline.builds.running_or_pending.each(&:success)
+
+ expect(builds.running_or_pending).not_to be_empty
+
+ expect(builds.pluck(:name)).to contain_exactly('build', 'test')
+ expect(builds.pluck(:status)).to contain_exactly('success', 'pending')
+ pipeline.builds.running_or_pending.each(&:cancel)
+
+ expect(builds.running_or_pending).to be_empty
+ expect(pipeline.reload.status).to eq('canceled')
+ end
+ end
+
+ context 'when listing manual actions' do
+ it 'returns only for skipped builds' do
+ # currently all builds are created
+ expect(create_builds).to be_truthy
+ expect(manual_actions).to be_empty
+
+ # succeed stage build
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_empty
+
+ # succeed stage test
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_one # production
+
+ # succeed stage deploy
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_many # production and clear cache
+ end
+
+ def manual_actions
+ pipeline.manual_actions
+ end
+ end
+ end
+
+ context 'creates a builds from .gitlab-ci.yml' do
+ let(:config) do
+ YAML.dump({
+ rspec: {
+ stage: 'test',
+ script: 'rspec'
+ },
+ rubocop: {
+ stage: 'test',
+ script: 'rubocop'
+ },
+ deploy: {
+ stage: 'deploy',
+ script: 'deploy'
+ }
+ })
+ end
+
+ # Using stubbed .gitlab-ci.yml created in commit factory
+ #
+
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ create(:ci_build, :created, pipeline: pipeline, name: 'linux', stage: 'build', stage_idx: 0)
+ create(:ci_build, :created, pipeline: pipeline, name: 'mac', stage: 'build', stage_idx: 0)
+ end
+
+ it 'when processing a pipeline' do
+ # Currently we have two builds with state created
+ expect(builds.count).to eq(0)
+ expect(all_builds.count).to eq(2)
+
+ # Create builds will mark the created as pending
+ expect(create_builds).to be_truthy
+ expect(builds.count).to eq(2)
+ expect(all_builds.count).to eq(2)
+
+ # When we builds succeed we will create a rest of pipeline from .gitlab-ci.yml
+ # We will have 2 succeeded, 2 pending (from stage test), total 5 (one more build from deploy)
+ succeed_pending
+ expect(create_builds).to be_truthy
+ expect(builds.success.count).to eq(2)
+ expect(builds.pending.count).to eq(2)
+ expect(all_builds.count).to eq(5)
+
+ # When we succeed the 2 pending from stage test,
+ # We will queue a deploy stage, no new builds will be created
+ succeed_pending
+ expect(create_builds).to be_truthy
+ expect(builds.pending.count).to eq(1)
+ expect(builds.success.count).to eq(4)
+ expect(all_builds.count).to eq(5)
+
+ # When we succeed last pending build, we will have a total of 5 succeeded builds, no new builds will be created
+ succeed_pending
+ expect(create_builds).to be_falsey
+ expect(builds.success.count).to eq(5)
+ expect(all_builds.count).to eq(5)
+ end
+ end
+ end
+end
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
deleted file mode 100644
index d4c5e584421..00000000000
--- a/spec/services/create_commit_builds_service_spec.rb
+++ /dev/null
@@ -1,241 +0,0 @@
-require 'spec_helper'
-
-describe CreateCommitBuildsService, services: true do
- let(:service) { CreateCommitBuildsService.new }
- let(:project) { FactoryGirl.create(:empty_project) }
- let(:user) { create(:user) }
-
- before do
- stub_ci_pipeline_to_return_yaml_file
- end
-
- describe '#execute' do
- context 'valid params' do
- let(:pipeline) do
- service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: [{ message: "Message" }]
- )
- end
-
- it { expect(pipeline).to be_kind_of(Ci::Pipeline) }
- it { expect(pipeline).to be_valid }
- it { expect(pipeline).to be_persisted }
- it { expect(pipeline).to eq(project.pipelines.last) }
- it { expect(pipeline).to have_attributes(user: user) }
- it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
- end
-
- context "skip tag if there is no build for it" do
- it "creates commit if there is appropriate job" do
- result = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: [{ message: "Message" }]
- )
- expect(result).to be_persisted
- end
-
- it "creates commit if there is no appropriate job but deploy job has right ref setting" do
- config = YAML.dump({ deploy: { script: "ls", only: ["0_1"] } })
- stub_ci_pipeline_yaml_file(config)
-
- result = service.execute(project, user,
- ref: 'refs/heads/0_1',
- before: '00000000',
- after: '31das312',
- commits: [{ message: "Message" }]
- )
- expect(result).to be_persisted
- end
- end
-
- it 'skips creating pipeline for refs without .gitlab-ci.yml' do
- stub_ci_pipeline_yaml_file(nil)
- result = service.execute(project, user,
- ref: 'refs/heads/0_1',
- before: '00000000',
- after: '31das312',
- commits: [{ message: 'Message' }]
- )
- expect(result).to be_falsey
- expect(Ci::Pipeline.count).to eq(0)
- end
-
- it 'fails commits if yaml is invalid' do
- message = 'message'
- allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
- stub_ci_pipeline_yaml_file('invalid: file: file')
- commits = [{ message: message }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq('failed')
- expect(pipeline.yaml_errors).not_to be_nil
- end
-
- context 'when commit contains a [ci skip] directive' do
- let(:message) { "some message[ci skip]" }
- let(:messageFlip) { "some message[skip ci]" }
- let(:capMessage) { "some message[CI SKIP]" }
- let(:capMessageFlip) { "some message[SKIP CI]" }
-
- before do
- allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { message }
- end
-
- it "skips builds creation if there is [ci skip] tag in commit message" do
- commits = [{ message: message }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
-
- it "skips builds creation if there is [skip ci] tag in commit message" do
- commits = [{ message: messageFlip }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
-
- it "skips builds creation if there is [CI SKIP] tag in commit message" do
- commits = [{ message: capMessage }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
-
- it "skips builds creation if there is [SKIP CI] tag in commit message" do
- commits = [{ message: capMessageFlip }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- end
-
- it "does not skips builds creation if there is no [ci skip] or [skip ci] tag in commit message" do
- allow_any_instance_of(Ci::Pipeline).to receive(:git_commit_message) { "some message" }
-
- commits = [{ message: "some message" }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.builds.first.name).to eq("staging")
- end
-
- it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
- stub_ci_pipeline_yaml_file('invalid: file: fiile')
- commits = [{ message: message }]
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(pipeline).to be_persisted
- expect(pipeline.builds.any?).to be false
- expect(pipeline.status).to eq("skipped")
- expect(pipeline.yaml_errors).to be_nil
- end
- end
-
- it "skips build creation if there are already builds" do
- allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { gitlab_ci_yaml }
-
- commits = [{ message: "message" }]
- pipeline = service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(pipeline).to be_persisted
- expect(pipeline.builds.count(:all)).to eq(2)
-
- pipeline = service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
- expect(pipeline).to be_persisted
- expect(pipeline.builds.count(:all)).to eq(2)
- end
-
- it "creates commit with failed status if yaml is invalid" do
- stub_ci_pipeline_yaml_file('invalid: file')
-
- commits = [{ message: "some message" }]
-
- pipeline = service.execute(project, user,
- ref: 'refs/tags/0_1',
- before: '00000000',
- after: '31das312',
- commits: commits
- )
-
- expect(pipeline).to be_persisted
- expect(pipeline.status).to eq("failed")
- expect(pipeline.builds.any?).to be false
- end
-
- context 'when there are no jobs for this pipeline' do
- before do
- config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
- stub_ci_pipeline_yaml_file(config)
- end
-
- it 'does not create a new pipeline' do
- result = service.execute(project, user,
- ref: 'refs/heads/master',
- before: '00000000',
- after: '31das312',
- commits: [{ message: 'some msg' }])
-
- expect(result).to be_falsey
- expect(Ci::Build.all).to be_empty
- expect(Ci::Pipeline.count).to eq(0)
- end
- end
- end
-end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 7a850066bf8..d81d0fd76c9 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -19,7 +19,7 @@ describe CreateSnippetService, services: true do
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
- it 'non-admins should not be able to create a public snippet' do
+ it 'non-admins are not able to create a public snippet' do
snippet = create_snippet(nil, @user, @opts)
expect(snippet.errors.messages).to have_key(:visibility_level)
expect(snippet.errors.messages[:visibility_level].first).to(
@@ -27,7 +27,7 @@ describe CreateSnippetService, services: true do
)
end
- it 'admins should be able to create a public snippet' do
+ it 'admins are able to create a public snippet' do
snippet = create_snippet(nil, @admin, @opts)
expect(snippet.errors.any?).to be_falsey
expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/services/delete_user_service_spec.rb b/spec/services/delete_user_service_spec.rb
index a65938fa03b..418a12a83a9 100644
--- a/spec/services/delete_user_service_spec.rb
+++ b/spec/services/delete_user_service_spec.rb
@@ -9,13 +9,15 @@ describe DeleteUserService, services: true do
context 'no options are given' do
it 'deletes the user' do
- DeleteUserService.new(current_user).execute(user)
+ user_data = DeleteUserService.new(current_user).execute(user)
- expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { user_data['email'].to eq(user.email) }
+ expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { Namespace.with_deleted.find(user.namespace.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'will delete the project in the near future' do
- expect_any_instance_of(Projects::DestroyService).to receive(:pending_delete!).once
+ expect_any_instance_of(Projects::DestroyService).to receive(:async_execute).once
DeleteUserService.new(current_user).execute(user)
end
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
index eca8ddd8ea4..da724643604 100644
--- a/spec/services/destroy_group_service_spec.rb
+++ b/spec/services/destroy_group_service_spec.rb
@@ -7,38 +7,52 @@ describe DestroyGroupService, services: true do
let!(:gitlab_shell) { Gitlab::Shell.new }
let!(:remove_path) { group.path + "+#{group.id}+deleted" }
- context 'database records' do
- before do
- destroy_group(group, user)
+ shared_examples 'group destruction' do |async|
+ context 'database records' do
+ before do
+ destroy_group(group, user, async)
+ end
+
+ it { expect(Group.all).not_to include(group) }
+ it { expect(Project.all).not_to include(project) }
end
- it { expect(Group.all).not_to include(group) }
- it { expect(Project.all).not_to include(project) }
- end
+ context 'file system' do
+ context 'Sidekiq inline' do
+ before do
+ # Run sidekiq immediatly to check that renamed dir will be removed
+ Sidekiq::Testing.inline! { destroy_group(group, user, async) }
+ end
- context 'file system' do
- context 'Sidekiq inline' do
- before do
- # Run sidekiq immediatly to check that renamed dir will be removed
- Sidekiq::Testing.inline! { destroy_group(group, user) }
+ it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
+ it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
end
- it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
- it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey }
- end
+ context 'Sidekiq fake' do
+ before do
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_group(group, user, async) }
+ end
- context 'Sidekiq fake' do
- before do
- # Dont run sidekiq to check if renamed repository exists
- Sidekiq::Testing.fake! { destroy_group(group, user) }
+ it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
+ it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
end
+ end
- it { expect(gitlab_shell.exists?(project.repository_storage_path, group.path)).to be_falsey }
- it { expect(gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy }
+ def destroy_group(group, user, async)
+ if async
+ DestroyGroupService.new(group, user).async_execute
+ else
+ DestroyGroupService.new(group, user).execute
+ end
end
end
- def destroy_group(group, user)
- DestroyGroupService.new(group, user).execute
+ describe 'asynchronous delete' do
+ it_behaves_like 'group destruction', true
+ end
+
+ describe 'synchronous delete' do
+ it_behaves_like 'group destruction', false
end
end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index 789836f71bb..16a9956fe7f 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -41,7 +41,7 @@ describe EventCreateService, services: true do
it { expect(service.open_mr(merge_request, merge_request.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count }
end
end
@@ -51,7 +51,7 @@ describe EventCreateService, services: true do
it { expect(service.close_mr(merge_request, merge_request.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count }
end
end
@@ -61,7 +61,7 @@ describe EventCreateService, services: true do
it { expect(service.merge_mr(merge_request, merge_request.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count }
end
end
@@ -71,7 +71,7 @@ describe EventCreateService, services: true do
it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count }
end
end
@@ -85,7 +85,7 @@ describe EventCreateService, services: true do
it { expect(service.open_milestone(milestone, user)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.open_milestone(milestone, user) }.to change { Event.count }
end
end
@@ -95,7 +95,7 @@ describe EventCreateService, services: true do
it { expect(service.close_milestone(milestone, user)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.close_milestone(milestone, user) }.to change { Event.count }
end
end
@@ -105,7 +105,7 @@ describe EventCreateService, services: true do
it { expect(service.destroy_milestone(milestone, user)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.destroy_milestone(milestone, user) }.to change { Event.count }
end
end
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
new file mode 100644
index 00000000000..d019e50649f
--- /dev/null
+++ b/spec/services/files/update_service_spec.rb
@@ -0,0 +1,84 @@
+require "spec_helper"
+
+describe Files::UpdateService do
+ subject { described_class.new(project, user, commit_params) }
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:new_contents) { "New Content" }
+ let(:commit_params) do
+ {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: new_contents,
+ file_content_encoding: "text",
+ last_commit_sha: last_commit_sha,
+ source_project: project,
+ source_branch: project.default_branch,
+ target_branch: project.default_branch,
+ }
+ end
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe "#execute" do
+ context "when the file's last commit sha does not match the supplied last_commit_sha" do
+ let(:last_commit_sha) { "foo" }
+
+ it "returns a hash with the correct error message and a :error status " do
+ expect { subject.execute }.
+ to raise_error(Files::UpdateService::FileChangedError,
+ "You are attempting to update a file that has changed since you started editing it.")
+ end
+ end
+
+ context "when the file's last commit sha does match the supplied last_commit_sha" do
+ let(:last_commit_sha) { Gitlab::Git::Commit.last_for_path(project.repository, project.default_branch, file_path).sha }
+
+ it "returns a hash with the :success status " do
+ results = subject.execute
+
+ expect(results).to match({ status: :success })
+ end
+
+ it "updates the file with the new contents" do
+ subject.execute
+
+ results = project.repository.blob_at_branch(project.default_branch, file_path)
+
+ expect(results.data).to eq(new_contents)
+ end
+ end
+
+ context "when the last_commit_sha is not supplied" do
+ let(:commit_params) do
+ {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: new_contents,
+ file_content_encoding: "text",
+ source_project: project,
+ source_branch: project.default_branch,
+ target_branch: project.default_branch,
+ }
+ end
+
+ it "returns a hash with the :success status " do
+ results = subject.execute
+
+ expect(results).to match({ status: :success })
+ end
+
+ it "updates the file with the new contents" do
+ subject.execute
+
+ results = project.repository.blob_at_branch(project.default_branch, file_path)
+
+ expect(results.data).to eq(new_contents)
+ end
+ end
+ end
+end
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index 3fc37a315c0..41b0968b8b4 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -17,7 +17,7 @@ describe GitHooksService, services: true do
describe '#execute' do
context 'when receive hooks were successful' do
- it 'should call post-receive hook' do
+ it 'calls post-receive hook' do
hook = double(trigger: [true, nil])
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
@@ -26,7 +26,7 @@ describe GitHooksService, services: true do
end
context 'when pre-receive hook failed' do
- it 'should not call post-receive hook' do
+ it 'does not call post-receive hook' do
expect(service).to receive(:run_hook).with('pre-receive').and_return([false, ''])
expect(service).not_to receive(:run_hook).with('post-receive')
@@ -37,7 +37,7 @@ describe GitHooksService, services: true do
end
context 'when update hook failed' do
- it 'should not call post-receive hook' do
+ it 'does not call post-receive hook' do
expect(service).to receive(:run_hook).with('pre-receive').and_return([true, nil])
expect(service).to receive(:run_hook).with('update').and_return([false, ''])
expect(service).not_to receive(:run_hook).with('post-receive')
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 47c0580e0f0..80f6ebac86c 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -7,6 +7,7 @@ describe GitPushService, services: true do
let(:project) { create :project }
before do
+ project.team << [user, :master]
@blankrev = Gitlab::Git::BLANK_SHA
@oldrev = sample_commit.parent_id
@newrev = sample_commit.id
@@ -172,7 +173,7 @@ describe GitPushService, services: true do
describe "Push Event" do
before do
service = execute_service(project, user, @oldrev, @newrev, @ref )
- @event = Event.last
+ @event = Event.find_by_action(Event::PUSHED)
@push_data = service.push_data
end
@@ -224,8 +225,10 @@ describe GitPushService, services: true do
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: false })
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER)
+ expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::MASTER)
end
it "when pushing a branch for the first time with default branch protection disabled" do
@@ -233,8 +236,8 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).not_to receive(:create)
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+ expect(project.protected_branches).to be_empty
end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
@@ -242,9 +245,12 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true, developers_can_merge: false })
- execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.last.push_access_level.access_level).to eq(Gitlab::Access::DEVELOPER)
+ expect(project.protected_branches.last.merge_access_level.access_level).to eq(Gitlab::Access::MASTER)
end
it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
@@ -252,8 +258,10 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: true })
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
+ expect(project.protected_branches).not_to be_empty
+ expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER)
+ expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::DEVELOPER)
end
it "when pushing new commits to existing branch" do
@@ -412,7 +420,7 @@ describe GitPushService, services: true do
context "mentioning an issue" do
let(:message) { "this is some work.\n\nrelated to JIRA-1" }
- it "should initiate one api call to jira server to mention the issue" do
+ it "initiates one api call to jira server to mention the issue" do
execute_service(project, user, @oldrev, @newrev, @ref )
expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
@@ -424,7 +432,7 @@ describe GitPushService, services: true do
context "closing an issue" do
let(:message) { "this is some work.\n\ncloses JIRA-1" }
- it "should initiate one api call to jira server to close the issue" do
+ it "initiates one api call to jira server to close the issue" do
transition_body = {
transition: {
id: '2'
@@ -437,7 +445,7 @@ describe GitPushService, services: true do
).once
end
- it "should initiate one api call to jira server to comment on the issue" do
+ it "initiates one api call to jira server to comment on the issue" do
comment_body = {
body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
}.to_json
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
new file mode 100644
index 00000000000..81b1d327696
--- /dev/null
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe ImportExportCleanUpService, services: true do
+ describe '#execute' do
+ let(:service) { described_class.new }
+
+ let(:tmp_import_export_folder) { 'tmp/project_exports' }
+
+ context 'when the import/export directory does not exist' do
+ it 'does not remove any archives' do
+ path = '/invalid/path/'
+ stub_repository_downloads_path(path)
+
+ expect(File).to receive(:directory?).with(path + tmp_import_export_folder).and_return(false).at_least(:once)
+ expect(service).not_to receive(:clean_up_export_files)
+
+ service.execute
+ end
+ end
+
+ context 'when the import/export directory exists' do
+ it 'removes old files' do
+ in_directory_with_files(mtime: 2.days.ago) do |dir, files|
+ service.execute
+
+ files.each { |file| expect(File.exist?(file)).to eq false }
+ expect(File.directory?(dir)).to eq false
+ end
+ end
+
+ it 'does not remove new files' do
+ in_directory_with_files(mtime: 2.hours.ago) do |dir, files|
+ service.execute
+
+ files.each { |file| expect(File.exist?(file)).to eq true }
+ expect(File.directory?(dir)).to eq true
+ end
+ end
+ end
+
+ def in_directory_with_files(mtime:)
+ Dir.mktmpdir do |tmpdir|
+ stub_repository_downloads_path(tmpdir)
+ dir = File.join(tmpdir, tmp_import_export_folder, 'subfolder')
+ FileUtils.mkdir_p(dir)
+
+ files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime.to_time)
+
+ yield(dir, files)
+ end
+ end
+
+ def stub_repository_downloads_path(path)
+ new_shared_settings = Settings.shared.merge('path' => path)
+ allow(Settings).to receive(:shared).and_return(new_shared_settings)
+ end
+
+ def file_list(dir)
+ Array.new(5) do |num|
+ File.join(dir, "random-#{num}.tar.gz")
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 321b54ac39d..ac08aa53b0b 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -217,7 +217,7 @@ describe Issues::BulkUpdateService, services: true do
let(:labels) { [merge_requests] }
let(:remove_labels) { [regression] }
- it 'remove the label IDs from all issues passed' do
+ it 'removes the label IDs from all issues passed' do
expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 67a919ba8ee..1318607a388 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -23,13 +23,13 @@ describe Issues::CloseService, services: true do
it { expect(@issue).to be_valid }
it { expect(@issue).to be_closed }
- it 'should send email to user2 about assign of new issue' do
+ it 'sends email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
end
- it 'should create system note about issue reassign' do
+ it 'creates system note about issue reassign' do
note = @issue.notes.last
expect(note.note).to include "Status changed to closed"
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index dacbcd8fb46..088c3d48bf7 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -53,7 +53,7 @@ describe Issues::UpdateService, services: true do
it { expect(@issue.labels.count).to eq(1) }
it { expect(@issue.labels.first.title).to eq(label.name) }
- it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do
+ it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
recipients = deliveries.last(2).map(&:to).flatten
@@ -61,14 +61,14 @@ describe Issues::UpdateService, services: true do
expect(email.subject).to include(issue.title)
end
- it 'should create system note about issue reassign' do
+ it 'creates system note about issue reassign' do
note = find_note('Reassigned to')
expect(note).not_to be_nil
expect(note.note).to include "Reassigned to \@#{user2.username}"
end
- it 'should create system note about issue label edit' do
+ it 'creates system note about issue label edit' do
note = find_note('Added ~')
expect(note).not_to be_nil
@@ -267,7 +267,7 @@ describe Issues::UpdateService, services: true do
expect(note).to be_nil
end
- it 'should not generate a new note at all' do
+ it 'does not generate a new note at all' do
expect do
update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" })
end.not_to change { Note.count }
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 782d74ec5ec..232508cda23 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -61,7 +61,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'one commit in the diff' do
- let(:commits) { [commit_1] }
+ let(:commits) { Commit.decorate([commit_1], project) }
it 'allows the merge request to be created' do
expect(merge_request.can_be_created).to eq(true)
@@ -84,7 +84,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'commit has no description' do
- let(:commits) { [commit_2] }
+ let(:commits) { Commit.decorate([commit_2], project) }
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_2.safe_message)
@@ -111,7 +111,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'commit has no description' do
- let(:commits) { [commit_2] }
+ let(:commits) { Commit.decorate([commit_2], project) }
it 'sets the description to "Closes #$issue-iid"' do
expect(merge_request.description).to eq("Closes ##{issue.iid}")
@@ -121,7 +121,7 @@ describe MergeRequests::BuildService, services: true do
end
context 'more than one commit in the diff' do
- let(:commits) { [commit_1, commit_2] }
+ let(:commits) { Commit.decorate([commit_1, commit_2], project) }
it 'allows the merge request to be created' do
expect(merge_request.can_be_created).to eq(true)
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index c1db4f3284b..403533be5d9 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -32,13 +32,13 @@ describe MergeRequests::CloseService, services: true do
with(@merge_request, 'close')
end
- it 'should send email to user2 about assign of new merge_request' do
+ it 'sends email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(merge_request.title)
end
- it 'should create system note about merge_request reassign' do
+ it 'creates system note about merge_request reassign' do
note = @merge_request.notes.last
expect(note.note).to include 'Status changed to closed'
end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index d0b55d2d509..b84a580967a 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -32,7 +32,7 @@ describe MergeRequests::CreateService, services: true do
it { expect(@merge_request.assignee).to be_nil }
it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
- it 'should execute hooks with default action' do
+ it 'executes hooks with default action' do
expect(service).to have_received(:execute_hooks).with(@merge_request)
end
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
new file mode 100644
index 00000000000..8a4b76367e3
--- /dev/null
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -0,0 +1,134 @@
+require "spec_helper"
+
+describe MergeRequests::GetUrlsService do
+ let(:project) { create(:project, :public) }
+ let(:service) { MergeRequests::GetUrlsService.new(project) }
+ let(:source_branch) { "my_branch" }
+ let(:new_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
+ let(:show_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
+ let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
+ let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
+ let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
+ let(:default_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master" }
+
+ describe "#execute" do
+ shared_examples 'new_merge_request_link' do
+ it 'returns url to create new merge request' do
+ result = service.execute(changes)
+ expect(result).to match([{
+ branch_name: source_branch,
+ url: new_merge_request_url,
+ new_merge_request: true
+ }])
+ end
+ end
+
+ shared_examples 'show_merge_request_url' do
+ it 'returns url to view merge request' do
+ result = service.execute(changes)
+ expect(result).to match([{
+ branch_name: source_branch,
+ url: show_merge_request_url,
+ new_merge_request: false
+ }])
+ end
+ end
+
+ shared_examples 'no_merge_request_url' do
+ it 'returns no URL' do
+ result = service.execute(changes)
+ expect(result).to be_empty
+ end
+ end
+
+ context 'pushing to default branch' do
+ let(:changes) { default_branch_changes }
+ it_behaves_like 'no_merge_request_url'
+ end
+
+ context 'pushing to project with MRs disabled' do
+ let(:changes) { new_branch_changes }
+
+ before do
+ project.merge_requests_enabled = false
+ end
+
+ it_behaves_like 'no_merge_request_url'
+ end
+
+ context 'pushing one completely new branch' do
+ let(:changes) { new_branch_changes }
+ it_behaves_like 'new_merge_request_link'
+ end
+
+ context 'pushing to existing branch but no merge request' do
+ let(:changes) { existing_branch_changes }
+ it_behaves_like 'new_merge_request_link'
+ end
+
+ context 'pushing to deleted branch' do
+ let(:changes) { deleted_branch_changes }
+ it_behaves_like 'no_merge_request_url'
+ end
+
+ context 'pushing to existing branch and merge request opened' do
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) }
+ let(:changes) { existing_branch_changes }
+ it_behaves_like 'show_merge_request_url'
+ end
+
+ context 'pushing to existing branch and merge request is reopened' do
+ let!(:merge_request) { create(:merge_request, :reopened, source_project: project, source_branch: source_branch) }
+ let(:changes) { existing_branch_changes }
+ it_behaves_like 'show_merge_request_url'
+ end
+
+ context 'pushing to existing branch from forked project' do
+ let(:user) { create(:user) }
+ let!(:forked_project) { Projects::ForkService.new(project, user).execute }
+ let!(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project, source_branch: source_branch) }
+ let(:changes) { existing_branch_changes }
+ # Source project is now the forked one
+ let(:service) { MergeRequests::GetUrlsService.new(forked_project) }
+
+ before do
+ allow(forked_project).to receive(:empty_repo?).and_return(false)
+ end
+
+ it_behaves_like 'show_merge_request_url'
+ end
+
+ context 'pushing to existing branch and merge request is closed' do
+ let!(:merge_request) { create(:merge_request, :closed, source_project: project, source_branch: source_branch) }
+ let(:changes) { existing_branch_changes }
+ it_behaves_like 'new_merge_request_link'
+ end
+
+ context 'pushing to existing branch and merge request is merged' do
+ let!(:merge_request) { create(:merge_request, :merged, source_project: project, source_branch: source_branch) }
+ let(:changes) { existing_branch_changes }
+ it_behaves_like 'new_merge_request_link'
+ 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(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
+ let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/existing_branch" }
+ let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
+ let(:new_merge_request_url) { "http://localhost/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
+
+ it 'returns 2 urls for both creating new and showing merge request' do
+ result = service.execute(changes)
+ expect(result).to match([{
+ branch_name: "new_branch",
+ url: new_merge_request_url,
+ new_merge_request: true
+ }, {
+ branch_name: "existing_branch",
+ url: show_merge_request_url,
+ new_merge_request: false
+ }])
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
new file mode 100644
index 00000000000..c4b87468275
--- /dev/null
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe MergeRequests::MergeRequestDiffCacheService do
+ let(:subject) { MergeRequests::MergeRequestDiffCacheService.new }
+
+ describe '#execute' do
+ it 'retrieves the diff files to cache the highlighted result' do
+ merge_request = create(:merge_request)
+ cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options]
+
+ expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
+ expect(Rails.cache).to receive(:write).with(cache_key, anything)
+
+ subject.execute(merge_request)
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index f5bf3c1e367..159f6817e8d 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -26,13 +26,13 @@ describe MergeRequests::MergeService, services: true do
it { expect(merge_request).to be_valid }
it { expect(merge_request).to be_merged }
- it 'should send email to user2 about merge of new merge_request' do
+ it 'sends email to user2 about merge of new merge_request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(merge_request.title)
end
- it 'should create system note about merge_request merge' do
+ it 'creates system note about merge_request merge' do
note = merge_request.notes.last
expect(note.note).to include 'Status changed to merged'
end
@@ -75,6 +75,17 @@ describe MergeRequests::MergeService, services: true do
expect(merge_request.merge_error).to eq("error")
end
+
+ it 'aborts if there is a merge conflict' do
+ allow_any_instance_of(Repository).to receive(:merge).and_return(false)
+ allow(service).to receive(:execute_hooks)
+
+ service.execute(merge_request)
+
+ expect(merge_request.open?).to be_truthy
+ expect(merge_request.merge_commit_sha).to be_nil
+ expect(merge_request.merge_error).to eq("Conflicts detected during merge")
+ end
end
end
end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 4da8146e3d6..520e906b21f 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -110,19 +110,15 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
context 'properly handles multiple stages' do
let(:ref) { mr_merge_if_green_enabled.source_branch }
- let(:build) { create(:ci_build, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') }
- let(:test) { create(:ci_build, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') }
+ let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') }
+ let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') }
+ let(:pipeline) { create(:ci_empty_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project) }
before do
# This behavior of MergeRequest: we instantiate a new object
allow_any_instance_of(MergeRequest).to receive(:pipeline).and_wrap_original do
Ci::Pipeline.find(pipeline.id)
end
-
- # We create test after the build
- allow(pipeline).to receive(:create_next_builds).and_wrap_original do
- test
- end
end
it "doesn't merge if some stages failed" do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index ce643b3f860..fff86480c6d 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -55,9 +55,9 @@ describe MergeRequests::RefreshService, services: true do
reload_mrs
end
- it 'should execute hooks with update action' do
+ it 'executes hooks with update action' do
expect(refresh_service).to have_received(:execute_hooks).
- with(@merge_request, 'update')
+ with(@merge_request, 'update', @oldrev)
end
it { expect(@merge_request.notes).not_to be_empty }
@@ -111,9 +111,9 @@ describe MergeRequests::RefreshService, services: true do
reload_mrs
end
- it 'should execute hooks with update action' do
+ it 'executes hooks with update action' do
expect(refresh_service).to have_received(:execute_hooks).
- with(@fork_merge_request, 'update')
+ with(@fork_merge_request, 'update', @oldrev)
end
it { expect(@merge_request.notes).to be_empty }
@@ -158,7 +158,7 @@ describe MergeRequests::RefreshService, services: true do
it 'refreshes the merge request' do
expect(refresh_service).to receive(:execute_hooks).
- with(@fork_merge_request, 'update')
+ with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA)
allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index 88c9c640514..3419b8bf5e6 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -27,18 +27,18 @@ describe MergeRequests::ReopenService, services: true do
it { expect(merge_request).to be_valid }
it { expect(merge_request).to be_reopened }
- it 'should execute hooks with reopen action' do
+ it 'executes hooks with reopen action' do
expect(service).to have_received(:execute_hooks).
with(merge_request, 'reopen')
end
- it 'should send email to user2 about reopen of merge_request' do
+ it 'sends email to user2 about reopen of merge_request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(merge_request.title)
end
- it 'should create system note about merge_request reopen' do
+ it 'creates system note about merge_request reopen' do
note = merge_request.notes.last
expect(note.note).to include 'Status changed to reopened'
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index d4ebe28c276..283a336afd9 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -64,12 +64,12 @@ describe MergeRequests::UpdateService, services: true do
it { expect(@merge_request.target_branch).to eq('target') }
it { expect(@merge_request.merge_params['force_remove_source_branch']).to eq('1') }
- it 'should execute hooks with update action' do
+ it 'executes hooks with update action' do
expect(service).to have_received(:execute_hooks).
with(@merge_request, 'update')
end
- it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
+ it 'sends email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
recipients = deliveries.last(2).map(&:to).flatten
@@ -77,14 +77,14 @@ describe MergeRequests::UpdateService, services: true do
expect(email.subject).to include(merge_request.title)
end
- it 'should create system note about merge_request reassign' do
+ it 'creates system note about merge_request reassign' do
note = find_note('Reassigned to')
expect(note).not_to be_nil
expect(note.note).to include "Reassigned to \@#{user2.username}"
end
- it 'should create system note about merge_request label edit' do
+ it 'creates system note about merge_request label edit' do
note = find_note('Added ~')
expect(note).not_to be_nil
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 9fc93f325f7..92b441c28ca 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -15,7 +15,7 @@ describe NotificationService, services: true do
it { expect(notification.new_key(key)).to be_truthy }
- it 'should sent email to key owner' do
+ it 'sends email to key owner' do
expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
end
end
@@ -27,7 +27,7 @@ describe NotificationService, services: true do
it { expect(notification.new_email(email)).to be_truthy }
- it 'should send email to email owner' do
+ it 'sends email to email owner' do
expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
end
end
@@ -593,7 +593,7 @@ describe NotificationService, services: true do
update_custom_notification(:close_issue, @u_custom_global)
end
- it 'should sent email to issue assignee and issue author' do
+ it 'sends email to issue assignee and issue author' do
notification.close_issue(issue, @u_disabled)
should_email(issue.assignee)
@@ -646,7 +646,7 @@ describe NotificationService, services: true do
update_custom_notification(:reopen_issue, @u_custom_global)
end
- it 'should send email to issue assignee and issue author' do
+ it 'sends email to issue assignee and issue author' do
notification.reopen_issue(issue, @u_disabled)
should_email(issue.assignee)
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 0971fec2e9f..7916c2d957c 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -13,7 +13,7 @@ describe Projects::AutocompleteService, services: true do
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
- it 'should not list project confidential issues for guests' do
+ it 'does not list project confidential issues for guests' do
autocomplete = described_class.new(project, nil)
issues = autocomplete.issues.map(&:iid)
@@ -23,7 +23,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 1
end
- it 'should not list project confidential issues for non project members' do
+ it 'does not list project confidential issues for non project members' do
autocomplete = described_class.new(project, non_member)
issues = autocomplete.issues.map(&:iid)
@@ -33,7 +33,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 1
end
- it 'should not list project confidential issues for project members with guest role' do
+ it 'does not list project confidential issues for project members with guest role' do
project.team << [member, :guest]
autocomplete = described_class.new(project, non_member)
@@ -45,7 +45,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 1
end
- it 'should list project confidential issues for author' do
+ it 'lists project confidential issues for author' do
autocomplete = described_class.new(project, author)
issues = autocomplete.issues.map(&:iid)
@@ -55,7 +55,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 2
end
- it 'should list project confidential issues for assignee' do
+ it 'lists project confidential issues for assignee' do
autocomplete = described_class.new(project, assignee)
issues = autocomplete.issues.map(&:iid)
@@ -65,7 +65,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 2
end
- it 'should list project confidential issues for project members' do
+ it 'lists project confidential issues for project members' do
project.team << [member, :developer]
autocomplete = described_class.new(project, member)
@@ -77,7 +77,7 @@ describe Projects::AutocompleteService, services: true do
expect(issues.count).to eq 3
end
- it 'should list all project issues for admin' do
+ it 'lists all project issues for admin' do
autocomplete = described_class.new(project, admin)
issues = autocomplete.issues.map(&:iid)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index fd114359467..bbced59ff02 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -109,7 +109,7 @@ describe Projects::CreateService, services: true do
)
end
- it 'should not allow a restricted visibility level for non-admins' do
+ it 'does not allow a restricted visibility level for non-admins' do
project = create_project(@user, @opts)
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:visibility_level)
@@ -118,7 +118,7 @@ describe Projects::CreateService, services: true do
)
end
- it 'should allow a restricted visibility level for admins' do
+ it 'allows a restricted visibility level for admins' do
admin = create(:admin)
project = create_project(admin, @opts)
@@ -128,7 +128,7 @@ describe Projects::CreateService, services: true do
end
context 'repository creation' do
- it 'should synchronously create the repository' do
+ it 'synchronously creates the repository' do
expect_any_instance_of(Project).to receive(:create_repository)
project = create_project(@user, @opts)
diff --git a/spec/services/projects/enable_deploy_key_service_spec.rb b/spec/services/projects/enable_deploy_key_service_spec.rb
new file mode 100644
index 00000000000..a37510cf159
--- /dev/null
+++ b/spec/services/projects/enable_deploy_key_service_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Projects::EnableDeployKeyService, services: true do
+ let(:deploy_key) { create(:deploy_key, public: true) }
+ let(:project) { create(:empty_project) }
+ let(:user) { project.creator}
+ let!(:params) { { key_id: deploy_key.id } }
+
+ it 'enables the key' do
+ expect do
+ service.execute
+ end.to change { project.deploy_keys.count }.from(0).to(1)
+ end
+
+ context 'trying to add an unaccessable key' do
+ let(:another_key) { create(:another_key) }
+ let!(:params) { { key_id: another_key.id } }
+
+ it 'returns nil if the key cannot be added' do
+ expect(service.execute).to be nil
+ end
+ end
+
+ def service
+ Projects::EnableDeployKeyService.new(project, user, params)
+ end
+end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 31bb7120d84..ef2036c78b1 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -26,7 +26,7 @@ describe Projects::ForkService, services: true do
end
context 'project already exists' do
- it "should fail due to validation, not transaction failure" do
+ it "fails due to validation, not transaction failure" do
@existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace)
@to_project = fork_project(@from_project, @to_user)
expect(@existing_project.persisted?).to be_truthy
@@ -36,7 +36,7 @@ describe Projects::ForkService, services: true do
end
context 'GitLab CI is enabled' do
- it "fork and enable CI for fork" do
+ it "forks and enables CI for fork" do
@from_project.enable_ci
@to_project = fork_project(@from_project, @to_user)
expect(@to_project.builds_enabled?).to be_truthy
@@ -97,14 +97,14 @@ describe Projects::ForkService, services: true do
end
context 'fork project for group when user not owner' do
- it 'group developer should fail to fork project into the group' do
+ it 'group developer fails to fork project into the group' do
to_project = fork_project(@project, @developer, @opts)
expect(to_project.errors[:namespace]).to eq(['is not valid'])
end
end
context 'project already exists in group' do
- it 'should fail due to validation, not transaction failure' do
+ it 'fails due to validation, not transaction failure' do
existing_project = create(:project, name: @project.name,
namespace: @group)
to_project = fork_project(@project, @group_owner, @opts)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index e8b9e6b9238..e139be19140 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -9,7 +9,7 @@ describe Projects::UpdateService, services: true do
@opts = {}
end
- context 'should be private when updated to private' do
+ context 'is private when updated to private' do
before do
@created_private = @project.private?
@@ -21,7 +21,7 @@ describe Projects::UpdateService, services: true do
it { expect(@project.private?).to be_truthy }
end
- context 'should be internal when updated to internal' do
+ context 'is internal when updated to internal' do
before do
@created_private = @project.private?
@@ -33,7 +33,7 @@ describe Projects::UpdateService, services: true do
it { expect(@project.internal?).to be_truthy }
end
- context 'should be public when updated to public' do
+ context 'is public when updated to public' do
before do
@created_private = @project.private?
@@ -50,7 +50,7 @@ describe Projects::UpdateService, services: true do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
- context 'should be private when updated to private' do
+ context 'is private when updated to private' do
before do
@created_private = @project.private?
@@ -62,7 +62,7 @@ describe Projects::UpdateService, services: true do
it { expect(@project.private?).to be_truthy }
end
- context 'should be internal when updated to internal' do
+ context 'is internal when updated to internal' do
before do
@created_private = @project.private?
@@ -74,7 +74,7 @@ describe Projects::UpdateService, services: true do
it { expect(@project.internal?).to be_truthy }
end
- context 'should be private when updated to public' do
+ context 'is private when updated to public' do
before do
@created_private = @project.private?
@@ -86,7 +86,7 @@ describe Projects::UpdateService, services: true do
it { expect(@project.private?).to be_truthy }
end
- context 'should be public when updated to public by admin' do
+ context 'is public when updated to public by admin' do
before do
@created_private = @project.private?
@@ -114,7 +114,7 @@ describe Projects::UpdateService, services: true do
@fork_created_internal = forked_project.internal?
end
- context 'should update forks visibility level when parent set to more restrictive' do
+ context 'updates forks visibility level when parent set to more restrictive' do
before do
opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
update_project(project, user, opts).inspect
@@ -126,7 +126,7 @@ describe Projects::UpdateService, services: true do
it { expect(project.forks.first.private?).to be_truthy }
end
- context 'should not update forks visibility level when parent set to less restrictive' do
+ context 'does not update forks visibility level when parent set to less restrictive' do
before do
opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
update_project(project, user, opts).inspect
diff --git a/spec/services/repair_ldap_blocked_user_service_spec.rb b/spec/services/repair_ldap_blocked_user_service_spec.rb
index ce7d1455975..87192457298 100644
--- a/spec/services/repair_ldap_blocked_user_service_spec.rb
+++ b/spec/services/repair_ldap_blocked_user_service_spec.rb
@@ -6,14 +6,14 @@ describe RepairLdapBlockedUserService, services: true do
subject(:service) { RepairLdapBlockedUserService.new(user) }
describe '#execute' do
- it 'change to normal block after destroying last ldap identity' do
+ it 'changes to normal block after destroying last ldap identity' do
identity.destroy
service.execute
expect(user.reload).not_to be_ldap_blocked
end
- it 'change to normal block after changing last ldap identity to another provider' do
+ it 'changes to normal block after changing last ldap identity to another provider' do
identity.update_attribute(:provider, 'twitter')
service.execute
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 7b3a9a75d7c..bd89c4a7c11 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -16,7 +16,7 @@ describe 'Search::GlobalService', services: true do
describe '#execute' do
context 'unauthenticated' do
- it 'should return public projects only' do
+ it 'returns public projects only' do
context = Search::GlobalService.new(nil, search: "searchable")
results = context.execute
expect(results.objects('projects')).to match_array [public_project]
@@ -24,19 +24,19 @@ describe 'Search::GlobalService', services: true do
end
context 'authenticated' do
- it 'should return public, internal and private projects' do
+ it 'returns public, internal and private projects' do
context = Search::GlobalService.new(user, search: "searchable")
results = context.execute
expect(results.objects('projects')).to match_array [public_project, found_project, internal_project]
end
- it 'should return only public & internal projects' do
+ it 'returns only public & internal projects' do
context = Search::GlobalService.new(internal_user, search: "searchable")
results = context.execute
expect(results.objects('projects')).to match_array [internal_project, public_project]
end
- it 'namespace name should be searchable' do
+ it 'namespace name is searchable' do
context = Search::GlobalService.new(user, search: found_project.namespace.path)
results = context.execute
expect(results.objects('projects')).to match_array [found_project]
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 43693441450..00427d6db2a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -471,15 +471,15 @@ describe SystemNoteService, services: true do
shared_examples 'cross project mentionable' do
include GitlabMarkdownHelper
- it 'should contain cross reference to new noteable' do
+ it 'contains cross reference to new noteable' do
expect(subject.note).to include cross_project_reference(new_project, new_noteable)
end
- it 'should mention referenced noteable' do
+ it 'mentions referenced noteable' do
expect(subject.note).to include new_noteable.to_reference
end
- it 'should mention referenced project' do
+ it 'mentions referenced project' do
expect(subject.note).to include new_project.to_reference
end
end
@@ -489,7 +489,7 @@ describe SystemNoteService, services: true do
it_behaves_like 'cross project mentionable'
- it 'should notify about noteable being moved to' do
+ it 'notifies about noteable being moved to' do
expect(subject.note).to match /Moved to/
end
end
@@ -499,7 +499,7 @@ describe SystemNoteService, services: true do
it_behaves_like 'cross project mentionable'
- it 'should notify about noteable being moved from' do
+ it 'notifies about noteable being moved from' do
expect(subject.note).to match /Moved from/
end
end
@@ -507,7 +507,7 @@ describe SystemNoteService, services: true do
context 'invalid direction' do
let(:direction) { :invalid }
- it 'should raise error' do
+ it 'raises error' do
expect { subject }.to raise_error StandardError, /Invalid direction/
end
end
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
index 4f47e89b4b5..4f6dd8c6d3f 100644
--- a/spec/services/test_hook_service_spec.rb
+++ b/spec/services/test_hook_service_spec.rb
@@ -6,7 +6,7 @@ describe TestHookService, services: true do
let(:hook) { create :project_hook, project: project }
describe '#execute' do
- it "should execute successfully" do
+ it "executes successfully" do
stub_request(:post, hook.url).to_return(status: 200)
expect(TestHookService.new.execute(hook, user)).to be_truthy
end
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 34d8ea9090e..6c3cbeae13c 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -472,6 +472,42 @@ describe TodoService, services: true do
expect(john_doe.todos_pending_count).to eq(1)
end
+ describe '#mark_todos_as_done' do
+ let(:issue) { create(:issue, project: project, author: author, assignee: john_doe) }
+
+ it 'marks a relation of todos as done' do
+ create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+
+ todos = TodosFinder.new(john_doe, {}).execute
+ expect { TodoService.new.mark_todos_as_done(todos, john_doe) }
+ .to change { john_doe.todos.done.count }.from(0).to(1)
+ end
+
+ it 'marks an array of todos as done' do
+ todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+
+ expect { TodoService.new.mark_todos_as_done([todo], john_doe) }
+ .to change { todo.reload.state }.from('pending').to('done')
+ end
+
+ it 'returns the number of updated todos' do # Needed on API
+ todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+
+ expect(TodoService.new.mark_todos_as_done([todo], john_doe)).to eq(1)
+ end
+
+ it 'caches the number of todos of a user', :caching do
+ create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+ todo = create(:todo, :mentioned, user: john_doe, target: issue, project: project)
+ TodoService.new.mark_todos_as_done([todo], john_doe)
+
+ expect_any_instance_of(TodosFinder).not_to receive(:execute)
+
+ expect(john_doe.todos_done_count).to eq(1)
+ expect(john_doe.todos_pending_count).to eq(1)
+ end
+ end
+
def should_create_todo(attributes = {})
attributes.reverse_merge!(
project: project,
diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb
new file mode 100644
index 00000000000..6f8f7109e14
--- /dev/null
+++ b/spec/simplecov_env.rb
@@ -0,0 +1,54 @@
+require 'simplecov'
+
+module SimpleCovEnv
+ extend self
+
+ def start!
+ return unless ENV['SIMPLECOV']
+
+ configure_profile
+ configure_job
+
+ SimpleCov.start
+ end
+
+ def configure_job
+ SimpleCov.configure do
+ if ENV['CI_BUILD_NAME']
+ coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}"
+ command_name ENV['CI_BUILD_NAME']
+ end
+
+ if ENV['CI']
+ SimpleCov.at_exit do
+ # In CI environment don't generate formatted reports
+ # Only generate .resultset.json
+ SimpleCov.result
+ end
+ end
+ end
+ end
+
+ def configure_profile
+ SimpleCov.configure do
+ load_profile 'test_frameworks'
+ track_files '{app,lib}/**/*.rb'
+
+ add_filter '/vendor/ruby/'
+ add_filter 'config/initializers/'
+
+ add_group 'Controllers', 'app/controllers'
+ add_group 'Models', 'app/models'
+ add_group 'Mailers', 'app/mailers'
+ add_group 'Helpers', 'app/helpers'
+ add_group 'Workers', %w(app/jobs app/workers)
+ add_group 'Libraries', 'lib'
+ add_group 'Services', 'app/services'
+ add_group 'Finders', 'app/finders'
+ add_group 'Uploaders', 'app/uploaders'
+ add_group 'Validators', 'app/validators'
+
+ merge_timeout 7200
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3638dcbb2d3..2e2aa7c4fc0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,5 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
- SimpleCov.start :rails
-end
+require './spec/simplecov_env'
+SimpleCovEnv.start!
ENV["RAILS_ENV"] ||= 'test'
@@ -44,6 +42,13 @@ RSpec.configure do |config|
config.before(:suite) do
TestEnv.init
end
+
+ config.around(:each, :caching) do |example|
+ caching_store = Rails.cache
+ Rails.cache = ActiveSupport::Cache::MemoryStore.new if example.metadata[:caching]
+ example.run
+ Rails.cache = caching_store
+ end
end
FactoryGirl::SyntaxRunner.class_eval do
diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb
new file mode 100644
index 00000000000..dab71a35a55
--- /dev/null
+++ b/spec/support/api/members_shared_examples.rb
@@ -0,0 +1,11 @@
+shared_examples 'a 404 response when source is private' do
+ before do
+ source.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it 'returns 404' do
+ route
+
+ expect(response).to have_http_status(404)
+ end
+end
diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb
new file mode 100644
index 00000000000..85241793743
--- /dev/null
+++ b/spec/support/issue_helpers.rb
@@ -0,0 +1,13 @@
+module IssueHelpers
+ def visit_issues(project, opts = {})
+ visit namespace_project_issues_path project.namespace, project, opts
+ end
+
+ def first_issue
+ page.all('ul.issues-list > li').first.text
+ end
+
+ def last_issue
+ page.all('ul.issues-list > li').last.text
+ end
+end
diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb
new file mode 100644
index 00000000000..d5801c8272f
--- /dev/null
+++ b/spec/support/merge_request_helpers.rb
@@ -0,0 +1,13 @@
+module MergeRequestHelpers
+ def visit_merge_requests(project, opts = {})
+ visit namespace_project_merge_requests_path project.namespace, project, opts
+ end
+
+ def first_merge_request
+ page.all('ul.mr-list > li').first.text
+ end
+
+ def last_merge_request
+ page.all('ul.mr-list > li').last.text
+ end
+end
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
index 04d25b5e9e9..35cc51725c6 100644
--- a/spec/support/select2_helper.rb
+++ b/spec/support/select2_helper.rb
@@ -11,7 +11,7 @@
#
module Select2Helper
- def select2(value, options={})
+ def select2(value, options = {})
raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
selector = options.fetch(:from)
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 83f2ad96fd8..1c0c66969e3 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -6,6 +6,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
'empty-branch' => '7efb185',
+ 'ends-with.json' => '98b0d8b3',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
@@ -20,7 +21,9 @@ module TestEnv
'gitattributes' => '5a62481',
'expand-collapse-diffs' => '4842455',
'expand-collapse-files' => '025db92',
- 'expand-collapse-lines' => '238e82d'
+ 'expand-collapse-lines' => '238e82d',
+ 'video' => '8879059',
+ 'crlf-diff' => '5938907'
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index d2c056d8e14..548e7780c36 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -42,7 +42,7 @@ describe 'gitlab:app namespace rake task' do
before do
allow(Dir).to receive(:glob).and_return([])
allow(Dir).to receive(:chdir)
- allow(File).to receive(:exists?).and_return(true)
+ allow(File).to receive(:exist?).and_return(true)
allow(Kernel).to receive(:system).and_return(true)
allow(FileUtils).to receive(:cp_r).and_return(true)
allow(FileUtils).to receive(:mv).and_return(true)
@@ -53,7 +53,7 @@ describe 'gitlab:app namespace rake task' do
let(:gitlab_version) { Gitlab::VERSION }
- it 'should fail on mismatch' do
+ it 'fails on mismatch' do
allow(YAML).to receive(:load_file).
and_return({ gitlab_version: "not #{gitlab_version}" })
@@ -61,7 +61,7 @@ describe 'gitlab:app namespace rake task' do
to raise_error(SystemExit)
end
- it 'should invoke restoration on match' do
+ it 'invokes restoration on match' do
allow(YAML).to receive(:load_file).
and_return({ gitlab_version: gitlab_version })
expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke)
@@ -107,7 +107,7 @@ describe 'gitlab:app namespace rake task' do
end
context 'archive file permissions' do
- it 'should set correct permissions on the tar file' do
+ it 'sets correct permissions on the tar file' do
expect(File.exist?(@backup_tar)).to be_truthy
expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600')
end
@@ -127,7 +127,7 @@ describe 'gitlab:app namespace rake task' do
end
end
- it 'should set correct permissions on the tar contents' do
+ it 'sets correct permissions on the tar contents' do
tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz registry.tar.gz}
)
@@ -142,7 +142,7 @@ describe 'gitlab:app namespace rake task' do
expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/)
end
- it 'should delete temp directories' do
+ it 'deletes temp directories' do
temp_dirs = Dir.glob(
File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs,registry}')
)
@@ -153,7 +153,7 @@ describe 'gitlab:app namespace rake task' do
context 'registry disabled' do
let(:enable_registry) { false }
- it 'should not create registry.tar.gz' do
+ it 'does not create registry.tar.gz' do
tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar}}
)
@@ -191,7 +191,7 @@ describe 'gitlab:app namespace rake task' do
FileUtils.rm(@backup_tar)
end
- it 'should include repositories in all repository storages' do
+ it 'includes repositories in all repository storages' do
tar_contents, exit_status = Gitlab::Popen.popen(
%W{tar -tvf #{@backup_tar} repositories}
)
diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb
index 36d03a224e4..fc52c04e78d 100644
--- a/spec/tasks/gitlab/db_rake_spec.rb
+++ b/spec/tasks/gitlab/db_rake_spec.rb
@@ -19,7 +19,7 @@ describe 'gitlab:db namespace rake task' do
end
describe 'configure' do
- it 'should invoke db:migrate when schema has already been loaded' do
+ it 'invokes db:migrate when schema has already been loaded' do
allow(ActiveRecord::Base.connection).to receive(:tables).and_return(['default'])
expect(Rake::Task['db:migrate']).to receive(:invoke)
expect(Rake::Task['db:schema:load']).not_to receive(:invoke)
@@ -27,7 +27,7 @@ describe 'gitlab:db namespace rake task' do
expect { run_rake_task('gitlab:db:configure') }.not_to raise_error
end
- it 'should invoke db:shema:load and db:seed_fu when schema is not loaded' do
+ it 'invokes db:shema:load and db:seed_fu when schema is not loaded' do
allow(ActiveRecord::Base.connection).to receive(:tables).and_return([])
expect(Rake::Task['db:schema:load']).to receive(:invoke)
expect(Rake::Task['db:seed_fu']).to receive(:invoke)
@@ -35,7 +35,7 @@ describe 'gitlab:db namespace rake task' do
expect { run_rake_task('gitlab:db:configure') }.not_to raise_error
end
- it 'should not invoke any other rake tasks during an error' do
+ it 'does not invoke any other rake tasks during an error' do
allow(ActiveRecord::Base).to receive(:connection).and_raise(RuntimeError, 'error')
expect(Rake::Task['db:migrate']).not_to receive(:invoke)
expect(Rake::Task['db:schema:load']).not_to receive(:invoke)
@@ -45,7 +45,7 @@ describe 'gitlab:db namespace rake task' do
allow(ActiveRecord::Base).to receive(:connection).and_call_original
end
- it 'should not invoke seed after a failed schema_load' do
+ it 'does not invoke seed after a failed schema_load' do
allow(ActiveRecord::Base.connection).to receive(:tables).and_return([])
allow(Rake::Task['db:schema:load']).to receive(:invoke).and_raise(RuntimeError, 'error')
expect(Rake::Task['db:schema:load']).to receive(:invoke)
diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb
index 69b2b9b6d5b..5ea020f313c 100644
--- a/spec/teaspoon_env.rb
+++ b/spec/teaspoon_env.rb
@@ -38,7 +38,7 @@ Teaspoon.configure do |config|
# Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These
# files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
- suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
+ suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.es6,es6}"
# Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
# suite.javascripts = []
@@ -149,7 +149,7 @@ Teaspoon.configure do |config|
# Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage
# on the CLI.
# Set this to "true" or the name of your coverage config.
- # config.use_coverage = nil
+ config.use_coverage = true
# You can have multiple coverage configs by passing a name to config.coverage.
# e.g. config.coverage :ci do |coverage|
@@ -158,15 +158,15 @@ Teaspoon.configure do |config|
# Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
#
# Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
- # coverage.reports = ["text-summary", "html"]
+ coverage.reports = ["text-summary", "html"]
# The path that the coverage should be written to - when there's an artifact to write to disk.
# Note: Relative to `config.root`.
- # coverage.output_path = "coverage"
+ coverage.output_path = "coverage-javascript"
# Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The
# default excludes assets from vendor, gems and support libraries.
- # coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
+ coverage.ignore = [%r{vendor/}, %r{spec/}]
# Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any
# aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil.
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 05a76ee4bdb..ee362e6fcb3 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -31,7 +31,7 @@ describe 'devise/shared/_signin_box' do
def enable_crowd
allow(view).to receive(:form_based_providers).and_return([:crowd])
allow(view).to receive(:crowd_enabled?).and_return(true)
- allow(view).to receive(:user_omniauth_authorize_path).with('crowd').
+ allow(view).to receive(:omniauth_authorize_path).with(:user, :crowd).
and_return('/crowd')
end
end
diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb
index 42220a20c75..464051063d8 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -44,9 +44,29 @@ describe 'projects/builds/show' do
it 'shows commit title and not show commit message' do
render
-
+
expect(rendered).to have_css('p.build-light-text.append-bottom-0',
text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
end
end
+
+ describe 'shows trigger variables in sidebar' do
+ let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline) }
+
+ before do
+ build.trigger_request = trigger_request
+ render
+ end
+
+ it 'shows trigger variables in separate lines' do
+ expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_1', 'TRIGGER_VALUE_1'))
+ expect(rendered).to have_css('code', text: variable_regexp('TRIGGER_KEY_2', 'TRIGGER_VALUE_2'))
+ end
+ end
+
+ private
+
+ def variable_regexp(key, value)
+ /\A#{Regexp.escape("#{key}=#{value}")}\Z/
+ end
end
diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
new file mode 100644
index 00000000000..78af61f15a7
--- /dev/null
+++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'projects/issues/_related_branches' do
+ include Devise::TestHelpers
+
+ let(:project) { create(:project) }
+ let(:branch) { project.repository.find_branch('feature') }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: branch.target.id, ref: 'feature') }
+
+ before do
+ assign(:project, project)
+ assign(:related_branches, ['feature'])
+
+ render
+ end
+
+ it 'shows the related branches with their build status' do
+ expect(rendered).to match('feature')
+ expect(rendered).to have_css('.related-branch-ci-status')
+ end
+end
diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
new file mode 100644
index 00000000000..733b2dfa7ff
--- /dev/null
+++ b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe 'projects/merge_requests/widget/_heading' do
+ include Devise::TestHelpers
+
+ context 'when released to an environment' do
+ let(:project) { merge_request.target_project }
+ let(:merge_request) { create(:merge_request, :merged) }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) do
+ create(:deployment, environment: environment, sha: project.commit('master').id)
+ end
+
+ before do
+ assign(:merge_request, merge_request)
+ assign(:project, project)
+
+ render
+ end
+
+ it 'displays that the environment is deployed' do
+ expect(rendered).to match("Deployed to")
+ expect(rendered).to match("#{environment.name}")
+ end
+ end
+end
diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb
new file mode 100644
index 00000000000..0f3fc1ee1ac
--- /dev/null
+++ b/spec/views/projects/tree/show.html.haml_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe 'projects/tree/show' do
+ include Devise::TestHelpers
+
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ before do
+ assign(:project, project)
+ assign(:repository, repository)
+
+ allow(view).to receive(:can?).and_return(true)
+ allow(view).to receive(:can_collaborate_with_project?).and_return(true)
+ end
+
+ context 'for branch names ending on .json' do
+ let(:ref) { 'ends-with.json' }
+ let(:commit) { repository.commit(ref) }
+ let(:path) { '' }
+ let(:tree) { repository.tree(commit.id, path) }
+
+ before do
+ assign(:ref, ref)
+ assign(:commit, commit)
+ assign(:id, commit.id)
+ assign(:tree, tree)
+ assign(:path, path)
+ end
+
+ it 'displays correctly' do
+ render
+ expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
+ expect(rendered).to have_css('.readme-holder .file-content', text: ref)
+ end
+ end
+end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index de40a6f78af..fe70501eeac 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -17,7 +17,7 @@ describe EmailReceiverWorker do
context "when an error occurs" do
before do
- allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError)
+ allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError)
end
it "sends out a rejection email" do
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
new file mode 100644
index 00000000000..4e4eaf9b2f7
--- /dev/null
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe GroupDestroyWorker do
+ let(:group) { create(:group) }
+ let(:user) { create(:admin) }
+ let!(:project) { create(:project, namespace: group) }
+
+ subject { GroupDestroyWorker.new }
+
+ describe "#perform" do
+ it "deletes the project" do
+ subject.perform(group.id, user.id)
+
+ expect(Group.all).not_to include(group)
+ expect(Project.all).not_to include(project)
+ expect(Dir.exist?(project.path)).to be_falsey
+ end
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 20b1a343c27..1d2cf7acddd 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -22,7 +22,7 @@ describe PostReceive do
context "branches" do
let(:changes) { "123456 789012 refs/heads/tést" }
- it "should call GitTagPushService" do
+ it "calls GitTagPushService" do
expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
expect_any_instance_of(GitTagPushService).not_to receive(:execute)
PostReceive.new.perform(pwd(project), key_id, base64_changes)
@@ -32,7 +32,7 @@ describe PostReceive do
context "tags" do
let(:changes) { "123456 789012 refs/tags/tag" }
- it "should call GitTagPushService" do
+ it "calls GitTagPushService" do
expect_any_instance_of(GitPushService).not_to receive(:execute)
expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
PostReceive.new.perform(pwd(project), key_id, base64_changes)
@@ -42,7 +42,7 @@ describe PostReceive do
context "merge-requests" do
let(:changes) { "123456 789012 refs/merge-requests/123" }
- it "should not call any of the services" do
+ it "does not call any of the services" do
expect_any_instance_of(GitPushService).not_to receive(:execute)
expect_any_instance_of(GitTagPushService).not_to receive(:execute)
PostReceive.new.perform(pwd(project), key_id, base64_changes)
@@ -53,7 +53,13 @@ describe PostReceive do
subject { PostReceive.new.perform(pwd(project), key_id, base64_changes) }
context "creates a Ci::Pipeline for every change" do
- before { stub_ci_pipeline_to_return_yaml_file }
+ before do
+ allow_any_instance_of(Ci::CreatePipelineService).to receive(:commit) do
+ OpenStruct.new(id: '123456')
+ end
+ allow_any_instance_of(Ci::CreatePipelineService).to receive(:branch?).and_return(true)
+ stub_ci_pipeline_to_return_yaml_file
+ end
it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) }
end
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
new file mode 100644
index 00000000000..1b910d9b91e
--- /dev/null
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe ProjectDestroyWorker do
+ let(:project) { create(:project) }
+ let(:path) { project.repository.path_to_repo }
+
+ subject { ProjectDestroyWorker.new }
+
+ describe "#perform" do
+ it "deletes the project" do
+ subject.perform(project.id, project.owner, {})
+
+ expect(Project.all).not_to include(project)
+ expect(Dir.exist?(path)).to be_falsey
+ end
+
+ it "deletes the project but skips repo deletion" do
+ subject.perform(project.id, project.owner, { "skip_repo" => true })
+
+ expect(Project.all).not_to include(project)
+ expect(Dir.exist?(path)).to be_truthy
+ end
+ end
+end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 5f762282b5e..60605460adb 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -14,21 +14,24 @@ describe RepositoryForkWorker do
describe "#perform" do
it "creates a new repository from a fork" do
expect(shell).to receive(:fork_repository).with(
- project.repository_storage_path,
+ '/test/path',
project.path_with_namespace,
+ project.repository_storage_path,
fork_project.namespace.path
).and_return(true)
subject.perform(
project.id,
+ '/test/path',
project.path_with_namespace,
fork_project.namespace.path)
end
it 'flushes various caches' do
expect(shell).to receive(:fork_repository).with(
- project.repository_storage_path,
+ '/test/path',
project.path_with_namespace,
+ project.repository_storage_path,
fork_project.namespace.path
).and_return(true)
@@ -38,7 +41,7 @@ describe RepositoryForkWorker do
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
- subject.perform(project.id, project.path_with_namespace,
+ subject.perform(project.id, '/test/path', project.path_with_namespace,
fork_project.namespace.path)
end
@@ -49,6 +52,7 @@ describe RepositoryForkWorker do
subject.perform(
project.id,
+ '/test/path',
project.path_with_namespace,
fork_project.namespace.path)
end
diff --git a/vendor/gitignore/Elm.gitignore b/vendor/gitignore/Elm.gitignore
index a594364e2c0..8b631e7de00 100644
--- a/vendor/gitignore/Elm.gitignore
+++ b/vendor/gitignore/Elm.gitignore
@@ -1,4 +1,4 @@
# elm-package generated files
-elm-stuff/
+elm-stuff
# elm-repl generated files
repl-temp-*
diff --git a/vendor/gitignore/Global/VisualStudioCode.gitignore b/vendor/gitignore/Global/VisualStudioCode.gitignore
index faa18382a3c..d9960081c98 100644
--- a/vendor/gitignore/Global/VisualStudioCode.gitignore
+++ b/vendor/gitignore/Global/VisualStudioCode.gitignore
@@ -1,2 +1,4 @@
-.vscode
-
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore
index daf913b1b34..cd0d5d1e2f4 100644
--- a/vendor/gitignore/Go.gitignore
+++ b/vendor/gitignore/Go.gitignore
@@ -22,3 +22,6 @@ _testmain.go
*.exe
*.test
*.prof
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore
index 47fed6c20d9..a9fe6fba80d 100644
--- a/vendor/gitignore/Leiningen.gitignore
+++ b/vendor/gitignore/Leiningen.gitignore
@@ -1,6 +1,7 @@
pom.xml
pom.xml.asc
-*jar
+*.jar
+*.class
/lib/
/classes/
/target/
diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 86f21d8e0ff..20592083931 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -52,7 +52,7 @@ Carthage/Build
fastlane/report.xml
fastlane/screenshots
-#Code Injection
+# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
diff --git a/vendor/gitignore/Scala.gitignore b/vendor/gitignore/Scala.gitignore
index c58d83b3189..a02d882cb88 100644
--- a/vendor/gitignore/Scala.gitignore
+++ b/vendor/gitignore/Scala.gitignore
@@ -15,3 +15,7 @@ project/plugins/project/
# Scala-IDE specific
.scala_dependencies
.worksheet
+
+# ENSIME specific
+.ensime_cache/
+.ensime
diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore
index 842c3ec518b..e9270205fd5 100644
--- a/vendor/gitignore/SugarCRM.gitignore
+++ b/vendor/gitignore/SugarCRM.gitignore
@@ -7,6 +7,7 @@
# For development the cache directory can be safely ignored and
# therefore it is ignored.
/cache/
+!/cache/index.html
# Ignore some files and directories from the custom directory.
/custom/history/
/custom/modulebuilder/
@@ -22,4 +23,5 @@
*.log
# Ignore the new upload directories.
/upload/
+!/upload/index.html
/upload_backup/
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 3cb097c9d5e..34f999df3e7 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -19,6 +19,9 @@
# *.eps
# *.pdf
+## Generated if empty string is given at "Please type another file name for output:"
+.pdf
+
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bcf
@@ -31,6 +34,7 @@
## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
+*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
@@ -84,6 +88,10 @@ acs-*.bib
# gnuplottex
*-gnuplottex-*
+# gregoriotex
+*.gaux
+*.gtex
+
# hyperref
*.brf
@@ -128,6 +136,9 @@ _minted*
*.sagetex.py
*.sagetex.scmd
+# scrwfile
+*.wrt
+
# sympy
*.sout
*.sympy
diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore
index 7868d16d216..41859c81f1c 100644
--- a/vendor/gitignore/Terraform.gitignore
+++ b/vendor/gitignore/Terraform.gitignore
@@ -1,3 +1,6 @@
# Compiled files
*.tfstate
*.tfstate.backup
+
+# Module directory
+.terraform/
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 5aafcbb7f1d..1c10388911b 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -5,8 +5,9 @@
/[Bb]uilds/
/Assets/AssetStoreTools*
-# Autogenerated VS/MD solution and project files
+# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
+.consulo/
*.csproj
*.unityproj
*.sln
diff --git a/vendor/gitlab-ci-yml/C++.gitlab-ci.yml b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml
new file mode 100644
index 00000000000..c83c49d8c95
--- /dev/null
+++ b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml
@@ -0,0 +1,26 @@
+# use the official gcc image, based on debian
+# can use verions as well, like gcc:5.2
+# see https://hub.docker.com/_/gcc/
+image: gcc
+
+build:
+ stage: build
+ # instead of calling g++ directly you can also use some build toolkit like make
+ # install the necessary build tools when needed
+ # before_script:
+ # - apt update && apt -y install make autoconf
+ script:
+ - g++ helloworld.cpp -o mybinary
+ artifacts:
+ paths:
+ - mybinary
+ # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
+ # cache:
+ # paths:
+ # - "*.o"
+
+# run tests using the binary built before
+test:
+ stage: test
+ script:
+ - ./runmytests.sh
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
index 0b329aaf1c4..00f9541e89b 100644
--- a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -2,7 +2,7 @@
# The image already has Hex installed. You might want to consider to use `elixir:latest`
image: trenpixster/elixir:latest
-# Pic zero or more services to be used on all builds.
+# Pick zero or more services to be used on all builds.
# Only needed when using a docker container to run your tests in.
# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service
services:
diff --git a/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml
new file mode 100644
index 00000000000..7fc698d50cf
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml
@@ -0,0 +1,40 @@
+# This template uses the java:8 docker image because there isn't any
+# official Grails image at this moment
+#
+# Grails Framework https://grails.org/ is a powerful Groovy-based web application framework for the JVM
+#
+# This yml works with Grails 3.x only
+# Feel free to change GRAILS_VERSION version with your project version (3.0.1, 3.1.1,...)
+# Feel free to change GRADLE_VERSION version with your gradle project version (2.13, 2.14,...)
+# If you use Angular profile, this yml it's prepared to work with it
+
+image: java:8
+
+variables:
+ GRAILS_VERSION: "3.1.9"
+ GRADLE_VERSION: "2.13"
+
+# We use SDKMan as tool for managing versions
+before_script:
+ - apt-get update -qq && apt-get install -y -qq unzip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install gradle $GRADLE_VERSION < /dev/null
+ - sdk use gradle $GRADLE_VERSION
+# As it's not a good idea to version gradle.properties feel free to add your
+# environments variable here
+ - echo grailsVersion=$GRAILS_VERSION > gradle.properties
+ - echo gradleWrapperVersion=2.14 >> gradle.properties
+# refresh dependencies from your project
+ - ./gradlew --refresh-dependencies
+# Be aware that if you are using Angular profile,
+# Bower cannot be run as root if you don't allow it before.
+# Feel free to remove next line if you are not using Bower
+ - echo {\"allow_root\":true} > /root/.bowerrc
+
+# This build job does the full grails pipeline
+# (compile, test, integrationTest, war, assemble).
+build:
+ script:
+ - ./gradlew build \ No newline at end of file
diff --git a/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml
new file mode 100644
index 00000000000..a4aed36889e
--- /dev/null
+++ b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml
@@ -0,0 +1,11 @@
+# use docker image with latex preinstalled
+# since there is no official latex image, use https://github.com/blang/latex-docker
+# possible alternative: https://github.com/natlownes/docker-latex
+image: blang/latex
+
+build:
+ script:
+ - latexmk -pdf
+ artifacts:
+ paths:
+ - "*.pdf"
diff --git a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
index b468d79bcad..908463c9d12 100644
--- a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
@@ -1,25 +1,17 @@
# Full project: https://gitlab.com/pages/hexo
-image: python:2.7
-
-cache:
- paths:
- - vendor/
-
-test:
- stage: test
- script:
- - pip install hyde
- - hyde gen
- except:
- - master
+image: node:4.2.2
pages:
- stage: deploy
+ cache:
+ paths:
+ - node_modules/
+
script:
- - pip install hyde
- - hyde gen -d public
+ - npm install hexo-cli -g
+ - npm install
+ - hexo deploy
artifacts:
paths:
- public
only:
- - master
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
new file mode 100644
index 00000000000..bc36a4e6966
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml
@@ -0,0 +1,32 @@
+# This template uses the java:8 docker image because there isn't any
+# official JBake image at this moment
+#
+# JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers
+#
+# This yml works with jBake 2.4.0
+# Feel free to change JBAKE_VERSION version
+#
+# HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/
+
+image: java:8
+
+variables:
+ JBAKE_VERSION: 2.4.0
+
+
+# We use SDKMan as tool for managing versions
+before_script:
+ - apt-get update -qq && apt-get install -y -qq unzip
+ - curl -sSL https://get.sdkman.io | bash
+ - echo sdkman_auto_answer=true > /root/.sdkman/etc/config
+ - source /root/.sdkman/bin/sdkman-init.sh
+ - sdk install jbake $JBAKE_VERSION < /dev/null
+ - sdk use jbake $JBAKE_VERSION
+
+# This build job produced the output directory of your site
+pages:
+ script:
+ - jbake . public
+ artifacts:
+ paths:
+ - public \ No newline at end of file
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
index 2a761bbd127..166f146ee05 100644
--- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -10,6 +10,9 @@ services:
- redis:latest
- postgres:latest
+variables:
+ POSTGRES_DB: database_name
+
# Cache gems in between builds
cache:
paths:
@@ -19,6 +22,8 @@ cache:
# services such as redis or postgres
before_script:
- ruby -v # Print out ruby version for debugging
+ # Uncomment next line if your rails app needs a JS runtime:
+ # - apt-get update -q && apt-get install nodejs -yqq
- gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
@@ -32,6 +37,9 @@ rspec:
- rspec spec
rails:
+ variables:
+ DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB"
script:
- bundle exec rake db:migrate
+ - bundle exec rake db:seed
- bundle exec rake test