Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml8
-rw-r--r--.rubocop.yml1
-rw-r--r--CHANGELOG.md13
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock10
-rw-r--r--PROCESS.md100
-rw-r--r--app/assets/javascripts/behaviors/autosize.js1
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js26
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js.es66
-rw-r--r--app/assets/javascripts/dispatcher.js.es637
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es64
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js.es69
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es64
-rw-r--r--app/assets/javascripts/gl_dropdown.js9
-rw-r--r--app/assets/javascripts/gl_form.js62
-rw-r--r--app/assets/javascripts/gl_form.js.es692
-rw-r--r--app/assets/javascripts/groups_select.js4
-rw-r--r--app/assets/javascripts/label_manager.js.es68
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es61
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es622
-rw-r--r--app/assets/javascripts/notes.js5
-rw-r--r--app/assets/javascripts/project.js29
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es62
-rw-r--r--app/assets/javascripts/search.js8
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js9
-rw-r--r--app/assets/javascripts/todos.js.es62
-rw-r--r--app/assets/javascripts/users/calendar.js5
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es66
-rw-r--r--app/assets/stylesheets/framework/blocks.scss10
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss6
-rw-r--r--app/assets/stylesheets/framework/nav.scss11
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss17
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss61
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/controllers/admin/projects_controller.rb2
-rw-r--r--app/controllers/admin/runner_projects_controller.rb2
-rw-r--r--app/controllers/concerns/creates_commit.rb53
-rw-r--r--app/controllers/concerns/spammable_actions.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb4
-rw-r--r--app/controllers/dashboard_controller.rb5
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb19
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/git_http_client_controller.rb2
-rw-r--r--app/controllers/projects/labels_controller.rb41
-rw-r--r--app/controllers/projects/mattermosts_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb14
-rw-r--r--app/controllers/projects/snippets_controller.rb8
-rw-r--r--app/controllers/projects/uploads_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb8
-rw-r--r--app/controllers/root_controller.rb3
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/blob_helper.rb4
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/merge_requests_helper.rb12
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb4
-rw-r--r--app/models/ci/build.rb32
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/spammable.rb8
-rw-r--r--app/models/environment.rb18
-rw-r--r--app/models/issue.rb5
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/merge_request_diff.rb3
-rw-r--r--app/models/project.rb35
-rw-r--r--app/models/project_services/chat_slash_commands_service.rb14
-rw-r--r--app/models/project_services/jira_service.rb4
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb4
-rw-r--r--app/models/project_snippet.rb4
-rw-r--r--app/models/repository.rb495
-rw-r--r--app/models/snippet.rb14
-rw-r--r--app/models/todo.rb4
-rw-r--r--app/policies/ci/build_policy.rb2
-rw-r--r--app/serializers/base_serializer.rb1
-rw-r--r--app/serializers/pipeline_serializer.rb3
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/commits/change_service.rb34
-rw-r--r--app/services/compare_service.rb26
-rw-r--r--app/services/create_branch_service.rb30
-rw-r--r--app/services/create_snippet_service.rb9
-rw-r--r--app/services/delete_tag_service.rb2
-rw-r--r--app/services/files/base_service.rb25
-rw-r--r--app/services/files/create_dir_service.rb10
-rw-r--r--app/services/files/create_service.rb14
-rw-r--r--app/services/files/delete_service.rb10
-rw-r--r--app/services/files/multi_service.rb8
-rw-r--r--app/services/files/update_service.rb10
-rw-r--r--app/services/git_hooks_service.rb6
-rw-r--r--app/services/git_operation_service.rb179
-rw-r--r--app/services/labels/promote_service.rb71
-rw-r--r--app/services/merge_requests/build_service.rb132
-rw-r--r--app/services/merge_requests/merge_service.rb14
-rw-r--r--app/services/notification_service.rb8
-rw-r--r--app/services/search/global_service.rb5
-rw-r--r--app/services/validate_new_branch_service.rb22
-rw-r--r--app/views/admin/application_settings/_form.html.haml2
-rw-r--r--app/views/admin/builds/index.html.haml2
-rw-r--r--app/views/admin/dashboard/_head.html.haml4
-rw-r--r--app/views/admin/runners/index.html.haml10
-rw-r--r--app/views/admin/runners/show.html.haml8
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml2
-rw-r--r--app/views/ci/status/_graph_badge.html.haml6
-rw-r--r--app/views/dashboard/issues.html.haml4
-rw-r--r--app/views/dashboard/merge_requests.html.haml4
-rw-r--r--app/views/dashboard/todos/_todo.html.haml5
-rw-r--r--app/views/dashboard/todos/index.html.haml16
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml4
-rw-r--r--app/views/groups/group_members/index.html.haml6
-rw-r--r--app/views/groups/issues.html.haml3
-rw-r--r--app/views/groups/merge_requests.html.haml3
-rw-r--r--app/views/help/_shortcuts.html.haml2
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_project.html.haml4
-rw-r--r--app/views/notify/build_fail_email.html.haml4
-rw-r--r--app/views/notify/build_fail_email.text.erb2
-rw-r--r--app/views/notify/build_success_email.html.haml4
-rw-r--r--app/views/notify/build_success_email.text.erb2
-rw-r--r--app/views/notify/links/ci/builds/_build.text.erb2
-rw-r--r--app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb2
-rw-r--r--app/views/projects/_customize_workflow.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml4
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/builds/_header.html.haml4
-rw-r--r--app/views/projects/builds/_sidebar.html.haml10
-rw-r--r--app/views/projects/builds/_table.html.haml4
-rw-r--r--app/views/projects/builds/index.html.haml4
-rw-r--r--app/views/projects/builds/show.html.haml20
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/commit/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/pipelines.html.haml9
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/edit.html.haml8
-rw-r--r--app/views/projects/environments/show.html.haml6
-rw-r--r--app/views/projects/graphs/ci/_builds.haml8
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/labels/destroy.js.haml2
-rw-r--r--app/views/projects/labels/index.html.haml58
-rw-r--r--app/views/projects/mattermosts/_no_teams.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/_file_actions.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml7
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml12
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_build_failed.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml2
-rw-r--r--app/views/projects/pipelines/_head.html.haml4
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml4
-rw-r--r--app/views/projects/pipelines_settings/show.html.haml2
-rw-r--r--app/views/projects/runners/_form.html.haml2
-rw-r--r--app/views/projects/runners/index.html.haml8
-rw-r--r--app/views/projects/snippets/_actions.html.haml5
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/projects/triggers/index.html.haml6
-rw-r--r--app/views/projects/variables/_content.html.haml2
-rw-r--r--app/views/shared/_import_form.html.haml4
-rw-r--r--app/views/shared/_issues.html.haml15
-rw-r--r--app/views/shared/_label.html.haml8
-rw-r--r--app/views/shared/_merge_requests.html.haml14
-rw-r--r--app/views/shared/_outdated_browser.html.haml3
-rw-r--r--app/views/shared/empty_states/_labels.html.haml11
-rw-r--r--app/views/shared/empty_states/_priority_labels.html.haml3
-rw-r--r--app/views/shared/empty_states/icons/_labels.svg1
-rw-r--r--app/views/shared/empty_states/icons/_priority_labels.svg1
-rw-r--r--app/views/shared/empty_states/icons/_todos_all_done.svg (renamed from app/views/shared/empty_states/_todos_all_done.svg)0
-rw-r--r--app/views/shared/empty_states/icons/_todos_empty.svg (renamed from app/views/shared/empty_states/_todos_empty.svg)0
-rw-r--r--app/views/shared/icons/_icon_action_cancel.svg1
-rw-r--r--app/views/shared/icons/_icon_action_play.svg1
-rw-r--r--app/views/shared/icons/_icon_action_retry.svg1
-rw-r--r--app/views/shared/icons/_icon_action_stop.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/form/_branch_chooser.html.haml9
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml16
-rw-r--r--app/views/shared/notifications/_custom_notifications.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml3
-rw-r--r--app/views/shared/web_hooks/_form.html.haml4
-rw-r--r--app/views/snippets/_actions.html.haml5
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--app/workers/emails_on_push_worker.rb6
-rw-r--r--changelogs/unreleased/17662-rename-builds.yml4
-rw-r--r--changelogs/unreleased/19164-mobile-settings.yml4
-rw-r--r--changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml4
-rw-r--r--changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml4
-rw-r--r--changelogs/unreleased/23634-remove-project-grouping.yml4
-rw-r--r--changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml4
-rw-r--r--changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml4
-rw-r--r--changelogs/unreleased/24606-force-password-reset-on-next-login.yml4
-rw-r--r--changelogs/unreleased/24795_refactor_merge_request_build_service.yml4
-rw-r--r--changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml4
-rw-r--r--changelogs/unreleased/25460-replace-word-users-with-members.yml4
-rw-r--r--changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml4
-rw-r--r--changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml4
-rw-r--r--changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml4
-rw-r--r--changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml4
-rw-r--r--changelogs/unreleased/26852-fix-slug-for-openshift.yml4
-rw-r--r--changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml4
-rw-r--r--changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml4
-rw-r--r--changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml4
-rw-r--r--changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml4
-rw-r--r--changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml4
-rw-r--r--changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml4
-rw-r--r--changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml4
-rw-r--r--changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml4
-rw-r--r--changelogs/unreleased/27484-environment-show-name.yml4
-rw-r--r--changelogs/unreleased/27488-fix-jwt-version.yml4
-rw-r--r--changelogs/unreleased/27494-environment-list-column-headers.yml4
-rw-r--r--changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml4
-rw-r--r--changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml4
-rw-r--r--changelogs/unreleased/cop-gem-fetcher.yml4
-rw-r--r--changelogs/unreleased/document-how-to-vue.yml4
-rw-r--r--changelogs/unreleased/dz-nested-groups-improvements-2.yml4
-rw-r--r--changelogs/unreleased/empty-selection-reply-shortcut.yml4
-rw-r--r--changelogs/unreleased/fix-26518.yml4
-rw-r--r--changelogs/unreleased/fix-27479.yml4
-rw-r--r--changelogs/unreleased/fix-ci-build-policy.yml4
-rw-r--r--changelogs/unreleased/fix-depr-warn.yml4
-rw-r--r--changelogs/unreleased/fix-filtering-username-with-multiple-words.yml4
-rw-r--r--changelogs/unreleased/fix-import-encrypt-atts.yml4
-rw-r--r--changelogs/unreleased/fix-import-user-validation-error.yml4
-rw-r--r--changelogs/unreleased/fwn-to-find-by-full-path.yml4
-rw-r--r--changelogs/unreleased/group-label-sidebar-link.yml4
-rw-r--r--changelogs/unreleased/hardcode-title-system-note.yml4
-rw-r--r--changelogs/unreleased/improve-ci-example-php-doc.yml4
-rw-r--r--changelogs/unreleased/issue-20428.yml4
-rw-r--r--changelogs/unreleased/issue-sidebar-empty-assignee.yml4
-rw-r--r--changelogs/unreleased/label-promotion.yml4
-rw-r--r--changelogs/unreleased/label-select-toggle.yml4
-rw-r--r--changelogs/unreleased/markdown-plantuml.yml4
-rw-r--r--changelogs/unreleased/refresh-authorizations-fork-join.yml4
-rw-r--r--changelogs/unreleased/revert-filter-assigned-to-me.yml4
-rw-r--r--changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml4
-rw-r--r--changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml4
-rw-r--r--changelogs/unreleased/snippet-spam.yml4
-rw-r--r--changelogs/unreleased/zj-format-chat-messages.yml4
-rw-r--r--changelogs/unreleased/zj-slow-service-fetch.yml4
-rw-r--r--config/initializers/plantuml_lexer.rb2
-rw-r--r--config/initializers/request_profiler.rb2
-rw-r--r--config/routes/project.rb2
-rw-r--r--config/routes/snippets.rb1
-rw-r--r--db/fixtures/development/10_merge_requests.rb2
-rw-r--r--db/migrate/20161114024742_add_coverage_regex_to_builds.rb13
-rw-r--r--db/migrate/20161207231626_add_environment_slug.rb18
-rw-r--r--db/migrate/20170130204620_add_index_to_project_authorizations.rb11
-rw-r--r--db/schema.rb4
-rw-r--r--doc/administration/container_registry.md4
-rw-r--r--doc/administration/integration/plantuml.md18
-rw-r--r--doc/api/groups.md13
-rw-r--r--doc/api/users.md1
-rw-r--r--doc/ci/autodeploy/index.md3
-rw-r--r--doc/ci/environments.md8
-rw-r--r--doc/ci/examples/php.md6
-rw-r--r--doc/ci/yaml/README.md38
-rw-r--r--doc/development/frontend.md106
-rw-r--r--doc/development/ux_guide/animation.md2
-rw-r--r--doc/install/README.md3
-rw-r--r--doc/install/digitaloceandocker.md136
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/project_services/slack.md2
-rw-r--r--doc/ssh/README.md10
-rw-r--r--doc/user/project/settings/import_export.md6
-rw-r--r--features/dashboard/shortcuts.feature21
-rw-r--r--features/steps/dashboard/shortcuts.rb7
-rw-r--r--features/steps/dashboard/todos.rb45
-rw-r--r--features/steps/project/builds/summary.rb2
-rw-r--r--features/steps/project/graph.rb6
-rw-r--r--features/steps/project/issues/labels.rb9
-rw-r--r--features/steps/shared/builds.rb4
-rw-r--r--lib/api/builds.rb2
-rw-r--r--lib/api/commits.rb4
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/files.rb2
-rw-r--r--lib/api/helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb4
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/services.rb13
-rw-r--r--lib/api/snippets.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/banzai/cross_project_reference.rb2
-rw-r--r--lib/banzai/filter/plantuml_filter.rb39
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb1
-rw-r--r--lib/constraints/project_url_constrainer.rb2
-rw-r--r--lib/gitlab/auth.rb11
-rw-r--r--lib/gitlab/chat_commands/base_command.rb4
-rw-r--r--lib/gitlab/chat_commands/command.rb35
-rw-r--r--lib/gitlab/chat_commands/deploy.rb24
-rw-r--r--lib/gitlab/chat_commands/help.rb28
-rw-r--r--lib/gitlab/chat_commands/issue_new.rb (renamed from lib/gitlab/chat_commands/issue_create.rb)20
-rw-r--r--lib/gitlab/chat_commands/issue_search.rb8
-rw-r--r--lib/gitlab/chat_commands/issue_show.rb8
-rw-r--r--lib/gitlab/chat_commands/presenter.rb131
-rw-r--r--lib/gitlab/chat_commands/presenters/access.rb40
-rw-r--r--lib/gitlab/chat_commands/presenters/base.rb77
-rw-r--r--lib/gitlab/chat_commands/presenters/deploy.rb21
-rw-r--r--lib/gitlab/chat_commands/presenters/help.rb27
-rw-r--r--lib/gitlab/chat_commands/presenters/issuable.rb43
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_new.rb50
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_search.rb47
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_show.rb61
-rw-r--r--lib/gitlab/ci/config/entry/coverage.rb22
-rw-r--r--lib/gitlab/ci/config/entry/global.rb5
-rw-r--r--lib/gitlab/ci/config/entry/job.rb8
-rw-r--r--lib/gitlab/ci/config/entry/legacy_validation_helpers.rb10
-rw-r--r--lib/gitlab/ci/config/entry/trigger.rb10
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb45
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb2
-rw-r--r--lib/gitlab/ci/status/build/play.rb6
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb2
-rw-r--r--lib/gitlab/ci/status/build/stop.rb2
-rw-r--r--lib/gitlab/ci/status/core.rb3
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb2
-rw-r--r--lib/gitlab/cycle_analytics/code_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/issue_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/review_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/stage_summary.rb2
-rw-r--r--lib/gitlab/cycle_analytics/staging_event_fetcher.rb2
-rw-r--r--lib/gitlab/email/handler/create_issue_handler.rb2
-rw-r--r--lib/gitlab/git.rb2
-rw-r--r--lib/gitlab/git_post_receive.rb4
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/import_export/members_mapper.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb24
-rw-r--r--lib/gitlab/request_profiler/middleware.rb3
-rw-r--r--lib/rouge/lexers/plantuml.rb21
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
-rw-r--r--lib/tasks/gitlab/import.rake4
-rw-r--r--lib/tasks/gitlab/sidekiq.rake2
-rw-r--r--rubocop/cop/gem_fetcher.rb28
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb24
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb45
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb45
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb84
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb3
-rw-r--r--spec/controllers/snippets_controller_spec.rb59
-rw-r--r--spec/factories/projects.rb36
-rw-r--r--spec/features/admin/admin_builds_spec.rb34
-rw-r--r--spec/features/boards/sidebar_spec.rb30
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb29
-rw-r--r--spec/features/environment_spec.rb4
-rw-r--r--spec/features/environments_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb15
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb21
-rw-r--r--spec/features/issues/new_branch_button_spec.rb20
-rw-r--r--spec/features/login_spec.rb5
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb4
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb28
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb2
-rw-r--r--spec/features/merge_requests/widget_spec.rb34
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb6
-rw-r--r--spec/features/projects/builds_spec.rb44
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb3
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb3
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin682154 -> 681799 bytes
-rw-r--r--spec/features/projects/issuable_templates_spec.rb40
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb8
-rw-r--r--spec/features/projects/new_project_spec.rb20
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb60
-rw-r--r--spec/features/projects/ref_switcher_spec.rb5
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb9
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb18
-rw-r--r--spec/features/todos/todos_spec.rb2
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/helpers/issuables_helper_spec.rb40
-rw-r--r--spec/helpers/search_helper_spec.rb5
-rw-r--r--spec/javascripts/.eslintrc5
-rw-r--r--spec/javascripts/awards_handler_spec.js2
-rw-r--r--spec/javascripts/boards/mock_data.js.es65
-rw-r--r--spec/javascripts/environments/environment_spec.js.es64
-rw-r--r--spec/javascripts/environments/mock_data.js.es66
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js.es640
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js.es61
-rw-r--r--spec/javascripts/fixtures/environments/table.html.haml2
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es61
-rw-r--r--spec/javascripts/gl_form_spec.js.es6122
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js.es64
-rw-r--r--spec/javascripts/issuable_time_tracker_spec.js.es62
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js.es614
-rw-r--r--spec/javascripts/merge_request_widget_spec.js26
-rw-r--r--spec/javascripts/project_title_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js2
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js49
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js16
-rw-r--r--spec/javascripts/vue_pagination/pagination_spec.js.es613
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb32
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb34
-rw-r--r--spec/lib/gitlab/auth_spec.rb96
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb50
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb24
-rw-r--r--spec/lib/gitlab/chat_commands/issue_new_spec.rb (renamed from spec/lib/gitlab/chat_commands/issue_create_spec.rb)14
-rw-r--r--spec/lib/gitlab/chat_commands/issue_search_spec.rb12
-rw-r--r--spec/lib/gitlab/chat_commands/issue_show_spec.rb25
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/access_spec.rb49
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb47
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb17
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb23
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb8
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb10
-rw-r--r--spec/lib/gitlab/git_access_spec.rb8
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/project.json13
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb14
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb15
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb18
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb18
-rw-r--r--spec/models/ci/build_spec.rb45
-rw-r--r--spec/models/cycle_analytics/code_spec.rb22
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb12
-rw-r--r--spec/models/cycle_analytics/production_spec.rb26
-rw-r--r--spec/models/cycle_analytics/review_spec.rb4
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb26
-rw-r--r--spec/models/cycle_analytics/test_spec.rb50
-rw-r--r--spec/models/environment_spec.rb5
-rw-r--r--spec/models/issue_spec.rb65
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb5
-rw-r--r--spec/models/project_spec.rb46
-rw-r--r--spec/models/repository_spec.rb194
-rw-r--r--spec/models/todo_spec.rb8
-rw-r--r--spec/policies/ci/build_policy_spec.rb93
-rw-r--r--spec/requests/api/builds_spec.rb60
-rw-r--r--spec/requests/api/groups_spec.rb11
-rw-r--r--spec/requests/api/project_snippets_spec.rb50
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/snippets_spec.rb32
-rw-r--r--spec/requests/api/users_spec.rb7
-rw-r--r--spec/requests/ci/api/builds_spec.rb6
-rw-r--r--spec/routing/project_routing_spec.rb8
-rw-r--r--spec/serializers/analytics_build_serializer_spec.rb10
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb5
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb5
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb14
-rw-r--r--spec/serializers/analytics_summary_serializer_spec.rb19
-rw-r--r--spec/serializers/environment_serializer_spec.rb9
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb6
-rw-r--r--spec/services/compare_service_spec.rb6
-rw-r--r--spec/services/event_create_service_spec.rb6
-rw-r--r--spec/services/files/update_service_spec.rb33
-rw-r--r--spec/services/git_hooks_service_spec.rb2
-rw-r--r--spec/services/labels/promote_service_spec.rb187
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb2
-rw-r--r--spec/services/merge_requests/resolve_service_spec.rb8
-rw-r--r--spec/services/notification_service_spec.rb24
-rw-r--r--spec/services/search_service_spec.rb19
-rw-r--r--spec/services/system_note_service_spec.rb4
-rw-r--r--spec/support/cycle_analytics_helpers.rb8
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb50
-rw-r--r--spec/support/test_env.rb3
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb2
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb40
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb3
-rw-r--r--spec/workers/post_receive_spec.rb6
-rw-r--r--spec/workers/project_destroy_worker_spec.rb4
-rw-r--r--vendor/assets/javascripts/jquery.ba-resize.js246
-rw-r--r--vendor/assets/javascripts/xterm/fit.js4
487 files changed, 5287 insertions, 2261 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 782a43cbe74..deb5345d3bd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -271,7 +271,7 @@ rake db:migrate:reset:
<<: *use-db
<<: *dedicated-runner
script:
- - rake db:migrate:reset
+ - bundle exec rake db:migrate:reset
rake db:seed_fu:
stage: test
@@ -302,7 +302,7 @@ teaspoon:
script:
- npm install
- npm link istanbul
- - rake teaspoon
+ - bundle exec rake teaspoon
artifacts:
name: coverage-javascript
expire_in: 31d
@@ -353,10 +353,10 @@ migration paths:
- cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- - rake db:drop db:create db:schema:load db:seed_fu
+ - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF
- source scripts/prepare_build.sh
- - rake db:migrate
+ - bundle exec rake db:migrate
coverage:
stage: post-test
diff --git a/.rubocop.yml b/.rubocop.yml
index bf2b2d8afc2..cfff42e5c99 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -17,6 +17,7 @@ AllCops:
# Exclude some GitLab files
Exclude:
- 'vendor/**/*'
+ - 'node_modules/**/*'
- 'db/*'
- 'db/fixtures/**/*'
- 'tmp/**/*'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9712b32232e..25e02b1ae1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.16.3 (2017-01-27)
+
+- Add caching of droplab ajax requests. !8725
+- Fix access to the wiki code via HTTP when repository feature disabled. !8758
+- Revert 3f17f29a. !8785
+- Fix race conditions for AuthorizedProjectsWorker.
+- Fix autocomplete initial undefined state.
+- Fix Error 500 when repositories contain annotated tags pointing to blobs.
+- Fix /explore sorting.
+- Fixed label dropdown toggle text not correctly updating.
+
## 8.16.2 (2017-01-25)
- allow issue filter bar to be operated with mouse only. !8681
@@ -18,7 +29,7 @@ entry.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
-## 8.16.0 (2017-02-22)
+## 8.16.0 (2017-01-22)
- Add LDAP Rake task to rename a provider. !2181
- Validate label's title length. !5767 (Tomáš Kukrál)
diff --git a/Gemfile b/Gemfile
index 37a7666602f..479df411881 100644
--- a/Gemfile
+++ b/Gemfile
@@ -36,7 +36,7 @@ gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.2.0'
gem 'rack-oauth2', '~> 1.2.1'
-gem 'jwt'
+gem 'jwt', '~> 1.5.6'
# Spam and anti-bot protection
gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
@@ -109,7 +109,7 @@ gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
-gem 'asciidoctor-plantuml', '0.0.6'
+gem 'asciidoctor-plantuml', '0.0.7'
gem 'rouge', '~> 2.0'
gem 'truncato', '~> 0.7.8'
diff --git a/Gemfile.lock b/Gemfile.lock
index 671d7788a86..f6b889dcca4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -54,7 +54,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.3)
- asciidoctor-plantuml (0.0.6)
+ asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5)
ast (2.3.0)
attr_encrypted (3.0.3)
@@ -379,7 +379,7 @@ GEM
json (1.8.3)
json-schema (2.6.2)
addressable (~> 2.3.8)
- jwt (1.5.4)
+ jwt (1.5.6)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -841,7 +841,7 @@ DEPENDENCIES
allocations (~> 1.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
- asciidoctor-plantuml (= 0.0.6)
+ asciidoctor-plantuml (= 0.0.7)
attr_encrypted (~> 3.0.0)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
@@ -912,7 +912,7 @@ DEPENDENCIES
jquery-rails (~> 4.1.0)
jquery-ui-rails (~> 5.0.0)
json-schema (~> 2.6.2)
- jwt
+ jwt (~> 1.5.6)
kaminari (~> 0.17.0)
knapsack (~> 1.11.0)
kubeclient (~> 2.2.0)
@@ -1022,4 +1022,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.13.7
+ 1.14.2
diff --git a/PROCESS.md b/PROCESS.md
index cbeb781cd3c..993d60bbba8 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -12,106 +12,54 @@ etc.).
## Common actions
-### Issue team
-
-- Looks for issues without [workflow labels](#how-we-handle-issues) and triages
- issue
-- Closes invalid issues with a comment (duplicates,
- [fixed in newer version](#issue-fixed-in-newer-version),
- [issue report for old version](#issue-report-for-old-version), not a problem
- in GitLab, etc.)
-- Asks for feedback from issue reporter
- ([invalid issue reports](#improperly-formatted-issue),
- [format code](#code-format), etc.)
-- Monitors all issues for feedback (but especially ones commented on since
- automatically watching them)
-- Closes issues with no feedback from the reporter for two weeks
-
-### Merge marshall & merge request coach
-
-- Responds to merge requests the issue team mentions them in and monitors for
- new merge requests
-- Provides feedback to the merge request submitter to improve the merge request
- (style, tests, etc.)
-- Mark merge requests `Ready for Merge` when they meet the
- [contribution acceptance criteria]
-- Mention developer(s) based on the
- [list of members and their specialities][team]
-- Closes merge requests with no feedback from the reporter for two weeks
-
-## Priorities of the issue team
-
-1. Mentioning people (critical)
-1. Workflow labels (normal)
-1. Functional labels (minor)
-1. Assigning issues (avoid if possible)
-
-## Mentioning people
+### Issue triaging
+
+Our issue triage policies are [described in our handbook]. You are very welcome
+to help the GitLab team triage issues. We also organize [issue bash events] once
+every quarter.
The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help
on those issues. Please select someone with relevant experience from
-[GitLab core team][core-team]. If there is nobody mentioned with that expertise
+[GitLab team][team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a
timely response. If the involvement of the lead developer is needed the other
core team members will mention this person.
-## Workflow labels
+[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
+[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
-Workflow labels are purposely not very detailed since that would be hard to keep
-updated as you would need to re-evaluate them after every comment. We optionally
-use functional labels on demand when we want to group related issues to get an
-overview (for example all issues related to RVM, to tackle them in one go) and
-to add details to the issue.
+### Merge request coaching
-- ~"Awaiting Feedback" Feedback pending from the reporter
-- ~UX needs help from a UX designer
-- ~Frontend needs help from a Front-end engineer. Please follow the
- ["Implement design & UI elements" guidelines].
-- ~"Accepting Merge Requests" is a low priority, well-defined issue that we
- encourage people to contribute to. Not exclusive with other labels.
-- ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
-in support or comment for further detail. Do not use `feature request`.
-- ~bug is an issue reporting undesirable or incorrect behavior.
-- ~customer is an issue reported by enterprise subscribers. This label should
-be accompanied by *bug* or *feature proposal* labels.
+Several people from the [GitLab team][team] are helping community members to get
+their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done].
-Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
+What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
+
+## Workflow labels
-## Functional labels
+Labelling issues is described in the [GitLab Inc engineering workflow].
-These labels describe what development specialities are involved such as: `CI`,
-`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
-`Release`, `Repository`, `UX`.
+[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
-## Label colors
-
-- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
- feedback, awaiting confirmation of fix)
-- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
- awaiting developer action/feedback)
-- Light blue `#82C5FF`: functional labels
-- Green labels `#009800`: issues that can generally be ignored. For example,
- issues given the following labels normally can be closed immediately:
- - Support (see copy & paste response:
- [Support requests and configuration questions](#support-requests-and-configuration-questions)
-
## Be kind
Be kind to people trying to contribute. Be aware that people may be a non-native
English speaker, they might not understand things or they might be very
sensitive as to how you word things. Use Emoji to express your feelings (heart,
-star, smile, etc.). Some good tips about giving feedback to merge requests is in
-the [Thoughtbot code review guide].
+star, smile, etc.). Some good tips about code reviews can be found in our
+[Code Review Guidelines].
+
+[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
## Feature Freeze
-5 working days before the 22nd the stable branches for the upcoming release will
+On the 7th of each month, the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
@@ -120,10 +68,9 @@ things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
-decision will be made by those reviewing a merge request and the release
-manager.
+decision will be made by the maintainers and the release managers.
-During the feature freeze all merge requests that are meant to go into the next
+During the feature freeze all merge requests that are meant to go into the upcoming
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
@@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
-[core-team]: https://about.gitlab.com/core-team/
[team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index a6bc262b657..7e6c44fa1cd 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,7 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
/* global autosize */
-/*= require jquery.ba-resize */
/*= require autosize */
(function() {
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 6a49715590c..a7181904ac9 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,6 +1,19 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) {
$(function() {
+ var toggleContainer = function(container, /* optional */toggleState) {
+ var $container = $(container);
+
+ $container
+ .find('.js-toggle-button .fa')
+ .toggleClass('fa-chevron-up', toggleState)
+ .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
+
+ $container
+ .find('.js-toggle-content')
+ .toggle(toggleState);
+ };
+
// Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style.
//
@@ -10,14 +23,7 @@
//
$('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault();
- $(this)
- .find('.fa')
- .toggleClass('fa-chevron-down fa-chevron-up')
- .end()
- .closest('.js-toggle-container')
- .find('.js-toggle-content')
- .toggle()
- ;
+ toggleContainer($(this).closest('.js-toggle-container'));
});
// If we're accessing a permalink, ensure it is not inside a
@@ -26,8 +32,8 @@
var anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container');
- if (container && container.find('.js-toggle-content').is(':hidden')) {
- container.find('.js-toggle-button').trigger('click');
+ if (container) {
+ toggleContainer(container, true);
anchor.scrollIntoView();
}
});
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6
index 02459722bbf..75dfcb66bb0 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js.es6
+++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6
@@ -29,6 +29,12 @@
watch: {
detail: {
handler () {
+ if (this.issue.id !== this.detail.issue.id) {
+ $('.js-issue-board-sidebar', this.$el).each((i, el) => {
+ $(el).data('glDropdown').clearMenu();
+ });
+ }
+
this.issue = this.detail.issue;
},
deep: true
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 529d476ca4e..edec21e3b63 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -8,7 +8,6 @@
/* global ShortcutsIssuable */
/* global ZenMode */
/* global Milestone */
-/* global GLForm */
/* global IssuableForm */
/* global LabelsSelect */
/* global MilestoneSelect */
@@ -64,17 +63,6 @@
new UsernameValidator();
new ActiveTabMemoizer();
break;
- case 'sessions:create':
- if (!gon.u2f) break;
- window.gl.u2fAuthenticate = new gl.U2FAuthenticate(
- $("#js-authenticate-u2f"),
- '#js-login-u2f-form',
- gon.u2f,
- document.querySelector('#js-login-2fa-device'),
- document.querySelector('.js-2fa-form'),
- );
- window.gl.u2fAuthenticate.start();
- break;
case 'projects:boards:show':
case 'projects:boards:index':
shortcut_handler = new ShortcutsNavigation();
@@ -110,7 +98,7 @@
case 'projects:milestones:edit':
new ZenMode();
new gl.DueDateSelectors();
- new GLForm($('.milestone-form'));
+ new gl.GLForm($('.milestone-form'));
break;
case 'groups:milestones:new':
new ZenMode();
@@ -121,7 +109,7 @@
case 'projects:issues:new':
case 'projects:issues:edit':
shortcut_handler = new ShortcutsNavigation();
- new GLForm($('.issue-form'));
+ new gl.GLForm($('.issue-form'));
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
@@ -131,7 +119,7 @@
case 'projects:merge_requests:edit':
new gl.Diff();
shortcut_handler = new ShortcutsNavigation();
- new GLForm($('.merge-request-form'));
+ new gl.GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
@@ -139,11 +127,11 @@
break;
case 'projects:tags:new':
new ZenMode();
- new GLForm($('.tag-form'));
+ new gl.GLForm($('.tag-form'));
break;
case 'projects:releases:edit':
new ZenMode();
- new GLForm($('.release-form'));
+ new gl.GLForm($('.release-form'));
break;
case 'projects:merge_requests:show':
new gl.Diff();
@@ -280,6 +268,17 @@
break;
}
switch (path.first()) {
+ case 'sessions':
+ case 'omniauth_callbacks':
+ if (!gon.u2f) break;
+ gl.u2fAuthenticate = new gl.U2FAuthenticate(
+ $('#js-authenticate-u2f'),
+ '#js-login-u2f-form',
+ gon.u2f,
+ document.querySelector('#js-login-2fa-device'),
+ document.querySelector('.js-2fa-form'),
+ );
+ gl.u2fAuthenticate.start();
case 'admin':
new Admin();
switch (path[1]) {
@@ -332,7 +331,7 @@
new gl.Wikis();
shortcut_handler = new ShortcutsNavigation();
new ZenMode();
- new GLForm($('.wiki-form'));
+ new gl.GLForm($('.wiki-form'));
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
@@ -357,7 +356,7 @@
}
// If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) {
- return new Shortcuts();
+ new Shortcuts();
}
};
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index fea642467fa..558828e1bc9 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -180,9 +180,9 @@
<tr>
<th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th>
- <th class="environments-build">Build</th>
+ <th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
- <th class="environments-date">Created</th>
+ <th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th>
</tr>
</thead>
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
index 7bf199d9274..162fd6044e5 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
@@ -39,8 +39,15 @@
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ let value = lastToken.value || '';
- return lastToken.value || '';
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if (value[0] === '"' || value[0] === '\'') {
+ value = value.slice(1);
+ }
+
+ return value;
}
init() {
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 3f23095dad9..7f1f2a5d278 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF");
- regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi');
+ regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext);
if (match) {
- return match[2] || match[1];
+ return (match[1] || match[1] === "") ? match[1] : match[2];
} else {
return null;
}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index cc1c0877cdf..5c86e98567a 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -249,7 +249,7 @@
_this.fullData = data;
_this.parseData(_this.fullData);
_this.focusTextInput();
- if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val().trim() !== '') {
+ if (_this.options.filterable && _this.filter && _this.filter.input && _this.filter.input.val() && _this.filter.input.val().trim() !== '') {
return _this.filter.input.trigger('input');
}
};
@@ -512,12 +512,17 @@
// Append the menu into the dropdown
GitLabDropdown.prototype.appendMenu = function(html) {
+ return this.clearMenu().append(html);
+ };
+
+ GitLabDropdown.prototype.clearMenu = function() {
var selector;
selector = '.dropdown-content';
if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one .dropdown-content";
}
- return $(selector, this.dropdown).empty().append(html);
+
+ return $(selector, this.dropdown).empty();
};
GitLabDropdown.prototype.renderItem = function(data, group, index) {
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
deleted file mode 100644
index 08b2494f3df..00000000000
--- a/app/assets/javascripts/gl_form.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
-/* global GitLab */
-/* global DropzoneInput */
-/* global autosize */
-
-(function() {
- this.GLForm = (function() {
- function GLForm(form) {
- this.form = form;
- this.textarea = this.form.find('textarea.js-gfm-input');
- // Before we start, we should clean up any previous data for this form
- this.destroy();
- // Setup the form
- this.setupForm();
- this.form.data('gl-form', this);
- }
-
- GLForm.prototype.destroy = function() {
- // Clean form listeners
- this.clearEventListeners();
- return this.form.data('gl-form', null);
- };
-
- GLForm.prototype.setupForm = function() {
- var isNewForm;
- isNewForm = this.form.is(':not(.gfm-form)');
- this.form.removeClass('js-new-note-form');
- if (isNewForm) {
- this.form.find('.div-dropzone').remove();
- this.form.addClass('gfm-form');
- // remove notify commit author checkbox for non-commit notes
- gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
- gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
- new DropzoneInput(this.form);
- autosize(this.textarea);
- // form and textarea event listeners
- this.addEventListeners();
- }
- gl.text.init(this.form);
- // hide discard button
- this.form.find('.js-note-discard').hide();
- return this.form.show();
- };
-
- GLForm.prototype.clearEventListeners = function() {
- this.textarea.off('focus');
- this.textarea.off('blur');
- return gl.text.removeListeners(this.form);
- };
-
- GLForm.prototype.addEventListeners = function() {
- this.textarea.on('focus', function() {
- return $(this).closest('.md-area').addClass('is-focused');
- });
- return this.textarea.on('blur', function() {
- return $(this).closest('.md-area').removeClass('is-focused');
- });
- };
-
- return GLForm;
- })();
-}).call(this);
diff --git a/app/assets/javascripts/gl_form.js.es6 b/app/assets/javascripts/gl_form.js.es6
new file mode 100644
index 00000000000..0b446ff364a
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js.es6
@@ -0,0 +1,92 @@
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
+/* global GitLab */
+/* global DropzoneInput */
+/* global autosize */
+
+(() => {
+ const global = window.gl || (window.gl = {});
+
+ function GLForm(form) {
+ this.form = form;
+ this.textarea = this.form.find('textarea.js-gfm-input');
+ // Before we start, we should clean up any previous data for this form
+ this.destroy();
+ // Setup the form
+ this.setupForm();
+ this.form.data('gl-form', this);
+ }
+
+ GLForm.prototype.destroy = function() {
+ // Clean form listeners
+ this.clearEventListeners();
+ return this.form.data('gl-form', null);
+ };
+
+ GLForm.prototype.setupForm = function() {
+ var isNewForm;
+ isNewForm = this.form.is(':not(.gfm-form)');
+ this.form.removeClass('js-new-note-form');
+ if (isNewForm) {
+ this.form.find('.div-dropzone').remove();
+ this.form.addClass('gfm-form');
+ // remove notify commit author checkbox for non-commit notes
+ gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
+ gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
+ new DropzoneInput(this.form);
+ autosize(this.textarea);
+ // form and textarea event listeners
+ this.addEventListeners();
+ }
+ gl.text.init(this.form);
+ // hide discard button
+ this.form.find('.js-note-discard').hide();
+ this.form.show();
+ if (this.isAutosizeable) this.setupAutosize();
+ };
+
+ GLForm.prototype.setupAutosize = function () {
+ this.textarea.off('autosize:resized')
+ .on('autosize:resized', this.setHeightData.bind(this));
+
+ this.textarea.off('mouseup.autosize')
+ .on('mouseup.autosize', this.destroyAutosize.bind(this));
+
+ setTimeout(() => {
+ autosize(this.textarea);
+ this.textarea.css('resize', 'vertical');
+ }, 0);
+ };
+
+ GLForm.prototype.setHeightData = function () {
+ this.textarea.data('height', this.textarea.outerHeight());
+ };
+
+ GLForm.prototype.destroyAutosize = function () {
+ const outerHeight = this.textarea.outerHeight();
+
+ if (this.textarea.data('height') === outerHeight) return;
+
+ autosize.destroy(this.textarea);
+
+ this.textarea.data('height', outerHeight);
+ this.textarea.outerHeight(outerHeight);
+ this.textarea.css('max-height', window.outerHeight);
+ };
+
+ GLForm.prototype.clearEventListeners = function() {
+ this.textarea.off('focus');
+ this.textarea.off('blur');
+ return gl.text.removeListeners(this.form);
+ };
+
+ GLForm.prototype.addEventListeners = function() {
+ this.textarea.on('focus', function() {
+ return $(this).closest('.md-area').addClass('is-focused');
+ });
+ return this.textarea.on('blur', function() {
+ return $(this).closest('.md-area').removeClass('is-focused');
+ });
+ };
+
+ global.GLForm = GLForm;
+})();
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index a50bc4a9057..bc88dc2d092 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -59,11 +59,11 @@
} else {
avatar = gon.default_avatar_url;
}
- return "<div class='group-result'> <div class='group-name'>" + group.name + "</div> <div class='group-path'>" + group.path + "</div> </div>";
+ return "<div class='group-result'> <div class='group-name'>" + group.full_name + "</div> <div class='group-path'>" + group.full_path + "</div> </div>";
};
GroupsSelect.prototype.formatSelection = function(group) {
- return group.name;
+ return group.full_name;
};
return GroupsSelect;
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index 8f48b1f57ce..2a50b72c8aa 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
@@ -8,6 +8,7 @@
this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
+ this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.prioritizedLabels.sortable({
items: 'li',
placeholder: 'list-placeholder',
@@ -29,7 +30,12 @@
const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip.tooltip('destroy');
- return _this.toggleLabelPriority($label, action);
+ _this.toggleLabelPriority($label, action);
+ _this.toggleEmptyState($label, $btn, action);
+ }
+
+ toggleEmptyState($label, $btn, action) {
+ this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
}
toggleLabelPriority($label, action, persistState) {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 51993bb3420..e3bff2559fd 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -162,6 +162,7 @@
w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection();
+ if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null;
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 09ee8dbe9d7..37af422a09e 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -110,9 +110,8 @@
};
MergeRequest.prototype.initCommitMessageListeners = function() {
- var textarea = $('textarea.js-commit-message');
-
- $('a.js-with-description-link').on('click', function(e) {
+ $(document).on('click', 'a.js-with-description-link', function(e) {
+ var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithDescription'));
@@ -120,7 +119,8 @@
$('p.js-without-description-hint').show();
});
- $('a.js-without-description-link').on('click', function(e) {
+ $(document).on('click', 'a.js-without-description-link', function(e) {
+ var textarea = $('textarea.js-commit-message');
e.preventDefault();
textarea.val(textarea.data('messageWithoutDescription'));
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 7cc319e2f4e..fa782ebbedf 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -154,12 +154,22 @@
return;
}
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
- if (data.status !== _this.opts.ci_status && (data.status != null)) {
+ if (data.status !== _this.opts.ci_status ||
+ data.sha !== _this.opts.ci_sha ||
+ data.pipeline !== _this.opts.ci_pipeline) {
_this.opts.ci_status = data.status;
_this.showCIStatus(data.status);
if (data.coverage) {
_this.showCICoverage(data.coverage);
}
+ if (data.pipeline) {
+ _this.opts.ci_pipeline = data.pipeline;
+ _this.updatePipelineUrls(data.pipeline);
+ }
+ if (data.sha) {
+ _this.opts.ci_sha = data.sha;
+ _this.updateCommitUrls(data.sha);
+ }
if (showNotification) {
status = _this.ciLabelForStatus(data.status);
if (status === "preparing") {
@@ -248,6 +258,16 @@
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
};
+ MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
+ const pipelineUrl = this.opts.pipeline_path;
+ $('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/'));
+ };
+
+ MergeRequestWidget.prototype.updateCommitUrls = function(id) {
+ const commitsUrl = this.opts.commits_path;
+ $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/'));
+ };
+
return MergeRequestWidget;
})();
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 9db830a7ada..c4722be3625 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,6 +1,5 @@
/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
/* global Flash */
-/* global GLForm */
/* global Autosave */
/* global ResolveService */
/* global mrRefreshWidgetUrl */
@@ -420,7 +419,7 @@
Notes.prototype.setupNoteForm = function(form) {
var textarea;
- new GLForm(form);
+ new gl.GLForm(form);
textarea = form.find(".js-note-text");
return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
};
@@ -884,7 +883,7 @@
var targetId = $originalContentEl.data('target-id');
var targetType = $originalContentEl.data('target-type');
- new GLForm($editForm.find('form'));
+ new gl.GLForm($editForm.find('form'));
$editForm.find('form')
.attr('action', postUrl)
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 7cf630a1d76..67f8804666d 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -58,6 +58,11 @@
};
Project.prototype.initRefSwitcher = function() {
+ var refListItem = document.createElement('li'),
+ refLink = document.createElement('a');
+
+ refLink.href = '#';
+
return $('.js-project-refs-dropdown').each(function() {
var $dropdown, selected;
$dropdown = $(this);
@@ -67,7 +72,8 @@
return $.ajax({
url: $dropdown.data('refs-url'),
data: {
- ref: $dropdown.data('ref')
+ ref: $dropdown.data('ref'),
+ search: term
},
dataType: "json"
}).done(function(refs) {
@@ -76,16 +82,29 @@
},
selectable: true,
filterable: true,
+ filterRemote: true,
filterByText: true,
fieldName: $dropdown.data('field-name'),
renderRow: function(ref) {
- var link;
+ var li = refListItem.cloneNode(false);
+
if (ref.header != null) {
- return $('<li />').addClass('dropdown-header').text(ref.header);
+ li.className = 'dropdown-header';
+ li.textContent = ref.header;
} else {
- link = $('<a />').attr('href', '#').addClass(ref === selected ? 'is-active' : '').text(ref).attr('data-ref', ref);
- return $('<li />').append(link);
+ var link = refLink.cloneNode(false);
+
+ if (ref === selected) {
+ link.className = 'is-active';
+ }
+
+ link.textContent = ref;
+ link.dataset.ref = ref;
+
+ li.appendChild(link);
}
+
+ return li;
},
id: function(obj, $el) {
return $el.attr('data-ref');
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
index 03f4531abf5..5cf28aa7a73 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
@@ -49,7 +49,7 @@ class ProtectedBranchDropdown {
onClickCreateWildcard() {
// Refresh the dropdown's data, which ends up calling `getProtectedBranches`
this.$dropdown.data('glDropdown').remote.execute();
- this.$dropdown.data('glDropdown').selectRowAtIndex(0);
+ this.$dropdown.data('glDropdown').selectRowAtIndex();
}
getProtectedBranches(term, callback) {
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 489e567259c..b1c0dc37b4d 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -13,12 +13,12 @@
filterable: true,
fieldName: 'group_id',
search: {
- fields: ['name']
+ fields: ['full_name']
},
data: function(term, callback) {
return Api.groups(term, {}, function(data) {
data.unshift({
- name: 'Any'
+ full_name: 'Any'
});
data.splice(1, 0, 'divider');
return callback(data);
@@ -28,10 +28,10 @@
return obj.id;
},
text: function(obj) {
- return obj.name;
+ return obj.full_name;
},
toggleLabel: function(obj) {
- return ($groupDropdown.data('default-label')) + " " + obj.name;
+ return ($groupDropdown.data('default-label')) + " " + obj.full_name;
},
clicked: (function(_this) {
return function() {
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 4ef516af8c8..4dcc5ebe28f 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -39,17 +39,20 @@
}
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
- var quote, replyField, documentFragment, selected, separator;
+ var quote, documentFragment, selected, separator;
+ var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment();
- if (!documentFragment) return;
+ if (!documentFragment) {
+ replyField.focus();
+ return;
+ }
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
- replyField = $('.js-main-target-form #note_note');
if (selected.trim() === "") {
return;
}
diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6
index ef9c0a885fb..05622916ff8 100644
--- a/app/assets/javascripts/todos.js.es6
+++ b/app/assets/javascripts/todos.js.es6
@@ -85,7 +85,7 @@
},
success: (data) => {
$target.remove();
- $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
+ $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>');
return this.updateBadges(data);
}
});
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index e7280d643d3..6e40dfdf3d8 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -1,6 +1,5 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
/* global d3 */
-/* global dateFormat */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
@@ -33,7 +32,7 @@
date.setDate(date.getDate() + i);
var day = date.getDay();
- var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
+ var count = timestamps[date.format('yyyy-mm-dd')];
// Create a new group array if this is the first day of the week
// or if is first object
@@ -122,7 +121,7 @@
if (stamp.count > 0) {
contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
}
- dateText = dateFormat(date, 'mmm d, yyyy');
+ dateText = date.format('mmm d, yyyy');
return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
};
})(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
index b195b0ef3ba..01f8b6519a4 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -26,10 +26,9 @@
v-if='actions'
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
data-toggle="dropdown"
- title="Manual build"
+ title="Manual job"
data-placement="top"
- data-toggle="dropdown"
- aria-label="Manual build"
+ aria-label="Manual job"
>
<span v-html='svgs.iconPlay' aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
@@ -54,7 +53,6 @@
data-toggle="dropdown"
title="Artifacts"
data-placement="top"
- data-toggle="dropdown"
aria-label="Artifacts"
>
<i class="fa fa-download" aria-hidden="true"></i>
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 592ef0d647f..0f9213b98e3 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -278,6 +278,10 @@
display: inline-block;
}
+ .btn {
+ margin: $btn-side-margin $btn-side-margin 0 0;
+ }
+
@media(max-width: $screen-xs-max) {
margin-top: 50px;
text-align: center;
@@ -286,6 +290,12 @@
width: 100%;
}
}
+
+ @media(min-width: $screen-xs-max) {
+ &.labels .text-content {
+ margin-top: 70px;
+ }
+ }
}
.flex-container-block {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index bb6129158d9..cda46223492 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -330,10 +330,6 @@
}
}
-.btn-file-option {
- background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
-}
-
.btn-build {
margin-left: 10px;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 868f28cd356..db8d231a82a 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -58,3 +58,9 @@
fill: $gl-text-color;
}
}
+
+.icon-link {
+ &:hover {
+ text-decoration: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 401c2d0f6ee..fd081c2d7e1 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -294,16 +294,18 @@
.container-fluid {
position: relative;
+
+ .nav-control {
+ @media (max-width: $screen-sm-max) {
+ margin-right: 75px;
+ }
+ }
}
.controls {
float: right;
padding: 7px 0 0;
- @media (max-width: $screen-sm-max) {
- display: none;
- }
-
i {
color: $layout-link-gray;
}
@@ -361,6 +363,7 @@
.fade-left {
@include fade(right, $gray-light);
left: -5px;
+ text-align: center;
.fa {
left: -7px;
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 12d56359d7d..ea2d26dd5a0 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -162,6 +162,10 @@
}
}
}
+
+ &.panel-without-border {
+ border: 0;
+ }
}
.panel-succes .panel-heading,
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index ab68b360f93..0c013915a63 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -56,15 +56,24 @@
&.right {
float: right;
padding-right: 0;
+ }
- a {
- color: $gl-text-color;
- }
+ .modify-merge-commit-link {
+ color: $gl-text-color;
}
- .remove_source_checkbox {
+ .merge-param-checkbox {
margin: 0;
}
+
+ a .fa-question-circle {
+ color: $gl-text-color-secondary;
+
+ &:hover,
+ &:focus {
+ color: $link-hover-color;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index da0caa30c26..f310cc72da0 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -467,7 +467,7 @@ ul.notes {
}
.add-diff-note {
- margin-top: -4px;
+ margin-top: -8px;
border-radius: 40px;
background: $white-light;
padding: 4px;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 47dfc22d533..367a468e1ba 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -201,7 +201,8 @@
.stage-container {
display: inline-block;
position: relative;
- margin-right: 6px;
+ height: 22px;
+ margin: 3px 6px 3px 0;
.tooltip {
white-space: nowrap;
@@ -494,31 +495,27 @@
// Action Icons in big pipeline-graph nodes
> .ci-action-icon-container .ci-action-icon-wrapper {
- i {
- color: $border-color;
- border-radius: 100%;
- border: 1px solid $border-color;
- padding: 5px 6px;
- font-size: 13px;
- background: $white-light;
- height: 30px;
- width: 30px;
-
- &::before {
- position: relative;
- top: 3px;
- left: 3px;
- }
+ height: 30px;
+ width: 30px;
+ background: $white-light;
+ border: 1px solid $border-color;
+ border-radius: 100%;
+ display: block;
- &:hover {
- color: $gl-text-color;
- background-color: $stage-hover-bg;
- border: 1px solid $stage-hover-bg;
- }
+ &:hover {
+ background-color: $stage-hover-bg;
+ border: 1px solid $stage-hover-bg;
}
- .ci-play-icon {
- padding: 5px 5px 5px 7px;
+ svg {
+ fill: $border-color;
+ position: relative;
+ left: -1px;
+ top: -1px;
+ }
+
+ &:hover svg {
+ fill: $gl-text-color;
}
}
@@ -657,7 +654,7 @@
font-weight: 100;
font-size: 15px;
position: absolute;
- right: 5px;
+ right: 13px;
top: 8px;
}
@@ -825,11 +822,23 @@
&:hover,
&:focus {
- text-decoration: none;
- color: $gl-text-color;
background-color: $stage-hover-bg;
border: 1px solid transparent;
}
+
+ svg {
+ width: 22px;
+ height: 22px;
+ left: -6px;
+ position: relative;
+ top: -3px;
+ fill: $action-icon-color;
+ }
+
+ &:hover svg,
+ &:focus svg {
+ fill: $gl-text-color;
+ }
}
// link to the build
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 01675acc62e..0d5604aae69 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -76,6 +76,10 @@
font-size: 14px;
}
+ .action-name {
+ font-weight: normal;
+ }
+
.todo-body {
.todo-note {
word-wrap: break-word;
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index b09ae423096..39c8c6d8a0c 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -45,7 +45,7 @@ class Admin::ProjectsController < Admin::ApplicationController
protected
def project
- @project = Project.find_with_namespace(
+ @project = Project.find_by_full_path(
[params[:namespace_id], '/', params[:id]].join('')
)
@project || render_404
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index bc65dcc33d3..70ac6a75434 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -24,7 +24,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
private
def project
- @project = Project.find_with_namespace(
+ @project = Project.find_by_full_path(
[params[:namespace_id], '/', params[:project_id]].join('')
)
@project || render_404
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index 6f43ce5226d..6286d67d30c 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -4,13 +4,15 @@ module CreatesCommit
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables
+ start_branch = @mr_target_branch unless initial_commit?
commit_params = @commit_params.merge(
- source_project: @project,
- source_branch: @ref,
- target_branch: @target_branch
+ start_project: @mr_target_project,
+ start_branch: start_branch,
+ target_branch: @mr_source_branch
)
- result = service.new(@tree_edit_project, current_user, commit_params).execute
+ result = service.new(
+ @mr_source_project, current_user, commit_params).execute
if result[:status] == :success
update_flash_notice(success_notice)
@@ -89,20 +91,18 @@ module CreatesCommit
@mr_source_project != @mr_target_project
end
- def different_branch?
- @mr_source_branch != @mr_target_branch || different_project?
- end
-
def create_merge_request?
- params[:create_merge_request].present? && different_branch?
+ # XXX: Even if the field is set, if we're checking the same branch
+ # as the target branch in the same project,
+ # we don't want to create a merge request.
+ params[:create_merge_request].present? &&
+ (different_project? || @ref != @target_branch)
end
+ # TODO: We should really clean this up
def set_commit_variables
- @mr_source_branch ||= @target_branch
-
if can?(current_user, :push_code, @project)
# Edit file in this project
- @tree_edit_project = @project
@mr_source_project = @project
if @project.forked?
@@ -112,15 +112,34 @@ module CreatesCommit
else
# Merge request to this project
@mr_target_project = @project
- @mr_target_branch ||= @ref
+ @mr_target_branch = @ref || @target_branch
end
else
- # Edit file in fork
- @tree_edit_project = current_user.fork_of(@project)
# Merge request from fork to this project
- @mr_source_project = @tree_edit_project
+ @mr_source_project = current_user.fork_of(@project)
@mr_target_project = @project
- @mr_target_branch ||= @ref
+ @mr_target_branch = @ref || @target_branch
end
+
+ @mr_source_branch = guess_mr_source_branch
+ end
+
+ def initial_commit?
+ @mr_target_branch.nil? ||
+ !@mr_target_project.repository.branch_exists?(@mr_target_branch)
+ end
+
+ def guess_mr_source_branch
+ # XXX: Happens when viewing a commit without a branch. In this case,
+ # @target_branch would be the default branch for @mr_source_project,
+ # however we want a generated new branch here. Thus we can't use
+ # @target_branch, but should pass nil to indicate that we want a new
+ # branch instead of @target_branch.
+ return if
+ create_merge_request? &&
+ # XXX: Don't understand why rubocop prefers this indention
+ @mr_source_project.repository.branch_exists?(@target_branch)
+
+ @target_branch
end
end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index 99acd98ae13..562f92bd83c 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
+ redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index c08eb811532..3ba8c2f8bb9 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -10,10 +10,8 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
- @last_push = current_user.recent_push
-
respond_to do |format|
- format.html
+ format.html { @last_push = current_user.recent_push }
format.atom do
event_filter
load_events
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 4dda4e51f6a..79d420a32d3 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController
before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests]
+ before_action :set_show_full_reference, only: [:issues, :merge_requests]
respond_to :html
@@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController
@events = @event_filter.apply_filter(@events).with_associations
@events = @events.limit(20).offset(params[:offset] || 0)
end
+
+ def set_show_full_reference
+ @show_full_reference = true
+ end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index f81237db991..264b14713fb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
- @group.reset_path!
+ @group.restore_path!
render action: "edit"
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index b2ff36f6538..ba523b190bf 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -24,7 +24,7 @@ class Projects::ApplicationController < ApplicationController
end
project_path = "#{namespace}/#{id}"
- @project = Project.find_with_namespace(project_path)
+ @project = Project.find_by_full_path(project_path)
if can?(current_user, :read_project, @project) && !@project.pending_delete?
if @project.path_with_namespace != project_path
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index bfc59bcc862..b5a7078a3a1 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController
end
def pipelines
+ @pipelines = @commit.pipelines.order(id: :desc)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: PipelineSerializer
+ .new(project: @project, user: @current_user)
+ .with_pagination(request, response)
+ .represent(@pipelines)
+ end
+ end
end
def branches
@@ -39,7 +50,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def revert
- assign_change_commit_vars(@commit.revert_branch_name)
+ assign_change_commit_vars
return render_404 if @target_branch.blank?
@@ -48,7 +59,7 @@ class Projects::CommitController < Projects::ApplicationController
end
def cherry_pick
- assign_change_commit_vars(@commit.cherry_pick_branch_name)
+ assign_change_commit_vars
return render_404 if @target_branch.blank?
@@ -105,11 +116,9 @@ class Projects::CommitController < Projects::ApplicationController
}
end
- def assign_change_commit_vars(mr_source_branch)
+ def assign_change_commit_vars
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
- @mr_source_branch = mr_source_branch
- @mr_target_branch = @target_branch
@commit_params = {
commit: @commit,
create_merge_request: params[:create_merge_request].present? || different_project?
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index d32966645c8..321cde255c3 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -46,7 +46,8 @@ class Projects::CompareController < Projects::ApplicationController
end
def define_diff_vars
- @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
+ @compare = CompareService.new(@project, @head_ref)
+ .execute(@project, @start_ref)
if @compare
@commits = @compare.commits
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 70845617d3c..216c158e41e 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -79,7 +79,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
if project_id.blank?
@project = nil
else
- @project = Project.find_with_namespace("#{params[:namespace_id]}/#{project_id}")
+ @project = Project.find_by_full_path("#{params[:namespace_id]}/#{project_id}")
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 824ed7be73e..1593b5c1afb 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController
include ToggleSubscriptionAction
before_action :module_enabled
- before_action :label, only: [:edit, :update, :destroy]
+ before_action :label, only: [:edit, :update, :destroy, :promote]
before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription]
before_action :authorize_read_label!
before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update,
:generate, :destroy, :remove_priority,
:set_priorities]
+ before_action :authorize_admin_group!, only: [:promote]
respond_to :js, :html
@@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController
@label.destroy
@labels = find_labels
- respond_to do |format|
- format.html do
- redirect_to(namespace_project_labels_path(@project.namespace, @project),
- notice: 'Label was removed')
- end
- format.js
- end
+ redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed')
end
def remove_priority
@@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController
end
end
+ def promote
+ promote_service = Labels::PromoteService.new(@project, @current_user)
+
+ begin
+ return render_404 unless promote_service.execute(@label)
+ respond_to do |format|
+ format.html do
+ redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ notice: 'Label was promoted to a Group Label')
+ end
+ format.js
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label"
+ Gitlab::AppLogger.error e
+
+ respond_to do |format|
+ format.html do
+ redirect_to(namespace_project_labels_path(@project.namespace, @project),
+ notice: 'Failed to promote label due to internal error. Please contact administrators.')
+ end
+ format.js
+ end
+ end
+ end
+
protected
def module_enabled
@@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
+
+ def authorize_admin_group!
+ return render_404 unless can?(current_user, :admin_group, @project.group)
+ end
end
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 01d99c7df35..38f7e6eb5e9 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController
end
def teams
- @teams ||= @service.list_teams(current_user)
+ @teams, @teams_error_message = @service.list_teams(current_user)
end
def service
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 9ac5bf4b9f8..6eb542e4bd8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render 'show'
end
- format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } }
+
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/merge_requests/show/_pipelines'),
+ pipelines: PipelineSerializer
+ .new(project: @project, user: @current_user)
+ .with_pagination(request, response)
+ .represent(@pipelines)
+ }
+ end
end
end
@@ -425,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
title: merge_request.title,
sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
status: status,
- coverage: coverage
+ coverage: coverage,
+ pipeline: pipeline.try(:id)
}
render json: response
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 02a97c1c574..5d193f26a8e 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,8 +1,9 @@
class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
+ include SpammableActions
before_action :module_enabled
- before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
+ before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
@@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def create
- @snippet = CreateSnippetService.new(@project, current_user,
- snippet_params).execute
+ create_params = snippet_params.merge(request: request)
+ @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid?
respond_with(@snippet,
@@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
alias_method :awardable, :snippet
+ alias_method :spammable, :snippet
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb
index e617be8f9fb..50ba33ed570 100644
--- a/app/controllers/projects/uploads_controller.rb
+++ b/app/controllers/projects/uploads_controller.rb
@@ -36,7 +36,7 @@ class Projects::UploadsController < Projects::ApplicationController
namespace = params[:namespace_id]
id = params[:project_id]
- file_project = Project.find_with_namespace("#{namespace}/#{id}")
+ file_project = Project.find_by_full_path("#{namespace}/#{id}")
if file_project.nil?
@uploader = nil
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 444ff837bb3..acca821782c 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -231,12 +231,16 @@ class ProjectsController < Projects::ApplicationController
end
def refs
+ branches = BranchesFinder.new(@repository, params).execute.map(&:name)
+
options = {
- 'Branches' => @repository.branch_names,
+ 'Branches' => branches.take(100),
}
unless @repository.tag_count.zero?
- options['Tags'] = VersionSorter.rsort(@repository.tag_names)
+ tags = TagsFinder.new(@repository, params).execute.map(&:name)
+
+ options['Tags'] = tags.take(100)
end
# If reference is commit id - we should add it to branch/tag selectbox
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
index 627be74a38f..db2817fadf6 100644
--- a/app/controllers/root_controller.rb
+++ b/app/controllers/root_controller.rb
@@ -7,6 +7,7 @@
# For users who haven't customized the setting, we simply delegate to
# `DashboardController#show`, which is the default.
class RootController < Dashboard::ProjectsController
+ skip_before_action :authenticate_user!, only: [:index]
before_action :redirect_to_custom_dashboard, only: [:index]
def index
@@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController
private
def redirect_to_custom_dashboard
- return unless current_user
+ return redirect_to new_user_session_path unless current_user
case current_user.dashboard
when 'stars'
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index dee57e4a388..b169d993688 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,5 +1,6 @@
class SnippetsController < ApplicationController
include ToggleAwardEmoji
+ include SpammableActions
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
@@ -40,8 +41,8 @@ class SnippetsController < ApplicationController
end
def create
- @snippet = CreateSnippetService.new(nil, current_user,
- snippet_params).execute
+ create_params = snippet_params.merge(request: request)
+ @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet)
end
@@ -96,6 +97,7 @@ class SnippetsController < ApplicationController
end
end
alias_method :awardable, :snippet
+ alias_method :spammable, :snippet
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a112928c6de..bee323993a0 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -37,7 +37,7 @@ module ApplicationHelper
if project_id.is_a?(Project)
project_id
else
- Project.find_with_namespace(project_id)
+ Project.find_by_full_path(project_id)
end
if project.avatar_url
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index c3508443d8a..311a70725ab 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -21,7 +21,7 @@ module BlobHelper
options[:link_opts])
if !on_top_of_branch?(project, ref)
- button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project)
@@ -32,7 +32,7 @@ module BlobHelper
}
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
- link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
+ link_to "Edit", fork_path, class: 'btn', method: :post
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index e9461b9f859..6dcb624c4da 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -198,7 +198,7 @@ module CommitsHelper
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff_new_path)),
- class: 'btn view-file js-view-file btn-file-option'
+ class: 'btn view-file js-view-file'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index e5bb8b93e76..03354c235eb 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -162,6 +162,10 @@ module IssuablesHelper
]
end
+ def issuable_reference(issuable)
+ @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
+ end
+
def issuable_filter_present?
issuable_filter_params.any? { |k| params.key?(k) }
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 8c2c4e8833b..83ff898e68a 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -143,4 +143,16 @@ module MergeRequestsHelper
def different_base?(version1, version2)
version1 && version2 && version1.base_commit_sha != version2.base_commit_sha
end
+
+ def merge_params(merge_request)
+ {
+ merge_when_build_succeeds: true,
+ should_remove_source_branch: true,
+ sha: merge_request.diff_head_sha
+ }.merge(merge_params_ee(merge_request))
+ end
+
+ def merge_params_ee(merge_request)
+ {}
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 6654f6997ce..37b69423c97 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -89,7 +89,7 @@ module SearchHelper
{
category: "Groups",
id: group.id,
- label: "#{search_result_sanitize(group.name)}",
+ label: "#{search_result_sanitize(group.full_name)}",
url: group_path(group)
}
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 3a83ae15dd8..fc93acfe63e 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -93,10 +93,6 @@ module VisibilityLevelHelper
current_application_settings.default_project_visibility
end
- def default_snippet_visibility
- current_application_settings.default_snippet_visibility
- end
-
def default_group_visibility
current_application_settings.default_group_visibility
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5fe8ddf69d7..b1f77bf242c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -275,29 +275,23 @@ module Ci
end
def update_coverage
- return unless project
- coverage_regex = project.build_coverage_regex
- return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
-
- if coverage.is_a? Numeric
- update_attributes(coverage: coverage)
- end
+ update_attributes(coverage: coverage) if coverage.present?
end
def extract_coverage(text, regex)
- begin
- matches = text.scan(Regexp.new(regex)).last
- matches = matches.last if matches.kind_of?(Array)
- coverage = matches.gsub(/\d+(\.\d+)?/).first
+ return unless regex
- if coverage.present?
- coverage.to_f
- end
- rescue
- # if bad regex or something goes wrong we dont want to interrupt transition
- # so we just silentrly ignore error for now
+ matches = text.scan(Regexp.new(regex)).last
+ matches = matches.last if matches.kind_of?(Array)
+ coverage = matches.gsub(/\d+(\.\d+)?/).first
+
+ if coverage.present?
+ coverage.to_f
end
+ rescue
+ # if bad regex or something goes wrong we dont want to interrupt transition
+ # so we just silentrly ignore error for now
end
def has_trace_file?
@@ -522,6 +516,10 @@ module Ci
self.update(artifacts_expire_at: nil)
end
+ def coverage_regex
+ super || project.try(:build_coverage_regex)
+ end
+
def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 316bd2e512b..46f06733da1 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -100,8 +100,8 @@ class Commit
commit_reference(from_project, id, full: full)
end
- def reference_link_text(from_project = nil)
- commit_reference(from_project, short_id)
+ def reference_link_text(from_project = nil, full: false)
+ commit_reference(from_project, short_id, full: full)
end
def diff_line_count
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 1aa97debe42..1acff093aa1 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -34,7 +34,13 @@ module Spammable
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?
+ if spam?
+ self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
+ end
+ end
+
+ def spammable_entity_type
+ self.class.name.underscore
end
def spam_title
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 652abf18a8a..577367f1eed 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -1,7 +1,8 @@
class Environment < ActiveRecord::Base
# Used to generate random suffixes for the slug
+ LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
- SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
+ SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
belongs_to :project, required: true, validate: true
@@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter
- slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
+ slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
+
+ # Repeated dashes are invalid (OpenShift limitation)
+ slugified.gsub!(/\-+/, '-')
# Maximum length: 24 characters (OpenShift limitation)
slugified = slugified[0..23]
- # Cannot end with a "-" character (Kubernetes label limitation)
- slugified = slugified[0..-2] if slugified[-1] == "-"
+ # Cannot end with a dash (Kubernetes label limitation)
+ slugified.chop! if slugified.end_with?('-')
# Add a random suffix, shortening the current string if necessary, if it
# has been slugified. This ensures uniqueness.
- slugified = slugified[0..16] + "-" + random_suffix if slugified != name
+ if slugified != name
+ slugified = slugified[0..16]
+ slugified << '-' unless slugified.end_with?('-')
+ slugified << random_suffix
+ end
self.slug = slugified
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 65638d9a299..d8826b65fcc 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base
end
end
- def to_reference(from_project = nil, full: false)
+ # `from` argument can be a Namespace or Project.
+ def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
- "#{project.to_reference(from_project, full: full)}#{reference}"
+ "#{project.to_reference(from, full: full)}#{reference}"
end
def referenced_merge_requests(current_user = nil)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 6753504acff..082adcafcc8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
- def to_reference(from_project = nil, full: false)
+ # `from` argument can be a Namespace or Project.
+ def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
- "#{project.to_reference(from_project, full: full)}#{reference}"
+ "#{project.to_reference(from, full: full)}#{reference}"
end
def first_commit
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index dadb81f9b6e..70bad2a4396 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -169,7 +169,8 @@ class MergeRequestDiff < ActiveRecord::Base
# When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
- CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight)
+ CompareService.new(project, head_commit_sha)
+ .execute(project, sha, straight: straight)
end
def commits_count
diff --git a/app/models/project.rb b/app/models/project.rb
index 59faf35e051..0d286bfbaa8 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -225,6 +225,7 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
+ scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@@ -369,10 +370,6 @@ class Project < ActiveRecord::Base
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
-
- # Add alias for Routable method for compatibility with old code.
- # In future all calls `find_with_namespace` should be replaced with `find_by_full_path`
- alias_method :find_with_namespace, :find_by_full_path
end
def lfs_enabled?
@@ -591,10 +588,11 @@ class Project < ActiveRecord::Base
end
end
- def to_reference(from_project = nil, full: false)
- if full || cross_namespace_reference?(from_project)
+ # `from` argument can be a Namespace or Project.
+ def to_reference(from = nil, full: false)
+ if full || cross_namespace_reference?(from)
path_with_namespace
- elsif cross_project_reference?(from_project)
+ elsif cross_project_reference?(from)
path
end
end
@@ -1291,21 +1289,26 @@ class Project < ActiveRecord::Base
private
+ def cross_namespace_reference?(from)
+ case from
+ when Project
+ namespace != from.namespace
+ when Namespace
+ namespace != from
+ end
+ end
+
# Check if a reference is being done cross-project
- #
- # from_project - Refering Project object
- def cross_project_reference?(from_project)
- from_project && self != from_project
+ def cross_project_reference?(from)
+ return true if from.is_a?(Namespace)
+
+ from && self != from
end
def pushes_since_gc_redis_key
"projects/#{id}/pushes_since_gc"
end
- def cross_namespace_reference?(from_project)
- from_project && namespace != from_project.namespace
- end
-
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
@@ -1345,6 +1348,6 @@ class Project < ActiveRecord::Base
def pending_delete_twin
return false unless path
- Project.unscoped.where(pending_delete: true).find_with_namespace(path_with_namespace)
+ Project.unscoped.where(pending_delete: true).find_by_full_path(path_with_namespace)
end
end
diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb
index 2bcff541cc0..5eb1bd86e9d 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/chat_slash_commands_service.rb
@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
return unless valid_token?(params[:token])
user = find_chat_user(params)
- unless user
+
+ if user
+ Gitlab::ChatCommands::Command.new(project, user, params).execute
+ else
url = authorize_chat_name_url(params)
- return presenter.authorize_chat_name(url)
+ Gitlab::ChatCommands::Presenters::Access.new(url).authorize
end
-
- Gitlab::ChatCommands::Command.new(project, user,
- params).execute
end
private
@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute
end
-
- def presenter
- Gitlab::ChatCommands::Presenter.new
- end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2ac76e97de0..80d002f9c32 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -60,9 +60,9 @@ class JiraService < IssueTrackerService
end
def help
- 'You need to configure JIRA before enabling this service. For more details
+ "You need to configure JIRA before enabling this service. For more details
read the
- [JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).'
+ [JIRA service documentation](#{help_page_url('project_services/jira')})."
end
def title
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 50a011db74e..b0f7a42f9a3 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
[false, e.message]
end
- def list_teams(user)
- Mattermost::Team.new(user).all
+ def list_teams(current_user)
+ [Mattermost::Team.new(current_user).all, nil]
rescue Mattermost::Error => e
[[], e.message]
end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 25b5d777641..9bb456eee24 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet
participant :author
participant :notes_with_associations
+
+ def check_for_spam?
+ super && project.public?
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d77b7692d75..7cf09c52bf4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -5,7 +5,7 @@ class Repository
attr_accessor :path_with_namespace, :project
- class CommitError < StandardError; end
+ CommitError = Class.new(StandardError)
# Methods that cache data from the Git repository.
#
@@ -64,10 +64,6 @@ class Repository
@raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
end
- def update_autocrlf_option
- raw_repository.autocrlf = :input if raw_repository.autocrlf != :input
- end
-
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.expand_path(
@@ -168,63 +164,46 @@ class Repository
tags.find { |tag| tag.name == name }
end
- def add_branch(user, branch_name, target)
- oldrev = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
- target = commit(target).try(:id)
+ def add_branch(user, branch_name, ref)
+ newrev = commit(ref).try(:sha)
- return false unless target
+ return false unless newrev
- GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
- update_ref!(ref, target, oldrev)
- end
+ GitOperationService.new(user, self).add_branch(branch_name, newrev)
after_create_branch
find_branch(branch_name)
end
def add_tag(user, tag_name, target, message = nil)
- oldrev = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
- target = commit(target).try(:id)
-
- return false unless target
-
+ newrev = commit(target).try(:id)
options = { message: message, tagger: user_to_committer(user) } if message
- GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service|
- raw_tag = rugged.tags.create(tag_name, target, options)
- service.newrev = raw_tag.target_id
- end
+ return false unless newrev
+
+ GitOperationService.new(user, self).add_tag(tag_name, newrev, options)
find_tag(tag_name)
end
def rm_branch(user, branch_name)
before_remove_branch
-
branch = find_branch(branch_name)
- oldrev = branch.try(:dereferenced_target).try(:id)
- newrev = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
- GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
- update_ref!(ref, newrev, oldrev)
- end
+ GitOperationService.new(user, self).rm_branch(branch)
after_remove_branch
true
end
- def rm_tag(tag_name)
+ def rm_tag(user, tag_name)
before_remove_tag
+ tag = find_tag(tag_name)
- begin
- rugged.tags.delete(tag_name)
- true
- rescue Rugged::ReferenceError
- false
- end
+ GitOperationService.new(user, self).rm_tag(tag)
+
+ after_remove_tag
+ true
end
def ref_names
@@ -241,21 +220,6 @@ class Repository
false
end
- def update_ref!(name, newrev, oldrev)
- # We use 'git update-ref' because libgit2/rugged currently does not
- # offer 'compare and swap' ref updates. Without compare-and-swap we can
- # (and have!) accidentally reset the ref to an earlier state, clobbering
- # commits. See also https://github.com/libgit2/libgit2/issues/1534.
- command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
- _, status = Gitlab::Popen.popen(command, path_to_repo) do |stdin|
- stdin.write("update #{name}\x00#{newrev}\x00#{oldrev}\x00")
- end
-
- return if status.zero?
-
- raise CommitError.new("Could not update branch #{name.sub('refs/heads/', '')}. Please refresh and try again.")
- end
-
# Makes sure a commit is kept around when Git garbage collection runs.
# Git GC will delete commits from the repository that are no longer in any
# branches or tags, but we want to keep some of these commits around, for
@@ -435,6 +399,11 @@ class Repository
repository_event(:remove_tag)
end
+ # Runs code after removing a tag.
+ def after_remove_tag
+ expire_tags_cache
+ end
+
def before_import
expire_content_cache
end
@@ -779,121 +748,132 @@ class Repository
@tags ||= raw_repository.tags
end
- def commit_dir(user, path, message, branch, author_email: nil, author_name: nil)
- update_branch_with_hooks(user, branch) do |ref|
- options = {
- commit: {
- branch: ref,
- message: message,
- update_ref: false
- }
- }
-
- options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
-
- raw_repository.mkdir(path, options)
- end
- end
-
- def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil)
- update_branch_with_hooks(user, branch) do |ref|
- options = {
- commit: {
- branch: ref,
- message: message,
- update_ref: false
- },
- file: {
- content: content,
- path: path,
- update: update
- }
- }
-
- options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
+ # rubocop:disable Metrics/ParameterLists
+ def commit_dir(
+ user, path,
+ message:, branch_name:,
+ author_email: nil, author_name: nil,
+ start_branch_name: nil, start_project: project)
+ check_tree_entry_for_dir(branch_name, path)
- Gitlab::Git::Blob.commit(raw_repository, options)
+ if start_branch_name
+ start_project.repository.
+ check_tree_entry_for_dir(start_branch_name, path)
end
- end
-
- def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil)
- update_branch_with_hooks(user, branch) do |ref|
- options = {
- commit: {
- branch: ref,
- message: message,
- update_ref: false
- },
- file: {
- content: content,
- path: path,
- update: true
- }
- }
-
- options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
- if previous_path && previous_path != path
- options[:file][:previous_path] = previous_path
- Gitlab::Git::Blob.rename(raw_repository, options)
- else
- Gitlab::Git::Blob.commit(raw_repository, options)
+ commit_file(
+ user,
+ "#{path}/.gitkeep",
+ '',
+ message: message,
+ branch_name: branch_name,
+ update: false,
+ author_email: author_email,
+ author_name: author_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project)
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ # rubocop:disable Metrics/ParameterLists
+ def commit_file(
+ user, path, content,
+ message:, branch_name:, update: true,
+ author_email: nil, author_name: nil,
+ start_branch_name: nil, start_project: project)
+ unless update
+ error_message = "Filename already exists; update not allowed"
+
+ if tree_entry_at(branch_name, path)
+ raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
end
- end
- end
-
- def remove_file(user, path, message, branch, author_email: nil, author_name: nil)
- update_branch_with_hooks(user, branch) do |ref|
- options = {
- commit: {
- branch: ref,
- message: message,
- update_ref: false
- },
- file: {
- path: path
- }
- }
-
- options.merge!(get_committer_and_author(user, email: author_email, name: author_name))
- Gitlab::Git::Blob.remove(raw_repository, options)
+ if start_branch_name &&
+ start_project.repository.tree_entry_at(start_branch_name, path)
+ raise Gitlab::Git::Repository::InvalidBlobName.new(error_message)
+ end
end
- end
- def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil)
- update_branch_with_hooks(user, branch) do |ref|
+ multi_action(
+ user: user,
+ message: message,
+ branch_name: branch_name,
+ author_email: author_email,
+ author_name: author_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project,
+ actions: [{ action: :create,
+ file_path: path,
+ content: content }])
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ # rubocop:disable Metrics/ParameterLists
+ def update_file(
+ user, path, content,
+ message:, branch_name:, previous_path:,
+ author_email: nil, author_name: nil,
+ start_branch_name: nil, start_project: project)
+ action = if previous_path && previous_path != path
+ :move
+ else
+ :update
+ end
+
+ multi_action(
+ user: user,
+ message: message,
+ branch_name: branch_name,
+ author_email: author_email,
+ author_name: author_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project,
+ actions: [{ action: action,
+ file_path: path,
+ content: content,
+ previous_path: previous_path }])
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ # rubocop:disable Metrics/ParameterLists
+ def remove_file(
+ user, path,
+ message:, branch_name:,
+ author_email: nil, author_name: nil,
+ start_branch_name: nil, start_project: project)
+ multi_action(
+ user: user,
+ message: message,
+ branch_name: branch_name,
+ author_email: author_email,
+ author_name: author_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project,
+ actions: [{ action: :delete,
+ file_path: path }])
+ end
+ # rubocop:enable Metrics/ParameterLists
+
+ # rubocop:disable Metrics/ParameterLists
+ def multi_action(
+ user:, branch_name:, message:, actions:,
+ author_email: nil, author_name: nil,
+ start_branch_name: nil, start_project: project)
+ GitOperationService.new(user, self).with_branch(
+ branch_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project) do |start_commit|
index = rugged.index
- parents = []
- branch = find_branch(ref)
- if branch
- last_commit = branch.dereferenced_target
- index.read_tree(last_commit.raw_commit.tree)
- parents = [last_commit.sha]
- end
+ parents = if start_commit
+ index.read_tree(start_commit.raw_commit.tree)
+ [start_commit.sha]
+ else
+ []
+ end
- actions.each do |action|
- case action[:action]
- when :create, :update, :move
- mode =
- case action[:action]
- when :update
- index.get(action[:file_path])[:mode]
- when :move
- index.get(action[:previous_path])[:mode]
- end
- mode ||= 0o100644
-
- index.remove(action[:previous_path]) if action[:action] == :move
-
- content = action[:encoding] == 'base64' ? Base64.decode64(action[:content]) : action[:content]
- oid = rugged.write(content, :blob)
-
- index.add(path: action[:file_path], oid: oid, mode: mode)
- when :delete
- index.remove(action[:file_path])
- end
+ actions.each do |act|
+ git_action(index, act)
end
options = {
@@ -906,6 +886,7 @@ class Repository
Rugged::Commit.create(rugged, options)
end
end
+ # rubocop:enable Metrics/ParameterLists
def get_committer_and_author(user, email: nil, name: nil)
committer = user_to_committer(user)
@@ -918,7 +899,7 @@ class Repository
end
def user_to_committer(user)
- Gitlab::Git::committer_hash(email: user.email, name: user.name)
+ Gitlab::Git.committer_hash(email: user.email, name: user.name)
end
def can_be_merged?(source_sha, target_branch)
@@ -932,17 +913,18 @@ class Repository
end
end
- def merge(user, merge_request, options = {})
- our_commit = rugged.branches[merge_request.target_branch].target
- their_commit = rugged.lookup(merge_request.diff_head_sha)
+ def merge(user, source, merge_request, options = {})
+ GitOperationService.new(user, self).with_branch(
+ merge_request.target_branch) do |start_commit|
+ our_commit = start_commit.sha
+ their_commit = source
- raise "Invalid merge target" if our_commit.nil?
- raise "Invalid merge source" if their_commit.nil?
+ raise 'Invalid merge target' unless our_commit
+ raise 'Invalid merge source' unless their_commit
- merge_index = rugged.merge_commits(our_commit, their_commit)
- return false if merge_index.conflicts?
+ merge_index = rugged.merge_commits(our_commit, their_commit)
+ break if merge_index.conflicts?
- update_branch_with_hooks(user, merge_request.target_branch) do
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
@@ -952,34 +934,48 @@ class Repository
merge_request.update(in_progress_merge_commit_sha: commit_id)
commit_id
end
+ rescue Repository::CommitError # when merge_index.conflicts?
+ false
end
- def revert(user, commit, base_branch, revert_tree_id = nil)
- source_sha = find_branch(base_branch).dereferenced_target.sha
- revert_tree_id ||= check_revert_content(commit, base_branch)
+ def revert(
+ user, commit, branch_name, revert_tree_id = nil,
+ start_branch_name: nil, start_project: project)
+ revert_tree_id ||= check_revert_content(commit, branch_name)
return false unless revert_tree_id
- update_branch_with_hooks(user, base_branch) do
+ GitOperationService.new(user, self).with_branch(
+ branch_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project) do |start_commit|
+
committer = user_to_committer(user)
- source_sha = Rugged::Commit.create(rugged,
+
+ Rugged::Commit.create(rugged,
message: commit.revert_message(user),
author: committer,
committer: committer,
tree: revert_tree_id,
- parents: [rugged.lookup(source_sha)])
+ parents: [start_commit.sha])
end
end
- def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil)
- source_sha = find_branch(base_branch).dereferenced_target.sha
- cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch)
+ def cherry_pick(
+ user, commit, branch_name, cherry_pick_tree_id = nil,
+ start_branch_name: nil, start_project: project)
+ cherry_pick_tree_id ||= check_cherry_pick_content(commit, branch_name)
return false unless cherry_pick_tree_id
- update_branch_with_hooks(user, base_branch) do
+ GitOperationService.new(user, self).with_branch(
+ branch_name,
+ start_branch_name: start_branch_name,
+ start_project: start_project) do |start_commit|
+
committer = user_to_committer(user)
- source_sha = Rugged::Commit.create(rugged,
+
+ Rugged::Commit.create(rugged,
message: commit.message,
author: {
email: commit.author_email,
@@ -988,22 +984,22 @@ class Repository
},
committer: committer,
tree: cherry_pick_tree_id,
- parents: [rugged.lookup(source_sha)])
+ parents: [start_commit.sha])
end
end
- def resolve_conflicts(user, branch, params)
- update_branch_with_hooks(user, branch) do
+ def resolve_conflicts(user, branch_name, params)
+ GitOperationService.new(user, self).with_branch(branch_name) do
committer = user_to_committer(user)
Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer))
end
end
- def check_revert_content(commit, base_branch)
- source_sha = find_branch(base_branch).dereferenced_target.sha
- args = [commit.id, source_sha]
- args << { mainline: 1 } if commit.merge_commit?
+ def check_revert_content(target_commit, branch_name)
+ source_sha = commit(branch_name).sha
+ args = [target_commit.sha, source_sha]
+ args << { mainline: 1 } if target_commit.merge_commit?
revert_index = rugged.revert_commit(*args)
return false if revert_index.conflicts?
@@ -1014,10 +1010,10 @@ class Repository
tree_id
end
- def check_cherry_pick_content(commit, base_branch)
- source_sha = find_branch(base_branch).dereferenced_target.sha
- args = [commit.id, source_sha]
- args << 1 if commit.merge_commit?
+ def check_cherry_pick_content(target_commit, branch_name)
+ source_sha = commit(branch_name).sha
+ args = [target_commit.sha, source_sha]
+ args << 1 if target_commit.merge_commit?
cherry_pick_index = rugged.cherrypick_commit(*args)
return false if cherry_pick_index.conflicts?
@@ -1075,6 +1071,28 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip)
end
+ def with_repo_branch_commit(start_repository, start_branch_name)
+ branch_name_or_sha =
+ if start_repository == self
+ start_branch_name
+ else
+ tmp_ref = "refs/tmp/#{SecureRandom.hex}/head"
+
+ fetch_ref(
+ start_repository.path_to_repo,
+ "#{Gitlab::Git::BRANCH_REF_PREFIX}#{start_branch_name}",
+ tmp_ref
+ )
+
+ start_repository.commit(start_branch_name).sha
+ end
+
+ yield(commit(branch_name_or_sha))
+
+ ensure
+ rugged.references.delete(tmp_ref) if tmp_ref
+ end
+
def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
@@ -1084,39 +1102,6 @@ class Repository
fetch_ref(path_to_repo, ref, ref_path)
end
- def update_branch_with_hooks(current_user, branch)
- update_autocrlf_option
-
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
- target_branch = find_branch(branch)
- was_empty = empty?
-
- # Make commit
- newrev = yield(ref)
-
- unless newrev
- raise CommitError.new('Failed to create commit')
- end
-
- if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil?
- oldrev = Gitlab::Git::BLANK_SHA
- else
- oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
- end
-
- GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
- update_ref!(ref, newrev, oldrev)
-
- if was_empty || !target_branch
- # If repo was empty expire cache
- after_create if was_empty
- after_create_branch
- end
- end
-
- newrev
- end
-
def ls_files(ref)
actual_ref = ref || root_ref
raw_repository.ls_files(actual_ref)
@@ -1175,8 +1160,76 @@ class Repository
end
end
+ protected
+
+ def tree_entry_at(branch_name, path)
+ branch_exists?(branch_name) &&
+ # tree_entry is private
+ raw_repository.send(:tree_entry, commit(branch_name), path)
+ end
+
+ def check_tree_entry_for_dir(branch_name, path)
+ return unless branch_exists?(branch_name)
+
+ entry = tree_entry_at(branch_name, path)
+
+ return unless entry
+
+ if entry[:type] == :blob
+ raise Gitlab::Git::Repository::InvalidBlobName.new(
+ "Directory already exists as a file")
+ else
+ raise Gitlab::Git::Repository::InvalidBlobName.new(
+ "Directory already exists")
+ end
+ end
+
private
+ def git_action(index, action)
+ path = normalize_path(action[:file_path])
+
+ if action[:action] == :move
+ previous_path = normalize_path(action[:previous_path])
+ end
+
+ case action[:action]
+ when :create, :update, :move
+ mode =
+ case action[:action]
+ when :update
+ index.get(path)[:mode]
+ when :move
+ index.get(previous_path)[:mode]
+ end
+ mode ||= 0o100644
+
+ index.remove(previous_path) if action[:action] == :move
+
+ content = if action[:encoding] == 'base64'
+ Base64.decode64(action[:content])
+ else
+ action[:content]
+ end
+
+ oid = rugged.write(content, :blob)
+
+ index.add(path: path, oid: oid, mode: mode)
+ when :delete
+ index.remove(path)
+ end
+ end
+
+ def normalize_path(path)
+ pathname = Gitlab::Git::PathHelper.normalize_path(path)
+
+ if pathname.each_filename.include?('..')
+ raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
+ end
+
+ pathname.to_s
+ end
+
def refs_directory_exists?
return false unless path_with_namespace
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 771a7350556..2665a7249a3 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base
include Sortable
include Awardable
include Mentionable
+ include Spammable
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :content
@@ -17,7 +18,7 @@ class Snippet < ActiveRecord::Base
default_content_html_invalidator || file_name_changed?
end
- default_value_for :visibility_level, Snippet::PRIVATE
+ default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility }
belongs_to :author, class_name: 'User'
belongs_to :project
@@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base
participant :author
participant :notes_with_associations
+ attr_spammable :title, spam_title: true
+ attr_spammable :content, spam_description: true
+
def self.reference_prefix
'$'
end
@@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base
notes.includes(:author)
end
+ def check_for_spam?
+ public?
+ end
+
+ def spammable_entity_type
+ 'snippet'
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 4c99aa0d3be..2adf494ce11 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base
def target_reference
if for_commit?
- target.short_id
+ target.reference_link_text(full: true)
else
- target.to_reference
+ target.to_reference(full: true)
end
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 7b1752df0e1..8b25332b73c 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -1,8 +1,6 @@
module Ci
class BuildPolicy < CommitStatusPolicy
def rules
- can! :read_build if @subject.project.public_builds?
-
super
# If we can't read build we should also not have that
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
index de9a181db90..311ee9c96be 100644
--- a/app/serializers/base_serializer.rb
+++ b/app/serializers/base_serializer.rb
@@ -6,6 +6,7 @@ class BaseSerializer
def represent(resource, opts = {})
self.class.entity_class
.represent(resource, opts.merge(request: @request))
+ .as_json
end
def self.entity(entity_class)
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
index cfa86cc2553..b2de6c5832e 100644
--- a/app/serializers/pipeline_serializer.rb
+++ b/app/serializers/pipeline_serializer.rb
@@ -1,9 +1,10 @@
class PipelineSerializer < BaseSerializer
- entity PipelineEntity
class InvalidResourceError < StandardError; end
include API::Helpers::Pagination
Struct.new('Pagination', :request, :response)
+ entity PipelineEntity
+
def represent(resource, opts = {})
if paginated?
raise InvalidResourceError unless resource.respond_to?(:page)
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index c00c5aebf57..5cb7a86a5ee 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -61,7 +61,7 @@ module Auth
end
def process_repository_access(type, name, actions)
- requested_project = Project.find_with_namespace(name)
+ requested_project = Project.find_by_full_path(name)
return unless requested_project
actions = actions.select do |action|
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index 4d410f66c55..25e22f14e60 100644
--- a/app/services/commits/change_service.rb
+++ b/app/services/commits/change_service.rb
@@ -4,7 +4,8 @@ module Commits
class ChangeError < StandardError; end
def execute
- @source_project = params[:source_project] || @project
+ @start_project = params[:start_project] || @project
+ @start_branch = params[:start_branch]
@target_branch = params[:target_branch]
@commit = params[:commit]
@create_merge_request = params[:create_merge_request].present?
@@ -25,13 +26,28 @@ module Commits
def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action)
- into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
- tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
+ if @create_merge_request
+ into = @commit.public_send("#{action}_branch_name")
+ tree_branch = @start_branch
+ else
+ into = tree_branch = @target_branch
+ end
+
+ tree_id = repository.public_send(
+ "check_#{action}_content", @commit, tree_branch)
if tree_id
- create_target_branch(into) if @create_merge_request
+ validate_target_branch(into) if @create_merge_request
+
+ repository.public_send(
+ action,
+ current_user,
+ @commit,
+ into,
+ tree_id,
+ start_project: @start_project,
+ start_branch_name: @start_branch)
- repository.public_send(action, current_user, @commit, into, tree_id)
success
else
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically.
@@ -50,12 +66,12 @@ module Commits
true
end
- def create_target_branch(new_branch)
+ def validate_target_branch(new_branch)
# Temporary branch exists and contains the change commit
- return success if repository.find_branch(new_branch)
+ return if repository.find_branch(new_branch)
- result = CreateBranchService.new(@project, current_user)
- .execute(new_branch, @target_branch, source_project: @source_project)
+ result = ValidateNewBranchService.new(@project, current_user)
+ .execute(new_branch)
if result[:status] == :error
raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 5e8fafca98c..ab4c02a97a0 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,23 +3,27 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
- def execute(source_project, source_branch, target_project, target_branch, straight: false)
- source_commit = source_project.commit(source_branch)
- return unless source_commit
+ attr_reader :start_project, :start_branch_name
- source_sha = source_commit.sha
+ def initialize(new_start_project, new_start_branch_name)
+ @start_project = new_start_project
+ @start_branch_name = new_start_branch_name
+ end
+ def execute(target_project, target_branch, straight: false)
# If compare with other project we need to fetch ref first
- unless target_project == source_project
- random_string = SecureRandom.hex
+ target_project.repository.with_repo_branch_commit(
+ start_project.repository,
+ start_branch_name) do |commit|
+ break unless commit
- target_project.repository.fetch_ref(
- source_project.repository.path_to_repo,
- "refs/heads/#{source_branch}",
- "refs/tmp/#{random_string}/head"
- )
+ compare(commit.sha, target_project, target_branch, straight)
end
+ end
+
+ private
+ def compare(source_sha, target_project, target_branch, straight)
raw_compare = Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index e004a303496..77459d8779d 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,31 +1,11 @@
class CreateBranchService < BaseService
- def execute(branch_name, ref, source_project: @project)
- valid_branch = Gitlab::GitRefValidator.validate(branch_name)
+ def execute(branch_name, ref)
+ result = ValidateNewBranchService.new(project, current_user)
+ .execute(branch_name)
- unless valid_branch
- return error('Branch name is invalid')
- end
-
- repository = project.repository
- existing_branch = repository.find_branch(branch_name)
-
- if existing_branch
- return error('Branch already exists')
- end
-
- new_branch = if source_project != @project
- repository.fetch_ref(
- source_project.repository.path_to_repo,
- "refs/heads/#{ref}",
- "refs/heads/#{branch_name}"
- )
-
- repository.after_create_branch
+ return result if result[:status] == :error
- repository.find_branch(branch_name)
- else
- repository.add_branch(current_user, branch_name, ref)
- end
+ new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
success(new_branch)
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 95cc9baf406..14f5ba064ff 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -1,5 +1,8 @@
class CreateSnippetService < BaseService
def execute
+ request = params.delete(:request)
+ api = params.delete(:api)
+
snippet = if project
project.snippets.build(params)
else
@@ -12,8 +15,12 @@ class CreateSnippetService < BaseService
end
snippet.author = current_user
+ snippet.spam = SpamService.new(snippet, request).check(api)
+
+ if snippet.save
+ UserAgentDetailService.new(snippet, request).create
+ end
- snippet.save
snippet
end
end
diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb
index a44dee14a0f..9d4bffb93e9 100644
--- a/app/services/delete_tag_service.rb
+++ b/app/services/delete_tag_service.rb
@@ -7,7 +7,7 @@ class DeleteTagService < BaseService
return error('No such tag', 404)
end
- if repository.rm_tag(tag_name)
+ if repository.rm_tag(current_user, tag_name)
release = project.releases.find_by(tag: tag_name)
release.destroy if release
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 9bd4bd464f7..0a25f56d24c 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -3,9 +3,9 @@ module Files
class ValidationError < StandardError; end
def execute
- @source_project = params[:source_project] || @project
- @source_branch = params[:source_branch]
- @target_branch = params[:target_branch]
+ @start_project = params[:start_project] || @project
+ @start_branch = params[:start_branch]
+ @target_branch = params[:target_branch]
@commit_message = params[:commit_message]
@file_path = params[:file_path]
@@ -22,10 +22,8 @@ module Files
# Validate parameters
validate
- # Create new branch if it different from source_branch
- if different_branch?
- create_target_branch
- end
+ # Create new branch if it different from start_branch
+ validate_target_branch if different_branch?
result = commit
if result
@@ -40,7 +38,7 @@ module Files
private
def different_branch?
- @source_branch != @target_branch || @source_project != @project
+ @start_branch != @target_branch || @start_project != @project
end
def file_has_changed?
@@ -61,22 +59,23 @@ module Files
end
unless project.empty_repo?
- unless @source_project.repository.branch_names.include?(@source_branch)
+ unless @start_project.repository.branch_exists?(@start_branch)
raise_error('You can only create or edit files when you are on a branch')
end
if different_branch?
- if repository.branch_names.include?(@target_branch)
+ if repository.branch_exists?(@target_branch)
raise_error('Branch with such name already exists. You need to switch to this branch in order to make changes')
end
end
end
end
- def create_target_branch
- result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
+ def validate_target_branch
+ result = ValidateNewBranchService.new(project, current_user).
+ execute(@target_branch)
- unless result[:status] == :success
+ if result[:status] == :error
raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end
end
diff --git a/app/services/files/create_dir_service.rb b/app/services/files/create_dir_service.rb
index e5b4d60e467..858de5f0538 100644
--- a/app/services/files/create_dir_service.rb
+++ b/app/services/files/create_dir_service.rb
@@ -1,7 +1,15 @@
module Files
class CreateDirService < Files::BaseService
def commit
- repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
+ repository.commit_dir(
+ current_user,
+ @file_path,
+ message: @commit_message,
+ branch_name: @target_branch,
+ author_email: @author_email,
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch)
end
def validate
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index b23576b9a28..88dd7bbaedb 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,7 +1,17 @@
module Files
class CreateService < Files::BaseService
def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name)
+ repository.commit_file(
+ current_user,
+ @file_path,
+ @file_content,
+ message: @commit_message,
+ branch_name: @target_branch,
+ update: false,
+ author_email: @author_email,
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch)
end
def validate
@@ -24,7 +34,7 @@ module Files
unless project.empty_repo?
@file_path.slice!(0) if @file_path.start_with?('/')
- blob = repository.blob_at_branch(@source_branch, @file_path)
+ blob = repository.blob_at_branch(@start_branch, @file_path)
if blob
raise_error('Your changes could not be committed because a file with the same name already exists')
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 4f7e7a5baaa..50f0ffcac9f 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,7 +1,15 @@
module Files
class DeleteService < Files::BaseService
def commit
- repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name)
+ repository.remove_file(
+ current_user,
+ @file_path,
+ message: @commit_message,
+ branch_name: @target_branch,
+ author_email: @author_email,
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch)
end
end
end
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index 54446e90007..6ba868df04d 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -5,11 +5,13 @@ module Files
def commit
repository.multi_action(
user: current_user,
- branch: @target_branch,
message: @commit_message,
+ branch_name: @target_branch,
actions: params[:actions],
author_email: @author_email,
- author_name: @author_name
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch
)
end
@@ -61,7 +63,7 @@ module Files
end
def last_commit
- Gitlab::Git::Commit.last_for_path(repository, @source_branch, @file_path)
+ Gitlab::Git::Commit.last_for_path(repository, @start_branch, @file_path)
end
def regex_check(file)
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 47a18e3e132..a71fe61a4b6 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -4,11 +4,13 @@ module Files
def commit
repository.update_file(current_user, @file_path, @file_content,
- branch: @target_branch,
- previous_path: @previous_path,
message: @commit_message,
+ branch_name: @target_branch,
+ previous_path: @previous_path,
author_email: @author_email,
- author_name: @author_name)
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch)
end
private
@@ -23,7 +25,7 @@ module Files
def last_commit
@last_commit ||= Gitlab::Git::Commit.
- last_for_path(@source_project.repository, @source_branch, @file_path)
+ last_for_path(@start_project.repository, @start_branch, @file_path)
end
end
end
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
index 6cd3908d43a..d222d1e63aa 100644
--- a/app/services/git_hooks_service.rb
+++ b/app/services/git_hooks_service.rb
@@ -18,9 +18,9 @@ class GitHooksService
end
end
- yield self
-
- run_hook('post-receive')
+ yield(self).tap do
+ run_hook('post-receive')
+ end
end
private
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
new file mode 100644
index 00000000000..27bcc047601
--- /dev/null
+++ b/app/services/git_operation_service.rb
@@ -0,0 +1,179 @@
+class GitOperationService
+ attr_reader :user, :repository
+
+ def initialize(new_user, new_repository)
+ @user = new_user
+ @repository = new_repository
+ end
+
+ def add_branch(branch_name, newrev)
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ oldrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev)
+ end
+
+ def rm_branch(branch)
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch.name
+ oldrev = branch.target
+ newrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev)
+ end
+
+ def add_tag(tag_name, newrev, options = {})
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag_name
+ oldrev = Gitlab::Git::BLANK_SHA
+
+ with_hooks(ref, newrev, oldrev) do |service|
+ # We want to pass the OID of the tag object to the hooks. For an
+ # annotated tag we don't know that OID until after the tag object
+ # (raw_tag) is created in the repository. That is why we have to
+ # update the value after creating the tag object. Only the
+ # "post-receive" hook will receive the correct value in this case.
+ raw_tag = repository.rugged.tags.create(tag_name, newrev, options)
+ service.newrev = raw_tag.target_id
+ end
+ end
+
+ def rm_tag(tag)
+ ref = Gitlab::Git::TAG_REF_PREFIX + tag.name
+ oldrev = tag.target
+ newrev = Gitlab::Git::BLANK_SHA
+
+ update_ref_in_hooks(ref, newrev, oldrev) do
+ repository.rugged.tags.delete(tag_name)
+ end
+ end
+
+ # Whenever `start_branch_name` is passed, if `branch_name` doesn't exist,
+ # it would be created from `start_branch_name`.
+ # If `start_project` is passed, and the branch doesn't exist,
+ # it would try to find the commits from it instead of current repository.
+ def with_branch(
+ branch_name,
+ start_branch_name: nil,
+ start_project: repository.project,
+ &block)
+
+ check_with_branch_arguments!(
+ branch_name, start_branch_name, start_project)
+
+ update_branch_with_hooks(branch_name) do
+ repository.with_repo_branch_commit(
+ start_project.repository,
+ start_branch_name || branch_name,
+ &block)
+ end
+ end
+
+ private
+
+ def update_branch_with_hooks(branch_name)
+ update_autocrlf_option
+
+ was_empty = repository.empty?
+
+ # Make commit
+ newrev = yield
+
+ unless newrev
+ raise Repository::CommitError.new('Failed to create commit')
+ end
+
+ branch = repository.find_branch(branch_name)
+ oldrev = find_oldrev_from_branch(newrev, branch)
+
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ update_ref_in_hooks(ref, newrev, oldrev)
+
+ # If repo was empty expire cache
+ repository.after_create if was_empty
+ repository.after_create_branch if
+ was_empty || Gitlab::Git.blank_ref?(oldrev)
+
+ newrev
+ end
+
+ def find_oldrev_from_branch(newrev, branch)
+ return Gitlab::Git::BLANK_SHA unless branch
+
+ oldrev = branch.target
+
+ if oldrev == repository.rugged.merge_base(newrev, branch.target)
+ oldrev
+ else
+ raise Repository::CommitError.new('Branch diverged')
+ end
+ end
+
+ def update_ref_in_hooks(ref, newrev, oldrev)
+ with_hooks(ref, newrev, oldrev) do
+ update_ref(ref, newrev, oldrev)
+ end
+ end
+
+ def with_hooks(ref, newrev, oldrev)
+ GitHooksService.new.execute(
+ user,
+ repository.path_to_repo,
+ oldrev,
+ newrev,
+ ref) do |service|
+
+ yield(service)
+ end
+ end
+
+ def update_ref(ref, newrev, oldrev)
+ # We use 'git update-ref' because libgit2/rugged currently does not
+ # offer 'compare and swap' ref updates. Without compare-and-swap we can
+ # (and have!) accidentally reset the ref to an earlier state, clobbering
+ # commits. See also https://github.com/libgit2/libgit2/issues/1534.
+ command = %W[#{Gitlab.config.git.bin_path} update-ref --stdin -z]
+ _, status = Gitlab::Popen.popen(
+ command,
+ repository.path_to_repo) do |stdin|
+ stdin.write("update #{ref}\x00#{newrev}\x00#{oldrev}\x00")
+ end
+
+ unless status.zero?
+ raise Repository::CommitError.new(
+ "Could not update branch #{Gitlab::Git.branch_name(ref)}." \
+ " Please refresh and try again.")
+ end
+ end
+
+ def update_autocrlf_option
+ if repository.raw_repository.autocrlf != :input
+ repository.raw_repository.autocrlf = :input
+ end
+ end
+
+ def check_with_branch_arguments!(
+ branch_name, start_branch_name, start_project)
+ return if repository.branch_exists?(branch_name)
+
+ if repository.project != start_project
+ unless start_branch_name
+ raise ArgumentError,
+ 'Should also pass :start_branch_name if' +
+ ' :start_project is different from current project'
+ end
+
+ unless start_project.repository.branch_exists?(start_branch_name)
+ raise ArgumentError,
+ "Cannot find branch #{branch_name} nor" \
+ " #{start_branch_name} from" \
+ " #{start_project.path_with_namespace}"
+ end
+ elsif start_branch_name
+ unless repository.branch_exists?(start_branch_name)
+ raise ArgumentError,
+ "Cannot find branch #{branch_name} nor" \
+ " #{start_branch_name} from" \
+ " #{repository.project.path_with_namespace}"
+ end
+ end
+ end
+end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
new file mode 100644
index 00000000000..76d0ba67b07
--- /dev/null
+++ b/app/services/labels/promote_service.rb
@@ -0,0 +1,71 @@
+module Labels
+ class PromoteService < BaseService
+ BATCH_SIZE = 1000
+
+ def execute(label)
+ return unless project.group &&
+ label.is_a?(ProjectLabel)
+
+ Label.transaction do
+ new_label = clone_label_to_group_label(label)
+
+ label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids|
+ update_issuables(new_label, batched_ids)
+ update_issue_board_lists(new_label, batched_ids)
+ update_priorities(new_label, batched_ids)
+ # Order is important, project labels need to be last
+ update_project_labels(batched_ids)
+ end
+
+ # We skipped validations during creation. Let's run them now, after deleting conflicting labels
+ raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid?
+ new_label
+ end
+ end
+
+ private
+
+ def label_ids_for_merge(new_label)
+ LabelsFinder.
+ new(current_user, title: new_label.title, group_id: project.group.id).
+ execute(skip_authorization: true).
+ where.not(id: new_label).
+ select(:id) # Can't use pluck() to avoid object-creation because of the batching
+ end
+
+ def update_issuables(new_label, label_ids)
+ LabelLink.
+ where(label: label_ids).
+ update_all(label_id: new_label)
+ end
+
+ def update_issue_board_lists(new_label, label_ids)
+ List.
+ where(label: label_ids).
+ update_all(label_id: new_label)
+ end
+
+ def update_priorities(new_label, label_ids)
+ LabelPriority.
+ where(label: label_ids).
+ update_all(label_id: new_label)
+ end
+
+ def update_project_labels(label_ids)
+ Label.where(id: label_ids).delete_all
+ end
+
+ def clone_label_to_group_label(label)
+ params = label.attributes.slice('title', 'description', 'color')
+ # Since the title of the new label has to be the same as the previous labels
+ # and we're merging old labels in batches we'll skip validation to omit 2-step
+ # merge process and do it in one batch
+ # We'll be forcing validation at the end of the transaction to ensure everything
+ # was merged correctly
+ new_label = GroupLabel.new(params.merge(group: project.group))
+ new_label.save(validate: false)
+
+ new_label
+ end
+ end
+end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 6a7393a9921..f4d52e3ebbd 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -1,62 +1,89 @@
module MergeRequests
class BuildService < MergeRequests::BaseService
def execute
- merge_request = MergeRequest.new(params)
-
- # Set MR attributes
- merge_request.can_be_created = true
+ self.merge_request = MergeRequest.new(params)
+ merge_request.can_be_created = true
merge_request.compare_commits = []
- merge_request.source_project = project unless merge_request.source_project
+ merge_request.source_project = find_source_project
+ merge_request.target_project = find_target_project
+ merge_request.target_branch = find_target_branch
- merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+ if branches_specified? && branches_valid?
+ compare_branches
+ assign_title_and_description
+ else
+ merge_request.can_be_created = false
+ end
- merge_request.target_project ||= (project.forked_from_project || project)
- merge_request.target_branch ||= merge_request.target_project.default_branch
+ merge_request
+ end
- messages = validate_branches(merge_request)
- return build_failed(merge_request, messages) unless messages.empty?
+ private
- compare = CompareService.new.execute(
- merge_request.source_project,
- merge_request.source_branch,
- merge_request.target_project,
- merge_request.target_branch,
- )
+ attr_accessor :merge_request
- merge_request.compare_commits = compare.commits
- merge_request.compare = compare
+ delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request
- set_title_and_description(merge_request)
+ def find_source_project
+ source_project || project
end
- private
+ def find_target_project
+ return target_project if target_project.present? && can?(current_user, :read_project, target_project)
+ project.forked_from_project || project
+ end
- def validate_branches(merge_request)
- messages = []
+ def find_target_branch
+ target_branch || target_project.default_branch
+ end
- if merge_request.target_branch.blank? || merge_request.source_branch.blank?
- messages <<
- if params[:source_branch] || params[:target_branch]
- "You must select source and target branch"
- end
- end
+ def branches_specified?
+ params[:source_branch] && params[:target_branch]
+ end
- if merge_request.source_project == merge_request.target_project &&
- merge_request.target_branch == merge_request.source_branch
+ def branches_valid?
+ validate_branches
+ errors.blank?
+ end
- messages << 'You must select different branches'
- end
+ def compare_branches
+ compare = CompareService.new(
+ source_project,
+ source_branch
+ ).execute(
+ target_project,
+ target_branch
+ )
- # See if source and target branches exist
- if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch)
- messages << "Source branch \"#{merge_request.source_branch}\" does not exist"
- end
+ merge_request.compare_commits = compare.commits
+ merge_request.compare = compare
+ end
- if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch)
- messages << "Target branch \"#{merge_request.target_branch}\" does not exist"
- end
+ def validate_branches
+ add_error('You must select source and target branch') unless branches_present?
+ add_error('You must select different branches') if same_source_and_target?
+ add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists?
+ add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists?
+ end
+
+ def add_error(message)
+ errors.add(:base, message)
+ end
+
+ def branches_present?
+ target_branch.present? && source_branch.present?
+ end
+
+ def same_source_and_target?
+ source_project == target_project && target_branch == source_branch
+ end
- messages
+ def source_branch_exists?
+ source_branch.blank? || source_project.commit(source_branch)
+ end
+
+ def target_branch_exists?
+ target_branch.blank? || target_project.commit(target_branch)
end
# When your branch name starts with an iid followed by a dash this pattern will be
@@ -71,17 +98,17 @@ module MergeRequests
# - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is
# more than one commit in the MR
#
- def set_title_and_description(merge_request)
- if match = merge_request.source_branch.match(/\A(\d+)-/)
+ def assign_title_and_description
+ if match = source_branch.match(/\A(\d+)-/)
iid = match[1]
end
- commits = merge_request.compare_commits
+ commits = compare_commits
if commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
merge_request.description ||= commit.description.try(:strip)
- elsif iid && issue = merge_request.target_project.get_issue(iid, current_user)
+ elsif iid && issue = target_project.get_issue(iid, current_user)
case issue
when Issue
merge_request.title = "Resolve \"#{issue.title}\""
@@ -89,31 +116,20 @@ module MergeRequests
merge_request.title = "Resolve #{issue.title}"
end
else
- merge_request.title = merge_request.source_branch.titleize.humanize
+ merge_request.title = source_branch.titleize.humanize
end
if iid
closes_issue = "Closes ##{iid}"
- if merge_request.description.present?
+ if description.present?
merge_request.description += closes_issue.prepend("\n\n")
else
merge_request.description = closes_issue
end
end
- merge_request.title = merge_request.wip_title if commits.empty?
-
- merge_request
- end
-
- def build_failed(merge_request, messages)
- messages.compact.each do |message|
- merge_request.errors.add(:base, message)
- end
- merge_request.compare_commits = []
- merge_request.can_be_created = false
- merge_request
+ merge_request.title = wip_title if commits.empty?
end
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index ab9056a3250..5ca6fec962d 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -6,13 +6,17 @@ module MergeRequests
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
- attr_reader :merge_request
+ attr_reader :merge_request, :source
def execute(merge_request)
@merge_request = merge_request
return log_merge_error('Merge request is not mergeable', true) unless @merge_request.mergeable?
+ @source = find_merge_source
+
+ return log_merge_error('No source for merge', true) unless @source
+
merge_request.in_locked_state do
if commit
after_merge
@@ -34,7 +38,7 @@ module MergeRequests
committer: committer
}
- commit_id = repository.merge(current_user, merge_request, options)
+ commit_id = repository.merge(current_user, source, merge_request, options)
if commit_id
merge_request.update(merge_commit_sha: commit_id)
@@ -73,9 +77,11 @@ module MergeRequests
end
def merge_request_info
- project = merge_request.project
+ merge_request.to_reference(full: true)
+ end
- "#{project.to_reference}#{merge_request.to_reference}"
+ def find_merge_source
+ merge_request.diff_head_sha
end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index f74e6cac174..b2cc39763f3 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -365,7 +365,7 @@ class NotificationService
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
- users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users)
+ users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users)
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end
@@ -415,8 +415,8 @@ class NotificationService
end
# Build a list of users based on group notification settings
- def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
- uids = notification_settings_for(project, :watch)
+ def select_group_member_setting(group, project_members, global_setting, users_global_level_watch)
+ uids = notification_settings_for(group, :watch)
# Group setting is watch, add to users list if user is not project member
users = []
@@ -473,7 +473,7 @@ class NotificationService
setting = user.notification_settings_for(project)
- if !setting && project.group
+ if project.group && (setting.nil? || setting.global?)
setting = user.notification_settings_for(project.group)
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index aa9837038a6..781cd13b44b 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -9,7 +9,10 @@ module Search
def execute
group = Group.find_by(id: params[:group_id]) if params[:group_id].present?
projects = ProjectsFinder.new.execute(current_user)
- projects = projects.in_namespace(group.id) if group
+
+ if group
+ projects = projects.inside_path(group.full_path)
+ end
Gitlab::SearchResults.new(current_user, projects, params[:search])
end
diff --git a/app/services/validate_new_branch_service.rb b/app/services/validate_new_branch_service.rb
new file mode 100644
index 00000000000..2f61be184ce
--- /dev/null
+++ b/app/services/validate_new_branch_service.rb
@@ -0,0 +1,22 @@
+require_relative 'base_service'
+
+class ValidateNewBranchService < BaseService
+ def execute(branch_name)
+ valid_branch = Gitlab::GitRefValidator.validate(branch_name)
+
+ unless valid_branch
+ return error('Branch name is invalid')
+ end
+
+ repository = project.repository
+ existing_branch = repository.find_branch(branch_name)
+
+ if existing_branch
+ return error('Branch already exists')
+ end
+
+ success
+ rescue GitHooksService::PreReceiveError => ex
+ error(ex.message)
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 558bbe07b16..e7701d75a6e 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -204,7 +204,7 @@
.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
+ Set the maximum file size each jobs's artifacts can have
= link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
- if Gitlab.config.registry.enabled
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 5e3f105d41f..66d633119c2 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -12,7 +12,7 @@
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.row-content-block.second-block
- #{(@scope || 'all').capitalize} builds
+ #{(@scope || 'all').capitalize} jobs
%ul.content-list.builds-content-list.admin-builds-table
= render "projects/builds/table", builds: @builds, admin: true
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
index b5f96363230..7893c1dee97 100644
--- a/app/views/admin/dashboard/_head.html.haml
+++ b/app/views/admin/dashboard/_head.html.haml
@@ -20,9 +20,9 @@
%span
Groups
= nav_link path: 'builds#index' do
- = link_to admin_builds_path, title: 'Builds' do
+ = link_to admin_builds_path, title: 'Jobs' do
%span
- Builds
+ Jobs
= nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path, title: 'Runners' do
%span
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 124f970524e..721bc77cc2f 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -26,7 +26,7 @@
.bs-callout
%p
- A 'Runner' is a process which runs a build.
+ A 'Runner' is a process which runs a job.
You can setup as many Runners as you need.
%br
Runners can be placed on separate users, servers, even on your local machine.
@@ -37,16 +37,16 @@
%ul
%li
%span.label.label-success shared
- \- Runner runs builds from all unassigned projects
+ \- Runner runs jobs from all unassigned projects
%li
%span.label.label-info specific
- \- Runner runs builds from assigned projects
+ \- Runner runs jobs from assigned projects
%li
%span.label.label-warning locked
\- Runner cannot be assigned to other projects
%li
%span.label.label-danger paused
- \- Runner will not receive any new builds
+ \- Runner will not receive any new jobs
.append-bottom-20.clearfix
.pull-left
@@ -68,7 +68,7 @@
%th Runner token
%th Description
%th Projects
- %th Builds
+ %th Jobs
%th Tags
%th Last contact
%th
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 39e103e3062..dc4116e1ce0 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -11,13 +11,13 @@
- if @runner.shared?
.bs-callout.bs-callout-success
- %h4 This Runner will process builds from ALL UNASSIGNED projects
+ %h4 This Runner will process jobs from ALL UNASSIGNED projects
%p
If you want Runners to build only specific projects, enable them in the table below.
Keep in mind that this is a one way transition.
- else
.bs-callout.bs-callout-info
- %h4 This Runner will process builds only from ASSIGNED projects
+ %h4 This Runner will process jobs only from ASSIGNED projects
%p You can't make this a shared Runner.
%hr
@@ -70,11 +70,11 @@
= paginate @projects, theme: "gitlab"
.col-md-6
- %h4 Recent builds served by this Runner
+ %h4 Recent jobs served by this Runner
%table.table.ci-table.runner-builds
%thead
%tr
- %th Build
+ %th Job
%th Status
%th Project
%th Commit
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index 8dea3479f82..8ed23ac4919 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -16,4 +16,4 @@
- if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
- = icon(status.action_icon, class: status.action_class)
+ = custom_icon(status.action_icon)
diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml
index dd2f649de9a..0530d21a7e2 100644
--- a/app/views/ci/status/_graph_badge.html.haml
+++ b/app/views/ci/status/_graph_badge.html.haml
@@ -2,7 +2,7 @@
- subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user)
-- klass = "ci-status-icon ci-status-icon-#{status.group}"
+- klass = "ci-status-icon ci-status-icon-#{status.group} js-ci-status-icon-#{status.group}"
- tooltip = "#{subject.name} - #{status.label}"
- if status.has_details?
@@ -16,5 +16,5 @@
- if status.has_action?
= link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
- %i.ci-action-icon-wrapper
- = icon(status.action_icon, class: status.action_class)
+ %i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" }
+ = custom_icon(status.action_icon)
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 3caaf827ff5..653052f7c54 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -15,6 +15,4 @@
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues
-
-.prepend-top-default
- = render 'shared/issues'
+= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index fb016599fef..e64c78c4cb8 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -7,6 +7,4 @@
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests
-
-.prepend-top-default
- = render 'shared/merge_requests'
+= render 'shared/merge_requests'
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 9d7bcdb9d16..605bfd0cf8d 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -11,8 +11,11 @@
= link_to_author(todo)
- else
(removed)
- %span.todo-label
+
+ %span.action-name
= todo_action_name(todo)
+
+ %span.todo-label
- if todo.target
= todo_target_link(todo)
- else
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f4efcfb27b2..c4bf2c90cc2 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -67,21 +67,17 @@
= sort_title_oldest_created
-.prepend-top-default
+.js-todos-all
- if @todos.any?
.js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} }
- - @todos.group_by(&:project).each do |group|
- .panel.panel-default.panel-small
- - project = group[0]
- .panel-heading
- = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
-
+ .panel.panel-default.panel-small.panel-without-border
%ul.content-list.todos-list
- = render group[1]
+ = render @todos
= paginate @todos, theme: "gitlab"
+
- elsif current_user.todos.any?
.todos-all-done
- = render "shared/empty_states/todos_all_done.svg"
+ = render "shared/empty_states/icons/todos_all_done.svg"
- if todos_filter_empty?
%h4.text-center
= Gitlab.config.gitlab.no_todos_messages.sample
@@ -98,7 +94,7 @@
- else
.todos-empty
.todos-empty-hero
- = render "shared/empty_states/todos_empty.svg"
+ = render "shared/empty_states/icons/todos_empty.svg"
.todos-empty-content
%h4
Todos let you see what you should do next.
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index b185b81db7f..5b1a4630c56 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -3,7 +3,7 @@
.col-md-4.col-lg-6
= users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true)
.help-block.append-bottom-10
- Search for users by name, username, or email, or invite new ones using their email address.
+ Search for members by name, username, or email, or invite new ones using their email address.
.col-md-3.col-lg-2
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select"
@@ -16,7 +16,7 @@
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
.help-block.append-bottom-10
- On this date, the user(s) will automatically lose access to this group and all of its projects.
+ On this date, the member(s) will automatically lose access to this group and all of its projects.
.col-md-2
= f.submit 'Add to group', class: "btn btn-create btn-block"
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index f4c432a095a..2e4e4511bb6 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -7,7 +7,7 @@
- if can?(current_user, :admin_group_member, @group)
.project-members-new.append-bottom-default
%p.clearfix
- Add new user to
+ Add new member to
%strong= @group.name
= render "new_group_member"
@@ -15,7 +15,7 @@
.append-bottom-default.clearfix
%h5.member.existing-title
- Existing users
+ Existing members
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
@@ -24,7 +24,7 @@
= render 'shared/members/sort_dropdown'
.panel.panel-default
.panel-heading
- Users with access to
+ Members with access to
%strong= @group.name
%span.badge= @members.total_count
%ul.content-list
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 6ad03a60b3a..83edb719692 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -23,7 +23,6 @@
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
- .prepend-top-default
- = render 'shared/issues'
+ = render 'shared/issues'
- else
= render 'shared/empty_states/issues', project_select_button: true
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index af73554086b..6ad76d23df5 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -15,5 +15,4 @@
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
-.prepend-top-default
- = render 'shared/merge_requests'
+= render 'shared/merge_requests'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index b74cc822295..da2df0d8080 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -143,7 +143,7 @@
.key g
.key b
%td
- Go to builds
+ Go to jobs
%tr
%td.shortcut
.key g
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ac04f57e217..19a947af4ca 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,5 +1,5 @@
+= render 'layouts/nav/admin_settings'
.scrolling-tabs-container{ class: nav_control_class }
- = render 'layouts/nav/admin_settings'
.fade-left
= icon('angle-left')
.fade-right
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index a8bbd67de80..7883823b21e 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -96,8 +96,8 @@
-# Shortcut to builds page
- if project_nav_tab? :builds
%li.hidden
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
- Builds
+ = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
+ Jobs
-# Shortcut to commits page
- if project_nav_tab? :commits
diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml
index a744c4be9d6..060b50ffc69 100644
--- a/app/views/notify/build_fail_email.html.haml
+++ b/app/views/notify/build_fail_email.html.haml
@@ -1,6 +1,6 @@
- content_for :header do
%h1{ style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
- GitLab (build failed)
+ GitLab (job failed)
%h3
Project:
@@ -21,4 +21,4 @@
Message: #{@build.pipeline.git_commit_message}
%p
- Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
+ Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb
index 9d497983498..2a94688a6b0 100644
--- a/app/views/notify/build_fail_email.text.erb
+++ b/app/views/notify/build_fail_email.text.erb
@@ -1,4 +1,4 @@
-Build failed for <%= @project.name %>
+Job failed for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.pipeline.short_sha %>
diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml
index 8c2e6db1426..ca0eaa96a9d 100644
--- a/app/views/notify/build_success_email.html.haml
+++ b/app/views/notify/build_success_email.html.haml
@@ -1,6 +1,6 @@
- content_for :header do
%h1{ style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" }
- GitLab (build successful)
+ GitLab (job successful)
%h3
Project:
@@ -21,4 +21,4 @@
Message: #{@build.pipeline.git_commit_message}
%p
- Build details: #{link_to "Build #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
+ Job details: #{link_to "Job #{@build.id}", namespace_project_build_url(@build.project.namespace, @build.project, @build)}
diff --git a/app/views/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb
index c5ed4f84861..445cd46e64f 100644
--- a/app/views/notify/build_success_email.text.erb
+++ b/app/views/notify/build_success_email.text.erb
@@ -1,4 +1,4 @@
-Build successful for <%= @project.name %>
+Job successful for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.pipeline.short_sha %>
diff --git a/app/views/notify/links/ci/builds/_build.text.erb b/app/views/notify/links/ci/builds/_build.text.erb
index f495a2e5486..741c7f344c8 100644
--- a/app/views/notify/links/ci/builds/_build.text.erb
+++ b/app/views/notify/links/ci/builds/_build.text.erb
@@ -1 +1 @@
-Build #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
+Job #<%= build.id %> ( <%= pipeline_build_url(pipeline, build) %> )
diff --git a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
index 8e89c52a1f3..af8924bad57 100644
--- a/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
+++ b/app/views/notify/links/generic_commit_statuses/_generic_commit_status.text.erb
@@ -1 +1 @@
-Build #<%= build.id %>
+Job #<%= build.id %>
diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml
index e2b73cee5a9..a41791f0eca 100644
--- a/app/views/projects/_customize_workflow.html.haml
+++ b/app/views/projects/_customize_workflow.html.haml
@@ -3,6 +3,6 @@
%h4
Customize your workflow!
%p
- Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production!
+ Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and pipelines, GitLab can help manage your workflow from idea to production!
- if can?(current_user, :admin_project, @project)
= link_to "Get started", edit_project_path(@project), class: "btn btn-success"
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index 1a1327fb53c..27d25a6b682 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -4,10 +4,10 @@
.checkbox.builds-feature
= form.label :only_allow_merge_if_build_succeeds do
= form.check_box :only_allow_merge_if_build_succeeds
- %strong Only allow merge requests to be merged if the build succeeds
+ %strong Only allow merge requests to be merged if the pipeline succeeds
%br
%span.descr
- Builds need to be configured to enable this feature.
+ Pipelines need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')
.checkbox
= form.label :only_allow_merge_if_all_discussions_are_resolved do
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index d0ff14e45e6..edf55d59f28 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,4 +1,4 @@
-- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
+- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
.top-block.row-content-block.clearfix
.pull-right
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index 736b485bf06..27e81c2bec3 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,7 +1,7 @@
.content-block.build-header
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
- Build
+ Job
%strong.js-build-id ##{@build.id}
in pipeline
= link_to pipeline_path(@build.pipeline) do
@@ -17,6 +17,6 @@
= render "user"
= time_ago_with_tooltip(@build.created_at)
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
+ = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 37bf085130a..56fc5f5e68b 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -2,7 +2,7 @@
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
- Build
+ Job
%strong ##{@build.id}
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
= icon('angle-double-right')
@@ -17,7 +17,7 @@
- if can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?)
.block{ class: ("block-first" if !@build.coverage) }
.title
- Build artifacts
+ Job artifacts
- if @build.artifacts_expired?
%p.build-detail-row
The artifacts were removed
@@ -42,9 +42,9 @@
.block{ class: ("block-first" if !@build.coverage && !(can?(current_user, :read_build, @project) && (@build.artifacts? || @build.artifacts_expired?))) }
.title
- Build details
+ Job details
- if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry build", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
+ = link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'pull-right retry-link', method: :post
- if @build.merge_request
%p.build-detail-row
%span.build-light-text Merge Request:
@@ -136,4 +136,4 @@
- else
= build.id
- if build.retried?
- %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Build was retried' }
+ %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml
index 028664f5bba..acfdb250aff 100644
--- a/app/views/projects/builds/_table.html.haml
+++ b/app/views/projects/builds/_table.html.haml
@@ -2,14 +2,14 @@
- if builds.blank?
%div
- .nothing-here-block No builds to show
+ .nothing-here-block No jobs to show
- else
.table-holder
%table.table.ci-table.builds-page
%thead
%tr
%th Status
- %th Build
+ %th Job
%th Pipeline
- if admin
%th Project
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index c623e39b21f..5ffc0e20d10 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title "Builds"
+- page_title "Jobs"
= render "projects/pipelines/head"
%div{ class: container_class }
@@ -14,7 +14,7 @@
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
- = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
+ = link_to 'Get started with CI/CD Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index c613e473e4c..228dad528ab 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title "#{@build.name} (##{@build.id})", "Builds"
+- page_title "#{@build.name} (##{@build.id})", "Jobs"
- trace_with_state = @build.trace_with_state
= render "projects/pipelines/head", build_subnav: true
@@ -12,14 +12,14 @@
.bs-callout.bs-callout-warning
%p
- if no_runners_for_project?(@build.project)
- This build is stuck, because the project doesn't have any runners online assigned to it.
+ This job is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any?
- This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
+ This job is stuck, because you don't have any active runners online with any of these tags assigned to them:
- @build.tags.each do |tag|
%span.label.label-primary
= tag
- else
- This build is stuck, because you don't have any active runners that can run this build.
+ This job is stuck, because you don't have any active runners that can run this job.
%br
Go to
@@ -37,14 +37,14 @@
- environment = environment_for_build(@build.project, @build)
- if @build.success? && @build.last_deployment.present?
- if @build.last_deployment.last?
- This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
+ This job is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
- else
- This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
+ This job is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
View the most recent deployment #{deployment_link(environment.last_deployment)}.
- elsif @build.complete? && !@build.success?
- The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed.
+ The deployment of this job to #{environment_link_for_build(@build.project, @build)} did not succeed.
- else
- This build is creating a deployment to #{environment_link_for_build(@build.project, @build)}
+ This job is creating a deployment to #{environment_link_for_build(@build.project, @build)}
- if environment.try(:last_deployment)
and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
@@ -52,9 +52,9 @@
- if @build.erased?
.erased.alert.alert-warning
- if @build.erased_by_user?
- Build has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
+ Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
- else
- Build has been erased #{time_ago_with_tooltip(@build.erased_at)}
+ Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- else
#js-build-scroll.scroll-controls
.scroll-step
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index c1e496455d1..5ea85f9fd4c 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -32,10 +32,10 @@
= 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.')
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
- if retried
- = icon('refresh', class: 'text-warning has-tooltip', title: 'Build was retried')
+ = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
.label-container
- if build.tags.any?
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index 818a70f38f1..cdab1e1b1a6 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -15,7 +15,7 @@
- else
%span.api.monospace API
- if pipeline.latest?
- %span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
+ %span.label.label-success.has-tooltip{ title: 'Latest job for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
- if pipeline.yaml_errors.present?
@@ -78,7 +78,7 @@
.btn-group.inline
- if actions.any?
.btn-group
- %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual build', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual build' }
+ %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual job', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual job' }
= custom_icon('icon_play')
= icon('caret-down', 'aria-hidden' => 'true')
%ul.dropdown-menu.dropdown-menu-align-right
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 6dba42d5226..4d0b7a5ca85 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -63,9 +63,10 @@
- if @commit.status
.well-segment.pipeline-info
%div{ class: "icon-container ci-status-icon-#{@commit.status}" }
- = ci_icon_for_status(@commit.status)
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
+ = ci_icon_for_status(@commit.status)
Pipeline
- = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace"
+ = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
for
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
%span.ci-status-label
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 08d3443b3d0..6abff6aaf95 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -13,7 +13,7 @@
Pipeline
= link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace"
with
- = pluralize pipeline.statuses.count(:id), "build"
+ = pluralize pipeline.statuses.count(:id), "job"
- if pipeline.ref
for
= link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace"
@@ -44,7 +44,7 @@
%thead
%tr
%th Status
- %th Build ID
+ %th Job ID
%th Name
%th
- if pipeline.project.build_coverage_enabled?
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index 00e7cdd1729..89968cf4e0d 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -1,6 +1,5 @@
-- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits"
+- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits'
-= render "commit_box"
-
-= render "ci_menu"
-= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
+= render 'commit_box'
+= render 'ci_menu'
+= render 'pipelines_list', pipelines: @pipelines
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c37a33bbcd5..fc478ccc995 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -5,7 +5,7 @@
- unless diff_file.submodule?
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
- if editable_diff?(diff_file)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 84787155168..7a2dacdb1e7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -63,7 +63,7 @@
.row
.col-md-9.project-feature.nested
- = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+ = feature_fields.label :builds_access_level, "Pipelines", class: 'label-light'
%span.help-block Submit, test and deploy your changes before merge
.col-md-3
= project_feature_access_select(:builds_access_level)
@@ -180,11 +180,13 @@
%p
The following items will NOT be exported:
%ul
- %li Build traces and artifacts
+ %li Job traces and artifacts
%li LFS objects
%li Container registry images
- %hr
+ %li CI variables
+ %li Any encrypted tokens
- if can? current_user, :archive_project, @project
+ %hr
.row.prepend-top-default
.col-lg-3
%h4.warning-title.prepend-top-0
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6e0d9456900..7800d6ac382 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -5,7 +5,7 @@
%div{ class: container_class }
.top-area.adjust
.col-md-9
- %h3.page-title= @environment.name.capitalize
+ %h3.page-title= @environment.name
.col-md-3
.nav-controls
= render 'projects/environments/terminal_button', environment: @environment
@@ -32,8 +32,8 @@
%tr
%th ID
%th Commit
- %th Build
- %th
+ %th Job
+ %th Created
%th.hidden-xs
= render @deployments
diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml
index 431657c4dcb..b6f453b9736 100644
--- a/app/views/projects/graphs/ci/_builds.haml
+++ b/app/views/projects/graphs/ci/_builds.haml
@@ -1,4 +1,4 @@
-%h4 Build charts
+%h4 Pipelines charts
%p
&nbsp;
%span.cgreen
@@ -11,19 +11,19 @@
.prepend-top-default
%p.light
- Builds for last week
+ Jobs for last week
(#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 }
.prepend-top-default
%p.light
- Builds for last month
+ Jobs for last month
(#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 }
.prepend-top-default
%p.light
- Builds for last year
+ Jobs for last year
%canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope|
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index bd46af339cf..f3be343daae 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -34,7 +34,7 @@
= note_count
.issue-info
- #{issue.to_reference} &middot;
+ #{issuable_reference(issue)} &middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
- if issue.milestone
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 9fa00811af0..11636d7ebc7 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -75,7 +75,7 @@
// This element is filled in using JavaScript.
.content-block.content-block-small
- = render 'new_branch'
+ = render 'new_branch' unless @issue.confidential?
= render 'award_emoji/awards_block', awardable: @issue, inline: true
%section.issuable-discussion
diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml
deleted file mode 100644
index 8d09e2bda11..00000000000
--- a/app/views/projects/labels/destroy.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- if @labels.empty?
- $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 05a8475dcd6..29f861c09c6 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,37 +3,35 @@
- hide_class = ''
= render "projects/issues/head"
-%div{ class: container_class }
- .top-area.adjust
- .nav-text
- Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
+- if @labels.exists? || @prioritized_labels.exists?
+ %div{ class: container_class }
+ .top-area.adjust
+ .nav-text
+ Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- .nav-controls
- - if can?(current_user, :admin_label, @project)
- = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
- New label
-
- .labels
- - if can?(current_user, :admin_label, @project)
- -# Only show it in the first page
- - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
- .prioritized-labels{ class: ('hide' if hide) }
- %h5 Prioritized Labels
- %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
- %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet
- - if @prioritized_labels.present?
- = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
+ .nav-controls
+ - if can?(current_user, :admin_label, @project)
+ = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
+ New label
- .other-labels
+ .labels
- if can?(current_user, :admin_label, @project)
- %h5{ class: ('hide' if hide) } Other Labels
- %ul.content-list.manage-labels-list.js-other-labels
- - if @labels.present?
- = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
- = paginate @labels, theme: 'gitlab'
- - if @labels.blank?
- .nothing-here-block
+ -# Only show it in the first page
+ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
+ .prioritized-labels{ class: ('hide' if hide) }
+ %h5 Prioritized Labels
+ %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) }
+ #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" }
+ = render 'shared/empty_states/priority_labels'
+ - if @prioritized_labels.present?
+ = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label
+
+ - if @labels.present?
+ .other-labels
- if can?(current_user, :admin_label, @project)
- Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}.
- - else
- No labels created
+ %h5{ class: ('hide' if hide) } Other Labels
+ %ul.content-list.manage-labels-list.js-other-labels
+ = render partial: 'shared/label', subject: @project, collection: @labels, as: :label
+ = paginate @labels, theme: 'gitlab'
+- else
+ = render 'shared/empty_states/labels'
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index 605c7f61dee..aac74a25b75 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -1,3 +1,7 @@
+- if @teams_error_message
+ = content_for :flash_message do
+ .alert.alert-danger= @teams_error_message
+
%p
You aren’t a member of any team on the Mattermost instance at
%strong= Gitlab.config.mattermost.host
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index e3b0aa7e644..513f0818169 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -46,7 +46,7 @@
= note_count
.merge-request-info
- #{merge_request.to_reference} &middot;
+ #{issuable_reference(merge_request)} &middot;
opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')}
by #{link_to_member(@project, merge_request.author, avatar: false)}
- if merge_request.target_project.default_branch != merge_request.target_branch
diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
index 2595ce74ac0..0839880713f 100644
--- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
@@ -8,5 +8,5 @@
'@click' => "onClickResolveModeButton(file, 'edit')",
type: 'button' }
Edit inline
- %a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" }
+ %a.btn.view-file{ ":href" => "file.blobPath" }
View file @{{conflictsData.shortCommitSha}}
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 5faa6c43f9f..ae134563ead 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -2,14 +2,15 @@
.mr-widget-heading
- %w[success success_with_warnings skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
- = ci_icon_for_status(status)
+ = link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
+ = ci_icon_for_status(status)
%span
Pipeline
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status)
for
= succeed "." do
- = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
+ = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace js-commit-link"
%span.ci-coverage
- elsif @merge_request.has_ci?
@@ -20,7 +21,7 @@
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" }
= ci_icon_for_status(status)
%span
- CI build
+ CI job
= ci_label_for_status(status)
for
- commit = @merge_request.diff_head_commit
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 38328501ffd..5de59473840 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -16,14 +16,18 @@
gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
ci_message: {
- normal: "Build {{status}} for \"{{title}}\"",
- preparing: "{{status}} build for \"{{title}}\""
+ normal: "Job {{status}} for \"{{title}}\"",
+ preparing: "{{status}} job for \"{{title}}\""
},
ci_enable: #{@project.ci_service ? "true" : "false"},
ci_title: {
- preparing: "{{status}} build",
- normal: "Build {{status}}"
+ preparing: "{{status}} job",
+ normal: "Job {{status}}"
},
+ ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}",
+ ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json},
+ commits_path: "#{project_commits_path(@project)}",
+ pipeline_path: "#{project_pipelines_path(@project)}",
pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
};
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 7809e9c8c72..39cb0ca9cdc 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -35,10 +35,10 @@
The source branch will be removed.
- elsif @merge_request.can_remove_source_branch?(current_user)
.accept-control.checkbox
- = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
+ = label_tag :should_remove_source_branch, class: "merge-param-checkbox" do
= check_box_tag :should_remove_source_branch
Remove source branch
- .accept-control.right
+ .accept-control
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit')
Modify commit message
diff --git a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
index 14f51af5360..a18c2ad768f 100644
--- a/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_build_failed.html.haml
@@ -1,6 +1,6 @@
%h4
= icon('exclamation-triangle')
- The build for this merge request failed
+ The job for this merge request failed
%p
- Please retry the build or push a new commit to fix the failure.
+ Please retry the job or push a new commit to fix the failure.
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index f70cd09c5f4..304b0afcf93 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -19,7 +19,7 @@
- if remove_source_branch_button || user_can_cancel_automatic_merge
.clearfix.prepend-top-10
- if remove_source_branch_button
- = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true, sha: @merge_request.diff_head_sha), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
+ = link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_params(@merge_request)), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times')
Remove Source Branch When Merged
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
index b10dd47709f..721a9b6beb5 100644
--- a/app/views/projects/pipelines/_head.html.haml
+++ b/app/views/projects/pipelines/_head.html.haml
@@ -11,9 +11,9 @@
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
- = link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds' do
+ = link_to project_builds_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
%span
- Builds
+ Jobs
- if project_nav_tab? :environments
= nav_link(controller: %w(environments)) do
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index ca76f13ef5e..a6cd2d83bd5 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -23,9 +23,9 @@
.info-well
- if @commit.status
.well-segment.pipeline-info
- %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
- = ci_icon_for_status(@commit.status)
- = pluralize @pipeline.statuses.count(:id), "build"
+ .icon-container
+ = icon('clock-o')
+ = pluralize @pipeline.statuses.count(:id), "job"
- if @pipeline.ref
from
= link_to @pipeline.ref, namespace_project_commits_path(@project.namespace, @project, @pipeline.ref), class: "monospace"
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 88af41aa835..53067cdcba4 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -5,7 +5,7 @@
Pipeline
%li.js-builds-tab-link
= link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
- Builds
+ Jobs
%span.badge.js-builds-counter= pipeline.statuses.count
@@ -33,7 +33,7 @@
%thead
%tr
%th Status
- %th Build ID
+ %th Job ID
%th Name
%th
- if pipeline.project.build_coverage_enabled?
diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml
index 1f698558bce..18328c67f02 100644
--- a/app/views/projects/pipelines_settings/show.html.haml
+++ b/app/views/projects/pipelines_settings/show.html.haml
@@ -66,7 +66,7 @@
%span.input-group-addon /
%p.help-block
A regular expression that will be used to find the test coverage
- output in the build trace. Leave blank to disable
+ output in the job trace. Leave blank to disable
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing')
.bs-callout.bs-callout-info
%p Below are examples of regex for existing tools:
diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index 33a9a96183c..98e72f6c547 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -5,7 +5,7 @@
.col-sm-10
.checkbox
= f.check_box :active
- %span.light Paused Runners don't accept new builds
+ %span.light Paused Runners don't accept new jobs
.form-group
= label :run_untagged, 'Run untagged jobs', class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/runners/index.html.haml b/app/views/projects/runners/index.html.haml
index 92957470070..d6f691d9c24 100644
--- a/app/views/projects/runners/index.html.haml
+++ b/app/views/projects/runners/index.html.haml
@@ -2,7 +2,7 @@
.light.prepend-top-default
%p
- A 'Runner' is a process which runs a build.
+ A 'Runner' is a process which runs a job.
You can setup as many Runners as you need.
%br
Runners can be placed on separate users, servers, and even on your local machine.
@@ -12,14 +12,14 @@
%ul
%li
%span.label.label-success active
- \- Runner is active and can process any new builds
+ \- Runner is active and can process any new jobs
%li
%span.label.label-danger paused
- \- Runner is paused and will not receive any new builds
+ \- Runner is paused and will not receive any new jobs
%hr
-%p.lead To start serving your builds you can either add specific Runners to your project or use shared Runners
+%p.lead To start serving your jobs you can either add specific Runners to your project or use shared Runners
.row
.col-sm-6
= render 'specific_runners'
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 068a6610350..e2a5107a883 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
- if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -27,3 +29,6 @@
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ %li
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 216f70f5605..fb39028529d 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
Edit Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
+= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 772a594269c..cfed3a79bc5 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/triggers/index.html.haml b/app/views/projects/triggers/index.html.haml
index 6e5dd1b196d..b9c4e323430 100644
--- a/app/views/projects/triggers/index.html.haml
+++ b/app/views/projects/triggers/index.html.haml
@@ -67,7 +67,7 @@
In the
%code .gitlab-ci.yml
of another project, include the following snippet.
- The project will be rebuilt at the end of the build.
+ The project will be rebuilt at the end of the job.
%pre
:plain
@@ -86,12 +86,12 @@
:plain
#{builds_trigger_url(@project.id, ref: 'REF_NAME')}?token=TOKEN
%h5.prepend-top-default
- Pass build variables
+ Pass job variables
%p.light
Add
%code variables[VARIABLE]=VALUE
- to an API request. Variable values can be used to distinguish between triggered builds and normal builds.
+ to an API request. Variable values can be used to distinguish between triggered jobs and normal jobs.
With cURL:
diff --git a/app/views/projects/variables/_content.html.haml b/app/views/projects/variables/_content.html.haml
index 0249e0c1bf1..06477aba103 100644
--- a/app/views/projects/variables/_content.html.haml
+++ b/app/views/projects/variables/_content.html.haml
@@ -5,4 +5,4 @@
%p
So you can use them for passwords, secret keys or whatever you want.
%p
- The value of the variable can be visible in build log if explicitly asked to do so.
+ The value of the variable can be visible in job log if explicitly asked to do so.
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 65a3a6bddab..54b5ae2402e 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
+ = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
.well.prepend-top-20
%ul
@@ -13,4 +13,4 @@
%li
The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination.
%li
- To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
+ To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}.
diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml
index 26b349e8a62..3a49227961f 100644
--- a/app/views/shared/_issues.html.haml
+++ b/app/views/shared/_issues.html.haml
@@ -1,16 +1,7 @@
- if @issues.to_a.any?
- - @issues.group_by(&:project).each do |group|
- .panel.panel-default.panel-small
- - project = group[0]
- .panel-heading
- = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project)
- - if can?(current_user, :create_issue, project)
- .pull-right
- = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project)
-
- %ul.content-list.issues-list
- - group[1].each do |issue|
- = render 'projects/issues/issue', issue: issue
+ .panel.panel-default.panel-small.panel-without-border
+ %ul.content-list.issues-list
+ = render partial: 'projects/issues/issue', collection: @issues
= paginate @issues, theme: "gitlab"
- else
= render 'shared/empty_states/issues'
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index f11f4471a9d..ead9b84b991 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -36,7 +36,7 @@
%li
= link_to 'Edit', edit_label_path(label)
%li
- = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'}
+ = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'}
.pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do
@@ -66,11 +66,15 @@
%a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } }
Group level
+ - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group)
+ = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do
+ %span.sr-only Promote to Group
+ = icon('level-up')
- if can?(current_user, :admin_label, label)
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit
= icon('pencil-square-o')
- = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
+ = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do
%span.sr-only Delete
= icon('trash-o')
diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml
index 2f3605b4d27..b7982b7fe9b 100644
--- a/app/views/shared/_merge_requests.html.haml
+++ b/app/views/shared/_merge_requests.html.haml
@@ -1,16 +1,8 @@
- if @merge_requests.to_a.any?
- - @merge_requests.group_by(&:target_project).each do |group|
- .panel.panel-default.panel-small
- - project = group[0]
- .panel-heading
- = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project)
- - if can?(current_user, :create_merge_request, project)
- .pull-right
- = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project)
+ .panel.panel-default.panel-small.panel-without-border
+ %ul.content-list.mr-list
+ = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests
- %ul.content-list.mr-list
- - group[1].each do |merge_request|
- = render 'projects/merge_requests/merge_request', merge_request: merge_request
= paginate @merge_requests, theme: "gitlab"
- else
diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml
index 0eba1fe075f..c06d1ffa59b 100644
--- a/app/views/shared/_outdated_browser.html.haml
+++ b/app/views/shared/_outdated_browser.html.haml
@@ -1,8 +1,7 @@
- if outdated_browser?
- - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers"
.browser-alert
GitLab may not work properly because you are using an outdated web browser.
%br
Please install a
- = link_to 'supported web browser', link
+ = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers')
for a better experience.
diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml
new file mode 100644
index 00000000000..ba5c2dae09d
--- /dev/null
+++ b/app/views/shared/empty_states/_labels.html.haml
@@ -0,0 +1,11 @@
+.row.empty-state.labels
+ .pull-right.col-xs-12.col-sm-6
+ .svg-content
+ = render 'shared/empty_states/icons/labels.svg'
+ .col-xs-12.col-sm-6
+ .text-content
+ %h4 Labels can be applied to issues and merge requests to categorize them.
+ %p You can also star label to make it a priority label.
+ - if can?(current_user, :admin_label, @project)
+ = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link'
+ = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link'
diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml
new file mode 100644
index 00000000000..bc268301a97
--- /dev/null
+++ b/app/views/shared/empty_states/_priority_labels.html.haml
@@ -0,0 +1,3 @@
+.text-center
+ = render 'shared/empty_states/icons/priority_labels.svg'
+ %p Star labels to start sorting by priority
diff --git a/app/views/shared/empty_states/icons/_labels.svg b/app/views/shared/empty_states/icons/_labels.svg
new file mode 100644
index 00000000000..dc041a4a78b
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_labels.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="787 240 386 274" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="37" cy="107" r="8"/><mask id="e" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="37" cy="75" r="8"/><mask id="f" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="42" cy="93" r="8"/><mask id="g" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask><circle id="d" cx="43" cy="75" r="8"/><mask id="h" width="16" height="16" x="0" y="0" fill="#fff"><use xlink:href="#d"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(791 244)"><g transform="rotate(30 49.554 229.722)"><rect width="74" height="124" x="8.6" y="95.9" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="87" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><circle cx="26.5" cy="178.5" r="3.5" fill="#FC8A51"/><circle cx="47.5" cy="178.5" r="3.5" fill="#FC8A51"/><rect width="50" height="4" x="12" y="127" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="139" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#e)" stroke-linecap="round" xlink:href="#a"/><path stroke="#EEE" stroke-width="4" d="M37.3 107S10.5 18.3 81 .6" stroke-linecap="round"/><path fill="#FDE5D8" d="M31 189c0 3.3 2.7 6 6 6s6-2.7 6-6"/></g><g transform="translate(105 47)"><rect width="74" height="124" y="64" fill="#FAFAFA" rx="8"/><rect width="74" height="124" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><rect width="50" height="4" x="12" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="18" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#f)" stroke-linecap="round" xlink:href="#b"/><path fill="#B5A7DD" d="M56 149.7c-.6-1-.2-2 .7-2.7l1.8-1c1-.6 2-.2 2.7.7.5 1 .2 2.2-.7 2.8l-1.8 1c-1 .5-2 .2-2.7-.8zm-37.8 0c.5-1 .2-2-.7-2.7l-1.8-1c-1-.6-2-.2-2.7.7-.6 1-.2 2.2.7 2.8l1.8 1c1 .5 2 .2 2.7-.8zM33 151h9v4h-9v-4z"/><path fill="#6B4FBB" d="M59 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6zM35 153c0-5.5-4.6-10-10-10-5.7 0-10 4.5-10 10s4.3 10 10 10c5.4 0 10-4.5 10-10zm-16 0c0-3.3 2.6-6 6-6 3.2 0 6 2.7 6 6s-2.8 6-6 6c-3.4 0-6-2.7-6-6z"/><path stroke="#EEE" stroke-width="4" d="M37 75S30 0 80 0" stroke-linecap="round"/></g><g transform="rotate(15 -82.507 752.644)"><rect width="74" height="124" x="14.6" y="81.8" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="5" y="73" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><path fill="#FDE5D8" d="M41 147c0-1 1-2 2-2s2 1 2 2v3c0 1-1 2-2 2s-2-1-2-2v-3zm16.8 6.2c.8-.7 2-.6 2.8.3.7.8.5 2-.3 2.8L58 158c-1 .8-2.2.7-3 0-.6-1-.4-2.3.4-3l2.4-1.8zm-32 3c-1-.6-1-2-.4-2.7.7-1 2-1 2.8-.3l2.4 1.8c.8.7 1 2 .3 3-.8.7-2 1-3 0l-2.3-1.7z"/><rect width="2" height="7" x="39" y="168" fill="#FC8A51" rx="1"/><rect width="2" height="7" x="45" y="168" fill="#FC8A51" rx="1"/><circle cx="40" cy="169" r="2" fill="#FC8A51"/><circle cx="46" cy="169" r="2" fill="#FC8A51"/><rect width="22" height="18" x="32" y="158" stroke="#FC8A51" stroke-width="4" rx="8"/><rect width="34" height="5" x="26" y="174" fill="#FC8A51" rx="2.5"/><rect width="50" height="4" x="17" y="113" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="23" y="125" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#g)" stroke-linecap="round" xlink:href="#c"/><path stroke="#EEE" stroke-width="4" d="M42 93S50 0 0 0" stroke-linecap="round"/></g><g transform="rotate(-15 276.18 -697.744)"><rect width="74" height="124" x="18.7" y="65.6" fill="#FAFAFA" rx="8"/><rect width="74" height="124" x="6" y="55" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="8"/><g transform="translate(25 129)"><path stroke="#B5A7DD" stroke-width="4" d="M32 14c0-7.7-6.3-14-14-14S4 6.3 4 14" stroke-linecap="round"/><path stroke="#B5A7DD" stroke-width="2" d="M33 15v13c0 4.4-3.6 8-8 8" stroke-linecap="round"/><rect width="7" height="4" x="20" y="34" fill="#6B4FBB" rx="2"/><rect width="7" height="13" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" rx="3.5"/><rect width="7" height="13" x="29" y="15" fill="#FFF" stroke="#6B4FBB" stroke-width="3" stroke-linejoin="round" transform="matrix(-1 0 0 1 65 0)" rx="3.5"/></g><rect width="50" height="4" x="18" y="95" fill="#E5E5E5" rx="2"/><rect width="38" height="4" x="24" y="107" fill="#E5E5E5" rx="2"/><use stroke="#E5E5E5" stroke-width="8" mask="url(#h)" stroke-linecap="round" xlink:href="#d"/><path stroke="#EEE" stroke-width="4" d="M43 75S50 0 0 0" stroke-linecap="round"/></g><circle cx="193" cy="47" r="12" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><circle cx="193" cy="47" r="5" fill="#FFF" stroke="#FDE5D8" stroke-width="4"/><g opacity=".2"><path fill="#FC8A51" d="M30.7 254.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM374.7 133.8l-2.6 1c-1 .5-1.7 0-1.7-1v-3l-1-2.7c-.4-1 .2-1.7 1.2-1.7h3l2.6-1c1.2-.4 2 .2 2 1.2l-.2 3 1 2.6c.5 1.2 0 2-1 2l-3-.2zM5.6 95H1.8c-1.3.2-2-.8-1.4-2l1.4-3.4-.2-3.8c0-1.3 1-2 2-1.4l3.6 1.4 3.7-.2c1.2 0 2 1 1.4 2L11 91.3V95c.2 1.2-.8 2-2 1.4L5.6 95z"/><path fill="#6B4FBB" d="M308.8 62l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8zM318 226.6h-3c-1-.2-1.4-1-1-2l1.4-2.5v-3c.2-1 1-1.4 2-1l2.6 1.4h3c1 .2 1.5 1 1 2l-1.4 2.6v3c-.2 1-1 1.5-2 1l-2.5-1.4zM121.8 8l-2-2.3c-.7-.8-.5-1.7.6-2l2.8-1 2-2c1-.6 1.8-.4 2.2.7l.8 2.8 2 2c.8 1 .5 1.8-.5 2.2l-2.8.8-2.3 2c-.8.8-1.7.5-2-.5l-1-2.8z"/></g></g></svg>
diff --git a/app/views/shared/empty_states/icons/_priority_labels.svg b/app/views/shared/empty_states/icons/_priority_labels.svg
new file mode 100644
index 00000000000..7282c2b215e
--- /dev/null
+++ b/app/views/shared/empty_states/icons/_priority_labels.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="116" height="68" viewBox="181 0 116 68"><g fill="none" fill-rule="evenodd" transform="translate(182)"><rect width="78" height="34" x="37" y="34" fill="#FAFAFA" rx="3"/><rect width="78" height="34" x="31" y="28" fill="#FFF" stroke="#EEE" stroke-width="4" stroke-linecap="round" rx="3"/><path fill="#FFF" stroke="#FC6D26" stroke-width="3" d="M34 35.8c-.6 0-1.4 0-1.8.4L29 38.8c-1 .7-1.7.4-2-.7l-.6-4c0-.5-.5-1.2-1-1.5L22 30.2c-1-.6-1-1.5 0-2l3.7-2c.5-.2 1-.8 1.2-1.3l1-4.2c.3-1 1-1.3 2-.5l3 3c.3.3 1 .6 1.6.6l4.2-.3c1 0 1.5.7 1 1.7L38 29c-.3.6-.3 1.4 0 2l1.3 3.8c.4 1 0 1.8-1.2 1.6l-4-.6z" stroke-linecap="round"/><path fill="#FDE5D8" d="M51.6 14.3c-.2-.2-.8-.4-1-.3l-2.8.5c-.7 0-1-.4-.8-1l1-2.8V9.5L46.6 7c-.3-.7 0-1.2.8-1h2.7c.3 0 .8-.2 1-.5l2-2c.6-.5 1-.4 1.3.3l.7 2.8c0 .3.4.8.7 1l2.3 1.2c.7.3.7 1 0 1.3l-2.2 1.7-.6 1-.4 3c-.2.6-.7.8-1.3.4l-2-1.7zM5.4 43.2c-.2-.2-.5-.2-.7-.2l-1.8.3c-.6 0-1-.2-.7-.7l.7-1.8V40l-1-1.7c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L6.5 36c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2zM10.4 9.2C10.2 9 10 9 9.7 9L8 9.3c-.6 0-1-.2-.7-.7L8 6.8V6L7 4.3c0-.4 0-.7.6-.7h1.8c.3 0 .6 0 .8-.2L11.5 2c.3-.3.7-.2.8.2l.5 2 .5.5 1.6.8c.3.2.3.7 0 1l-1.6 1c-.2 0-.4.4-.4.7l-.4 2c0 .3-.4.5-.8.2l-1.4-1.2z"/><rect width="52" height="4" x="43" y="38" fill="#EEE" rx="2"/><rect width="36" height="4" x="43" y="48" fill="#EEE" rx="2"/></g></svg>
diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/icons/_todos_all_done.svg
index 94b5c2e0ea0..94b5c2e0ea0 100644
--- a/app/views/shared/empty_states/_todos_all_done.svg
+++ b/app/views/shared/empty_states/icons/_todos_all_done.svg
diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/icons/_todos_empty.svg
index b1e661268fb..b1e661268fb 100644
--- a/app/views/shared/empty_states/_todos_empty.svg
+++ b/app/views/shared/empty_states/icons/_todos_empty.svg
diff --git a/app/views/shared/icons/_icon_action_cancel.svg b/app/views/shared/icons/_icon_action_cancel.svg
new file mode 100644
index 00000000000..a1f700eb0ff
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_cancel.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M19.25,14.9765625 C19.25,14.1380166 19.0234398,13.3697952 18.5703125,12.671875 L12.6796875,18.5546875 C13.3932327,19.0182315 14.1666625,19.25 15,19.25 C15.5781279,19.25 16.1289036,19.1367199 16.6523438,18.9101562 C17.1757839,18.6835926 17.6276023,18.3802102 18.0078125,18 C18.3880227,17.6197898 18.690103,17.1653672 18.9140625,16.6367188 C19.138022,16.1080703 19.25,15.5546904 19.25,14.9765625 Z M11.4453125,17.3125 L17.34375,11.421875 C16.6406215,10.9479143 15.8593793,10.7109375 15,10.7109375 C14.2291628,10.7109375 13.5182324,10.9010398 12.8671875,11.28125 C12.2161426,11.6614602 11.7005227,12.1796842 11.3203125,12.8359375 C10.9401023,13.4921908 10.75,14.2057253 10.75,14.9765625 C10.75,15.8203167 10.9817685,16.5989548 11.4453125,17.3125 Z M21,14.9765625 C21,15.7942749 20.8411474,16.5755171 20.5234375,17.3203125 C20.2057276,18.0651079 19.7799506,18.7057265 19.2460938,19.2421875 C18.7122369,19.7786485 18.0742225,20.2057276 17.3320312,20.5234375 C16.58984,20.8411474 15.8125041,21 15,21 C14.1874959,21 13.41016,20.8411474 12.6679688,20.5234375 C11.9257775,20.2057276 11.2877631,19.7786485 10.7539062,19.2421875 C10.2200494,18.7057265 9.79427242,18.0651079 9.4765625,17.3203125 C9.15885258,16.5755171 9,15.7942749 9,14.9765625 C9,14.1588501 9.15885258,13.37891 9.4765625,12.6367188 C9.79427242,11.8945275 10.2200494,11.255211 10.7539062,10.71875 C11.2877631,10.182289 11.9257775,9.75520992 12.6679688,9.4375 C13.41016,9.11979008 14.1874959,8.9609375 15,8.9609375 C15.8125041,8.9609375 16.58984,9.11979008 17.3320312,9.4375 C18.0742225,9.75520992 18.7122369,10.182289 19.2460938,10.71875 C19.7799506,11.255211 20.2057276,11.8945275 20.5234375,12.6367188 C20.8411474,13.37891 21,14.1588501 21,14.9765625 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_play.svg b/app/views/shared/icons/_icon_action_play.svg
new file mode 100644
index 00000000000..6ac192cd7e9
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_play.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M21.5401786,15.2320328 L11.90625,20.5858274 C11.7950143,20.6486998 11.6994982,20.6559541 11.6196987,20.6075908 C11.5398992,20.5592275 11.5,20.4721748 11.5,20.3464301 L11.5,9.66785867 C11.5,9.54211399 11.5398992,9.45506129 11.6196987,9.40669795 C11.6994982,9.35833462 11.7950143,9.36558901 11.90625,9.42846135 L21.5401786,14.782256 C21.6514142,14.8451283 21.7070312,14.9200904 21.7070312,15.0071444 C21.7070312,15.0941984 21.6514142,15.1691604 21.5401786,15.2320328 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_retry.svg b/app/views/shared/icons/_icon_action_retry.svg
new file mode 100644
index 00000000000..0fa0243f3c0
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_retry.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg"><path d="M20.6114971,16.0821413 C20.6114971,16.106323 20.6090789,16.1232499 20.6042426,16.1329226 C20.2947172,17.42906 19.6466582,18.4797378 18.6600462,19.2849873 C17.6734341,20.0902369 16.5175677,20.4928556 15.1924122,20.4928556 C14.4863075,20.4928556 13.8031856,20.3598584 13.1430261,20.0938601 C12.4828665,19.8278617 11.8940517,19.4482152 11.376564,18.9549092 L10.4407381,19.8907351 C10.3488478,19.9826254 10.2400319,20.0285699 10.1142872,20.0285699 C9.98854256,20.0285699 9.87972669,19.9826254 9.78783635,19.8907351 C9.69594601,19.7988447 9.65000153,19.6900289 9.65000153,19.5642842 L9.65000153,16.3142842 C9.65000153,16.1885395 9.69594601,16.0797236 9.78783635,15.9878333 C9.87972669,15.895943 9.98854256,15.8499985 10.1142872,15.8499985 L13.3642872,15.8499985 C13.4900319,15.8499985 13.5988478,15.895943 13.6907381,15.9878333 C13.7826285,16.0797236 13.828573,16.1885395 13.828573,16.3142842 C13.828573,16.4400289 13.7826285,16.5488447 13.6907381,16.6407351 L12.6968765,17.6345967 C13.0402562,17.9537947 13.4295752,18.200444 13.8648453,18.374552 C14.3001153,18.5486601 14.7523057,18.6357128 15.2214301,18.6357128 C15.8694988,18.6357128 16.4740315,18.4785343 17.0350462,18.1641726 C17.5960609,17.8498109 18.0458332,17.4169655 18.3843765,16.8656235 C18.4375762,16.7834058 18.5657371,16.5004845 18.7688631,16.0168512 C18.8075538,15.9056155 18.8800977,15.8499985 18.9864971,15.8499985 L20.3793542,15.8499985 C20.4422265,15.8499985 20.4966345,15.8729707 20.5425797,15.9189159 C20.5885248,15.9648611 20.6114971,16.019269 20.6114971,16.0821413 Z M20.7928587,10.2785699 L20.7928587,13.5285699 C20.7928587,13.6543146 20.7469142,13.7631305 20.6550238,13.8550208 C20.5631335,13.9469111 20.4543176,13.9928556 20.328573,13.9928556 L17.078573,13.9928556 C16.9528283,13.9928556 16.8440124,13.9469111 16.7521221,13.8550208 C16.6602317,13.7631305 16.6142872,13.6543146 16.6142872,13.5285699 C16.6142872,13.4028252 16.6602317,13.2940094 16.7521221,13.202119 L17.7532381,12.2010029 C17.0374607,11.5384252 16.1935332,11.2071413 15.2214301,11.2071413 C14.5733614,11.2071413 13.9688287,11.3643198 13.407814,11.6786815 C12.8467993,11.9930432 12.397027,12.4258886 12.0584837,12.9772306 C12.005284,13.0594483 11.8771231,13.3423696 11.6739971,13.8260029 C11.6353064,13.9372386 11.5627625,13.9928556 11.4563631,13.9928556 L10.0127247,13.9928556 C9.9498524,13.9928556 9.89544446,13.9698834 9.84949929,13.9239382 C9.80355412,13.877993 9.78058188,13.8235851 9.78058188,13.7607128 L9.78058188,13.7099315 C10.0949436,12.4137941 10.7478388,11.3631163 11.7392872,10.5578668 C12.7307356,9.75261722 13.8914383,9.34999847 15.2214301,9.34999847 C15.9275348,9.34999847 16.6142839,9.48420472 17.281698,9.75262124 C17.949112,10.0210378 18.541554,10.3994752 19.0590417,10.8879449 L20.0021221,9.95211901 C20.0940124,9.86022867 20.2028283,9.81428419 20.328573,9.81428419 C20.4543176,9.81428419 20.5631335,9.86022867 20.6550238,9.95211901 C20.7469142,10.0440094 20.7928587,10.1528252 20.7928587,10.2785699 Z"></path></svg>
diff --git a/app/views/shared/icons/_icon_action_stop.svg b/app/views/shared/icons/_icon_action_stop.svg
new file mode 100644
index 00000000000..1c8e2fe4156
--- /dev/null
+++ b/app/views/shared/icons/_icon_action_stop.svg
@@ -0,0 +1 @@
+<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M20.1357204,10.2985704 L20.1357204,19.7271418 C20.1357204,19.8432138 20.0933101,19.9436592 20.0084882,20.0284811 C19.9236664,20.1133029 19.823221,20.1557132 19.707149,20.1557132 L10.2785775,20.1557132 C10.1625055,20.1557132 10.0620601,20.1133029 9.97723825,20.0284811 C9.89241639,19.9436592 9.8500061,19.8432138 9.8500061,19.7271418 L9.8500061,10.2985704 C9.8500061,10.1824984 9.89241639,10.0820529 9.97723825,9.99723107 C10.0620601,9.91240922 10.1625055,9.86999893 10.2785775,9.86999893 L19.707149,9.86999893 C19.823221,9.86999893 19.9236664,9.91240922 20.0084882,9.99723107 C20.0933101,10.0820529 20.1357204,10.1824984 20.1357204,10.2985704 Z"></path></svg>
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 0a4de709fcd..cb92b2e97a7 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -43,6 +43,8 @@
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
+= render 'shared/issuable/form/merge_params', issuable: issuable
+
- if @merge_request_for_resolving_discussions
.form-group
.col-sm-10.col-sm-offset-2
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index ec9bcaf63dd..10fa7901874 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -130,7 +130,7 @@
.value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- selected_labels.each do |label|
- = link_to_label(label, type: issuable.to_ability_name)
+ = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
- else
%span.no-value None
.selectbox.hide-collapsed
diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml
index b757893ea04..2793e7bcff4 100644
--- a/app/views/shared/issuable/form/_branch_chooser.html.haml
+++ b/app/views/shared/issuable/form/_branch_chooser.html.haml
@@ -19,12 +19,3 @@
- if issuable.new_record?
&nbsp;
= link_to 'Change branches', mr_change_branches_path(issuable)
-
-- if issuable.can_remove_source_branch?(current_user)
- .form-group
- .col-sm-10.col-sm-offset-2
- .checkbox
- = label_tag 'merge_request[force_remove_source_branch]' do
- = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
- = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
- Remove source branch when merge request is accepted.
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
new file mode 100644
index 00000000000..03309722326
--- /dev/null
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -0,0 +1,16 @@
+- issuable = local_assigns.fetch(:issuable)
+
+- return unless issuable.is_a?(MergeRequest)
+- return if issuable.closed_without_fork?
+
+-# This check is duplicated below, to avoid conflicts with EE.
+- return unless issuable.can_remove_source_branch?(current_user)
+
+.form-group
+ .col-sm-10.col-sm-offset-2
+ - if issuable.can_remove_source_branch?(current_user)
+ .checkbox
+ = label_tag 'merge_request[force_remove_source_branch]' do
+ = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
+ = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
+ Remove source branch when merge request is accepted.
diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml
index b5c0a7fd6d4..a736bfd91e2 100644
--- a/app/views/shared/notifications/_custom_notifications.html.haml
+++ b/app/views/shared/notifications/_custom_notifications.html.haml
@@ -18,7 +18,7 @@
%p
Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out
= succeed "." do
- %a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails
+ %a{ href: help_page_path('workflow/notifications'), target: "_blank" } notification emails
.col-lg-8
- NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 0c788032020..2d22782eb36 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -11,7 +11,7 @@
.col-sm-10
= f.text_field :title, class: 'form-control', required: true, autofocus: true
- = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
+ = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
.file-editor
.form-group
@@ -34,4 +34,3 @@
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
-
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 13586a5a12a..c212d1c86bf 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -66,9 +66,9 @@
= f.check_box :build_events, class: 'pull-left'
.prepend-left-20
= f.label :build_events, class: 'list-label' do
- %strong Build events
+ %strong Jobs events
%p.light
- This URL will be triggered when the build status changes
+ This URL will be triggered when the job status changes
%li
= f.check_box :pipeline_events, class: 'pull-left'
.prepend-left-20
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 95fc7198104..9a9a3ff9220 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -26,3 +28,6 @@
%li
= link_to edit_snippet_path(@snippet) do
Edit
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ %li
+ = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 82f44a9a5c3..915bf98eb3e 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -2,4 +2,4 @@
%h3.page-title
Edit Snippet
%hr
-= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
+= render 'shared/snippets/form', url: snippet_path(@snippet)
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 79e2392490d..ca8afb4bb6a 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -2,4 +2,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index b9cd49985dc..f5ccc84c160 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -33,13 +33,15 @@ class EmailsOnPushWorker
reverse_compare = false
if action == :push
- compare = CompareService.new.execute(project, after_sha, project, before_sha)
+ compare = CompareService.new(project, after_sha)
+ .execute(project, before_sha)
diff_refs = compare.diff_refs
return false if compare.same
if compare.commits.empty?
- compare = CompareService.new.execute(project, before_sha, project, after_sha)
+ compare = CompareService.new(project, before_sha)
+ .execute(project, after_sha)
diff_refs = compare.diff_refs
reverse_compare = true
diff --git a/changelogs/unreleased/17662-rename-builds.yml b/changelogs/unreleased/17662-rename-builds.yml
new file mode 100644
index 00000000000..12f2998d1c8
--- /dev/null
+++ b/changelogs/unreleased/17662-rename-builds.yml
@@ -0,0 +1,4 @@
+---
+title: Rename Builds to Pipelines, CI/CD Pipelines, or Jobs everywhere
+merge_request: 8787
+author:
diff --git a/changelogs/unreleased/19164-mobile-settings.yml b/changelogs/unreleased/19164-mobile-settings.yml
new file mode 100644
index 00000000000..c26a20f87e2
--- /dev/null
+++ b/changelogs/unreleased/19164-mobile-settings.yml
@@ -0,0 +1,4 @@
+---
+title: 19164 Add settings dropdown to mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
new file mode 100644
index 00000000000..965d0648adf
--- /dev/null
+++ b/changelogs/unreleased/20452-remove-require-from-request_profiler-initializer.yml
@@ -0,0 +1,4 @@
+---
+title: Don't require lib/gitlab/request_profiler/middleware.rb in config/initializers/request_profiler.rb
+merge_request:
+author:
diff --git a/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
new file mode 100644
index 00000000000..eda872049fd
--- /dev/null
+++ b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml
@@ -0,0 +1,4 @@
+---
+title: Added labels empty state
+merge_request: 7443
+author:
diff --git a/changelogs/unreleased/23634-remove-project-grouping.yml b/changelogs/unreleased/23634-remove-project-grouping.yml
new file mode 100644
index 00000000000..dde8b2d1815
--- /dev/null
+++ b/changelogs/unreleased/23634-remove-project-grouping.yml
@@ -0,0 +1,4 @@
+---
+title: Don't group issues by project on group-level and dashboard issue indexes.
+merge_request: 8111
+author: Bernardo Castro
diff --git a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
new file mode 100644
index 00000000000..587ef4f9a73
--- /dev/null
+++ b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml
@@ -0,0 +1,4 @@
+---
+title: Fix disable storing of sensitive information when importing a new repo
+merge_request: 8885
+author: Bernard Pietraga
diff --git a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
new file mode 100644
index 00000000000..05fbd8f0bf2
--- /dev/null
+++ b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
@@ -0,0 +1,4 @@
+---
+title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms
+merge_request: 8752
+author:
diff --git a/changelogs/unreleased/24606-force-password-reset-on-next-login.yml b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
new file mode 100644
index 00000000000..fd671d04a9f
--- /dev/null
+++ b/changelogs/unreleased/24606-force-password-reset-on-next-login.yml
@@ -0,0 +1,4 @@
+---
+title: Force new password after password reset via API
+merge_request:
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/24795_refactor_merge_request_build_service.yml b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
new file mode 100644
index 00000000000..b735fb57649
--- /dev/null
+++ b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml
@@ -0,0 +1,4 @@
+---
+title: Refactor MergeRequests::BuildService
+merge_request: 8462
+author: Rydkin Maxim
diff --git a/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
new file mode 100644
index 00000000000..50a5c879446
--- /dev/null
+++ b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml
@@ -0,0 +1,4 @@
+---
+title: Remove flash warning from login page
+merge_request: 8864
+author: Gerald J. Padilla
diff --git a/changelogs/unreleased/25460-replace-word-users-with-members.yml b/changelogs/unreleased/25460-replace-word-users-with-members.yml
new file mode 100644
index 00000000000..dac90eaa34d
--- /dev/null
+++ b/changelogs/unreleased/25460-replace-word-users-with-members.yml
@@ -0,0 +1,4 @@
+---
+title: Replace word user with member
+merge_request: 8872
+author:
diff --git a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
new file mode 100644
index 00000000000..f74e9fa8b6d
--- /dev/null
+++ b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
@@ -0,0 +1,4 @@
+---
+title: Update pipeline and commit links when CI status is updated
+merge_request: 8351
+author:
diff --git a/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
new file mode 100644
index 00000000000..9506692dd40
--- /dev/null
+++ b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml
@@ -0,0 +1,4 @@
+---
+title: Convert pipeline action icons to svg to have them propperly positioned
+merge_request:
+author:
diff --git a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml b/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml
deleted file mode 100644
index 2d4ec482ee0..00000000000
--- a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix autocomplete initial undefined state
-merge_request:
-author:
diff --git a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml
deleted file mode 100644
index 4678297cfd4..00000000000
--- a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add caching of droplab ajax requests
-merge_request: 8725
-author:
diff --git a/changelogs/unreleased/26852-fix-slug-for-openshift.yml b/changelogs/unreleased/26852-fix-slug-for-openshift.yml
new file mode 100644
index 00000000000..fb65b068b23
--- /dev/null
+++ b/changelogs/unreleased/26852-fix-slug-for-openshift.yml
@@ -0,0 +1,4 @@
+---
+title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG
+merge_request: 8638
+author:
diff --git a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
new file mode 100644
index 00000000000..c5c57af5aaf
--- /dev/null
+++ b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
@@ -0,0 +1,4 @@
+---
+title: Improve pipeline status icon linking in widgets
+merge_request:
+author:
diff --git a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml
deleted file mode 100644
index 0f0a8940f72..00000000000
--- a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix /explore sorting
-merge_request:
-author:
diff --git a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
new file mode 100644
index 00000000000..1758ed9e9ea
--- /dev/null
+++ b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
@@ -0,0 +1,4 @@
+---
+title: Support non-ASCII characters in GFM autocomplete
+merge_request: 8729
+author:
diff --git a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
new file mode 100644
index 00000000000..ddd454da376
--- /dev/null
+++ b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
@@ -0,0 +1,4 @@
+---
+title: Fix permalink discussion note being collapsed
+merge_request:
+author:
diff --git a/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml b/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml
new file mode 100644
index 00000000000..7b307b501f4
--- /dev/null
+++ b/changelogs/unreleased/27267-unnecessary-queries-from-projects-dashboard-atom-and-json.yml
@@ -0,0 +1,4 @@
+---
+title: Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index
+merge_request: 8956
+author:
diff --git a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
new file mode 100644
index 00000000000..293aab67d39
--- /dev/null
+++ b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Unify MR diff file button style
+merge_request: 8874
+author:
diff --git a/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
new file mode 100644
index 00000000000..502927cd160
--- /dev/null
+++ b/changelogs/unreleased/27321-double-separator-line-in-edit-projects-settings.yml
@@ -0,0 +1,4 @@
+---
+title: Only render hr when user can't archive project.
+merge_request: !8917
+author:
diff --git a/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
new file mode 100644
index 00000000000..79316abbaf7
--- /dev/null
+++ b/changelogs/unreleased/27332-mini-pipeline-graph-with-many-stages-has-no-line-spacing-in-firefox-and-safari.yml
@@ -0,0 +1,4 @@
+---
+title: Fix pipeline graph vertical spacing in Firefox and Safari
+merge_request: 8886
+author:
diff --git a/changelogs/unreleased/27484-environment-show-name.yml b/changelogs/unreleased/27484-environment-show-name.yml
new file mode 100644
index 00000000000..dc400d65006
--- /dev/null
+++ b/changelogs/unreleased/27484-environment-show-name.yml
@@ -0,0 +1,4 @@
+---
+title: Don't capitalize environment name in show page
+merge_request:
+author:
diff --git a/changelogs/unreleased/27488-fix-jwt-version.yml b/changelogs/unreleased/27488-fix-jwt-version.yml
new file mode 100644
index 00000000000..5135ff0fd60
--- /dev/null
+++ b/changelogs/unreleased/27488-fix-jwt-version.yml
@@ -0,0 +1,4 @@
+---
+title: Update and pin the `jwt` gem to ~> 1.5.6
+merge_request:
+author:
diff --git a/changelogs/unreleased/27494-environment-list-column-headers.yml b/changelogs/unreleased/27494-environment-list-column-headers.yml
new file mode 100644
index 00000000000..798c01f3238
--- /dev/null
+++ b/changelogs/unreleased/27494-environment-list-column-headers.yml
@@ -0,0 +1,4 @@
+---
+title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
+merge_request:
+author:
diff --git a/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
new file mode 100644
index 00000000000..bc990c66866
--- /dev/null
+++ b/changelogs/unreleased/27516-fix-wrong-call-to-project_cache_worker-method.yml
@@ -0,0 +1,4 @@
+---
+title: Fix wrong call to ProjectCacheWorker.perform
+merge_request: 8910
+author:
diff --git a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
new file mode 100644
index 00000000000..11d1f55172b
--- /dev/null
+++ b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml
@@ -0,0 +1,4 @@
+---
+title: Fix notifications when set at group level
+merge_request: 6813
+author: Alexandre Maia
diff --git a/changelogs/unreleased/cop-gem-fetcher.yml b/changelogs/unreleased/cop-gem-fetcher.yml
new file mode 100644
index 00000000000..506815a5b54
--- /dev/null
+++ b/changelogs/unreleased/cop-gem-fetcher.yml
@@ -0,0 +1,4 @@
+---
+title: Cop for gem fetched from a git source
+merge_request: 8856
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/document-how-to-vue.yml b/changelogs/unreleased/document-how-to-vue.yml
new file mode 100644
index 00000000000..863e41b6413
--- /dev/null
+++ b/changelogs/unreleased/document-how-to-vue.yml
@@ -0,0 +1,4 @@
+---
+title: Adds documentation for how to use Vue.js
+merge_request: 8866
+author:
diff --git a/changelogs/unreleased/dz-nested-groups-improvements-2.yml b/changelogs/unreleased/dz-nested-groups-improvements-2.yml
new file mode 100644
index 00000000000..8e4eb7f1fff
--- /dev/null
+++ b/changelogs/unreleased/dz-nested-groups-improvements-2.yml
@@ -0,0 +1,4 @@
+---
+title: Add read-only full_path and full_name attributes to Group API
+merge_request: 8827
+author:
diff --git a/changelogs/unreleased/empty-selection-reply-shortcut.yml b/changelogs/unreleased/empty-selection-reply-shortcut.yml
new file mode 100644
index 00000000000..5a42c98a800
--- /dev/null
+++ b/changelogs/unreleased/empty-selection-reply-shortcut.yml
@@ -0,0 +1,4 @@
+---
+title: Change the reply shortcut to focus the field even without a selection.
+merge_request: 8873
+author: Brian Hall
diff --git a/changelogs/unreleased/fix-26518.yml b/changelogs/unreleased/fix-26518.yml
deleted file mode 100644
index 961ac2642fb..00000000000
--- a/changelogs/unreleased/fix-26518.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix access to the wiki code via HTTP when repository feature disabled
-merge_request: 8758
-author:
diff --git a/changelogs/unreleased/fix-27479.yml b/changelogs/unreleased/fix-27479.yml
new file mode 100644
index 00000000000..cc72a830695
--- /dev/null
+++ b/changelogs/unreleased/fix-27479.yml
@@ -0,0 +1,4 @@
+---
+title: Remove new branch button for confidential issues
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-ci-build-policy.yml b/changelogs/unreleased/fix-ci-build-policy.yml
new file mode 100644
index 00000000000..26003713ed4
--- /dev/null
+++ b/changelogs/unreleased/fix-ci-build-policy.yml
@@ -0,0 +1,4 @@
+---
+title: Improve build policy and access abilities
+merge_request: 8711
+author:
diff --git a/changelogs/unreleased/fix-depr-warn.yml b/changelogs/unreleased/fix-depr-warn.yml
new file mode 100644
index 00000000000..61817027720
--- /dev/null
+++ b/changelogs/unreleased/fix-depr-warn.yml
@@ -0,0 +1,4 @@
+---
+title: resolve deprecation warnings
+merge_request: 8855
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
new file mode 100644
index 00000000000..3513f5afdfb
--- /dev/null
+++ b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
@@ -0,0 +1,4 @@
+---
+title: Fix filtering usernames with multiple words
+merge_request: 8851
+author:
diff --git a/changelogs/unreleased/fix-import-encrypt-atts.yml b/changelogs/unreleased/fix-import-encrypt-atts.yml
new file mode 100644
index 00000000000..e34d895570b
--- /dev/null
+++ b/changelogs/unreleased/fix-import-encrypt-atts.yml
@@ -0,0 +1,4 @@
+---
+title: Ignore encrypted attributes in Import/Export
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-import-user-validation-error.yml b/changelogs/unreleased/fix-import-user-validation-error.yml
new file mode 100644
index 00000000000..985a3b0b26f
--- /dev/null
+++ b/changelogs/unreleased/fix-import-user-validation-error.yml
@@ -0,0 +1,4 @@
+---
+title: Remove old project members when retrying an export
+merge_request:
+author:
diff --git a/changelogs/unreleased/fwn-to-find-by-full-path.yml b/changelogs/unreleased/fwn-to-find-by-full-path.yml
new file mode 100644
index 00000000000..1427e4e7624
--- /dev/null
+++ b/changelogs/unreleased/fwn-to-find-by-full-path.yml
@@ -0,0 +1,4 @@
+---
+title: replace `find_with_namespace` with `find_by_full_path`
+merge_request: 8949
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/group-label-sidebar-link.yml b/changelogs/unreleased/group-label-sidebar-link.yml
new file mode 100644
index 00000000000..c11c2d4ede1
--- /dev/null
+++ b/changelogs/unreleased/group-label-sidebar-link.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed group label links in issue/merge request sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/hardcode-title-system-note.yml b/changelogs/unreleased/hardcode-title-system-note.yml
new file mode 100644
index 00000000000..1b0a63efa51
--- /dev/null
+++ b/changelogs/unreleased/hardcode-title-system-note.yml
@@ -0,0 +1,4 @@
+---
+title: Ensure autogenerated title does not cause failing spec
+merge_request: 8963
+author: brian m. carlson
diff --git a/changelogs/unreleased/improve-ci-example-php-doc.yml b/changelogs/unreleased/improve-ci-example-php-doc.yml
new file mode 100644
index 00000000000..39a85e3d261
--- /dev/null
+++ b/changelogs/unreleased/improve-ci-example-php-doc.yml
@@ -0,0 +1,4 @@
+---
+title: Changed composer installer script in the CI PHP example doc
+merge_request: 4342
+author: Jeffrey Cafferata
diff --git a/changelogs/unreleased/issue-20428.yml b/changelogs/unreleased/issue-20428.yml
new file mode 100644
index 00000000000..60da1c14702
--- /dev/null
+++ b/changelogs/unreleased/issue-20428.yml
@@ -0,0 +1,4 @@
+---
+title: Add ability to define a coverage regex in the .gitlab-ci.yml
+merge_request: 7447
+author: Leandro Camargo
diff --git a/changelogs/unreleased/issue-sidebar-empty-assignee.yml b/changelogs/unreleased/issue-sidebar-empty-assignee.yml
new file mode 100644
index 00000000000..263af75b9e9
--- /dev/null
+++ b/changelogs/unreleased/issue-sidebar-empty-assignee.yml
@@ -0,0 +1,4 @@
+---
+title: Resets assignee dropdown when sidebar is open
+merge_request:
+author:
diff --git a/changelogs/unreleased/label-promotion.yml b/changelogs/unreleased/label-promotion.yml
new file mode 100644
index 00000000000..2ab997bf420
--- /dev/null
+++ b/changelogs/unreleased/label-promotion.yml
@@ -0,0 +1,4 @@
+---
+title: "Project labels can now be promoted to group labels"
+merge_request: 7242
+author: Olaf Tomalka
diff --git a/changelogs/unreleased/label-select-toggle.yml b/changelogs/unreleased/label-select-toggle.yml
deleted file mode 100644
index af5b4246521..00000000000
--- a/changelogs/unreleased/label-select-toggle.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed label dropdown toggle text not correctly updating
-merge_request:
-author:
diff --git a/changelogs/unreleased/markdown-plantuml.yml b/changelogs/unreleased/markdown-plantuml.yml
new file mode 100644
index 00000000000..c855f0cbcf7
--- /dev/null
+++ b/changelogs/unreleased/markdown-plantuml.yml
@@ -0,0 +1,4 @@
+---
+title: PlantUML support for Markdown
+merge_request: 8588
+author: Horacio Sanson
diff --git a/changelogs/unreleased/refresh-authorizations-fork-join.yml b/changelogs/unreleased/refresh-authorizations-fork-join.yml
deleted file mode 100644
index b1349b9447d..00000000000
--- a/changelogs/unreleased/refresh-authorizations-fork-join.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix race conditions for AuthorizedProjectsWorker
-merge_request:
-author:
diff --git a/changelogs/unreleased/revert-filter-assigned-to-me.yml b/changelogs/unreleased/revert-filter-assigned-to-me.yml
deleted file mode 100644
index 37f9d2f5fc4..00000000000
--- a/changelogs/unreleased/revert-filter-assigned-to-me.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Revert 3f17f29a
-merge_request: 8785
-author:
diff --git a/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml
new file mode 100644
index 00000000000..e69fcd2aa63
--- /dev/null
+++ b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml
@@ -0,0 +1,4 @@
+---
+title: Add project ID index to `project_authorizations` table to optimize queries
+merge_request:
+author:
diff --git a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml
deleted file mode 100644
index ff2b38f21f2..00000000000
--- a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Error 500 when repositories contain annotated tags pointing to blobs
-merge_request:
-author:
diff --git a/changelogs/unreleased/snippet-spam.yml b/changelogs/unreleased/snippet-spam.yml
new file mode 100644
index 00000000000..4867f088953
--- /dev/null
+++ b/changelogs/unreleased/snippet-spam.yml
@@ -0,0 +1,4 @@
+---
+title: Check public snippets for spam
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml
new file mode 100644
index 00000000000..2494884f5c9
--- /dev/null
+++ b/changelogs/unreleased/zj-format-chat-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Reformat messages ChatOps
+merge_request: 8528
+author:
diff --git a/changelogs/unreleased/zj-slow-service-fetch.yml b/changelogs/unreleased/zj-slow-service-fetch.yml
new file mode 100644
index 00000000000..8037361d2fc
--- /dev/null
+++ b/changelogs/unreleased/zj-slow-service-fetch.yml
@@ -0,0 +1,4 @@
+---
+title: Improve performance of slash commands
+merge_request: 8876
+author:
diff --git a/config/initializers/plantuml_lexer.rb b/config/initializers/plantuml_lexer.rb
new file mode 100644
index 00000000000..e8a77b146fa
--- /dev/null
+++ b/config/initializers/plantuml_lexer.rb
@@ -0,0 +1,2 @@
+# Touch the lexers so it is registered with Rouge
+Rouge::Lexers::Plantuml
diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb
index a9aa802681a..fb5a7b8372e 100644
--- a/config/initializers/request_profiler.rb
+++ b/config/initializers/request_profiler.rb
@@ -1,5 +1,3 @@
-require 'gitlab/request_profiler/middleware'
-
Rails.application.configure do |config|
config.middleware.use(Gitlab::RequestProfiler::Middleware)
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 6620b765e02..efe2fbc521d 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get 'raw'
+ post :mark_as_spam
end
end
@@ -220,6 +221,7 @@ constraints(ProjectUrlConstrainer.new) do
end
member do
+ post :promote
post :toggle_subscription
delete :remove_priority
end
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index 3ca096f31ba..ce0d1314292 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do
member do
get 'raw'
get 'download'
+ post :mark_as_spam
end
end
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index c04afe97277..c304e0706dc 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -26,7 +26,7 @@ Gitlab::Seeder.quiet do
end
end
- project = Project.find_with_namespace('gitlab-org/gitlab-test')
+ project = Project.find_by_full_path('gitlab-org/gitlab-test')
params = {
source_branch: 'feature',
diff --git a/db/migrate/20161114024742_add_coverage_regex_to_builds.rb b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
new file mode 100644
index 00000000000..88aa5d52b39
--- /dev/null
+++ b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
@@ -0,0 +1,13 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddCoverageRegexToBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :coverage_regex, :string
+ end
+end
diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb
index 7153e6a32b1..8e98ee5b9ba 100644
--- a/db/migrate/20161207231626_add_environment_slug.rb
+++ b/db/migrate/20161207231626_add_environment_slug.rb
@@ -8,8 +8,9 @@ class AddEnvironmentSlug < ActiveRecord::Migration
DOWNTIME_REASON = 'Adding NOT NULL column environments.slug with dependent data'
# Used to generate random suffixes for the slug
+ LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
- SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a
+ SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
def up
environments = Arel::Table.new(:environments)
@@ -39,17 +40,24 @@ class AddEnvironmentSlug < ActiveRecord::Migration
slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
# Must start with a letter
- slugified = "env-" + slugified if NUMBERS.cover?(slugified[0])
+ slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
+
+ # Repeated dashes are invalid (OpenShift limitation)
+ slugified.gsub!(/\-+/, '-')
# Maximum length: 24 characters (OpenShift limitation)
slugified = slugified[0..23]
- # Cannot end with a "-" character (Kubernetes label limitation)
- slugified = slugified[0..-2] if slugified[-1] == "-"
+ # Cannot end with a dash (Kubernetes label limitation)
+ slugified.chop! if slugified.end_with?('-')
# Add a random suffix, shortening the current string if necessary, if it
# has been slugified. This ensures uniqueness.
- slugified = slugified[0..16] + "-" + random_suffix if slugified != name
+ if slugified != name
+ slugified = slugified[0..16]
+ slugified << '-' unless slugified.end_with?('-')
+ slugified << random_suffix
+ end
slugified
end
diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb
new file mode 100644
index 00000000000..e9a0aee4d6a
--- /dev/null
+++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb
@@ -0,0 +1,11 @@
+class AddIndexToProjectAuthorizations < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(:project_authorizations, :project_id)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3c836db27fc..c73c311ccb2 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: 20170121130655) do
+ActiveRecord::Schema.define(version: 20170130204620) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
t.datetime "queued_at"
t.string "token"
t.integer "lock_version"
+ t.string "coverage_regex"
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
@@ -874,6 +875,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do
t.integer "access_level"
end
+ add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
create_table "project_features", force: :cascade do |t|
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index d7cfb464f74..a6300e18dc0 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -379,6 +379,10 @@ Read more about the individual driver's config options in the
filesystem. Remember to enable backups with your object storage provider if
desired.
+> **Important** Enabling storage driver other than `filesystem` would mean
+that your Docker client needs to be able to access the storage backend directly.
+So you must use an address that resolves and is accessible outside GitLab server.
+
---
**Omnibus GitLab installations**
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index e5cf592e0a6..6515b1a264a 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -3,8 +3,8 @@
> [Introduced][ce-7810] in GitLab 8.16.
When [PlantUML](http://plantuml.com) integration is enabled and configured in
-GitLab we are able to create simple diagrams in AsciiDoc documents created in
-snippets, wikis, and repos.
+GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents
+created in snippets, wikis, and repos.
## PlantUML Server
@@ -54,7 +54,7 @@ that, login with an Admin account and do following:
## Creating Diagrams
With PlantUML integration enabled and configured, we can start adding diagrams to
-our AsciiDoc snippets, wikis and repos using blocks:
+our AsciiDoc snippets, wikis and repos using delimited blocks:
```
[plantuml, format="png", id="myDiagram", width="200px"]
@@ -64,7 +64,14 @@ Alice -> Bob : Go Away
--
```
-The above block will be converted to an HTML img tag with source pointing to the
+And in Markdown using fenced code blocks:
+
+ ```plantuml
+ Bob -> Alice : hello
+ Alice -> Bob : Go Away
+ ```
+
+The above blocks will be converted to an HTML img tag with source pointing to the
PlantUML instance. If the PlantUML server is correctly configured, this should
render a nice diagram instead of the block:
@@ -77,7 +84,7 @@ Inside the block you can add any of the supported diagrams by PlantUML such as
and [Object](http://plantuml.com/object-diagram) diagrams. You do not need to use the PlantUML
diagram delimiters `@startuml`/`@enduml` as these are replaced by the AsciiDoc `plantuml` block.
-Some parameters can be added to the block definition:
+Some parameters can be added to the AsciiDoc block definition:
- *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
all browsers so use with care. The default is `png`.
@@ -85,3 +92,4 @@ Some parameters can be added to the block definition:
- *width*: Width attribute added to the img tag.
- *height*: Height attribute added to the img tag.
+Markdown does not support any parameters and will always use PNG format.
diff --git a/doc/api/groups.md b/doc/api/groups.md
index f7807390e68..3b38e3e1bee 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -25,7 +25,14 @@ GET /groups
"id": 1,
"name": "Foobar Group",
"path": "foo-bar",
- "description": "An interesting group"
+ "description": "An interesting group",
+ "visibility_level": 20,
+ "lfs_enabled": true,
+ "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg",
+ "web_url": "http://localhost:3000/groups/foo-bar",
+ "request_access_enabled": false,
+ "full_name": "Foobar Group",
+ "full_path": "foo-bar"
}
]
```
@@ -149,6 +156,8 @@ Example response:
"avatar_url": null,
"web_url": "https://gitlab.example.com/groups/twitter",
"request_access_enabled": false,
+ "full_name": "Foobar Group",
+ "full_path": "foo-bar",
"projects": [
{
"id": 7,
@@ -372,6 +381,8 @@ Example response:
"avatar_url": null,
"web_url": "http://gitlab.example.com/groups/h5bp",
"request_access_enabled": false,
+ "full_name": "Foobar Group",
+ "full_path": "foo-bar",
"projects": [
{
"id": 9,
diff --git a/doc/api/users.md b/doc/api/users.md
index 28b6c7bd491..fea9bdf9639 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -271,6 +271,7 @@ Parameters:
- `can_create_group` (optional) - User can create groups - true or false
- `external` (optional) - Flags the user as external - true or false(default)
+On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
even in cases where a `409` (Conflict) would be more appropriate,
e.g. when renaming the email address to some existing one.
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 630207ffa09..c4c4d95b68a 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -1,6 +1,6 @@
# Auto deploy
-> [Introduced][mr-8135] in GitLab 8.15.
+> [Introduced][mr-8135] in GitLab 8.15. Currently requires a [Public project][project-settings].
Auto deploy is an easy way to configure GitLab CI for the deployment of your
application. GitLab Community maintains a list of `.gitlab-ci.yml`
@@ -33,6 +33,7 @@ enable [Kubernetes service][kubernetes-service].
created automatically for you.
[mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135
+[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html
[project-services]: ../../project_services/project_services.md
[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
[kubernetes-service]: ../../project_services/kubernetes.md
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 98cd29c9567..ef04c537367 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -297,7 +297,7 @@ deploy_review:
- echo "Deploy a review app"
environment:
name: review/$CI_BUILD_REF_NAME
- url: https://$CI_BUILD_REF_SLUG.review.example.com
+ url: https://$CI_ENVIRONMENT_SLUG.example.com
only:
- branches
except:
@@ -318,15 +318,15 @@ also contain `/`, or other characters that would be invalid in a domain name or
URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the
environment can get a specific and distinct URL for each branch. In this case,
given a `$CI_BUILD_REF_NAME` of `100-Do-The-Thing`, the URL will be something
-like `https://review-100-do-the-4f99a2.example.com`. Again, the way you set up
+like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
the web server to serve these requests is based on your setup.
You could also use `$CI_BUILD_REF_SLUG` in `environment:url`, e.g.:
-`https://$CI_BUILD_REF_SLUG.review.example.com`. We use `$CI_ENVIRONMENT_SLUG`
+`https://$CI_BUILD_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
here because it is guaranteed to be unique, but if you're using a workflow like
[GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer
environment names to be more closely based on the branch name - the example
-above would give you an URL like `https://100-do-the-thing.review.example.com`
+above would give you an URL like `https://100-do-the-thing.example.com`
Last but not least, we tell the job to run [`only`][only] on branches
[`except`][only] master.
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 82ffb841729..5eeec92d976 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -235,7 +235,11 @@ cache:
before_script:
# Install composer dependencies
-- curl --silent --show-error https://getcomposer.org/installer | php
+- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig
+- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+- php composer-setup.php
+- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
- php composer.phar install
...
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index f11257be5c3..06810898cfe 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables |
| cache | no | Define list of files that should be cached between subsequent runs |
+| coverage | no | Define coverage settings for all jobs |
### image and services
@@ -278,6 +279,23 @@ cache:
untracked: true
```
+### coverage
+
+`coverage` allows you to configure how coverage will be filtered out from the
+build outputs. Setting this up globally will make all the jobs to use this
+setting for output filtering and extracting the coverage information from your
+builds.
+
+Regular expressions are the only valid kind of value expected here. So, using
+surrounding `/` is mandatory in order to consistently and explicitly represent
+a regular expression string. You must escape special characters if you want to
+match them literally.
+
+A simple example:
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+```
+
## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
@@ -319,6 +337,7 @@ job_name:
| before_script | no | Override a set of commands that are executed before build |
| after_script | no | Override a set of commands that are executed after build |
| environment | no | Defines a name of environment to which deployment is done by this build |
+| coverage | no | Define coverage settings for a given job |
### script
@@ -993,6 +1012,25 @@ job:
- execute this after my script
```
+### job coverage
+
+This entry is pretty much the same as described in the global context in
+[`coverage`](#coverage). The only difference is that, by setting it inside
+the job level, whatever is set in there will take precedence over what has
+been defined in the global level. A quick example of one overriding the
+other would be:
+
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+
+job1:
+ coverage: /Code coverage: \d+\.\d+/
+```
+
+In the example above, considering the context of the job `job1`, the coverage
+regex that would be used is `/Code coverage: \d+\.\d+/` instead of
+`/\(\d+\.\d+\) covered\./`.
+
## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
diff --git a/doc/development/frontend.md b/doc/development/frontend.md
index f79bd23dc90..75fdf3d8e63 100644
--- a/doc/development/frontend.md
+++ b/doc/development/frontend.md
@@ -23,6 +23,69 @@ some ideas with React.js as well as Angular.
To get started with Vue, read through [their documentation][vue-docs].
+#### How to build a new feature with Vue.js
+**Components, Stores and Services**
+
+In some features implemented with Vue.js, like the [issue board][issue-boards]
+or [environments table][environments-table]
+you can find a clear separation of concerns:
+
+```
+new_feature
+├── components
+│ └── component.js.es6
+│ └── ...
+├── store
+│ └── new_feature_store.js.es6
+├── service
+│ └── new_feature_service.js.es6
+├── new_feature_bundle.js.es6
+```
+_For consistency purposes, we recommend you to follow the same structure._
+
+Let's look into each of them:
+
+**A `*_bundle.js` file**
+
+This is the index file of your new feature. This is where the root Vue instance
+of the new feature should be.
+
+Don't forget to follow [these steps.][page-specific-javascript]
+
+**A folder for Components**
+
+This folder holds all components that are specific of this new feature.
+If you need to use or create a component that will probably be used somewhere
+else, please refer to `vue_shared/components`.
+
+A good thumb rule to know when you should create a component is to think if
+it will be reusable elsewhere.
+
+For example, tables are used in a quite amount of places across GitLab, a table
+would be a good fit for a component.
+On the other hand, a table cell used only in on table, would not be a good use
+of this pattern.
+
+You can read more about components in Vue.js site, [Component System][component-system]
+
+**A folder for the Store**
+
+The Store is a simple object that allows us to manage the state in a single
+source of truth.
+
+The concept we are trying to follow is better explained by Vue documentation
+itself, please read this guide: [State Management][state-management]
+
+**A folder for the Service**
+
+The Service is used only to communicate with the server.
+It does not store or manipulate any data.
+We use [vue-resource][vue-resource-repo] to
+communicate with the server.
+
+The [issue boards service][issue-boards-service]
+is a good example of this pattern.
+
## Performance
### Resources
@@ -198,8 +261,8 @@ As long as the fixtures don't change, `rake teaspoon:tests` is sufficient
If you need to debug your tests and/or application code while they're
running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon)
-in your browser, open DevTools, and run tests for individual files by clicking
-on them. This is also much faster than setting up and running tests from the
+in your browser, open DevTools, and run tests for individual files by clicking
+on them. This is also much faster than setting up and running tests from the
command line.
Please note: Not all of the frontend fixtures are generated. Some are still static
@@ -294,20 +357,27 @@ For our currently-supported browsers, see our [requirements][requirements].
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[scss-style-guide]: scss_styleguide.md
[requirements]: ../install/requirements.md#supported-web-browsers
+[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
+[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
+[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
+[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
+[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
+[vue-resource-repo]: https://github.com/pagekit/vue-resource
+[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
## Gotchas
### Spec errors due to use of ES6 features in `.js` files
-If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being
-thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually,
-you may have included `ES6`-style JavaScript in files that don't have the
-`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file
-you're working in (`git mv <file.js> <file.js.es6>`).
+If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being
+thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually,
+you may have included `ES6`-style JavaScript in files that don't have the
+`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file
+you're working in (`git mv <file.js> <file.js.es6>`).
### Spec errors due to use of unsupported JavaScript
-Similar errors will be thrown if you're using JavaScript features not yet
+Similar errors will be thrown if you're using JavaScript features not yet
supported by our test runner's version of webkit, whether or not you've updated
the file extension. Examples of unsupported JavaScript features are:
@@ -322,20 +392,20 @@ the file extension. Examples of unsupported JavaScript features are:
- Symbol/Symbol.iterator
- Spread
-Until these are polyfilled or transpiled appropriately, they should not be used.
-Please update this list with additional unsupported features or when any of
+Until these are polyfilled or transpiled appropriately, they should not be used.
+Please update this list with additional unsupported features or when any of
these are made usable.
### Spec errors due to JavaScript not enabled
-If, as a result of a change you've made, a feature now depends on JavaScript to
+If, as a result of a change you've made, a feature now depends on JavaScript to
run correctly, you need to make sure a JavaScript web driver is enabled when
-specs are run. If you don't you'll see vague error messages from the spec
-runner, and an explosion of vague console errors in the HTML snapshot.
+specs are run. If you don't you'll see vague error messages from the spec
+runner, and an explosion of vague console errors in the HTML snapshot.
-To enable a JavaScript driver in an `rspec` test, add `js: true` to the
-individual spec or the context block containing multiple specs that need
-JavaScript enabled:
+To enable a JavaScript driver in an `rspec` test, add `js: true` to the
+individual spec or the context block containing multiple specs that need
+JavaScript enabled:
```ruby
@@ -354,8 +424,8 @@ describe "Admin::AbuseReports", js: true do
end
```
-In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
-file for the failing spec, add the `@javascript` flag above the Scenario:
+In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
+file for the failing spec, add the `@javascript` flag above the Scenario:
```
@javascript
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index 903e54bf9dc..5dae4bcc905 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net](
### Hover
-Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `200ms linear` transition for a color hover effect.
+Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `100ms - 150ms linear` transition for a color hover effect.
View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here.
diff --git a/doc/install/README.md b/doc/install/README.md
index 239f5f301ec..2d2fd8cb380 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -4,3 +4,6 @@
- [Requirements](requirements.md)
- [Structure](structure.md)
- [Database MySQL](database_mysql.md)
+- [Digital Ocean and Docker](digitaloceandocker.md)
+- [Docker](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/docker)
+- [All installation methods](https://about.gitlab.com/installation/)
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
new file mode 100644
index 00000000000..820060a489b
--- /dev/null
+++ b/doc/install/digitaloceandocker.md
@@ -0,0 +1,136 @@
+# Digital Ocean and Docker
+
+## Initial setup
+
+In this guide you'll configure a Digital Ocean droplet and set up Docker
+locally on either macOS or Linux.
+
+### On macOS
+
+#### Install Docker Toolbox
+
+1. [https://www.docker.com/products/docker-toolbox](https://www.docker.com/products/docker-toolbox)
+
+### On Linux
+
+#### Install Docker Engine
+
+1. [https://docs.docker.com/engine/installation/linux](https://docs.docker.com/engine/installation/linux/)
+
+#### Install Docker Machine
+
+1. [https://docs.docker.com/machine/install-machine](https://docs.docker.com/machine/install-machine/)
+
+_The rest of the steps are identical for macOS and Linux_
+
+### Create new docker host
+
+1. Login to Digital Ocean
+1. Generate a new API token at https://cloud.digitalocean.com/settings/api/tokens
+
+
+This command will create a new DO droplet called `gitlab-test-env-do` that will act as a docker host.
+
+**Note: 4GB is the minimum requirement for a Docker host that will run more then one GitLab instance**
+
++ RAM: 4GB
++ Name: `gitlab-test-env-do`
++ Driver: `digitalocean`
+
+
+**Set the DO token** - Replace the string below with your generated token
+
+```
+export DOTOKEN=cf3dfd0662933203005c4a73396214b7879d70aabc6352573fe178d340a80248
+```
+
+**Create the machine**
+
+```
+docker-machine create \
+ --driver digitalocean \
+ --digitalocean-access-token=$DOTOKEN \
+ --digitalocean-size "4gb" \
+ gitlab-test-env-do
+```
+
++ Resource: https://docs.docker.com/machine/drivers/digital-ocean/
+
+
+### Creating GitLab test instance
+
+
+#### Connect your shell to the new machine
+
+
+In this example we'll create a GitLab EE 8.10.8 instance.
+
+
+First connect the docker client to the docker host you created previously.
+
+```
+eval "$(docker-machine env gitlab-test-env-do)"
+```
+
+You can add this to your `~/.bash_profile` file to ensure the `docker` client uses the `gitlab-test-env-do` docker host
+
+
+#### Create new GitLab container
+
++ HTTP port: `8888`
++ SSH port: `2222`
+ + Set `gitlab_shell_ssh_port` using `--env GITLAB_OMNIBUS_CONFIG `
++ Hostname: IP of docker host
++ Container name: `gitlab-test-8.10`
++ GitLab version: **EE** `8.10.8-ee.0`
+
+##### Setup container settings
+
+```
+export SSH_PORT=2222
+export HTTP_PORT=8888
+export VERSION=8.10.8-ee.0
+export NAME=gitlab-test-8.10
+```
+
+##### Create container
+```
+docker run --detach \
+--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$(docker-machine ip gitlab-test-env-do):$HTTP_PORT'; gitlab_rails['gitlab_shell_ssh_port'] = $SSH_PORT;" \
+--hostname $(docker-machine ip gitlab-test-env-do) \
+-p $HTTP_PORT:$HTTP_PORT -p $SSH_PORT:22 \
+--name $NAME \
+gitlab/gitlab-ee:$VERSION
+```
+
+#### Connect to the GitLab container
+
+##### Retrieve the docker host IP
+
+```
+docker-machine ip gitlab-test-env-do
+# example output: 192.168.151.134
+```
+
+
++ Browse to: http://192.168.151.134:8888/
+
+
+##### Execute interactive shell/edit configuration
+
+
+```
+docker exec -it $NAME /bin/bash
+```
+
+```
+# example commands
+root@192:/# vi /etc/gitlab/gitlab.rb
+root@192:/# gitlab-ctl reconfigure
+```
+
+#### Resources
+
++ [https://docs.gitlab.com/omnibus/docker/](https://docs.gitlab.com/omnibus/docker/)
++ [https://docs.docker.com/machine/get-started/](https://docs.docker.com/machine/get-started/)
++ [https://docs.docker.com/machine/reference/ip/](https://docs.docker.com/machine/reference/ip/)+
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 425c5d93efb..b2d5d51d37d 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -124,7 +124,7 @@ Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.3.tar.gz
- echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
+ echo '1014ee699071aa2ddd501907d18cbe15399c997d ruby-2.3.3.tar.gz' | shasum -c - && tar xzf ruby-2.3.3.tar.gz
cd ruby-2.3.3
./configure --disable-install-rdoc
make
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
index 0b682b43810..eaceb2be137 100644
--- a/doc/project_services/slack.md
+++ b/doc/project_services/slack.md
@@ -15,7 +15,7 @@ Slack:
After you set up Slack, it's time to set up GitLab.
-Go to your project's **Settings > Services > Slack Notifications** and you will see a
+Go to your project's **Settings > Integrations > Slack Notifications** and you will see a
checkbox with the following events that can be triggered:
- Push
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 9803937fcf9..9e391d647a8 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -4,10 +4,12 @@ Git is a distributed version control system, which means you can work locally
but you can also share or "push" your changes to other servers.
Before you can push your changes to a GitLab server
you need a secure communication channel for sharing information.
-GitLab uses Public-key or asymmetric cryptography
-which encrypts a communication channel by locking it with your "private key"
-and allows trusted parties to unlock it with your "public key".
-If someone does not have your public key they cannot access the unencrypted message.
+
+The SSH protocol provides this security and allows you to authenticate to the
+GitLab remote server without supplying your username or password each time.
+
+For a more detailed explanation of how the SSH protocol works, we advise you to
+read [this nice tutorial by DigitalOcean](https://www.digitalocean.com/community/tutorials/understanding-the-ssh-encryption-and-connection-process).
## Locating an existing SSH key pair
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index dfc762fe1d3..cb1c1a84f8c 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -22,7 +22,8 @@ with all their related data and be moved into a new GitLab instance.
| GitLab version | Import/Export version |
| -------- | -------- |
-| 8.13.0 to current | 0.1.5 |
+| 8.16.2 to current | 0.1.6 |
+| 8.13.0 | 0.1.5 |
| 8.12.0 | 0.1.4 |
| 8.10.3 | 0.1.3 |
| 8.10.0 | 0.1.2 |
@@ -47,6 +48,9 @@ The following items will NOT be exported:
- Build traces and artifacts
- LFS objects
+- Container registry images
+- CI variables
+- Any encrypted tokens
## Exporting a project and its data
diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature
deleted file mode 100644
index 41d79aa6ec8..00000000000
--- a/features/dashboard/shortcuts.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Shortcuts
- Background:
- Given I sign in as a user
- And I visit dashboard page
-
- @javascript
- Scenario: Navigate to projects tab
- Given I press "g" and "p"
- Then the active main tab should be Projects
-
- @javascript
- Scenario: Navigate to issue tab
- Given I press "g" and "i"
- Then the active main tab should be Issues
-
- @javascript
- Scenario: Navigate to merge requests tab
- Given I press "g" and "m"
- Then the active main tab should be Merge Requests
-
diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb
deleted file mode 100644
index 118d27888df..00000000000
--- a/features/steps/dashboard/shortcuts.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedSidebarActiveTab
- include SharedShortcuts
-end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 344b6fda9a6..2bbc43b491f 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -25,15 +25,18 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
step 'I should see todos assigned to me' do
+ merge_request_reference = merge_request.to_reference(full: true)
+ issue_reference = issue.to_reference(full: true)
+
page.within('.todos-pending-count') { expect(page).to have_content '4' }
expect(page).to have_content 'To do 4'
expect(page).to have_content 'Done 0'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title)
- should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?")
- should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title)
- should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title)
+ should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?")
+ should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title)
+ should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title)
end
step 'I mark the todo as done' do
@@ -44,10 +47,13 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
page.within('.todos-pending-count') { expect(page).to have_content '3' }
expect(page).to have_content 'To do 3'
expect(page).to have_content 'Done 1'
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
end
step 'I mark all todos as done' do
+ merge_request_reference = merge_request.to_reference(full: true)
+ issue_reference = issue.to_reference(full: true)
+
click_link 'Mark all as done'
page.within('.todos-pending-count') { expect(page).to have_content '0' }
@@ -55,27 +61,30 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
expect(page).to have_content 'Done 4'
expect(page).to have_content "You're all done!"
expect('.prepend-top-default').not_to have_link project.name_with_namespace
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
- should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}"
- should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
- should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request_reference}"
+ should_not_see_todo "John Doe mentioned you on issue #{issue_reference}"
+ should_not_see_todo "John Doe assigned you issue #{issue_reference}"
+ should_not_see_todo "Mary Jane mentioned you on issue #{issue_reference}"
end
step 'I should see the todo marked as done' do
click_link 'Done 1'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, false)
end
step 'I should see all todos marked as done' do
+ merge_request_reference = merge_request.to_reference(full: true)
+ issue_reference = issue.to_reference(full: true)
+
click_link 'Done 4'
expect(page).to have_link project.name_with_namespace
- should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false)
- should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?", false)
- should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title, false)
- should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title, false)
+ should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, false)
+ should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", false)
+ should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, false)
+ should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, false)
end
step 'I filter by "Enterprise"' do
@@ -111,16 +120,16 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
step 'I should not see todos related to "Mary Jane" in the list' do
- should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}"
+ should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference(full: true)}"
end
step 'I should not see todos related to "Merge Requests" in the list' do
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
end
step 'I should not see todos related to "Assignments" in the list' do
- should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
- should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
+ should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}"
+ should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}"
end
step 'I click on the todo' do
diff --git a/features/steps/project/builds/summary.rb b/features/steps/project/builds/summary.rb
index 374eb0b0e07..19ff92f6dc6 100644
--- a/features/steps/project/builds/summary.rb
+++ b/features/steps/project/builds/summary.rb
@@ -33,7 +33,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
step 'recent build summary contains information saying that build has been erased' do
page.within('.erased') do
- expect(page).to have_content 'Build has been erased'
+ expect(page).to have_content 'Job has been erased'
end
end
diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb
index 7490d2bc6e7..48ac7a98f0d 100644
--- a/features/steps/project/graph.rb
+++ b/features/steps/project/graph.rb
@@ -34,9 +34,9 @@ class Spinach::Features::ProjectGraph < Spinach::FeatureSteps
step 'page should have CI graphs' do
expect(page).to have_content 'Overall'
- expect(page).to have_content 'Builds for last week'
- expect(page).to have_content 'Builds for last month'
- expect(page).to have_content 'Builds for last year'
+ expect(page).to have_content 'Jobs for last week'
+ expect(page).to have_content 'Jobs for last month'
+ expect(page).to have_content 'Jobs for last year'
expect(page).to have_content 'Commit duration in minutes for last 30 commits'
end
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index f74a9b5df47..4a35b71af2f 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -15,17 +15,16 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do
page.within '.labels' do
- page.all('.remove-row').each do |remove|
- remove.click
- sleep 0.05
+ page.all('.remove-row').each do
+ first('.remove-row').click
end
end
end
step 'I should see labels help message' do
page.within '.labels' do
- expect(page).to have_content 'Create a label or generate a default set '\
- 'of labels'
+ expect(page).to have_content 'Generate a default set of labels'
+ expect(page).to have_content 'New label'
end
end
diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb
index 70e6d4836b2..d008a8a26af 100644
--- a/features/steps/shared/builds.rb
+++ b/features/steps/shared/builds.rb
@@ -47,7 +47,7 @@ module SharedBuilds
end
step 'recent build has a build trace' do
- @build.trace = 'build trace'
+ @build.trace = 'job trace'
end
step 'download of build artifacts archive starts' do
@@ -60,7 +60,7 @@ module SharedBuilds
end
step 'I see details of a build' do
- expect(page).to have_content "Build ##{@build.id}"
+ expect(page).to have_content "Job ##{@build.id}"
end
step 'I see build trace' do
diff --git a/lib/api/builds.rb b/lib/api/builds.rb
index af61be343be..44fe0fc4a95 100644
--- a/lib/api/builds.rb
+++ b/lib/api/builds.rb
@@ -209,7 +209,7 @@ module API
build = get_build!(params[:build_id])
- bad_request!("Unplayable Build") unless build.playable?
+ bad_request!("Unplayable Job") unless build.playable?
build.play(current_user)
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index e6d707f3c3d..2fefe760d24 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -54,7 +54,7 @@ module API
authorize! :push_code, user_project
attrs = declared_params
- attrs[:source_branch] = attrs[:branch_name]
+ attrs[:start_branch] = attrs[:branch_name]
attrs[:target_branch] = attrs[:branch_name]
attrs[:actions].map! do |action|
action[:action] = action[:action].to_sym
@@ -139,8 +139,6 @@ module API
commit_params = {
commit: commit,
create_merge_request: false,
- source_project: user_project,
- source_branch: commit.cherry_pick_branch_name,
target_branch: params[:branch]
}
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 9f59939e9ae..a07b2a9ca0f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -137,6 +137,7 @@ module API
expose :avatar_url
expose :web_url
expose :request_access_enabled
+ expose :full_name, :full_path
expose :statistics, if: :statistics do
with_options format_with: -> (value) { value.to_i } do
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 2e79e22e649..c58472de578 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -5,7 +5,7 @@ module API
def commit_params(attrs)
{
file_path: attrs[:file_path],
- source_branch: attrs[:branch_name],
+ start_branch: attrs[:branch_name],
target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index a1d7b323f4f..eb5b947172a 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -45,7 +45,7 @@ module API
if id =~ /^\d+$/
Project.find_by(id: id)
else
- Project.find_with_namespace(id)
+ Project.find_by_full_path(id)
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index e8975eb57e0..080a6274957 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -30,7 +30,7 @@ module API
def wiki?
@wiki ||= project_path.end_with?('.wiki') &&
- !Project.find_with_namespace(project_path)
+ !Project.find_by_full_path(project_path)
end
def project
@@ -41,7 +41,7 @@ module API
# the wiki repository as well.
project_path.chomp!('.wiki') if wiki?
- Project.find_with_namespace(project_path)
+ Project.find_by_full_path(project_path)
end
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 9d8c5b63685..dcc0c82ee27 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -58,7 +58,7 @@ module API
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
- snippet_params = declared_params
+ snippet_params = declared_params.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code)
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
diff --git a/lib/api/services.rb b/lib/api/services.rb
index a0abec49438..1456fe4688b 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -661,6 +661,14 @@ module API
end
trigger_services.each do |service_slug, settings|
+ helpers do
+ def chat_command_service(project, service_slug, params)
+ project.services.active.where(template: false).find do |service|
+ service.try(:token) == params[:token] && service.to_param == service_slug.underscore
+ end
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -679,9 +687,8 @@ module API
# This is not accurate, but done to prevent leakage of the project names
not_found!('Service') unless project
- service = project.find_or_initialize_service(service_slug.underscore)
-
- result = service.try(:active?) && service.try(:trigger, params)
+ service = chat_command_service(project, service_slug, params)
+ result = service.try(:trigger, params)
if result
status result[:status] || 200
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index e096e636806..eb9ece49e7f 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -64,7 +64,7 @@ module API
desc: 'The visibility level of the snippet'
end
post do
- attrs = declared_params(include_missing: false)
+ attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
if snippet.persisted?
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 11a7368b4c0..0ed468626b7 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -160,6 +160,8 @@ module API
end
end
+ user_params.merge!(password_expires_at: Time.now) if user_params[:password].present?
+
if user.update_attributes(user_params.except(:extern_uid, :provider))
present user, with: Entities::UserPublic
else
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index 0257848b6bc..e2b57adf611 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -14,7 +14,7 @@ module Banzai
def project_from_ref(ref)
return context[:project] unless ref
- Project.find_with_namespace(ref)
+ Project.find_by_full_path(ref)
end
end
end
diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb
new file mode 100644
index 00000000000..e194cf59275
--- /dev/null
+++ b/lib/banzai/filter/plantuml_filter.rb
@@ -0,0 +1,39 @@
+require "nokogiri"
+require "asciidoctor-plantuml/plantuml"
+
+module Banzai
+ module Filter
+ # HTML that replaces all `code plantuml` tags with PlantUML img tags.
+ #
+ class PlantumlFilter < HTML::Pipeline::Filter
+ def call
+ return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled
+
+ plantuml_setup
+
+ doc.css('pre.plantuml').each do |el|
+ img_tag = Nokogiri::HTML::DocumentFragment.parse(
+ Asciidoctor::PlantUml::Processor.plantuml_content(el.content, {}))
+ el.replace img_tag
+ end
+
+ doc
+ end
+
+ private
+
+ def settings
+ ApplicationSetting.current || ApplicationSetting.create_from_defaults
+ end
+
+ def plantuml_setup
+ Asciidoctor::PlantUml.configure do |conf|
+ conf.url = settings.plantuml_url
+ conf.png_enable = settings.plantuml_enabled
+ conf.svg_enable = false
+ conf.txt_enable = false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index ac95a79009b..b25d6f18d59 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -10,6 +10,7 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SyntaxHighlightFilter,
+ Filter::PlantumlFilter,
Filter::SanitizationFilter,
Filter::MathFilter,
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 7463bd719d5..649ee4d018b 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -61,6 +61,7 @@ module Ci
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment_name],
+ coverage_regex: job[:coverage],
yaml_variables: yaml_variables(name),
options: {
image: job[:image],
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index 730b05bed97..a10b4657d7d 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -8,6 +8,6 @@ class ProjectUrlConstrainer
return false
end
- Project.find_with_namespace(full_path).present?
+ Project.find_by_full_path(full_path).present?
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 8dda65c71ef..f638905a1e0 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -10,13 +10,16 @@ module Gitlab
def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil?
+ # `user_with_password_for_git` should be the last check
+ # because it's the most expensive, especially when LDAP
+ # is enabled.
result =
service_request_check(login, password, project) ||
build_access_token_check(login, password) ||
- user_with_password_for_git(login, password) ||
- oauth_access_token_check(login, password) ||
lfs_token_check(login, password) ||
+ oauth_access_token_check(login, password) ||
personal_access_token_check(login, password) ||
+ user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login)
@@ -143,7 +146,9 @@ module Gitlab
read_authentication_abilities
end
- Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password)
+ if Devise.secure_compare(token_handler.token, password)
+ Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
+ end
end
def build_access_token_check(login, password)
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb
index 4fe53ce93a9..25da8474e95 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/chat_commands/base_command.rb
@@ -42,10 +42,6 @@ module Gitlab
def find_by_iid(iid)
collection.find_by(iid: iid)
end
-
- def presenter
- Gitlab::ChatCommands::Presenter.new
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 145086755e4..f34ed0f4cf2 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -3,7 +3,7 @@ module Gitlab
class Command < BaseCommand
COMMANDS = [
Gitlab::ChatCommands::IssueShow,
- Gitlab::ChatCommands::IssueCreate,
+ Gitlab::ChatCommands::IssueNew,
Gitlab::ChatCommands::IssueSearch,
Gitlab::ChatCommands::Deploy,
].freeze
@@ -13,51 +13,32 @@ module Gitlab
if command
if command.allowed?(project, current_user)
- present command.new(project, current_user, params).execute(match)
+ command.new(project, current_user, params).execute(match)
else
- access_denied
+ Gitlab::ChatCommands::Presenters::Access.new.access_denied
end
else
- help(help_messages)
+ Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
end
end
def match_command
match = nil
- service = available_commands.find do |klass|
- match = klass.match(command)
- end
+ service =
+ available_commands.find do |klass|
+ match = klass.match(params[:text])
+ end
[service, match]
end
private
- def help_messages
- available_commands.map(&:help_message)
- end
-
def available_commands
COMMANDS.select do |klass|
klass.available?(project)
end
end
-
- def command
- params[:text]
- end
-
- def help(messages)
- presenter.help(messages, params[:command])
- end
-
- def access_denied
- presenter.access_denied
- end
-
- def present(resource)
- presenter.present(resource)
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
index 7127d2f6d04..458d90f84e8 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -1,8 +1,6 @@
module Gitlab
module ChatCommands
class Deploy < BaseCommand
- include Gitlab::Routing.url_helpers
-
def self.match(text)
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
end
@@ -24,35 +22,29 @@ module Gitlab
to = match[:to]
actions = find_actions(from, to)
- return unless actions.present?
- if actions.one?
- play!(from, to, actions.first)
+ if actions.none?
+ Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
+ elsif actions.one?
+ action = play!(from, to, actions.first)
+ Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
else
- Result.new(:error, 'Too many actions defined')
+ Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
end
end
private
def play!(from, to, action)
- new_action = action.play(current_user)
-
- Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.")
+ action.play(current_user)
end
def find_actions(from, to)
environment = project.environments.find_by(name: from)
- return unless environment
+ return [] unless environment
environment.actions_for(to).select(&:starts_environment?)
end
-
- def url(subject)
- polymorphic_url(
- [subject.project.namespace.becomes(Namespace), subject.project, subject]
- )
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb
new file mode 100644
index 00000000000..6c0e4d304a4
--- /dev/null
+++ b/lib/gitlab/chat_commands/help.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ module ChatCommands
+ class Help < BaseCommand
+ # This class has to be used last, as it always matches. It has to match
+ # because other commands were not triggered and we want to show the help
+ # command
+ def self.match(_text)
+ true
+ end
+
+ def self.help_message
+ 'help'
+ end
+
+ def self.allowed?(_project, _user)
+ true
+ end
+
+ def execute(commands, text)
+ Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
+ end
+
+ def trigger
+ params[:command]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_new.rb
index cefb6775db8..016054ecd46 100644
--- a/lib/gitlab/chat_commands/issue_create.rb
+++ b/lib/gitlab/chat_commands/issue_new.rb
@@ -1,8 +1,8 @@
module Gitlab
module ChatCommands
- class IssueCreate < IssueCommand
+ class IssueNew < IssueCommand
def self.match(text)
- # we can not match \n with the dot by passing the m modifier as than
+ # we can not match \n with the dot by passing the m modifier as than
# the title and description are not seperated
/\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text)
end
@@ -19,8 +19,24 @@ module Gitlab
title = match[:title]
description = match[:description].to_s.rstrip
+ issue = create_issue(title: title, description: description)
+
+ if issue.persisted?
+ presenter(issue).present
+ else
+ presenter(issue).display_errors
+ end
+ end
+
+ private
+
+ def create_issue(title:, description:)
Issues::CreateService.new(project, current_user, title: title, description: description).execute
end
+
+ def presenter(issue)
+ Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb
index 51bf80c800b..3491b53093e 100644
--- a/lib/gitlab/chat_commands/issue_search.rb
+++ b/lib/gitlab/chat_commands/issue_search.rb
@@ -10,7 +10,13 @@ module Gitlab
end
def execute(match)
- collection.search(match[:query]).limit(QUERY_LIMIT)
+ issues = collection.search(match[:query]).limit(QUERY_LIMIT)
+
+ if issues.present?
+ Presenters::IssueSearch.new(issues).present
+ else
+ Presenters::Access.new(issues).not_found
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb
index 2a45d49cf6b..d6013f4d10c 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/chat_commands/issue_show.rb
@@ -10,7 +10,13 @@ module Gitlab
end
def execute(match)
- find_by_iid(match[:iid])
+ issue = find_by_iid(match[:iid])
+
+ if issue
+ Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
+ else
+ Gitlab::ChatCommands::Presenters::Access.new.not_found
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb
deleted file mode 100644
index 8930a21f406..00000000000
--- a/lib/gitlab/chat_commands/presenter.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-module Gitlab
- module ChatCommands
- class Presenter
- include Gitlab::Routing
-
- def authorize_chat_name(url)
- message = if url
- ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})."
- else
- ":sweat_smile: Couldn't identify you, nor can I autorize you!"
- end
-
- ephemeral_response(message)
- end
-
- def help(commands, trigger)
- if commands.none?
- ephemeral_response("No commands configured")
- else
- commands.map! { |command| "#{trigger} #{command}" }
- message = header_with_list("Available commands", commands)
-
- ephemeral_response(message)
- end
- end
-
- def present(subject)
- return not_found unless subject
-
- if subject.is_a?(Gitlab::ChatCommands::Result)
- show_result(subject)
- elsif subject.respond_to?(:count)
- if subject.none?
- not_found
- elsif subject.one?
- single_resource(subject.first)
- else
- multiple_resources(subject)
- end
- else
- single_resource(subject)
- end
- end
-
- def access_denied
- ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
- end
-
- private
-
- def show_result(result)
- case result.type
- when :success
- in_channel_response(result.message)
- else
- ephemeral_response(result.message)
- end
- end
-
- def not_found
- ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
- end
-
- def single_resource(resource)
- return error(resource) if resource.errors.any? || !resource.persisted?
-
- message = "#{title(resource)}:"
- message << "\n\n#{resource.description}" if resource.try(:description)
-
- in_channel_response(message)
- end
-
- def multiple_resources(resources)
- titles = resources.map { |resource| title(resource) }
-
- message = header_with_list("Multiple results were found:", titles)
-
- ephemeral_response(message)
- end
-
- def error(resource)
- message = header_with_list("The action was not successful, because:", resource.errors.messages)
-
- ephemeral_response(message)
- end
-
- def title(resource)
- reference = resource.try(:to_reference) || resource.try(:id)
- title = resource.try(:title) || resource.try(:name)
-
- "[#{reference} #{title}](#{url(resource)})"
- end
-
- def header_with_list(header, items)
- message = [header]
-
- items.each do |item|
- message << "- #{item}"
- end
-
- message.join("\n")
- end
-
- def url(resource)
- url_for(
- [
- resource.project.namespace.becomes(Namespace),
- resource.project,
- resource
- ]
- )
- end
-
- def ephemeral_response(message)
- {
- response_type: :ephemeral,
- text: message,
- status: 200
- }
- end
-
- def in_channel_response(message)
- {
- response_type: :in_channel,
- text: message,
- status: 200
- }
- end
- end
- end
-end
diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb
new file mode 100644
index 00000000000..92f4fa17f78
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/access.rb
@@ -0,0 +1,40 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Access < Presenters::Base
+ def access_denied
+ ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
+ end
+
+ def not_found
+ ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
+ end
+
+ def authorize
+ message =
+ if @resource
+ ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})."
+ else
+ ":sweat_smile: Couldn't identify you, nor can I autorize you!"
+ end
+
+ ephemeral_response(text: message)
+ end
+
+ def unknown_command(commands)
+ ephemeral_response(text: help_message(trigger))
+ end
+
+ private
+
+ def help_message(trigger)
+ header_with_list("Command not found, these are the commands you can use", full_commands(trigger))
+ end
+
+ def full_commands(trigger)
+ @resource.map { |command| "#{trigger} #{command.help_message}" }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb
new file mode 100644
index 00000000000..2700a5a2ad5
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/base.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Base
+ include Gitlab::Routing.url_helpers
+
+ def initialize(resource = nil)
+ @resource = resource
+ end
+
+ def display_errors
+ message = header_with_list("The action was not successful, because:", @resource.errors.full_messages)
+
+ ephemeral_response(text: message)
+ end
+
+ private
+
+ def header_with_list(header, items)
+ message = [header]
+
+ items.each do |item|
+ message << "- #{item}"
+ end
+
+ message.join("\n")
+ end
+
+ def ephemeral_response(message)
+ response = {
+ response_type: :ephemeral,
+ status: 200
+ }.merge(message)
+
+ format_response(response)
+ end
+
+ def in_channel_response(message)
+ response = {
+ response_type: :in_channel,
+ status: 200
+ }.merge(message)
+
+ format_response(response)
+ end
+
+ def format_response(response)
+ response[:text] = format(response[:text]) if response.has_key?(:text)
+
+ if response.has_key?(:attachments)
+ response[:attachments].each do |attachment|
+ attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext]
+ attachment[:text] = format(attachment[:text]) if attachment[:text]
+ end
+ end
+
+ response
+ end
+
+ # Convert Markdown to slacks format
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def resource_url
+ url_for(
+ [
+ @resource.project.namespace.becomes(Namespace),
+ @resource.project,
+ @resource
+ ]
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb
new file mode 100644
index 00000000000..863d0bf99ca
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/deploy.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Deploy < Presenters::Base
+ def present(from, to)
+ message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})."
+
+ in_channel_response(text: message)
+ end
+
+ def no_actions
+ ephemeral_response(text: "No action found to be executed")
+ end
+
+ def too_many_actions
+ ephemeral_response(text: "Too many actions defined")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb
new file mode 100644
index 00000000000..cd47b7f4c6a
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/help.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Help < Presenters::Base
+ def present(trigger, text)
+ ephemeral_response(text: help_message(trigger, text))
+ end
+
+ private
+
+ def help_message(trigger, text)
+ return "No commands available :thinking_face:" unless @resource.present?
+
+ if text.start_with?('help')
+ header_with_list("Available commands", full_commands(trigger))
+ else
+ header_with_list("Unknown command, these commands are available", full_commands(trigger))
+ end
+ end
+
+ def full_commands(trigger)
+ @resource.map { |command| "#{trigger} #{command.help_message}" }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb
new file mode 100644
index 00000000000..dfb1c8f6616
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issuable.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ module Issuable
+ def color(issuable)
+ issuable.open? ? '#38ae67' : '#d22852'
+ end
+
+ def status_text(issuable)
+ issuable.open? ? 'Open' : 'Closed'
+ end
+
+ def project
+ @resource.project
+ end
+
+ def author
+ @resource.author
+ end
+
+ def fields
+ [
+ {
+ title: "Assignee",
+ value: @resource.assignee ? @resource.assignee.name : "_None_",
+ short: true
+ },
+ {
+ title: "Milestone",
+ value: @resource.milestone ? @resource.milestone.title : "_None_",
+ short: true
+ },
+ {
+ title: "Labels",
+ value: @resource.labels.any? ? @resource.label_names : "_None_",
+ short: true
+ }
+ ]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb
new file mode 100644
index 00000000000..a1a3add56c9
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_new.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueNew < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ in_channel_response(new_issue)
+ end
+
+ private
+
+ def new_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "New issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :text,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext
+ "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
+ end
+
+ def project_link
+ "[#{project.name_with_namespace}](#{projects_url(project)})"
+ end
+
+ def author_profile_link
+ "[#{author.to_reference}](#{url_for(author)})"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb
new file mode 100644
index 00000000000..3478359b91d
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_search.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueSearch < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ text = if @resource.count >= 5
+ "Here are the first 5 issues I found:"
+ elsif @resource.one?
+ "Here is the only issue I found:"
+ else
+ "Here are the #{@resource.count} issues I found:"
+ end
+
+ ephemeral_response(text: text, attachments: attachments)
+ end
+
+ private
+
+ def attachments
+ @resource.map do |issue|
+ url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})"
+
+ {
+ color: color(issue),
+ fallback: "#{issue.to_reference} #{issue.title}",
+ text: "#{url} · #{issue.title} (#{status_text(issue)})",
+
+ mrkdwn_in: [
+ :text
+ ]
+ }
+ end
+ end
+
+ def project
+ @project ||= @resource.first.project
+ end
+
+ def namespace
+ @namespace ||= project.namespace.becomes(Namespace)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb
new file mode 100644
index 00000000000..fe5847ccd15
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_show.rb
@@ -0,0 +1,61 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueShow < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ if @resource.confidential?
+ ephemeral_response(show_issue)
+ else
+ in_channel_response(show_issue)
+ end
+ end
+
+ private
+
+ def show_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "Issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ text: text,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :pretext,
+ :text,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def text
+ message = "**#{status_text(@resource)}**"
+
+ if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero?
+ return message
+ end
+
+ message << " · "
+ message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero?
+ message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero?
+ message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero?
+
+ message
+ end
+
+ def pretext
+ "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb
new file mode 100644
index 00000000000..12a063059cb
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/coverage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents Coverage settings.
+ #
+ class Coverage < Node
+ include Validatable
+
+ validations do
+ validates :config, regexp: true
+ end
+
+ def value
+ @config[1...-1]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb
index a4ec8f0ff2f..ede97cc0504 100644
--- a/lib/gitlab/ci/config/entry/global.rb
+++ b/lib/gitlab/ci/config/entry/global.rb
@@ -33,8 +33,11 @@ module Gitlab
entry :cache, Entry::Cache,
description: 'Configure caching between build jobs.'
+ entry :coverage, Entry::Coverage,
+ description: 'Coverage configuration for this pipeline.'
+
helpers :before_script, :image, :services, :after_script,
- :variables, :stages, :types, :cache, :jobs
+ :variables, :stages, :types, :cache, :coverage, :jobs
def compose!(_deps = nil)
super(self) do
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index a55362f0b6b..69a5e6f433d 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
- after_script variables environment]
+ after_script variables environment coverage]
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -71,9 +71,12 @@ module Gitlab
entry :environment, Entry::Environment,
description: 'Environment configuration for this job.'
+ entry :coverage, Entry::Coverage,
+ description: 'Coverage configuration for this job.'
+
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
- :artifacts, :commands, :environment
+ :artifacts, :commands, :environment, :coverage
attributes :script, :tags, :allow_failure, :when, :dependencies
@@ -130,6 +133,7 @@ module Gitlab
variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
+ coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value,
after_script: after_script_value }
end
diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
index f01975aab5c..9b9a0a8125a 100644
--- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
+++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
@@ -28,17 +28,21 @@ module Gitlab
value.is_a?(String) || value.is_a?(Symbol)
end
+ def validate_regexp(value)
+ !value.nil? && Regexp.new(value.to_s) && true
+ rescue RegexpError, TypeError
+ false
+ end
+
def validate_string_or_regexp(value)
return true if value.is_a?(Symbol)
return false unless value.is_a?(String)
if value.first == '/' && value.last == '/'
- Regexp.new(value[1...-1])
+ validate_regexp(value[1...-1])
else
true
end
- rescue RegexpError
- false
end
def validate_boolean(value)
diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb
index 28b0a9ffe01..16b234e6c59 100644
--- a/lib/gitlab/ci/config/entry/trigger.rb
+++ b/lib/gitlab/ci/config/entry/trigger.rb
@@ -9,15 +9,7 @@ module Gitlab
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
+ validates :config, array_of_strings_or_regexps: true
end
end
end
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index 8632dd0e233..bd7428b1272 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -54,6 +54,51 @@ module Gitlab
end
end
+ class RegexpValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_regexp(value)
+ record.errors.add(attribute, 'must be a regular expression')
+ end
+ end
+
+ private
+
+ def look_like_regexp?(value)
+ value.is_a?(String) && value.start_with?('/') &&
+ value.end_with?('/')
+ end
+
+ def validate_regexp(value)
+ look_like_regexp?(value) &&
+ Regexp.new(value.to_s[1...-1]) &&
+ true
+ rescue RegexpError
+ false
+ end
+ end
+
+ class ArrayOfStringsOrRegexpsValidator < RegexpValidator
+ def validate_each(record, attribute, value)
+ unless validate_array_of_strings_or_regexps(value)
+ record.errors.add(attribute, 'should be an array of strings or regexps')
+ end
+ end
+
+ private
+
+ def validate_array_of_strings_or_regexps(values)
+ values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp))
+ end
+
+ def validate_string_or_regexp(value)
+ return false unless value.is_a?(String)
+ return validate_regexp(value) if look_like_regexp?(value)
+ true
+ end
+ end
+
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
type = options[:with]
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index a979fe7d573..67bbc3c4849 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def action_icon
- 'ban'
+ 'icon_action_cancel'
end
def action_path
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index 1bf949c96dd..0f4b7b24cef 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -26,17 +26,13 @@ module Gitlab
end
def action_icon
- 'play'
+ 'icon_action_play'
end
def action_title
'Play'
end
- def action_class
- 'ci-play-icon'
- end
-
def action_path
play_namespace_project_build_path(subject.project.namespace,
subject.project,
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 8e38d6a8523..6b362af7634 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def action_icon
- 'refresh'
+ 'icon_action_retry'
end
def action_title
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index e1dfdb76d41..90401cad0d2 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -26,7 +26,7 @@ module Gitlab
end
def action_icon
- 'stop'
+ 'icon_action_stop'
end
def action_title
diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb
index 73b6ab5a635..3dd2b9e01f6 100644
--- a/lib/gitlab/ci/status/core.rb
+++ b/lib/gitlab/ci/status/core.rb
@@ -42,9 +42,6 @@ module Gitlab
raise NotImplementedError
end
- def action_class
- end
-
def action_path
raise NotImplementedError
end
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index 74bbcdcb3dd..559e3939da6 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -13,7 +13,7 @@ module Gitlab
end
def as_json
- AnalyticsStageSerializer.new.represent(self).as_json
+ AnalyticsStageSerializer.new.represent(self)
end
def title
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 5245b9ca8fc..d5bf6149749 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
+ AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
index 0d8da99455e..3df9cbdcfce 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: @project).represent(event).as_json
+ AnalyticsIssueSerializer.new(project: @project).represent(event)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 88a8710dbe6..7d342a2d2cb 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -37,7 +37,7 @@ module Gitlab
def serialize_commit(event, st_commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
- AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json
+ AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index 4df0bd06393..4c7b3f4467f 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json
+ AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/stage_summary.rb b/lib/gitlab/cycle_analytics/stage_summary.rb
index b34baf5b081..fc77bd86097 100644
--- a/lib/gitlab/cycle_analytics/stage_summary.rb
+++ b/lib/gitlab/cycle_analytics/stage_summary.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def serialize(summary_object)
- AnalyticsSummarySerializer.new.represent(summary_object).as_json
+ AnalyticsSummarySerializer.new.represent(summary_object)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
index a34731a5fcd..36c0260dbfe 100644
--- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb
@@ -23,7 +23,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsBuildSerializer.new.represent(event['build']).as_json
+ AnalyticsBuildSerializer.new.represent(event['build'])
end
end
end
diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb
index 127fae159d5..b8ec9138c10 100644
--- a/lib/gitlab/email/handler/create_issue_handler.rb
+++ b/lib/gitlab/email/handler/create_issue_handler.rb
@@ -34,7 +34,7 @@ module Gitlab
end
def project
- @project ||= Project.find_with_namespace(project_path)
+ @project ||= Project.find_by_full_path(project_path)
end
private
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 3cd515e4a3a..d3df3f1bca1 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -6,7 +6,7 @@ module Gitlab
class << self
def ref_name(ref)
- ref.gsub(/\Arefs\/(tags|heads)\//, '')
+ ref.sub(/\Arefs\/(tags|heads)\//, '')
end
def branch_name(ref)
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index d32bdd86427..6babea144c7 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -30,11 +30,11 @@ module Gitlab
def retrieve_project_and_type
@type = :project
- @project = Project.find_with_namespace(@repo_path)
+ @project = Project.find_by_full_path(@repo_path)
if @repo_path.end_with?('.wiki') && !@project
@type = :wiki
- @project = Project.find_with_namespace(@repo_path.gsub(/\.wiki\z/, ''))
+ @project = Project.find_by_full_path(@repo_path.gsub(/\.wiki\z/, ''))
end
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index eb667a85b78..d679edec36b 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -3,7 +3,7 @@ module Gitlab
extend self
# For every version update, the version history in import_export.md has to be kept up to date.
- VERSION = '0.1.5'
+ VERSION = '0.1.6'
FILENAME_LIMIT = 50
def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 08ad3274b38..416194e57d7 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -39,7 +39,6 @@ project_tree:
- :author
- :events
- :statuses
- - :variables
- :triggers
- :deploy_keys
- :services
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 2405b94db50..a09577ae48d 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -41,6 +41,8 @@ module Gitlab
end
def ensure_default_member!
+ @project.project_members.destroy_all
+
ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true)
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 19e43cce768..0319d7707a8 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -4,7 +4,6 @@ module Gitlab
OVERRIDES = { snippets: :project_snippets,
pipelines: 'Ci::Pipeline',
statuses: 'commit_status',
- variables: 'Ci::Variable',
triggers: 'Ci::Trigger',
builds: 'Ci::Build',
hooks: 'ProjectHook',
@@ -24,6 +23,8 @@ module Gitlab
EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels].freeze
+ TOKEN_RESET_MODELS = %w[Ci::Trigger Ci::Build ProjectHook].freeze
+
def self.create(*args)
new(*args).create
end
@@ -61,7 +62,9 @@ module Gitlab
update_project_references
handle_group_label if group_label?
- reset_ci_tokens if @relation_name == 'Ci::Trigger'
+ reset_tokens!
+ remove_encrypted_attributes!
+
@relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data']
set_st_diffs if @relation_name == :merge_request_diff
end
@@ -140,11 +143,22 @@ module Gitlab
end
end
- def reset_ci_tokens
- return unless Gitlab::ImportExport.reset_tokens?
+ def reset_tokens!
+ return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name.to_s)
# If we import/export a project to the same instance, tokens will have to be reset.
- @relation_hash['token'] = nil
+ # We also have to reset them to avoid issues when the gitlab secrets file cannot be copied across.
+ relation_class.attribute_names.select { |name| name.include?('token') }.each do |token|
+ @relation_hash[token] = nil
+ end
+ end
+
+ def remove_encrypted_attributes!
+ return unless relation_class.respond_to?(:encrypted_attributes) && relation_class.encrypted_attributes.any?
+
+ relation_class.encrypted_attributes.each_key do |key|
+ @relation_hash[key.to_s] = nil
+ end
end
def relation_class
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
index 786e1d49f5e..ef42b0557e0 100644
--- a/lib/gitlab/request_profiler/middleware.rb
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -1,5 +1,4 @@
require 'ruby-prof'
-require_dependency 'gitlab/request_profiler'
module Gitlab
module RequestProfiler
@@ -20,7 +19,7 @@ module Gitlab
header_token = env['HTTP_X_PROFILE_TOKEN']
return unless header_token.present?
- profile_token = RequestProfiler.profile_token
+ profile_token = Gitlab::RequestProfiler.profile_token
return unless profile_token.present?
header_token == profile_token
diff --git a/lib/rouge/lexers/plantuml.rb b/lib/rouge/lexers/plantuml.rb
new file mode 100644
index 00000000000..7d5700b7f6d
--- /dev/null
+++ b/lib/rouge/lexers/plantuml.rb
@@ -0,0 +1,21 @@
+module Rouge
+ module Lexers
+ class Plantuml < Lexer
+ title "A passthrough lexer used for PlantUML input"
+ desc "A boring lexer that doesn't highlight anything"
+
+ tag 'plantuml'
+ mimetypes 'text/plain'
+
+ default_options token: 'Text'
+
+ def token
+ @token ||= Token[option :token]
+ end
+
+ def stream_tokens(string, &b)
+ yield self.token, string
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 4a696a52b4d..967f630ef20 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -58,7 +58,7 @@ namespace :gitlab do
sub(%r{^/*}, '').
chomp('.git').
chomp('.wiki')
- next if Project.find_with_namespace(repo_with_namespace)
+ next if Project.find_by_full_path(repo_with_namespace)
new_path = path + move_suffix
puts path.inspect + ' -> ' + new_path.inspect
File.rename(path, new_path)
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index a2eca74a3c8..b4015f5238e 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -29,7 +29,7 @@ namespace :gitlab do
next
end
- project = Project.find_with_namespace(path)
+ project = Project.find_by_full_path(path)
if project
puts " * #{project.name} (#{repo_path}) exists"
@@ -63,7 +63,7 @@ namespace :gitlab do
if project.persisted?
puts " * Created #{project.name} (#{repo_path})".color(:green)
- ProjectCacheWorker.perform(project.id)
+ ProjectCacheWorker.perform_async(project.id)
else
puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red)
puts " Errors: #{project.errors.messages}".color(:red)
diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake
index 7e2a6668e59..f2e12d85045 100644
--- a/lib/tasks/gitlab/sidekiq.rake
+++ b/lib/tasks/gitlab/sidekiq.rake
@@ -7,7 +7,7 @@ namespace :gitlab do
unless args.project.present?
abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]"
end
- project_path = Project.find_with_namespace(args.project).repository.path_to_repo
+ project_path = Project.find_by_full_path(args.project).repository.path_to_repo
Sidekiq.redis do |redis|
unless redis.exists(QUEUE)
diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb
new file mode 100644
index 00000000000..4a63c760744
--- /dev/null
+++ b/rubocop/cop/gem_fetcher.rb
@@ -0,0 +1,28 @@
+module RuboCop
+ module Cop
+ # Cop that checks for all gems specified in the Gemfile, and will
+ # alert if any gem is to be fetched not from the RubyGems index.
+ # This enforcement is done so as to minimize external build
+ # dependencies and build times.
+ class GemFetcher < RuboCop::Cop::Cop
+ MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
+
+ GIT_KEYS = [:git, :github]
+
+ def on_send(node)
+ file_path = node.location.expression.source_buffer.name
+ return unless file_path.end_with?("Gemfile")
+
+ func_name = node.children[1]
+ return unless func_name == :gem
+
+ node.children.last.each_node(:pair) do |pair|
+ key_name = pair.children[0].children[0].to_sym
+ if GIT_KEYS.include?(key_name)
+ add_offense(node, :selector)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 7922e19768b..7f20754ee51 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -1,3 +1,4 @@
require_relative 'migration_helpers'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/column_with_default'
+require_relative 'cop/gem_fetcher'
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index a95cfc5c6be..ebd2d0e092b 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -4,7 +4,6 @@ describe Projects::CommitController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:commit) { project.commit("master") }
- let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) }
let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
let(:master_pickable_commit) { project.commit(master_pickable_sha) }
@@ -322,11 +321,26 @@ describe Projects::CommitController do
end
context 'when the commit exists' do
- context 'when the commit has one or more pipelines' do
- it 'shows pipelines' do
- get_pipelines(id: commit.id)
+ context 'when the commit has pipelines' do
+ before do
+ create(:ci_pipeline, project: project, sha: commit.id)
+ end
+
+ context 'when rendering a HTML format' do
+ it 'shows pipelines' do
+ get_pipelines(id: commit.id)
+
+ expect(response).to be_ok
+ end
+ end
- expect(response).to be_ok
+ context 'when rendering a JSON format' do
+ it 'responds with serialized pipelines' do
+ get_pipelines(id: commit.id, format: :json)
+
+ expect(response).to be_ok
+ expect(JSON.parse(response.body)).not_to be_empty
+ end
end
end
end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index ec6cea5c0f4..3e0326dd47d 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -112,4 +112,49 @@ describe Projects::LabelsController do
post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param
end
end
+
+ describe 'POST #promote' do
+ let!(:promoted_label_name) { "Promoted Label" }
+ let!(:label_1) { create(:label, title: promoted_label_name, project: project) }
+
+ context 'not group owner' do
+ it 'denies access' do
+ post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'group owner' do
+ before do
+ GroupMember.add_users_to_group(group, [user], :owner)
+ end
+
+ it 'gives access' do
+ post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+ expect(response).to redirect_to(namespace_project_labels_path)
+ end
+
+ it 'promotes the label' do
+ post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+
+ expect(Label.where(id: label_1.id)).to be_empty
+ expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil
+ end
+
+ context 'service raising InvalidRecord' do
+ before do
+ expect_any_instance_of(Labels::PromoteService).to receive(:execute) do |label|
+ raise ActiveRecord::RecordInvalid.new(label_1)
+ end
+ end
+
+ it 'returns to label list' do
+ post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param
+ expect(response).to redirect_to(namespace_project_labels_path)
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index 2ae635a1244..cae733f0cfb 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -13,13 +13,13 @@ describe Projects::MattermostsController do
before do
allow_any_instance_of(MattermostSlashCommandsService).
to receive(:list_teams).and_return([])
+ end
+ it 'accepts the request' do
get(:new,
namespace_id: project.namespace.to_param,
project_id: project.to_param)
- end
- it 'accepts the request' do
expect(response).to have_http_status(200)
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 7ea3ea4f376..e019541e74f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Projects::MergeRequestsController do
+ include ApiHelpers
+
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -455,7 +457,7 @@ describe Projects::MergeRequestsController do
it 'renders the diffs template to a string' do
expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
@@ -494,7 +496,7 @@ describe Projects::MergeRequestsController do
it 'renders the diffs template to a string' do
expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
end
@@ -662,18 +664,45 @@ describe Projects::MergeRequestsController do
go format: 'json'
expect(response).to render_template('projects/merge_requests/show/_commits')
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
end
describe 'GET pipelines' do
- it_behaves_like "loads labels", :pipelines
+ before do
+ create(:ci_pipeline, project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+ end
+
+ context 'when using HTML format' do
+ it_behaves_like "loads labels", :pipelines
+ end
+
+ context 'when using JSON format' do
+ before do
+ get :pipelines,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid,
+ format: :json
+ end
+
+ it 'responds with a rendered HTML partial' do
+ expect(response)
+ .to render_template('projects/merge_requests/show/_pipelines')
+ expect(json_response).to have_key 'html'
+ end
+
+ it 'responds with serialized pipelines' do
+ expect(json_response).to have_key 'pipelines'
+ expect(json_response['pipelines']).not_to be_empty
+ end
+ end
end
describe 'GET conflicts' do
- let(:json_response) { JSON.parse(response.body) }
-
context 'when the conflicts cannot be resolved in the UI' do
before do
allow_any_instance_of(Gitlab::Conflict::Parser).
@@ -770,8 +799,6 @@ describe Projects::MergeRequestsController do
end
describe 'GET conflict_for_path' do
- let(:json_response) { JSON.parse(response.body) }
-
def conflict_for_path(path)
get :conflict_for_path,
namespace_id: merge_request_with_conflicts.project.namespace.to_param,
@@ -826,7 +853,6 @@ describe Projects::MergeRequestsController do
end
context 'POST resolve_conflicts' do
- let(:json_response) { JSON.parse(response.body) }
let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha }
def resolve_conflicts(files)
@@ -1024,7 +1050,6 @@ describe Projects::MergeRequestsController do
let!(:forked) { create(:project) }
let!(:environment) { create(:environment, project: forked) }
let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') }
- let(:json_response) { JSON.parse(response.body) }
let(:admin) { create(:admin) }
let(:merge_request) do
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 32b0e42c3cd..19e948d8fb8 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -6,8 +6,8 @@ describe Projects::SnippetsController do
let(:user2) { create(:user) }
before do
- project.team << [user, :master]
- project.team << [user2, :master]
+ project.add_master(user)
+ project.add_master(user2)
end
describe 'GET #index' do
@@ -69,6 +69,86 @@ describe Projects::SnippetsController do
end
end
+ describe 'POST #create' do
+ def create_snippet(project, snippet_params = {})
+ sign_in(user)
+
+ project.add_developer(user)
+
+ post :create, {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the project is private' do
+ let(:private_project) { create(:project_empty_repo, :private) }
+
+ context 'when the snippet is public' do
+ it 'creates the snippet' do
+ expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+ end
+
+ context 'when the project is public' do
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'POST #mark_as_spam' do
+ let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ before do
+ allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+ stub_application_setting(akismet_enabled: true)
+ end
+
+ def mark_as_spam
+ admin = create(:admin)
+ create(:user_agent_detail, subject: snippet)
+ project.add_master(admin)
+ sign_in(admin)
+
+ post :mark_as_spam,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: snippet.id
+ end
+
+ it 'updates the snippet' do
+ mark_as_spam
+
+ expect(snippet.reload).not_to be_submittable_as_spam
+ end
+ end
+
%w[show raw].each do |action|
describe "GET ##{action}" do
context 'when the project snippet is private' do
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 99d0bcfa8d1..80f84a388ce 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -14,7 +14,8 @@ describe Projects::TemplatesController do
before do
project.add_user(user, Gitlab::Access::MASTER)
- project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
+ project.repository.commit_file(user, file_path_1, 'something valid',
+ message: 'test 3', branch_name: 'master', update: false)
end
describe '#show' do
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index d76fe9f580f..dadcb90cfc2 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -138,6 +138,65 @@ describe SnippetsController do
end
end
+ describe 'POST #create' do
+ def create_snippet(snippet_params = {})
+ sign_in(user)
+
+ post :create, {
+ personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
+
+ describe 'POST #mark_as_spam' do
+ let(:snippet) { create(:personal_snippet, :public, author: user) }
+
+ before do
+ allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+ stub_application_setting(akismet_enabled: true)
+ end
+
+ def mark_as_spam
+ admin = create(:admin)
+ create(:user_agent_detail, subject: snippet)
+ sign_in(admin)
+
+ post :mark_as_spam, id: snippet.id
+ end
+
+ it 'updates the snippet' do
+ mark_as_spam
+
+ expect(snippet.reload).not_to be_submittable_as_spam
+ end
+ end
+
%w(raw download).each do |action|
describe "GET #{action}" do
context 'when the personal snippet is private' do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 992580a6b34..715b2a27b30 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -106,6 +106,42 @@ FactoryGirl.define do
path { 'gitlabhq' }
test_repo
+
+ transient do
+ create_template nil
+ end
+
+ after :create do |project, evaluator|
+ TestEnv.copy_repo(project)
+
+ if evaluator.create_template
+ args = evaluator.create_template
+
+ project.add_user(args[:user], args[:access])
+
+ project.repository.commit_file(
+ args[:user],
+ ".gitlab/#{args[:path]}/bug.md",
+ 'something valid',
+ message: 'test 3',
+ branch_name: 'master',
+ update: false)
+ project.repository.commit_file(
+ args[:user],
+ ".gitlab/#{args[:path]}/template_test.md",
+ 'template_test',
+ message: 'test 1',
+ branch_name: 'master',
+ update: false)
+ project.repository.commit_file(
+ args[:user],
+ ".gitlab/#{args[:path]}/feature_proposal.md",
+ 'feature_proposal',
+ message: 'test 2',
+ branch_name: 'master',
+ update: false)
+ end
+ end
end
factory :forked_project_with_submodules, parent: :empty_project do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e177059d959..9d5ce876c29 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -9,8 +9,8 @@ describe 'Admin Builds' do
let(:pipeline) { create(:ci_pipeline) }
context 'All tab' do
- context 'when have builds' do
- it 'shows all builds' do
+ context 'when have jobs' do
+ it 'shows all jobs' do
create(:ci_build, pipeline: pipeline, status: :pending)
create(:ci_build, pipeline: pipeline, status: :running)
create(:ci_build, pipeline: pipeline, status: :success)
@@ -19,26 +19,26 @@ describe 'Admin Builds' do
visit admin_builds_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
- expect(page).to have_selector('.row-content-block', text: 'All builds')
+ expect(page).to have_selector('.row-content-block', text: 'All jobs')
expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all'
end
end
- context 'when have no builds' do
+ context 'when have no jobs' do
it 'shows a message' do
visit admin_builds_path
expect(page).to have_selector('.nav-links li.active', text: 'All')
- expect(page).to have_content 'No builds to show'
+ expect(page).to have_content 'No jobs to show'
expect(page).not_to have_link 'Cancel all'
end
end
end
context 'Pending tab' do
- context 'when have pending builds' do
- it 'shows pending builds' do
+ context 'when have pending jobs' do
+ it 'shows pending jobs' do
build1 = create(:ci_build, pipeline: pipeline, status: :pending)
build2 = create(:ci_build, pipeline: pipeline, status: :running)
build3 = create(:ci_build, pipeline: pipeline, status: :success)
@@ -55,22 +55,22 @@ describe 'Admin Builds' do
end
end
- context 'when have no builds pending' do
+ context 'when have no jobs pending' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
visit admin_builds_path(scope: :pending)
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
- expect(page).to have_content 'No builds to show'
+ expect(page).to have_content 'No jobs to show'
expect(page).not_to have_link 'Cancel all'
end
end
end
context 'Running tab' do
- context 'when have running builds' do
- it 'shows running builds' do
+ context 'when have running jobs' do
+ it 'shows running jobs' do
build1 = create(:ci_build, pipeline: pipeline, status: :running)
build2 = create(:ci_build, pipeline: pipeline, status: :success)
build3 = create(:ci_build, pipeline: pipeline, status: :failed)
@@ -87,22 +87,22 @@ describe 'Admin Builds' do
end
end
- context 'when have no builds running' do
+ context 'when have no jobs running' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :success)
visit admin_builds_path(scope: :running)
expect(page).to have_selector('.nav-links li.active', text: 'Running')
- expect(page).to have_content 'No builds to show'
+ expect(page).to have_content 'No jobs to show'
expect(page).not_to have_link 'Cancel all'
end
end
end
context 'Finished tab' do
- context 'when have finished builds' do
- it 'shows finished builds' do
+ context 'when have finished jobs' do
+ it 'shows finished jobs' do
build1 = create(:ci_build, pipeline: pipeline, status: :pending)
build2 = create(:ci_build, pipeline: pipeline, status: :running)
build3 = create(:ci_build, pipeline: pipeline, status: :success)
@@ -117,14 +117,14 @@ describe 'Admin Builds' do
end
end
- context 'when have no builds finished' do
+ context 'when have no jobs finished' do
it 'shows a message' do
create(:ci_build, pipeline: pipeline, status: :running)
visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
- expect(page).to have_content 'No builds to show'
+ expect(page).to have_content 'No jobs to show'
expect(page).to have_link 'Cancel all'
end
end
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 188d33e8ef4..c28bb0dcdae 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -141,6 +141,36 @@ describe 'Issue Boards', feature: true, js: true do
end
end
end
+
+ it 'resets assignee dropdown' do
+ page.within(first('.board')) do
+ first('.card').click
+ end
+
+ page.within('.assignee') do
+ click_link 'Edit'
+
+ wait_for_ajax
+
+ page.within('.dropdown-menu-user') do
+ click_link user.name
+
+ wait_for_vue_resource
+ end
+
+ expect(page).to have_content(user.name)
+ end
+
+ page.within(first('.board')) do
+ find('.card:nth-child(2)').click
+ end
+
+ page.within('.assignee') do
+ click_link 'Edit'
+
+ expect(page).not_to have_selector('.is-active')
+ end
+ end
end
context 'milestone' do
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
new file mode 100644
index 00000000000..d9be4e5dbdd
--- /dev/null
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+feature 'Dashboard shortcuts', feature: true, js: true do
+ before do
+ login_as :user
+ visit dashboard_projects_path
+ end
+
+ scenario 'Navigate to tabs' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('p')
+
+ ensure_active_main_tab('Projects')
+
+ find('body').native.send_key('g')
+ find('body').native.send_key('i')
+
+ ensure_active_main_tab('Issues')
+
+ find('body').native.send_key('g')
+ find('body').native.send_key('m')
+
+ ensure_active_main_tab('Merge Requests')
+ end
+
+ def ensure_active_main_tab(content)
+ expect(find('.nav-sidebar li.active')).to have_content(content)
+ end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 56f6cd2e095..511c95b758f 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -19,6 +19,10 @@ feature 'Environment', :feature do
visit_environment(environment)
end
+ scenario 'shows environment name' do
+ expect(page).to have_content(environment.name)
+ end
+
context 'without deployments' do
scenario 'does show no deployments' do
expect(page).to have_content('You don\'t have any deployments right now.')
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 72b984cfab8..c033b693213 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do
end
scenario 'does create a new pipeline' do
- expect(page).to have_content('Production')
+ expect(page).to have_content('production')
end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 31156fcf994..93139dc9e94 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
feature 'GFM autocomplete', feature: true, js: true do
include WaitForAjax
- let(:user) { create(:user, username: 'someone.special') }
+ let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) }
@@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
end
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys('')
+ find('#note_note').native.send_keys("@#{user.name[0...8]}")
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ wait_for_ajax
+
+ expect(find('#at-view-64')).to have_content(user.name)
+ end
+
it 'selects the first item for non-assignee dropdowns if a query is entered' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
new file mode 100644
index 00000000000..fc8515cfe9b
--- /dev/null
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe 'Group label on issue', :feature do
+ it 'renders link to the project issues page' do
+ group = create(:group)
+ project = create(:empty_project, :public, namespace: group)
+ feature = create(:group_label, group: group, title: 'feature')
+ issue = create(:labeled_issue, project: project, labels: [feature])
+ label_link = namespace_project_issues_path(
+ project.namespace,
+ project,
+ label_name: [feature.name]
+ )
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ link = find('.issuable-show-labels a')
+
+ expect(link[:href]).to eq(label_link)
+ end
+end
diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb
index a4d3053d10c..c0ab42c6822 100644
--- a/spec/features/issues/new_branch_button_spec.rb
+++ b/spec/features/issues/new_branch_button_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-feature 'Start new branch from an issue', feature: true do
+feature 'Start new branch from an issue', feature: true, js: true do
let!(:project) { create(:project) }
let!(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)}
@@ -11,7 +11,7 @@ feature 'Start new branch from an issue', feature: true do
login_as(user)
end
- it 'shows the new branch button', js: true do
+ it 'shows the new branch button' do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).to have_css('#new-branch .available')
@@ -34,16 +34,26 @@ feature 'Start new branch from an issue', feature: true do
visit namespace_project_issue_path(project.namespace, project, issue)
end
- it "hides the new branch button", js: true do
+ it "hides the new branch button" do
expect(page).to have_css('#new-branch .unavailable')
expect(page).not_to have_css('#new-branch .available')
expect(page).to have_content /1 Related Merge Request/
end
end
+
+ context 'when issue is confidential' do
+ it 'hides the new branch button' do
+ issue = create(:issue, :confidential, project: project)
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+
+ expect(page).not_to have_css('#new-branch')
+ end
+ end
end
- context "for visiters" do
- it 'shows no buttons', js: true do
+ context 'for visitors' do
+ it 'shows no buttons' do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).not_to have_css('#new-branch')
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 76bcfbe523a..ab7d89306db 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -25,6 +25,11 @@ feature 'Login', feature: true do
expect(current_path).to eq root_path
end
+
+ it 'does not show flash messages when login page' do
+ visit root_path
+ expect(page).not_to have_content('You need to sign in or sign up before continuing.')
+ end
end
describe 'with two-factor authentication' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 7e2907cd26f..d2f5c4afc93 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -50,7 +50,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
visit_merge_request(merge_request)
expect(page).not_to have_button 'Accept Merge Request'
- expect(page).to have_content('Please retry the build or push a new commit to fix the failure.')
+ expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
end
end
@@ -61,7 +61,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
visit_merge_request(merge_request)
expect(page).not_to have_button 'Accept Merge Request'
- expect(page).to have_content('Please retry the build or push a new commit to fix the failure.')
+ expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
end
end
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
new file mode 100644
index 00000000000..44a9b545ff8
--- /dev/null
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'toggler_behavior', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project, author: user) }
+ let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+ let(:fragment_id) { "#note_#{note.id}" }
+
+ before do
+ login_as :admin
+ project = merge_request.source_project
+ visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+ page.current_window.resize_to(1000, 300)
+ end
+
+ describe 'scroll position' do
+ it 'should be scrolled down to fragment' do
+ page_height = page.current_window.size[1]
+ page_scroll_y = page.evaluate_script("window.scrollY")
+ fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top")
+ expect(find('.js-toggle-content').visible?).to eq true
+ expect(find(fragment_id).visible?).to eq true
+ expect(fragment_position_top).to be >= page_scroll_y
+ expect(fragment_position_top).to be < (page_scroll_y + page_height)
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index cf56715f508..2b454b38231 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -11,7 +11,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do
let(:issuable) { create(:merge_request, source_project: project) }
- let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } }
+ let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
end
describe 'merge-request-only commands' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
new file mode 100644
index 00000000000..7d1805f5001
--- /dev/null
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -0,0 +1,34 @@
+require 'rails_helper'
+
+describe 'Merge request', :feature, :js do
+ include WaitForAjax
+
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+
+ visit new_namespace_project_merge_request_path(
+ project.namespace,
+ project,
+ merge_request: {
+ source_project_id: project.id,
+ target_project_id: project.id,
+ source_branch: 'feature',
+ target_branch: 'master'
+ }
+ )
+ end
+
+ it 'shows widget status after creating new merge request' do
+ click_button 'Submit merge request'
+
+ expect(find('.mr-state-widget')).to have_content('Checking ability to merge automatically')
+
+ wait_for_ajax
+
+ expect(page).to have_selector('.accept_merge_request')
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index b785b2f7704..fab2d532e06 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -89,7 +89,7 @@ describe 'Comments', feature: true do
end
end
- it 'should reset the edit note form textarea with the original content of the note if cancelled' do
+ it 'resets the edit note form textarea with the original content of the note if cancelled' do
within('.current-note-edit-form') do
fill_in 'note[note]', with: 'Some new content'
find('.btn-cancel').click
@@ -198,7 +198,7 @@ describe 'Comments', feature: true do
end
describe 'the note form' do
- it "shouldn't add a second form for same row" do
+ it "does not add a second form for same row" do
click_diff_line
is_expected.
@@ -206,7 +206,7 @@ describe 'Comments', feature: true do
count: 1)
end
- it 'should be removed when canceled' do
+ it 'is removed when canceled' do
is_expected.to have_css('.js-temp-notes-holder')
page.within("form[data-line-code='#{line_code}']") do
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 11d27feab0b..f7e0115643e 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -27,7 +27,7 @@ feature 'Builds', :feature do
visit namespace_project_builds_path(project.namespace, project, scope: :pending)
end
- it "shows Pending tab builds" do
+ it "shows Pending tab jobs" do
expect(page).to have_link 'Cancel running'
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content build.short_sha
@@ -42,7 +42,7 @@ feature 'Builds', :feature do
visit namespace_project_builds_path(project.namespace, project, scope: :running)
end
- it "shows Running tab builds" do
+ it "shows Running tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_link 'Cancel running'
expect(page).to have_content build.short_sha
@@ -57,20 +57,20 @@ feature 'Builds', :feature do
visit namespace_project_builds_path(project.namespace, project, scope: :finished)
end
- it "shows Finished tab builds" do
+ it "shows Finished tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
- expect(page).to have_content 'No builds to show'
+ expect(page).to have_content 'No jobs to show'
expect(page).to have_link 'Cancel running'
end
end
- context "All builds" do
+ context "All jobs" do
before do
project.builds.running_or_pending.each(&:success)
visit namespace_project_builds_path(project.namespace, project)
end
- it "shows All tab builds" do
+ it "shows All tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content build.short_sha
expect(page).to have_content build.ref
@@ -98,7 +98,7 @@ feature 'Builds', :feature do
end
describe "GET /:project/builds/:id" do
- context "Build from project" do
+ context "Job from project" do
before do
visit namespace_project_build_path(project.namespace, project, build)
end
@@ -111,7 +111,7 @@ feature 'Builds', :feature do
end
end
- context "Build from other project" do
+ context "Job from other project" do
before do
visit namespace_project_build_path(project.namespace, project, build2)
end
@@ -149,7 +149,7 @@ feature 'Builds', :feature do
context 'when expire date is defined' do
let(:expire_at) { Time.now + 7.days }
- context 'when user has ability to update build' do
+ context 'when user has ability to update job' do
it 'keeps artifacts when keep button is clicked' do
expect(page).to have_content 'The artifacts will be removed'
@@ -160,7 +160,7 @@ feature 'Builds', :feature do
end
end
- context 'when user does not have ability to update build' do
+ context 'when user does not have ability to update job' do
let(:user_access_level) { :guest }
it 'does not have keep button' do
@@ -197,8 +197,8 @@ feature 'Builds', :feature do
visit namespace_project_build_path(project.namespace, project, build)
end
- context 'when build has an initial trace' do
- it 'loads build trace' do
+ context 'when job has an initial trace' do
+ it 'loads job trace' do
expect(page).to have_content 'BUILD TRACE'
build.append_trace(' and more trace', 11)
@@ -242,32 +242,32 @@ feature 'Builds', :feature do
end
end
- context 'when build starts environment' do
+ context 'when job starts environment' do
let(:environment) { create(:environment, project: project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
- context 'build is successfull and has deployment' do
+ context 'job is successfull and has deployment' do
let(:deployment) { create(:deployment) }
let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
- it 'shows a link for the build' do
+ it 'shows a link for the job' do
visit namespace_project_build_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
end
- context 'build is complete and not successfull' do
+ context 'job is complete and not successfull' do
let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) }
- it 'shows a link for the build' do
+ it 'shows a link for the job' do
visit namespace_project_build_path(project.namespace, project, build)
expect(page).to have_link environment.name
end
end
- context 'build creates a new deployment' do
+ context 'job creates a new deployment' do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
@@ -281,7 +281,7 @@ feature 'Builds', :feature do
end
describe "POST /:project/builds/:id/cancel" do
- context "Build from project" do
+ context "Job from project" do
before do
build.run!
visit namespace_project_build_path(project.namespace, project, build)
@@ -295,7 +295,7 @@ feature 'Builds', :feature do
end
end
- context "Build from other project" do
+ context "Job from other project" do
before do
build.run!
visit namespace_project_build_path(project.namespace, project, build)
@@ -307,13 +307,13 @@ feature 'Builds', :feature do
end
describe "POST /:project/builds/:id/retry" do
- context "Build from project" do
+ context "Job from project" do
before do
build.run!
visit namespace_project_build_path(project.namespace, project, build)
click_link 'Cancel'
page.within('.build-header') do
- click_link 'Retry build'
+ click_link 'Retry job'
end
end
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index fe047e00409..36a80d7575d 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -7,7 +7,7 @@ feature 'User wants to edit a file', feature: true do
let(:user) { create(:user) }
let(:commit_params) do
{
- source_branch: project.default_branch,
+ start_branch: project.default_branch,
target_branch: project.default_branch,
commit_message: "Committing First Update",
file_path: ".gitignore",
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 a521ce50f35..64094af29c0 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
@@ -6,7 +6,8 @@ feature 'project owner creates a license file', feature: true, js: true do
let(:project_master) { create(:user) }
let(:project) { create(:project) }
background do
- project.repository.remove_file(project_master, 'LICENSE', 'Remove LICENSE', 'master')
+ project.repository.remove_file(project_master, 'LICENSE',
+ message: 'Remove LICENSE', branch_name: 'master')
project.team << [project_master, :master]
login_as(project_master)
visit namespace_project_path(project.namespace, project)
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 52d08982c7a..16dddb2a86b 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -74,6 +74,9 @@ feature 'Import/Export - project export integration test', feature: true, js: tr
Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the key and the
correspondent hash or model as the value.
+ Also, if the attribute is a generated unique token, please add it to RelationFactory::TOKEN_RESET_MODELS if it needs to be
+ reset (to prevent duplicate column problems while importing to the same instance).
+
IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
CURRENT_SPEC: #{__FILE__}
MSG
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 7655c2b351f..20cdfbae24f 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 6dae5c64b30..e90a033b8c4 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -18,8 +18,20 @@ feature 'issuable templates', feature: true, js: true do
let(:description_addition) { ' appending to description' }
background do
- project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
- project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
+ project.repository.commit_file(
+ user,
+ '.gitlab/issue_templates/bug.md',
+ template_content,
+ message: 'added issue template',
+ branch_name: 'master',
+ update: false)
+ project.repository.commit_file(
+ user,
+ '.gitlab/issue_templates/test.md',
+ longtemplate_content,
+ message: 'added issue template',
+ branch_name: 'master',
+ update: false)
visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title'
end
@@ -67,7 +79,13 @@ feature 'issuable templates', feature: true, js: true do
let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do
- project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
+ project.repository.commit_file(
+ user,
+ '.gitlab/issue_templates/bug.md',
+ template_content,
+ message: 'added issue template',
+ branch_name: 'master',
+ update: false)
visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title'
fill_in :'issue[description]', with: prior_description
@@ -86,7 +104,13 @@ feature 'issuable templates', feature: true, js: true do
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
background do
- project.repository.commit_file(user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+ project.repository.commit_file(
+ user,
+ '.gitlab/merge_request_templates/feature-proposal.md',
+ template_content,
+ message: 'added merge request template',
+ branch_name: 'master',
+ update: false)
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
@@ -111,7 +135,13 @@ feature 'issuable templates', feature: true, js: true do
fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
login_as fork_user
- project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
+ project.repository.commit_file(
+ fork_user,
+ '.gitlab/merge_request_templates/feature-proposal.md',
+ template_content,
+ message: 'added merge request template',
+ branch_name: 'master',
+ update: false)
visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index c9fa8315e79..97ce9cdfd87 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -20,7 +20,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can prioritize a group label', js: true do
visit namespace_project_labels_path(project.namespace, project)
- expect(page).to have_content('No prioritized labels yet')
+ expect(page).to have_content('Star labels to start sorting by priority')
page.within('.other-labels') do
all('.js-toggle-priority')[1].click
@@ -29,7 +29,7 @@ feature 'Prioritize labels', feature: true do
end
page.within('.prioritized-labels') do
- expect(page).not_to have_content('No prioritized labels yet')
+ expect(page).not_to have_content('Star labels to start sorting by priority')
expect(page).to have_content('feature')
end
end
@@ -55,7 +55,7 @@ feature 'Prioritize labels', feature: true do
scenario 'user can prioritize a project label', js: true do
visit namespace_project_labels_path(project.namespace, project)
- expect(page).to have_content('No prioritized labels yet')
+ expect(page).to have_content('Star labels to start sorting by priority')
page.within('.other-labels') do
first('.js-toggle-priority').click
@@ -64,7 +64,7 @@ feature 'Prioritize labels', feature: true do
end
page.within('.prioritized-labels') do
- expect(page).not_to have_content('No prioritized labels yet')
+ expect(page).not_to have_content('Star labels to start sorting by priority')
expect(page).to have_content('bug')
end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index abfc46601fb..b56e562b2b6 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,11 +1,13 @@
require "spec_helper"
feature "New project", feature: true do
- context "Visibility level selector" do
- let(:user) { create(:admin) }
+ let(:user) { create(:admin) }
- before { login_as(user) }
+ before do
+ login_as(user)
+ end
+ context "Visibility level selector" do
Gitlab::VisibilityLevel.options.each do |key, level|
it "sets selector to #{key}" do
stub_application_setting(default_project_visibility: level)
@@ -16,4 +18,16 @@ feature "New project", feature: true do
end
end
end
+
+ context 'Import project options' do
+ before do
+ visit new_project_path
+ end
+
+ it 'does not autocomplete sensitive git repo URL' do
+ autocomplete = find('#project_import_url')['autocomplete']
+
+ expect(autocomplete).to eq('off')
+ end
+ end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index e673ece37c3..0b5ccc8c515 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -66,8 +66,8 @@ describe 'Pipeline', :feature, :js do
context 'when pipeline has running builds' do
it 'shows a running icon and a cancel action for the running build' do
page.within('#ci-badge-deploy') do
- expect(page).to have_selector('.ci-status-icon-running')
- expect(page).to have_selector('.ci-action-icon-container .fa-ban')
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ expect(page).to have_selector('.js-icon-action-cancel')
expect(page).to have_content('deploy')
end
end
@@ -82,63 +82,63 @@ describe 'Pipeline', :feature, :js do
context 'when pipeline has successful builds' do
it 'shows the success icon and a retry action for the successful build' do
page.within('#ci-badge-build') do
- expect(page).to have_selector('.ci-status-icon-success')
+ expect(page).to have_selector('.js-ci-status-icon-success')
expect(page).to have_content('build')
end
page.within('#ci-badge-build .ci-action-icon-container') do
- expect(page).to have_selector('.ci-action-icon-container .fa-refresh')
+ expect(page).to have_selector('.js-icon-action-retry')
end
end
- it 'should be possible to retry the success build' do
+ it 'should be possible to retry the success job' do
find('#ci-badge-build .ci-action-icon-container').trigger('click')
- expect(page).not_to have_content('Retry build')
+ expect(page).not_to have_content('Retry job')
end
end
context 'when pipeline has failed builds' do
it 'shows the failed icon and a retry action for the failed build' do
page.within('#ci-badge-test') do
- expect(page).to have_selector('.ci-status-icon-failed')
+ expect(page).to have_selector('.js-ci-status-icon-failed')
expect(page).to have_content('test')
end
page.within('#ci-badge-test .ci-action-icon-container') do
- expect(page).to have_selector('.ci-action-icon-container .fa-refresh')
+ expect(page).to have_selector('.js-icon-action-retry')
end
end
it 'should be possible to retry the failed build' do
find('#ci-badge-test .ci-action-icon-container').trigger('click')
- expect(page).not_to have_content('Retry build')
+ expect(page).not_to have_content('Retry job')
end
end
- context 'when pipeline has manual builds' do
+ context 'when pipeline has manual jobs' do
it 'shows the skipped icon and a play action for the manual build' do
page.within('#ci-badge-manual-build') do
- expect(page).to have_selector('.ci-status-icon-manual')
+ expect(page).to have_selector('.js-ci-status-icon-manual')
expect(page).to have_content('manual')
end
page.within('#ci-badge-manual-build .ci-action-icon-container') do
- expect(page).to have_selector('.ci-action-icon-container .fa-play')
+ expect(page).to have_selector('.js-icon-action-play')
end
end
- it 'should be possible to play the manual build' do
+ it 'should be possible to play the manual job' do
find('#ci-badge-manual-build .ci-action-icon-container').trigger('click')
- expect(page).not_to have_content('Play build')
+ expect(page).not_to have_content('Play job')
end
end
- context 'when pipeline has external build' do
+ context 'when pipeline has external job' do
it 'shows the success icon and the generic comit status build' do
- expect(page).to have_selector('.ci-status-icon-success')
+ expect(page).to have_selector('.js-ci-status-icon-success')
expect(page).to have_content('jenkins')
expect(page).to have_link('jenkins', href: 'http://gitlab.com/status')
end
@@ -146,12 +146,12 @@ describe 'Pipeline', :feature, :js do
end
context 'page tabs' do
- it 'shows Pipeline and Builds tabs with link' do
+ it 'shows Pipeline and Jobs tabs with link' do
expect(page).to have_link('Pipeline')
- expect(page).to have_link('Builds')
+ expect(page).to have_link('Jobs')
end
- it 'shows counter in Builds tab' do
+ it 'shows counter in Jobs tab' do
expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
end
@@ -160,7 +160,7 @@ describe 'Pipeline', :feature, :js do
end
end
- context 'retrying builds' do
+ context 'retrying jobs' do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
@@ -170,7 +170,7 @@ describe 'Pipeline', :feature, :js do
end
end
- context 'canceling builds' do
+ context 'canceling jobs' do
it { expect(page).not_to have_selector('.ci-canceled') }
context 'when canceling' do
@@ -191,7 +191,7 @@ describe 'Pipeline', :feature, :js do
visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)
end
- it 'shows a list of builds' do
+ it 'shows a list of jobs' do
expect(page).to have_content('Test')
expect(page).to have_content(build_passed.id)
expect(page).to have_content('Deploy')
@@ -203,26 +203,26 @@ describe 'Pipeline', :feature, :js do
expect(page).to have_link('Play')
end
- it 'shows Builds tab pane as active' do
+ it 'shows jobs tab pane as active' do
expect(page).to have_css('#js-tab-builds.active')
end
context 'page tabs' do
- it 'shows Pipeline and Builds tabs with link' do
+ it 'shows Pipeline and Jobs tabs with link' do
expect(page).to have_link('Pipeline')
- expect(page).to have_link('Builds')
+ expect(page).to have_link('Jobs')
end
- it 'shows counter in Builds tab' do
+ it 'shows counter in Jobs tab' do
expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s)
end
- it 'shows Builds tab as active' do
+ it 'shows Jobs tab as active' do
expect(page).to have_css('li.js-builds-tab-link.active')
end
end
- context 'retrying builds' do
+ context 'retrying jobs' do
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
@@ -233,7 +233,7 @@ describe 'Pipeline', :feature, :js do
end
end
- context 'canceling builds' do
+ context 'canceling jobs' do
it { expect(page).not_to have_selector('.ci-canceled') }
context 'when canceling' do
@@ -244,7 +244,7 @@ describe 'Pipeline', :feature, :js do
end
end
- context 'playing manual build' do
+ context 'playing manual job' do
before do
within '.pipeline-holder' do
click_link('Play')
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 472491188c9..38fe2d92885 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -17,14 +17,15 @@ feature 'Ref switcher', feature: true, js: true do
page.within '.project-refs-form' do
input = find('input[type="search"]')
- input.set 'expand'
+ input.set 'binary'
+ wait_for_ajax
input.native.send_keys :down
input.native.send_keys :down
input.native.send_keys :enter
end
- expect(page).to have_title 'expand-collapse-files'
+ expect(page).to have_title 'binary-encoding'
end
it "user selects ref with special characters" do
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index 86a07b2c679..042a1ccab51 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -99,6 +99,15 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(select_element.all('option').count).to eq(3)
end
+ it 'shows an error alert with the error message if there is an error requesting teams' do
+ allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [[], 'test mattermost error message'] }
+
+ click_link 'Add to Mattermost'
+
+ expect(page).to have_selector('.alert')
+ expect(page).to have_content('test mattermost error message')
+ end
+
def stub_teams(count: 0)
teams = create_teams(count)
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index 4bfaa499272..034b75c2e51 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -11,41 +11,41 @@ feature 'Project settings > Merge Requests', feature: true, js: true do
login_as(user)
end
- context 'when Merge Request and Builds are initially enabled' do
+ context 'when Merge Request and Pipelines are initially enabled' do
before do
project.project_feature.update_attribute('merge_requests_access_level', ProjectFeature::ENABLED)
end
- context 'when Builds are initially enabled' do
+ context 'when Pipelines are initially enabled' do
before do
project.project_feature.update_attribute('builds_access_level', ProjectFeature::ENABLED)
visit edit_project_path(project)
end
scenario 'shows the Merge Requests settings' do
- expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Disabled', from: "project_project_feature_attributes_merge_requests_access_level"
- expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
- context 'when Builds are initially disabled' do
+ context 'when Pipelines are initially disabled' do
before do
project.project_feature.update_attribute('builds_access_level', ProjectFeature::DISABLED)
visit edit_project_path(project)
end
scenario 'shows the Merge Requests settings that do not depend on Builds feature' do
- expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_builds_access_level"
- expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
@@ -58,12 +58,12 @@ feature 'Project settings > Merge Requests', feature: true, js: true do
end
scenario 'does not show the Merge Requests settings' do
- expect(page).not_to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).not_to have_content('Only allow merge requests to be merged if all discussions are resolved')
select 'Everyone with access', from: "project_project_feature_attributes_merge_requests_access_level"
- expect(page).to have_content('Only allow merge requests to be merged if the build succeeds')
+ expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
expect(page).to have_content('Only allow merge requests to be merged if all discussions are resolved')
end
end
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 3850e930b6d..1b352be9331 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -171,7 +171,7 @@ describe 'Dashboard Todos', feature: true do
it 'links to the pipelines for the merge request' do
href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target)
- expect(page).to have_link "merge request #{todo.target.to_reference}", href
+ expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 468bcc7badc..eae097126ce 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -134,7 +134,7 @@ describe DiffHelper do
let(:new_pos) { 50 }
let(:text) { 'some_text' }
- it "should generate foldable top match line for inline view with empty text by default" do
+ it "generates 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
@@ -143,7 +143,7 @@ describe DiffHelper do
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
+ it "allows 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
@@ -152,7 +152,7 @@ describe DiffHelper do
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
+ it "generates match line for parallel view" do
output = diff_match_line old_pos, new_pos, text: text, view: :parallel
expect(output).to be_html_safe
@@ -162,7 +162,7 @@ describe DiffHelper do
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
+ it "allows 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
@@ -171,7 +171,7 @@ describe DiffHelper do
expect(output).not_to have_css 'td:nth-child(3)'
end
- it "should allow to generate only right match line for parallel view" do
+ it "allows 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
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index a4f08dc4af0..df71680e44c 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -115,6 +115,46 @@ describe IssuablesHelper do
end
end
+ describe '#issuable_reference' do
+ context 'when show_full_reference truthy' do
+ it 'display issuable full reference' do
+ assign(:show_full_reference, true)
+ issue = build_stubbed(:issue)
+
+ expect(helper.issuable_reference(issue)).to eql(issue.to_reference(full: true))
+ end
+ end
+
+ context 'when show_full_reference falsey' do
+ context 'when @group present' do
+ it 'display issuable reference to @group' do
+ project = build_stubbed(:project)
+
+ assign(:show_full_reference, nil)
+ assign(:group, project.namespace)
+
+ issue = build_stubbed(:issue)
+
+ expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project.namespace))
+ end
+ end
+
+ context 'when @project present' do
+ it 'display issuable reference to @project' do
+ project = build_stubbed(:project)
+
+ assign(:show_full_reference, nil)
+ assign(:group, nil)
+ assign(:project, project)
+
+ issue = build_stubbed(:issue)
+
+ expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project))
+ end
+ end
+ end
+ end
+
describe '#issuable_filter_present?' do
it 'returns true when any key is present' do
allow(helper).to receive(:params).and_return(
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index e51720f10ed..b7e547dc1f5 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -41,6 +41,11 @@ describe SearchHelper do
expect(search_autocomplete_opts("gro").size).to eq(1)
end
+ it "includes nested group" do
+ create(:group, :nested, name: 'foo').add_owner(user)
+ expect(search_autocomplete_opts('foo').size).to eq(1)
+ end
+
it "includes the user's projects" do
project = create(:empty_project, namespace: create(:namespace, owner: user))
expect(search_autocomplete_opts(project.name).size).to eq(1)
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
index 3cd419b37c9..fbd9bb9f0ff 100644
--- a/spec/javascripts/.eslintrc
+++ b/spec/javascripts/.eslintrc
@@ -22,9 +22,10 @@
},
"plugins": ["jasmine"],
"rules": {
- "prefer-arrow-callback": 0,
"func-names": 0,
"jasmine/no-suite-dupes": [1, "branch"],
- "jasmine/no-spec-dupes": [1, "branch"]
+ "jasmine/no-spec-dupes": [1, "branch"],
+ "no-console": 0,
+ "prefer-arrow-callback": 0
}
}
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 71446b9df61..f1bfd529983 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -113,7 +113,7 @@
});
});
describe('::getAwardUrl', function() {
- return it('should return the url for request', function() {
+ return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
});
});
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6
index 8d3e2237fda..7a399b307ad 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js.es6
@@ -56,3 +56,8 @@ const boardsMockInterceptor = (request, next) => {
status: 200
}));
};
+
+window.listObj = listObj;
+window.listObjDuplicate = listObjDuplicate;
+window.BoardsMockData = BoardsMockData;
+window.boardsMockInterceptor = boardsMockInterceptor;
diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6
index 20e11ca3738..239cd69dd3a 100644
--- a/spec/javascripts/environments/environment_spec.js.es6
+++ b/spec/javascripts/environments/environment_spec.js.es6
@@ -8,12 +8,12 @@
//= require ./mock_data
describe('Environment', () => {
- preloadFixtures('environments/environments');
+ preloadFixtures('static/environments/environments.html.raw');
let component;
beforeEach(() => {
- loadFixtures('environments/environments');
+ loadFixtures('static/environments/environments.html.raw');
});
describe('successfull request', () => {
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6
index 8ecd01f9a83..58f6fb96afb 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars */
+
const environmentsList = [
{
id: 31,
@@ -134,6 +134,8 @@ const environmentsList = [
},
];
+window.environmentsList = environmentsList;
+
const environment = {
id: 4,
name: 'production',
@@ -147,3 +149,5 @@ const environment = {
created_at: '2016-12-16T11:51:04.690Z',
updated_at: '2016-12-16T12:04:51.133Z',
};
+
+window.environment = environment;
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
new file mode 100644
index 00000000000..5eba4343a1d
--- /dev/null
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
@@ -0,0 +1,40 @@
+//= require filtered_search/dropdown_utils
+//= require filtered_search/filtered_search_tokenizer
+//= require filtered_search/filtered_search_dropdown
+//= require filtered_search/dropdown_user
+
+(() => {
+ describe('Dropdown User', () => {
+ describe('getSearchInput', () => {
+ let dropdownUser;
+
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
+
+ dropdownUser = new gl.DropdownUser();
+ });
+
+ it('should not return the double quote found in value', () => {
+ spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+ lastToken: {
+ value: '"johnny appleseed',
+ },
+ });
+
+ expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
+ });
+
+ it('should not return the single quote found in value', () => {
+ spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+ lastToken: {
+ value: '\'larry boy',
+ },
+ });
+
+ expect(dropdownUser.getSearchInput()).toBe('larry boy');
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
index c8b5c2b36ad..a508dacf7f0 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
@@ -23,6 +23,7 @@
`);
spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {});
+ spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml
index 1ea1725c561..59edc0396d2 100644
--- a/spec/javascripts/fixtures/environments/table.html.haml
+++ b/spec/javascripts/fixtures/environments/table.html.haml
@@ -3,7 +3,7 @@
%tr
%th Environment
%th Last deployment
- %th Build
+ %th Job
%th Commit
%th
%th
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index 06fa64b1b4e..4e7eed2767c 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -44,6 +44,7 @@
describe('Dropdown', function describeDropdown() {
preloadFixtures('static/gl_dropdown.html.raw');
+ loadJSONFixtures('projects.json');
function initDropDown(hasRemote, isFilterable) {
this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({
diff --git a/spec/javascripts/gl_form_spec.js.es6 b/spec/javascripts/gl_form_spec.js.es6
new file mode 100644
index 00000000000..b5f99483bfb
--- /dev/null
+++ b/spec/javascripts/gl_form_spec.js.es6
@@ -0,0 +1,122 @@
+/* global autosize */
+/*= require gl_form */
+/*= require autosize */
+/*= require lib/utils/text_utility */
+/*= require lib/utils/common_utils */
+
+describe('GLForm', () => {
+ const global = window.gl || (window.gl = {});
+ const GLForm = global.GLForm;
+
+ it('should be defined in the global scope', () => {
+ expect(GLForm).toBeDefined();
+ });
+
+ describe('when instantiated', function () {
+ beforeEach((done) => {
+ this.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
+ this.textarea = this.form.find('textarea');
+ spyOn($.prototype, 'off').and.returnValue(this.textarea);
+ spyOn($.prototype, 'on').and.returnValue(this.textarea);
+ spyOn($.prototype, 'css');
+ spyOn(window, 'autosize');
+
+ this.glForm = new GLForm(this.form);
+ setTimeout(() => {
+ $.prototype.off.calls.reset();
+ $.prototype.on.calls.reset();
+ $.prototype.css.calls.reset();
+ autosize.calls.reset();
+ done();
+ });
+ });
+
+ describe('.setupAutosize', () => {
+ beforeEach((done) => {
+ this.glForm.setupAutosize();
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ it('should register an autosize event handler on the textarea', () => {
+ expect($.prototype.off).toHaveBeenCalledWith('autosize:resized');
+ expect($.prototype.on).toHaveBeenCalledWith('autosize:resized', jasmine.any(Function));
+ });
+
+ it('should register a mouseup event handler on the textarea', () => {
+ expect($.prototype.off).toHaveBeenCalledWith('mouseup.autosize');
+ expect($.prototype.on).toHaveBeenCalledWith('mouseup.autosize', jasmine.any(Function));
+ });
+
+ it('should autosize the textarea', () => {
+ expect(autosize).toHaveBeenCalledWith(jasmine.any(Object));
+ });
+
+ it('should set the resize css property to vertical', () => {
+ expect($.prototype.css).toHaveBeenCalledWith('resize', 'vertical');
+ });
+ });
+
+ describe('.setHeightData', () => {
+ beforeEach(() => {
+ spyOn($.prototype, 'data');
+ spyOn($.prototype, 'outerHeight').and.returnValue(200);
+ this.glForm.setHeightData();
+ });
+
+ it('should set the height data attribute', () => {
+ expect($.prototype.data).toHaveBeenCalledWith('height', 200);
+ });
+
+ it('should call outerHeight', () => {
+ expect($.prototype.outerHeight).toHaveBeenCalled();
+ });
+ });
+
+ describe('.destroyAutosize', () => {
+ describe('when called', () => {
+ beforeEach(() => {
+ spyOn($.prototype, 'data');
+ spyOn($.prototype, 'outerHeight').and.returnValue(200);
+ spyOn(window, 'outerHeight').and.returnValue(400);
+ spyOn(autosize, 'destroy');
+
+ this.glForm.destroyAutosize();
+ });
+
+ it('should call outerHeight', () => {
+ expect($.prototype.outerHeight).toHaveBeenCalled();
+ });
+
+ it('should get data-height attribute', () => {
+ expect($.prototype.data).toHaveBeenCalledWith('height');
+ });
+
+ it('should call autosize destroy', () => {
+ expect(autosize.destroy).toHaveBeenCalledWith(this.textarea);
+ });
+
+ it('should set the data-height attribute', () => {
+ expect($.prototype.data).toHaveBeenCalledWith('height', 200);
+ });
+
+ it('should set the outerHeight', () => {
+ expect($.prototype.outerHeight).toHaveBeenCalledWith(200);
+ });
+
+ it('should set the css', () => {
+ expect($.prototype.css).toHaveBeenCalledWith('max-height', window.outerHeight);
+ });
+ });
+
+ it('should return undefined if the data-height equals the outerHeight', () => {
+ spyOn($.prototype, 'outerHeight').and.returnValue(200);
+ spyOn($.prototype, 'data').and.returnValue(200);
+ spyOn(autosize, 'destroy');
+ expect(this.glForm.destroyAutosize()).toBeUndefined();
+ expect(autosize.destroy).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js.es6
index 92a20687ec5..d3c37d39431 100644
--- a/spec/javascripts/helpers/class_spec_helper.js.es6
+++ b/spec/javascripts/helpers/class_spec_helper.js.es6
@@ -1,5 +1,3 @@
-/* eslint-disable no-unused-vars */
-
class ClassSpecHelper {
static itShouldBeAStaticMethod(base, method) {
return it('should be a static method', () => {
@@ -7,3 +5,5 @@ class ClassSpecHelper {
});
}
}
+
+window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/javascripts/issuable_time_tracker_spec.js.es6 b/spec/javascripts/issuable_time_tracker_spec.js.es6
index a1e979e8d09..c5671af235e 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js.es6
+++ b/spec/javascripts/issuable_time_tracker_spec.js.es6
@@ -4,7 +4,7 @@
//= require issuable/time_tracking/components/time_tracker
function initTimeTrackingComponent(opts) {
- fixture.set(`
+ setFixtures(`
<div>
<div id="mock-container"></div>
</div>
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index 1ce8f28e568..32c96e2a088 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -10,9 +10,9 @@
// IE11 will return a relative pathname while other browsers will return a full pathname.
// parseUrl uses an anchor element for parsing an url. With relative urls, the anchor
// element will create an absolute url relative to the current execution context.
- // The JavaScript test suite is executed at '/teaspoon' which will lead to an absolute
- // url starting with '/teaspoon'.
- expect(gl.utils.parseUrl('" test="asf"').pathname).toEqual('/teaspoon/%22%20test=%22asf%22');
+ // The JavaScript test suite is executed at '/' which will lead to an absolute url
+ // starting with '/'.
+ expect(gl.utils.parseUrl('" test="asf"').pathname).toContain('/%22%20test=%22asf%22');
});
});
@@ -42,9 +42,13 @@
});
describe('gl.utils.getParameterByName', () => {
+ beforeEach(() => {
+ window.history.pushState({}, null, '?scope=all&p=2');
+ });
+
it('should return valid parameter', () => {
- const value = gl.utils.getParameterByName('reporter');
- expect(value).toBe('Console');
+ const value = gl.utils.getParameterByName('scope');
+ expect(value).toBe('all');
});
it('should return invalid parameter', () => {
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index bf45100af03..6f1d6406897 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */
/*= require merge_request_widget */
+/*= require smart_interval */
/*= require lib/utils/datetime_utility */
(function() {
@@ -21,7 +22,11 @@
normal: "Build {{status}}"
},
gitlab_icon: "gitlab_logo.png",
- builds_path: "http://sampledomain.local/sampleBuildsPath"
+ ci_pipeline: 80,
+ ci_sha: "12a34bc5",
+ builds_path: "http://sampledomain.local/sampleBuildsPath",
+ commits_path: "http://sampledomain.local/commits",
+ pipeline_path: "http://sampledomain.local/pipelines"
};
this["class"] = new window.gl.MergeRequestWidget(this.opts);
});
@@ -118,10 +123,11 @@
});
});
- return describe('getCIStatus', function() {
+ describe('getCIStatus', function() {
beforeEach(function() {
this.ciStatusData = {
"title": "Sample MR title",
+ "pipeline": 80,
"sha": "12a34bc5",
"status": "success",
"coverage": 98
@@ -165,6 +171,22 @@
this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled();
});
+ it('should update the pipeline URL when the pipeline changes', function() {
+ var spy;
+ spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
+ this["class"].getCIStatus(false);
+ this.ciStatusData.pipeline += 1;
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalled();
+ });
+ it('should update the commit URL when the sha changes', function() {
+ var spy;
+ spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
+ this["class"].getCIStatus(false);
+ this.ciStatusData.sha = "9b50b99a";
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalled();
+ });
});
});
}).call(this);
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 0202c9ba85e..e562385a6c6 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -17,6 +17,8 @@
describe('Project Title', function() {
preloadFixtures('static/project_title.html.raw');
+ loadJSONFixtures('projects.json');
+
beforeEach(function() {
loadFixtures('static/project_title.html.raw');
return this.project = new Project();
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 942778229b5..3a01a534557 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -37,6 +37,8 @@
describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw';
preloadFixtures(fixtureName);
+ loadJSONFixtures('todos.json');
+
beforeEach(function() {
loadFixtures(fixtureName);
this.sidebar = new Sidebar;
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 386fc8f514e..e0a5a7927bb 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -11,9 +11,9 @@
beforeEach(function() {
loadFixtures(fixtureName);
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
- return this.shortcut = new ShortcutsIssuable();
+ this.shortcut = new ShortcutsIssuable();
});
- return describe('#replyWithSelectedText', function() {
+ describe('#replyWithSelectedText', function() {
var stubSelection;
// Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
stubSelection = function(html) {
@@ -24,56 +24,57 @@
};
};
beforeEach(function() {
- return this.selector = 'form.js-main-target-form textarea#note_note';
+ this.selector = 'form.js-main-target-form textarea#note_note';
});
describe('with empty selection', function() {
- return it('does nothing', function() {
- stubSelection('');
+ it('does not return an error', function() {
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe('');
+ expect($(this.selector).val()).toBe('');
+ });
+ it('triggers `input`', function() {
+ var focused = false;
+ $(this.selector).on('focus', function() {
+ focused = true;
+ });
+ this.shortcut.replyWithSelectedText();
+ expect(focused).toBe(true);
});
});
describe('with any selection', function() {
beforeEach(function() {
- return stubSelection('<p>Selected text.</p>');
+ stubSelection('<p>Selected text.</p>');
});
it('leaves existing input intact', function() {
$(this.selector).val('This text was already here.');
expect($(this.selector).val()).toBe('This text was already here.');
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
+ expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
});
it('triggers `input`', function() {
- var triggered;
- triggered = false;
+ var triggered = false;
$(this.selector).on('input', function() {
- return triggered = true;
+ triggered = true;
});
this.shortcut.replyWithSelectedText();
- return expect(triggered).toBe(true);
+ expect(triggered).toBe(true);
});
- return it('triggers `focus`', function() {
- var focused;
- focused = false;
- $(this.selector).on('focus', function() {
- return focused = true;
- });
+ it('triggers `focus`', function() {
this.shortcut.replyWithSelectedText();
- return expect(focused).toBe(true);
+ expect(document.activeElement).toBe(document.querySelector(this.selector));
});
});
describe('with a one-line selection', function() {
- return it('quotes the selection', function() {
+ it('quotes the selection', function() {
stubSelection('<p>This text has been selected.</p>');
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
+ expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
});
});
- return describe('with a multi-line selection', function() {
- return it('quotes the selected lines as a group', function() {
+ describe('with a multi-line selection', function() {
+ it('quotes the selected lines as a group', function() {
stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>");
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
+ expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
});
});
});
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index 80163fd72d3..0e2fb07ba7f 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -25,19 +25,20 @@
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form')
);
+
+ // bypass automatic form submission within renderAuthenticated
+ spyOn(this.component, 'renderAuthenticated').and.returnValue(true);
+
return this.component.start();
});
it('allows authenticating via a U2F device', function() {
- var authenticatedMessage, deviceResponse, inProgressMessage;
+ var inProgressMessage;
inProgressMessage = this.container.find("p");
expect(inProgressMessage.text()).toContain("Trying to communicate with your device");
this.u2fDevice.respondToAuthenticateRequest({
deviceData: "this is data from the device"
});
- authenticatedMessage = this.container.find("p");
- deviceResponse = this.container.find('#js-device-response');
- expect(authenticatedMessage.text()).toContain('We heard back from your U2F device. You have been authenticated.');
- return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
+ expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
});
return describe("errors", function() {
it("displays an error message", function() {
@@ -51,7 +52,7 @@
return expect(errorMessage.text()).toContain("There was a problem communicating with your device");
});
return it("allows retrying authentication after an error", function() {
- var authenticatedMessage, retryButton, setupButton;
+ var retryButton, setupButton;
setupButton = this.container.find("#js-login-u2f-device");
setupButton.trigger('click');
this.u2fDevice.respondToAuthenticateRequest({
@@ -64,8 +65,7 @@
this.u2fDevice.respondToAuthenticateRequest({
deviceData: "this is data from the device"
});
- authenticatedMessage = this.container.find("p");
- return expect(authenticatedMessage.text()).toContain("We heard back from your U2F device. You have been authenticated.");
+ expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
});
});
});
diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6
index 1a7f2bb5fb8..efb11211ce2 100644
--- a/spec/javascripts/vue_pagination/pagination_spec.js.es6
+++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6
@@ -1,7 +1,6 @@
//= require vue
//= require lib/utils/common_utils
//= require vue_pagination/index
-/* global fixture, gl */
describe('Pagination component', () => {
let component;
@@ -17,7 +16,7 @@ describe('Pagination component', () => {
};
it('should render and start at page 1', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
@@ -40,7 +39,7 @@ describe('Pagination component', () => {
});
it('should go to the previous page', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
@@ -61,7 +60,7 @@ describe('Pagination component', () => {
});
it('should go to the next page', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
@@ -82,7 +81,7 @@ describe('Pagination component', () => {
});
it('should go to the last page', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
@@ -103,7 +102,7 @@ describe('Pagination component', () => {
});
it('should go to the first page', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
@@ -124,7 +123,7 @@ describe('Pagination component', () => {
});
it('should do nothing', () => {
- fixture.set('<div class="test-pagination-container"></div>');
+ setFixtures('<div class="test-pagination-container"></div>');
component = new window.gl.VueGlPagination({
el: document.querySelector('.test-pagination-container'),
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index 81b9a513ce3..deaabceef1c 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -24,7 +24,7 @@ describe Banzai::CrossProjectReference, lib: true do
it 'returns the referenced project' do
project2 = double('referenced project')
- expect(Project).to receive(:find_with_namespace).
+ expect(Project).to receive(:find_by_full_path).
with('cross/reference').and_return(project2)
expect(project_from_ref('cross/reference')).to eq project2
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
new file mode 100644
index 00000000000..f85a5dcbd8b
--- /dev/null
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Banzai::Filter::PlantumlFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'should replace plantuml pre tag with img tag' do
+ stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
+ input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+ output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>'
+ doc = filter(input)
+
+ expect(doc.to_s).to eq output
+ end
+
+ it 'should not replace plantuml pre tag with img tag if disabled' do
+ stub_application_setting(plantuml_enabled: false)
+ input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+ output = '<pre class="plantuml"><code>Bob -&gt; Sara : Hello</code><pre></pre></pre>'
+ doc = filter(input)
+
+ expect(doc.to_s).to eq output
+ end
+
+ it 'should not replace plantuml pre tag with img tag if url is invalid' do
+ stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
+ input = '<pre class="plantuml"><code>Bob -> Sara : Hello</code><pre>'
+ output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> PlantUML Error: cannot connect to PlantUML server at "invalid"</pre></div></div>'
+ doc = filter(input)
+
+ expect(doc.to_s).to eq output
+ 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 f824e2e1efe..49349035b3b 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -4,6 +4,33 @@ module Ci
describe GitlabCiYamlProcessor, lib: true do
let(:path) { 'path' }
+ describe '#build_attributes' do
+ context 'Coverage entry' do
+ subject { described_class.new(config, path).build_attributes(:rspec) }
+
+ let(:config_base) { { rspec: { script: "rspec" } } }
+ let(:config) { YAML.dump(config_base) }
+
+ context 'when config has coverage set at the global scope' do
+ before do
+ config_base.update(coverage: '/\(\d+\.\d+\) covered/')
+ end
+
+ context "and 'rspec' job doesn't have coverage set" do
+ it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') }
+ end
+
+ context "but 'rspec' job also has coverage set" do
+ before do
+ config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/'
+ end
+
+ it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') }
+ end
+ end
+ end
+ end
+
describe "#builds_for_ref" do
let(:type) { 'test' }
@@ -21,6 +48,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {},
allow_failure: false,
@@ -435,6 +463,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
@@ -463,6 +492,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.5",
@@ -702,6 +732,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
@@ -913,6 +944,7 @@ module Ci
stage_idx: 1,
name: "normal_job",
commands: "test",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
@@ -958,6 +990,7 @@ module Ci
stage_idx: 0,
name: "job1",
commands: "execute-script-for-job",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
@@ -970,6 +1003,7 @@ module Ci
stage_idx: 0,
name: "job2",
commands: "execute-script-for-job",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f251c0dd25a..b234de4c772 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -58,58 +58,102 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
- it 'recognizes user lfs tokens' do
- user = create(:user)
- token = Gitlab::LfsToken.new(user).token
+ context 'while using LFS authenticate' do
+ it 'recognizes user lfs tokens' do
+ user = create(:user)
+ token = Gitlab::LfsToken.new(user).token
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
- expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
- end
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
+ expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+ end
- it 'recognizes deploy key lfs tokens' do
- key = create(:deploy_key)
- token = Gitlab::LfsToken.new(key).token
+ it 'recognizes deploy key lfs tokens' do
+ key = create(:deploy_key)
+ token = Gitlab::LfsToken.new(key).token
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
- end
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
+ end
- context "while using OAuth tokens as passwords" do
- it 'succeeds for OAuth tokens with the `api` scope' do
+ it 'does not try password auth before oauth' do
user = create(:user)
- application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
- token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
+ token = Gitlab::LfsToken.new(user).token
+
+ expect(gl_auth).not_to receive(:find_with_user_password)
+ gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
+ end
+ end
+
+ context 'while using OAuth tokens as passwords' do
+ let(:user) { create(:user) }
+ let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
+ let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
+
+ it 'succeeds for OAuth tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
- expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
+ expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
end
it 'fails for OAuth tokens with other scopes' do
- user = create(:user)
- application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
- token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "read_user")
+ token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
+
+ it 'does not try password auth before oauth' do
+ expect(gl_auth).not_to receive(:find_with_user_password)
+
+ gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
+ end
end
- context "while using personal access tokens as passwords" do
- it 'succeeds for personal access tokens with the `api` scope' do
- user = create(:user)
- personal_access_token = create(:personal_access_token, user: user, scopes: ['api'])
+ context 'while using personal access tokens as passwords' do
+ let(:user) { create(:user) }
+ let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
+ it 'succeeds for personal access tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
- expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
end
it 'fails for personal access tokens with other scopes' do
- user = create(:user)
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
+
+ it 'does not try password auth before personal access tokens' do
+ expect(gl_auth).not_to receive(:find_with_user_password)
+
+ gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
+ end
+ end
+
+ context 'while using regular user and password' do
+ it 'falls through lfs authentication' do
+ user = create(
+ :user,
+ username: 'normal_user',
+ password: 'my-secret',
+ )
+
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
+
+ it 'falls through oauth authentication when the username is oauth2' do
+ user = create(
+ :user,
+ username: 'oauth2',
+ password: 'my-secret',
+ )
+
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
end
it 'returns double nil for invalid credentials' do
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index 1e81eaef18c..b6e924d67be 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'displays the help message' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Available commands')
+ expect(subject[:text]).to start_with('Unknown command')
expect(subject[:text]).to match('/gitlab issue show')
end
end
@@ -34,47 +34,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'rejects the actions' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! That action is not allowed')
- end
- end
-
- context 'issue is successfully created' do
- let(:params) { { text: "issue create my new issue" } }
-
- before do
- project.team << [user, :master]
- end
-
- it 'presents the issue' do
- expect(subject[:text]).to match("my new issue")
- end
-
- it 'shows a link to the new issue' do
- expect(subject[:text]).to match(/\/issues\/\d+/)
- end
- end
-
- context 'searching for an issue' do
- let(:params) { { text: 'issue search find me' } }
- let!(:issue) { create(:issue, project: project, title: 'find me') }
-
- before do
- project.team << [user, :master]
- end
-
- context 'a single issue is found' do
- it 'presents the issue' do
- expect(subject[:text]).to match(issue.title)
- end
- end
-
- context 'multiple issues found' do
- let!(:issue2) { create(:issue, project: project, title: "someone find me") }
-
- it 'shows a link to the new issue' do
- expect(subject[:text]).to match(issue.title)
- expect(subject[:text]).to match(issue2.title)
- end
+ expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end
end
@@ -90,7 +50,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'and user can not create deployment' do
it 'returns action' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+ expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end
end
@@ -100,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do
end
it 'returns action' do
- expect(subject[:text]).to include('Deployment from staging to production started.')
+ expect(subject[:text]).to include('Deployment started from staging to production')
expect(subject[:response_type]).to be(:in_channel)
end
@@ -130,7 +90,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'IssueCreate is triggered' do
let(:params) { { text: 'issue create my title' } }
- it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) }
+ it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
end
context 'IssueSearch is triggered' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index bd8099c92da..b3358a32161 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
context 'if no environment is defined' do
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'does not execute an action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
end
end
@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
context 'without actions' do
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'does not execute an action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
end
end
@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
it 'returns success result' do
- expect(subject.type).to eq(:success)
- expect(subject.message).to include('Deployment from staging to production started')
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with('Deployment started from staging to production')
end
context 'when duplicate action exists' do
@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
it 'returns error' do
- expect(subject.type).to eq(:error)
- expect(subject.message).to include('Too many actions defined')
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq('Too many actions defined')
end
end
@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
name: 'teardown', environment: 'production')
end
- it 'returns success result' do
- expect(subject.type).to eq(:success)
- expect(subject.message).to include('Deployment from staging to production started')
+ it 'returns the success message' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with('Deployment started from staging to production')
end
end
end
diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
index 6c71e79ff6d..84c22328064 100644
--- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ChatCommands::IssueCreate, service: true do
+describe Gitlab::ChatCommands::IssueNew, service: true do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
it 'creates the issue' do
expect { subject }.to change { project.issues.count }.by(1)
- expect(subject.title).to eq('bird is the word')
+ expect(subject[:response_type]).to be(:in_channel)
end
end
@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
expect { subject }.to change { project.issues.count }.by(1)
end
end
+
+ context 'issue cannot be created' do
+ let!(:issue) { create(:issue, project: project, title: 'bird is the word') }
+ let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
+
+ it 'displays the errors' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("- Title is too long")
+ end
+ end
end
describe '.match' do
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
index 24c06a967fa..551ccb79a58 100644
--- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueSearch, service: true do
describe '#execute' do
- let!(:issue) { create(:issue, title: 'find me') }
+ let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
- let(:project) { issue.project }
+ let(:project) { create(:empty_project) }
let(:user) { issue.author }
let(:regex_match) { described_class.match("issue search find") }
@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
context 'when the user has no access' do
it 'only returns the open issues' do
- expect(subject).not_to include(confidential)
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
end
end
@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
end
it 'returns all results' do
- expect(subject).to include(confidential, issue)
+ expect(subject).to have_key(:attachments)
+ expect(subject[:text]).to eq("Here are the 2 issues I found:")
end
end
context 'without hits on the query' do
it 'returns an empty collection' do
- expect(subject).to be_empty
+ expect(subject[:text]).to match("not found")
end
end
end
diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
index 2eab73e49e5..1f20d0a44ce 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueShow, service: true do
describe '#execute' do
- let(:issue) { create(:issue) }
- let(:project) { issue.project }
+ let(:issue) { create(:issue, project: project) }
+ let(:project) { create(:empty_project) }
let(:user) { issue.author }
let(:regex_match) { described_class.match("issue show #{issue.iid}") }
@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
end
context 'the issue exists' do
+ let(:title) { subject[:attachments].first[:title] }
+
it 'returns the issue' do
- expect(subject.iid).to be issue.iid
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(title).to start_with(issue.title)
end
context 'when its reference is given' do
let(:regex_match) { described_class.match("issue show #{issue.to_reference}") }
it 'shows the issue' do
- expect(subject.iid).to be issue.iid
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(title).to start_with(issue.title)
end
end
end
@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
context 'the issue does not exist' do
let(:regex_match) { described_class.match("issue show 2343242") }
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns not found" do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
end
end
end
- describe 'self.match' do
+ describe '.match' do
it 'matches the iid' do
match = described_class.match("issue show 123")
expect(match[:iid]).to eq("123")
end
+
+ it 'accepts a reference' do
+ match = described_class.match("issue show #{Issue.reference_prefix}123")
+
+ expect(match[:iid]).to eq("123")
+ end
end
end
diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
new file mode 100644
index 00000000000..ae41d75ab0c
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Access do
+ describe '#access_denied' do
+ subject { described_class.new.access_denied }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'displays an error message' do
+ expect(subject[:text]).to match("is not allowed")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ describe '#not_found' do
+ subject { described_class.new.not_found }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user the resource was not found' do
+ expect(subject[:text]).to match("not found!")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ describe '#authorize' do
+ context 'with an authorization URL' do
+ subject { described_class.new('http://authorize.me').authorize }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user to authorize' do
+ expect(subject[:text]).to match("connect your GitLab account")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ context 'without authorization url' do
+ subject { described_class.new.authorize }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user to authorize' do
+ expect(subject[:text]).to match("Couldn't identify you")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
new file mode 100644
index 00000000000..dc2dd300072
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Deploy do
+ let(:build) { create(:ci_build) }
+
+ describe '#present' do
+ subject { described_class.new(build).present('staging', 'prod') }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'messages the channel of the deploy' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with("Deployment started from staging to prod")
+ end
+ end
+
+ describe '#no_actions' do
+ subject { described_class.new(nil).no_actions }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'tells the user there is no action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
+ end
+ end
+
+ describe '#too_many_actions' do
+ subject { described_class.new([]).too_many_actions }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'tells the user there is no action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("Too many actions defined")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
new file mode 100644
index 00000000000..17fcdbc2452
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueNew do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
new file mode 100644
index 00000000000..ec6d3e34a96
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueSearch do
+ let(:project) { create(:empty_project) }
+ let(:message) { subject[:text] }
+
+ before { create_list(:issue, 2, project: project) }
+
+ subject { described_class.new(project.issues).present }
+
+ it 'formats the message correct' do
+ is_expected.to have_key(:text)
+ is_expected.to have_key(:status)
+ is_expected.to have_key(:response_type)
+ is_expected.to have_key(:attachments)
+ end
+
+ it 'shows a list of results' do
+ expect(subject[:response_type]).to be(:ephemeral)
+
+ expect(message).to start_with("Here are the 2 issues I found")
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
new file mode 100644
index 00000000000..5b678d31fce
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueShow do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+
+ context 'with upvotes' do
+ before do
+ create(:award_emoji, :upvote, awardable: issue)
+ end
+
+ it 'shows the upvote count' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(attachment[:text]).to start_with("**Open** · :+1: 1")
+ end
+ end
+
+ context 'confidential issue' do
+ let(:issue) { create(:issue, project: project) }
+
+ it 'shows an ephemeral response' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(attachment[:text]).to start_with("**Open**")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
new file mode 100644
index 00000000000..4c6bd859552
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Coverage do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validations' do
+ context "when entry config value doesn't have the surrounding '/'" do
+ let(:config) { 'Code coverage: \d+\.\d+' }
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to include(/coverage config must be a regular expression/) }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context "when entry config value has the surrounding '/'" do
+ let(:config) { '/Code coverage: \d+\.\d+/' }
+
+ describe '#value' do
+ subject { entry.value }
+ it { is_expected.to eq(config[1...-1]) }
+ end
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to be_empty }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'when entry value is not valid' do
+ let(:config) { '(malformed regexp' }
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to include(/coverage config must be a regular expression/) }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index e64c8d46bd8..d4f1780b174 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -4,12 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do
let(:global) { described_class.new(hash) }
describe '.nodes' do
- it 'can contain global config keys' do
- expect(described_class.nodes).to include :before_script
+ it 'returns a hash' do
+ expect(described_class.nodes).to be_a(Hash)
end
- it 'returns a hash' do
- expect(described_class.nodes).to be_a Hash
+ context 'when filtering all the entry/node names' do
+ it 'contains the expected node names' do
+ node_names = described_class.nodes.keys
+ expect(node_names).to match_array(%i[before_script image services
+ after_script variables stages
+ types cache coverage])
+ end
end
end
@@ -35,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end
it 'creates node object for each entry' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'creates node object using valid class' do
@@ -176,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do
it 'instantizes all nodes' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'contains unspecified nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index fc9b8b86dc4..d20f4ec207d 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -3,6 +3,20 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
+ describe '.nodes' do
+ context 'when filtering all the entry/node names' do
+ subject { described_class.nodes.keys }
+
+ let(:result) do
+ %i[before_script script stage type after_script cache
+ image services only except variables artifacts
+ environment coverage]
+ end
+
+ it { is_expected.to match_array result }
+ end
+ end
+
describe 'validations' do
before { entry.compose! }
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index b3c07347de1..8ad9b7cdf07 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Status::Build::Cancelable do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'ban' }
+ it { expect(subject.action_icon).to eq 'icon_action_cancel' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index f1b50a59ce6..f3e72ea1796 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -44,7 +44,7 @@ describe Gitlab::Ci::Status::Build::Play do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'play' }
+ it { expect(subject.action_icon).to eq 'icon_action_play' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 62036f8ec5d..2db0f8d29bd 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -62,7 +62,7 @@ describe Gitlab::Ci::Status::Build::Retryable do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'refresh' }
+ it { expect(subject.action_icon).to eq 'icon_action_retry' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 597e02e86e4..41c2b624774 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Status::Build::Stop do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'stop' }
+ it { expect(subject.action_icon).to eq 'icon_action_stop' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 1e21270d928..5893485634d 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with a diff file" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
- it 'should return Gitlab::Diff::Line elements' do
+ it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end
- it 'should not modify "match" lines' do
+ it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
@@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with diff lines" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
- it 'should return Gitlab::Diff::Line elements' do
+ it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end
- it 'should not modify "match" lines' do
+ it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index fe5fa048413..0f779339c54 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
subject { described_class.new(diff_file) }
describe '#parallelize' do
- it 'should return an array of arrays containing the parsed diff' do
+ it 'returns an array of arrays containing the parsed diff' do
diff_lines = diff_file.highlighted_diff_lines
expected = [
# Unchanged lines
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index f5822fed37c..8e3e4034c8f 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -99,7 +99,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
Files::CreateService.new(
project,
current_user,
- source_branch: branch_name,
+ start_branch: branch_name,
target_branch: branch_name,
commit_message: "Create file",
file_path: file_name,
@@ -112,7 +112,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
Files::UpdateService.new(
project,
current_user,
- source_branch: branch_name,
+ start_branch: branch_name,
target_branch: branch_name,
commit_message: "Update file",
file_path: file_name,
@@ -125,7 +125,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
Files::DeleteService.new(
project,
current_user,
- source_branch: branch_name,
+ start_branch: branch_name,
target_branch: branch_name,
commit_message: "Delete file",
file_path: file_name
@@ -1640,7 +1640,9 @@ describe Gitlab::Diff::PositionTracer, lib: true do
}
merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project)
- repository.merge(current_user, merge_request, options)
+
+ repository.merge(current_user, merge_request.diff_head_sha, merge_request, options)
+
project.commit(branch_name)
end
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index b080be62b34..116ab16ae74 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -209,7 +209,13 @@ describe Gitlab::GitAccess, lib: true do
stub_git_hooks
project.repository.add_branch(user, unprotected_branch, 'feature')
target_branch = project.repository.lookup('feature')
- source_branch = project.repository.commit_file(user, FFaker::InternetSE.login_user_name, FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.sentence, unprotected_branch, false)
+ source_branch = project.repository.commit_file(
+ user,
+ FFaker::InternetSE.login_user_name,
+ FFaker::HipsterIpsum.paragraph,
+ message: FFaker::HipsterIpsum.sentence,
+ branch_name: unprotected_branch,
+ update: false)
rugged = project.repository.rugged
author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index fadfe4d378e..e177d883158 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do
Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
end
- it 'should properly highlight all the lines' do
+ it 'highlights all the lines properly' do
expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
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})
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7fb6829f582..20241d4d63e 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -52,6 +52,7 @@ snippets:
- project
- notes
- award_emoji
+- user_agent_detail
releases:
- project
project_members:
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 0b7984d6ca9..f2cb028206f 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -92,5 +92,29 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
+
+ context 'importer same as group member' do
+ let(:user2) { create(:admin, authorized_projects_populated: true) }
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user2, project: project)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ end
+
+ it 'maps the project member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+
+ it 'maps the project member if it already exists' do
+ project.add_master(user2)
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 2c0750c3377..2e9f60432b4 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6981,11 +6981,16 @@
]
}
],
- "variables": [
-
- ],
"triggers": [
-
+ {
+ "id": 123,
+ "token": "cdbfasdf44a5958c83654733449e585",
+ "project_id": null,
+ "deleted_at": null,
+ "created_at": "2017-01-16T15:25:28.637Z",
+ "updated_at": "2017-01-16T15:25:28.637Z",
+ "gl_project_id": 123
+ }
],
"deploy_keys": [
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 4b07fa53bf5..40d7d59f03b 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -197,6 +197,20 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(restored_project_json).to be true
end
end
+
+ context 'tokens are regenerated' do
+ before do
+ restored_project_json
+ end
+
+ it 'has a new CI trigger token' do
+ expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ end
+
+ it 'has a new CI build token' do
+ expect(Ci::Build.where(token: 'abcd')).to be_empty
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index db0084d6823..57e412b0cef 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -55,8 +55,8 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
expect(created_object.project_id).to eq(project.id)
end
- it 'has a token' do
- expect(created_object.token).to eq(token)
+ it 'has a nil token' do
+ expect(created_object.token).to eq(nil)
end
context 'original service exists' do
@@ -178,4 +178,15 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do
expect(created_object.author).to eq(new_user)
end
end
+
+ context 'encrypted attributes' do
+ let(:relation_sym) { 'Ci::Variable' }
+ let(:relation_hash) do
+ create(:ci_variable).as_json
+ end
+
+ it 'has no value for the encrypted attribute' do
+ expect(created_object.value).to be_nil
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 493bc2db21a..95b230e4f5c 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -222,6 +222,7 @@ CommitStatus:
- queued_at
- token
- lock_version
+- coverage_regex
Ci::Variable:
- id
- project_id
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index b9d12c3c24c..9dd997aa7dc 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
- it 'should block user in GitLab' do
+ it 'blocks user in GitLab' do
expect(access).to receive(:block_user).with(user, 'does not exist anymore')
access.allowed?
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 45cec65a284..1335a2b8f35 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -4,16 +4,14 @@ describe Gitlab::Template::IssueTemplate do
subject { described_class }
let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:file_path_1) { '.gitlab/issue_templates/bug.md' }
- let(:file_path_2) { '.gitlab/issue_templates/template_test.md' }
- let(:file_path_3) { '.gitlab/issue_templates/feature_proposal.md' }
-
- before do
- project.add_user(user, Gitlab::Access::MASTER)
- project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
- project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
- project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
+
+ let(:project) do
+ create(:project,
+ :repository,
+ create_template: {
+ user: user,
+ access: Gitlab::Access::MASTER,
+ path: 'issue_templates' })
end
describe '.all' do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index ae51b79be22..320b870309a 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -4,16 +4,14 @@ describe Gitlab::Template::MergeRequestTemplate do
subject { described_class }
let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:file_path_1) { '.gitlab/merge_request_templates/bug.md' }
- let(:file_path_2) { '.gitlab/merge_request_templates/template_test.md' }
- let(:file_path_3) { '.gitlab/merge_request_templates/feature_proposal.md' }
-
- before do
- project.add_user(user, Gitlab::Access::MASTER)
- project.repository.commit_file(user, file_path_1, "something valid", "test 3", "master", false)
- project.repository.commit_file(user, file_path_2, "template_test", "test 1", "master", false)
- project.repository.commit_file(user, file_path_3, "feature_proposal", "test 2", "master", false)
+
+ let(:project) do
+ create(:project,
+ :repository,
+ create_template: {
+ user: user,
+ access: Gitlab::Access::MASTER,
+ path: 'merge_request_templates' })
end
describe '.all' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 47cd5075a7d..4080092405d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -221,6 +221,47 @@ describe Ci::Build, :models do
end
end
+ describe '#coverage_regex' do
+ subject { build.coverage_regex }
+
+ context 'when project has build_coverage_regex set' do
+ let(:project_regex) { '\(\d+\.\d+\) covered' }
+
+ before do
+ project.build_coverage_regex = project_regex
+ end
+
+ context 'and coverage_regex attribute is not set' do
+ it { is_expected.to eq(project_regex) }
+ end
+
+ context 'but coverage_regex attribute is also set' do
+ let(:build_regex) { 'Code coverage: \d+\.\d+' }
+
+ before do
+ build.coverage_regex = build_regex
+ end
+
+ it { is_expected.to eq(build_regex) }
+ end
+ end
+
+ context 'when neither project nor build has coverage regex set' do
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#update_coverage' do
+ context "regarding coverage_regex's value," do
+ it "saves the correct extracted coverage value" do
+ build.coverage_regex = '\(\d+.\d+\%\) covered'
+ allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' }
+ expect(build).to receive(:update_attributes).with(coverage: 98.29) { true }
+ expect(build.update_coverage).to be true
+ end
+ end
+ end
+
describe 'deployment' do
describe '#last_deployment' do
subject { build.last_deployment }
@@ -443,11 +484,11 @@ describe Ci::Build, :models do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
subject { build.erased? }
- context 'build has not been erased' do
+ context 'job has not been erased' do
it { is_expected.to be_falsey }
end
- context 'build has been erased' do
+ context 'job has been erased' do
before do
build.erase
end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 3b7cc7d9e2e..9053485939e 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -27,15 +27,13 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
+ issue = create(:issue, project: project)
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(issue, message: "Closes nothing")
+ create_commit_referencing_issue(issue)
+ create_merge_request_closing_issue(issue, message: "Closes nothing")
- merge_merge_requests_closing_issue(issue)
- deploy_master
- end
+ merge_merge_requests_closing_issue(issue)
+ deploy_master
expect(subject[:code].median).to be_nil
end
@@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
+ issue = create(:issue, project: project)
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(issue, message: "Closes nothing")
+ create_commit_referencing_issue(issue)
+ create_merge_request_closing_issue(issue, message: "Closes nothing")
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:code].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 5c73edbbc53..fc7d18bd40e 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do
context "when a regular label (instead of a list label) is added to the issue" do
it "returns nil" do
- 5.times do
- regular_label = create(:label)
- issue = create(:issue, project: project)
- issue.update(label_ids: [regular_label.id])
+ regular_label = create(:label)
+ issue = create(:issue, project: project)
+ issue.update(label_ids: [regular_label.id])
- create_merge_request_closing_issue(issue)
- merge_merge_requests_closing_issue(issue)
- end
+ create_merge_request_closing_issue(issue)
+ merge_merge_requests_closing_issue(issue)
expect(subject[:issue].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 591bbdddf55..b9fe492fe2c 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -21,7 +21,13 @@ describe 'CycleAnalytics#production', feature: true do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
- sha = context.project.repository.commit_file(context.user, context.random_git_name, "content", "commit message", 'master', false)
+ sha = context.project.repository.commit_file(
+ context.user,
+ context.random_git_name,
+ 'content',
+ message: 'commit message',
+ branch_name: 'master',
+ update: false)
context.project.repository.commit(sha)
context.deploy_master
@@ -29,11 +35,9 @@ describe 'CycleAnalytics#production', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do
- 5.times do
- merge_request = create(:merge_request)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master
- end
+ merge_request = create(:merge_request)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master
expect(subject[:production].median).to be_nil
end
@@ -41,12 +45,10 @@ describe 'CycleAnalytics#production', feature: true do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(environment: 'staging')
- end
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master(environment: 'staging')
expect(subject[:production].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 33d2c0a7416..febb18c9884 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do
context "when a regular merge request (that doesn't close the issue) is created and merged" do
it "returns nil" do
- 5.times do
- MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
- end
+ MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
expect(subject[:review].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 00693d67475..9a024d533a1 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -29,10 +29,10 @@ describe 'CycleAnalytics#staging', feature: true do
sha = context.project.repository.commit_file(
context.user,
context.random_git_name,
- "content",
- "commit message",
- 'master',
- false)
+ 'content',
+ message: 'commit message',
+ branch_name: 'master',
+ update: false)
context.project.repository.commit(sha)
context.deploy_master
@@ -40,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do
- 5.times do
- merge_request = create(:merge_request)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master
- end
+ merge_request = create(:merge_request)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master
expect(subject[:staging].median).to be_nil
end
@@ -52,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(environment: 'staging')
- end
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master(environment: 'staging')
expect(subject[:staging].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index f857ea6cbec..c2ba012a0e6 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -24,16 +24,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.succeed!
+ pipeline.run!
+ pipeline.succeed!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
@@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is not for a merge request" do
it "returns nil" do
- 5.times do
- pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
- pipeline.run!
- pipeline.succeed!
- end
+ pipeline.run!
+ pipeline.succeed!
expect(subject[:test].median).to be_nil
end
@@ -54,16 +50,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is dropped (failed)" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.drop!
+ pipeline.run!
+ pipeline.drop!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
@@ -71,16 +65,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is cancelled" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.cancel!
+ pipeline.run!
+ pipeline.cancel!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index b2e06541e66..eba392044bf 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -288,6 +288,11 @@ describe Environment, models: true do
"1-foo" => "env-1-foo" + SUFFIX,
"1/foo" => "env-1-foo" + SUFFIX,
"foo-" => "foo" + SUFFIX,
+ "foo--bar" => "foo-bar" + SUFFIX,
+ "foo**bar" => "foo-bar" + SUFFIX,
+ "*-foo" => "env-foo" + SUFFIX,
+ "staging-12345678-" => "staging-12345678" + SUFFIX,
+ "staging-12345678-01234567" => "staging-12345678" + SUFFIX,
}.each do |name, matcher|
it "returns a slug matching #{matcher}, given #{name}" do
slug = described_class.new(name: name).generate_slug
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 40c0a75c364..bba9058f394 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -23,21 +23,74 @@ describe Issue, models: true do
end
describe '#to_reference' do
- let(:project) { build(:empty_project, name: 'sample-project') }
- let(:issue) { build(:issue, iid: 1, project: project) }
+ let(:namespace) { build(:namespace, path: 'sample-namespace') }
+ let(:project) { build(:empty_project, name: 'sample-project', namespace: namespace) }
+ let(:issue) { build(:issue, iid: 1, project: project) }
+ let(:group) { create(:group, name: 'Group', path: 'sample-group') }
+
+ context 'when nil argument' do
+ it 'returns issue id' do
+ expect(issue.to_reference).to eq "#1"
+ end
+ end
+
+ context 'when full is true' do
+ it 'returns complete path to the issue' do
+ expect(issue.to_reference(full: true)).to eq 'sample-namespace/sample-project#1'
+ expect(issue.to_reference(project, full: true)).to eq 'sample-namespace/sample-project#1'
+ expect(issue.to_reference(group, full: true)).to eq 'sample-namespace/sample-project#1'
+ end
+ end
- it 'returns a String reference to the object' do
- expect(issue.to_reference).to eq "#1"
+ context 'when same project argument' do
+ it 'returns issue id' do
+ expect(issue.to_reference(project)).to eq("#1")
+ end
end
- it 'returns a String reference with the full path' do
- expect(issue.to_reference(full: true)).to eq(project.path_with_namespace + '#1')
+ context 'when cross namespace project argument' do
+ let(:another_namespace_project) { create(:empty_project, name: 'another-project') }
+
+ it 'returns complete path to the issue' do
+ expect(issue.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project#1'
+ end
end
it 'supports a cross-project reference' do
another_project = build(:empty_project, name: 'another-project', namespace: project.namespace)
expect(issue.to_reference(another_project)).to eq "sample-project#1"
end
+
+ context 'when same namespace / cross-project argument' do
+ let(:another_project) { create(:empty_project, namespace: namespace) }
+
+ it 'returns path to the issue with the project name' do
+ expect(issue.to_reference(another_project)).to eq 'sample-project#1'
+ end
+ end
+
+ context 'when different namespace / cross-project argument' do
+ let(:another_namespace) { create(:namespace, path: 'another-namespace') }
+ let(:another_project) { create(:empty_project, path: 'another-project', namespace: another_namespace) }
+
+ it 'returns full path to the issue' do
+ expect(issue.to_reference(another_project)).to eq 'sample-namespace/sample-project#1'
+ end
+ end
+
+ context 'when argument is a namespace' do
+ context 'with same project path' do
+ it 'returns path to the issue with the project name' do
+ expect(issue.to_reference(namespace)).to eq 'sample-project#1'
+ end
+ end
+
+ context 'with different project path' do
+ it 'returns full path to the issue' do
+ expect(issue.to_reference(group)).to eq 'sample-namespace/sample-project#1'
+ end
+ end
+ end
end
describe '#is_being_reassigned?' do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 90d14c2c0b9..e4be0aba7a6 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -117,7 +117,7 @@ describe ProjectMember, models: true do
users = create_list(:user, 2)
described_class.add_users_to_projects(
- [projects.first.id, projects.second],
+ [projects.first.id, projects.second.id],
[users.first.id, users.second],
described_class::MASTER)
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index c879edddfdd..98f3d420c8a 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -113,10 +113,7 @@ describe MattermostSlashCommandsService, :models do
end
it 'shows error messages' do
- teams, message = subject
-
- expect(teams).to be_empty
- expect(message).to eq('Failed to get team list.')
+ expect(subject).to eq([[], "Failed to get team list."])
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 646a1311462..48b085781e7 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -282,9 +282,10 @@ describe Project, models: true do
end
describe '#to_reference' do
- let(:owner) { create(:user, name: 'Gitlab') }
+ let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
- let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) }
+ let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) }
+ let(:group) { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
context 'when nil argument' do
it 'returns nil' do
@@ -292,6 +293,14 @@ describe Project, models: true do
end
end
+ context 'when full is true' do
+ it 'returns complete path to the project' do
+ expect(project.to_reference(full: true)).to eq 'sample-namespace/sample-project'
+ expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
+ expect(project.to_reference(group, full: true)).to eq 'sample-namespace/sample-project'
+ end
+ end
+
context 'when same project argument' do
it 'returns nil' do
expect(project.to_reference(project)).to be_nil
@@ -309,10 +318,33 @@ describe Project, models: true do
context 'when same namespace / cross-project argument' do
let(:another_project) { create(:empty_project, namespace: namespace) }
- it 'returns complete path to the project' do
+ it 'returns path to the project' do
expect(project.to_reference(another_project)).to eq 'sample-project'
end
end
+
+ context 'when different namespace / cross-project argument' do
+ let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
+ let(:another_project) { create(:empty_project, path: 'another-project', namespace: another_namespace) }
+
+ it 'returns full path to the project' do
+ expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project'
+ end
+ end
+
+ context 'when argument is a namespace' do
+ context 'with same project path' do
+ it 'returns path to the project' do
+ expect(project.to_reference(namespace)).to eq 'sample-project'
+ end
+ end
+
+ context 'with different project path' do
+ it 'returns full path to the project' do
+ expect(project.to_reference(group)).to eq 'sample-namespace/sample-project'
+ end
+ end
+ end
end
describe '#to_human_reference' do
@@ -1801,6 +1833,14 @@ describe Project, models: true do
end
end
+ describe 'inside_path' do
+ let!(:project1) { create(:empty_project) }
+ let!(:project2) { create(:empty_project) }
+ let!(:path) { project1.namespace.path }
+
+ it { expect(Project.inside_path(path)).to eq([project1]) }
+ end
+
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 829b69093c9..53b98ba05f8 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -15,7 +15,12 @@ describe Repository, models: true do
let(:merge_commit) do
merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
- merge_commit_id = repository.merge(user, merge_request, commit_options)
+
+ merge_commit_id = repository.merge(user,
+ merge_request.diff_head_sha,
+ merge_request,
+ commit_options)
+
repository.commit(merge_commit_id)
end
@@ -289,17 +294,39 @@ describe Repository, models: true do
describe "#commit_dir" do
it "commits a change that creates a new directory" do
expect do
- repository.commit_dir(user, 'newdir', 'Create newdir', 'master')
+ repository.commit_dir(user, 'newdir',
+ message: 'Create newdir', branch_name: 'master')
end.to change { repository.commits('master').count }.by(1)
newdir = repository.tree('master', 'newdir')
expect(newdir.path).to eq('newdir')
end
+ context "when committing to another project" do
+ let(:forked_project) { create(:project) }
+
+ it "creates a fork and commit to the forked project" do
+ expect do
+ repository.commit_dir(user, 'newdir',
+ message: 'Create newdir', branch_name: 'patch',
+ start_branch_name: 'master', start_project: forked_project)
+ end.to change { repository.commits('master').count }.by(0)
+
+ expect(repository.branch_exists?('patch')).to be_truthy
+ expect(forked_project.repository.branch_exists?('patch')).to be_falsy
+
+ newdir = repository.tree('patch', 'newdir')
+ expect(newdir.path).to eq('newdir')
+ end
+ end
+
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
- repository.commit_dir(user, "newdir", "Add newdir", 'master', author_email: author_email, author_name: author_name)
+ repository.commit_dir(user, 'newdir',
+ message: 'Add newdir',
+ branch_name: 'master',
+ author_email: author_email, author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
last_commit = repository.commit
@@ -314,8 +341,9 @@ describe Repository, models: true do
it 'commits change to a file successfully' do
expect do
repository.commit_file(user, 'CHANGELOG', 'Changelog!',
- 'Updates file content',
- 'master', true)
+ message: 'Updates file content',
+ branch_name: 'master',
+ update: true)
end.to change { repository.commits('master').count }.by(1)
blob = repository.blob_at('master', 'CHANGELOG')
@@ -326,8 +354,12 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
expect do
- repository.commit_file(user, "README", 'README!', 'Add README',
- 'master', true, author_email: author_email, author_name: author_name)
+ repository.commit_file(user, 'README', 'README!',
+ message: 'Add README',
+ branch_name: 'master',
+ update: true,
+ author_email: author_email,
+ author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
last_commit = repository.commit
@@ -342,7 +374,7 @@ describe Repository, models: true do
it 'updates filename successfully' do
expect do
repository.update_file(user, 'NEWLICENSE', 'Copyright!',
- branch: 'master',
+ branch_name: 'master',
previous_path: 'LICENSE',
message: 'Changes filename')
end.to change { repository.commits('master').count }.by(1)
@@ -355,15 +387,16 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
- repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+ repository.commit_file(user, 'README', 'README!',
+ message: 'Add README', branch_name: 'master', update: true)
expect do
- repository.update_file(user, 'README', "Updated README!",
- branch: 'master',
- previous_path: 'README',
- message: 'Update README',
- author_email: author_email,
- author_name: author_name)
+ repository.update_file(user, 'README', 'Updated README!',
+ branch_name: 'master',
+ previous_path: 'README',
+ message: 'Update README',
+ author_email: author_email,
+ author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
last_commit = repository.commit
@@ -376,10 +409,12 @@ describe Repository, models: true do
describe "#remove_file" do
it 'removes file successfully' do
- repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+ repository.commit_file(user, 'README', 'README!',
+ message: 'Add README', branch_name: 'master', update: true)
expect do
- repository.remove_file(user, "README", "Remove README", 'master')
+ repository.remove_file(user, 'README',
+ message: 'Remove README', branch_name: 'master')
end.to change { repository.commits('master').count }.by(1)
expect(repository.blob_at('master', 'README')).to be_nil
@@ -387,10 +422,13 @@ describe Repository, models: true do
context "when an author is specified" do
it "uses the given email/name to set the commit's author" do
- repository.commit_file(user, "README", 'README!', 'Add README', 'master', true)
+ repository.commit_file(user, 'README', 'README!',
+ message: 'Add README', branch_name: 'master', update: true)
expect do
- repository.remove_file(user, "README", "Remove README", 'master', author_email: author_email, author_name: author_name)
+ repository.remove_file(user, 'README',
+ message: 'Remove README', branch_name: 'master',
+ author_email: author_email, author_name: author_name)
end.to change { repository.commits('master').count }.by(1)
last_commit = repository.commit
@@ -538,11 +576,14 @@ describe Repository, models: true do
describe "#license_blob", caching: true do
before do
- repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
+ repository.remove_file(
+ user, 'LICENSE', message: 'Remove LICENSE', branch_name: 'master')
end
it 'handles when HEAD points to non-existent ref' do
- repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+ repository.commit_file(
+ user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master', update: false)
allow(repository).to receive(:file_on_head).
and_raise(Rugged::ReferenceError)
@@ -551,21 +592,27 @@ describe Repository, models: true do
end
it 'looks in the root_ref only' do
- repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'markdown')
- repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'markdown', false)
+ repository.remove_file(user, 'LICENSE',
+ message: 'Remove LICENSE', branch_name: 'markdown')
+ repository.commit_file(user, 'LICENSE',
+ Licensee::License.new('mit').content,
+ message: 'Add LICENSE', branch_name: 'markdown', update: false)
expect(repository.license_blob).to be_nil
end
it 'detects license file with no recognizable open-source license content' do
- repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+ repository.commit_file(user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master', update: false)
expect(repository.license_blob.name).to eq('LICENSE')
end
%w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
it "detects '#{filename}'" do
- repository.commit_file(user, filename, Licensee::License.new('mit').content, "Add #{filename}", 'master', false)
+ repository.commit_file(user, filename,
+ Licensee::License.new('mit').content,
+ message: "Add #{filename}", branch_name: 'master', update: false)
expect(repository.license_blob.name).to eq(filename)
end
@@ -574,7 +621,8 @@ describe Repository, models: true do
describe '#license_key', caching: true do
before do
- repository.remove_file(user, 'LICENSE', 'Remove LICENSE', 'master')
+ repository.remove_file(user, 'LICENSE',
+ message: 'Remove LICENSE', branch_name: 'master')
end
it 'returns nil when no license is detected' do
@@ -588,13 +636,16 @@ describe Repository, models: true do
end
it 'detects license file with no recognizable open-source license content' do
- repository.commit_file(user, 'LICENSE', 'Copyright!', 'Add LICENSE', 'master', false)
+ repository.commit_file(user, 'LICENSE', 'Copyright!',
+ message: 'Add LICENSE', branch_name: 'master', update: false)
expect(repository.license_key).to be_nil
end
it 'returns the license key' do
- repository.commit_file(user, 'LICENSE', Licensee::License.new('mit').content, 'Add LICENSE', 'master', false)
+ repository.commit_file(user, 'LICENSE',
+ Licensee::License.new('mit').content,
+ message: 'Add LICENSE', branch_name: 'master', update: false)
expect(repository.license_key).to eq('mit')
end
@@ -707,7 +758,7 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
- repository.rm_branch(user, 'new_feature')
+ repository.rm_branch(user, 'feature')
end.to raise_error(GitHooksService::PreReceiveError)
end
@@ -728,36 +779,51 @@ describe Repository, models: true do
context 'when pre hooks were successful' do
before do
- expect_any_instance_of(GitHooksService).to receive(:execute).
- with(user, repository.path_to_repo, old_rev, new_rev, 'refs/heads/feature').
- and_yield.and_return(true)
+ service = GitHooksService.new
+ expect(GitHooksService).to receive(:new).and_return(service)
+ expect(service).to receive(:execute).
+ with(
+ user,
+ repository.path_to_repo,
+ old_rev,
+ new_rev,
+ 'refs/heads/feature').
+ and_yield(service).and_return(true)
end
it 'runs without errors' do
expect do
- repository.update_branch_with_hooks(user, 'feature') { new_rev }
+ GitOperationService.new(user, repository).with_branch('feature') do
+ new_rev
+ end
end.not_to raise_error
end
it 'ensures the autocrlf Git option is set to :input' do
- expect(repository).to receive(:update_autocrlf_option)
+ service = GitOperationService.new(user, repository)
- repository.update_branch_with_hooks(user, 'feature') { new_rev }
+ expect(service).to receive(:update_autocrlf_option)
+
+ service.with_branch('feature') { new_rev }
end
context "when the branch wasn't empty" do
it 'updates the head' do
expect(repository.find_branch('feature').dereferenced_target.id).to eq(old_rev)
- repository.update_branch_with_hooks(user, 'feature') { new_rev }
+
+ GitOperationService.new(user, repository).with_branch('feature') do
+ new_rev
+ end
+
expect(repository.find_branch('feature').dereferenced_target.id).to eq(new_rev)
end
end
end
context 'when the update adds more than one commit' do
- it 'runs without errors' do
- old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9'
+ let(:old_rev) { '33f3729a45c02fc67d00adb1b8bca394b0e761d9' }
+ it 'runs without errors' do
# old_rev is an ancestor of new_rev
expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)
@@ -767,22 +833,28 @@ describe Repository, models: true do
branch = 'feature-ff-target'
repository.add_branch(user, branch, old_rev)
- expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error
+ expect do
+ GitOperationService.new(user, repository).with_branch(branch) do
+ new_rev
+ end
+ end.not_to raise_error
end
end
context 'when the update would remove commits from the target branch' do
- it 'raises an exception' do
- branch = 'master'
- old_rev = repository.find_branch(branch).dereferenced_target.sha
+ let(:branch) { 'master' }
+ let(:old_rev) { repository.find_branch(branch).dereferenced_target.sha }
+ it 'raises an exception' do
# The 'master' branch is NOT an ancestor of new_rev.
expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)
# Updating 'master' to new_rev would lose the commits on 'master' that
# are not contained in new_rev. This should not be allowed.
expect do
- repository.update_branch_with_hooks(user, branch) { new_rev }
+ GitOperationService.new(user, repository).with_branch(branch) do
+ new_rev
+ end
end.to raise_error(Repository::CommitError)
end
end
@@ -792,7 +864,9 @@ describe Repository, models: true do
allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, ''])
expect do
- repository.update_branch_with_hooks(user, 'feature') { new_rev }
+ GitOperationService.new(user, repository).with_branch('feature') do
+ new_rev
+ end
end.to raise_error(GitHooksService::PreReceiveError)
end
end
@@ -800,7 +874,6 @@ describe Repository, models: true do
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, ''])
- allow(repository).to receive(:update_ref!)
end
it 'expires branch cache' do
@@ -809,7 +882,10 @@ describe Repository, models: true do
expect(repository).not_to receive(:expire_emptiness_caches)
expect(repository).to receive(:expire_branches_cache)
- repository.update_branch_with_hooks(user, 'new-feature') { new_rev }
+ GitOperationService.new(user, repository).
+ with_branch('new-feature') do
+ new_rev
+ end
end
end
@@ -827,7 +903,9 @@ describe Repository, models: true do
expect(empty_repository).to receive(:expire_branches_cache)
empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!',
- 'Updates file content', 'master', false)
+ message: 'Updates file content',
+ branch_name: 'master',
+ update: false)
end
end
end
@@ -877,7 +955,7 @@ describe Repository, models: true do
end
it 'sets autocrlf to :input' do
- repository.update_autocrlf_option
+ GitOperationService.new(nil, repository).send(:update_autocrlf_option)
expect(repository.raw_repository.autocrlf).to eq(:input)
end
@@ -892,7 +970,7 @@ describe Repository, models: true do
expect(repository.raw_repository).not_to receive(:autocrlf=).
with(:input)
- repository.update_autocrlf_option
+ GitOperationService.new(nil, repository).send(:update_autocrlf_option)
end
end
end
@@ -1009,8 +1087,11 @@ describe Repository, models: true do
it 'sets the `in_progress_merge_commit_sha` flag for the given merge request' do
merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
- merge_commit_id = repository.merge(user, merge_request, commit_options)
- repository.commit(merge_commit_id)
+
+ merge_commit_id = repository.merge(user,
+ merge_request.diff_head_sha,
+ merge_request,
+ commit_options)
expect(merge_request.in_progress_merge_commit_sha).to eq(merge_commit_id)
end
@@ -1388,9 +1469,10 @@ describe Repository, models: true do
describe '#rm_tag' do
it 'removes a tag' do
expect(repository).to receive(:before_remove_tag)
- expect(repository.rugged.tags).to receive(:delete).with('v1.1.0')
- repository.rm_tag('v1.1.0')
+ repository.rm_tag(create(:user), 'v1.1.0')
+
+ expect(repository.find_tag('v1.1.0')).to be_nil
end
end
@@ -1458,16 +1540,16 @@ describe Repository, models: true do
end
end
- describe '#update_ref!' do
+ describe '#update_ref' do
it 'can create a ref' do
- repository.update_ref!('refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+ GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/foobar', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
expect(repository.find_branch('foobar')).not_to be_nil
end
it 'raises CommitError when the ref update fails' do
expect do
- repository.update_ref!('refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
+ GitOperationService.new(nil, repository).send(:update_ref, 'refs/heads/master', 'refs/heads/master', Gitlab::Git::BLANK_SHA)
end.to raise_error(Repository::CommitError)
end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 8017d1c3324..581305ad39f 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -109,7 +109,7 @@ describe Todo, models: true do
end
describe '#target_reference' do
- it 'returns the short commit id for commits' do
+ it 'returns commit full reference with short id' do
project = create(:project, :repository)
commit = project.commit
@@ -117,12 +117,12 @@ describe Todo, models: true do
subject.target_type = 'Commit'
subject.commit_id = commit.id
- expect(subject.target_reference).to eq commit.short_id
+ expect(subject.target_reference).to eq commit.reference_link_text(full: true)
end
- it 'returns reference for issuables' do
+ it 'returns full reference for issuables' do
subject.target = issue
- expect(subject.target_reference).to eq issue.to_reference
+ expect(subject.target_reference).to eq issue.to_reference(full: true)
end
end
end
diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb
new file mode 100644
index 00000000000..0f280f32eac
--- /dev/null
+++ b/spec/policies/ci/build_policy_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Ci::BuildPolicy, :models do
+ let(:user) { create(:user) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+
+ let(:policies) do
+ described_class.abilities(user, build).to_set
+ end
+
+ shared_context 'public pipelines disabled' do
+ before { project.update_attribute(:public_builds, false) }
+ end
+
+ describe '#rules' do
+ context 'when user does not have access to the project' do
+ let(:project) { create(:empty_project, :private) }
+
+ context 'when public builds are enabled' do
+ it 'does not include ability to read build' do
+ expect(policies).not_to include :read_build
+ end
+ end
+
+ context 'when public builds are disabled' do
+ include_context 'public pipelines disabled'
+
+ it 'does not include ability to read build' do
+ expect(policies).not_to include :read_build
+ end
+ end
+ end
+
+ context 'when anonymous user has access to the project' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'when public builds are enabled' do
+ it 'includes ability to read build' do
+ expect(policies).to include :read_build
+ end
+ end
+
+ context 'when public builds are disabled' do
+ include_context 'public pipelines disabled'
+
+ it 'does not include ability to read build' do
+ expect(policies).not_to include :read_build
+ end
+ end
+ end
+
+ context 'when team member has access to the project' do
+ let(:project) { create(:empty_project, :public) }
+
+ context 'team member is a guest' do
+ before { project.team << [user, :guest] }
+
+ context 'when public builds are enabled' do
+ it 'includes ability to read build' do
+ expect(policies).to include :read_build
+ end
+ end
+
+ context 'when public builds are disabled' do
+ include_context 'public pipelines disabled'
+
+ it 'does not include ability to read build' do
+ expect(policies).not_to include :read_build
+ end
+ end
+ end
+
+ context 'team member is a reporter' do
+ before { project.team << [user, :reporter] }
+
+ context 'when public builds are enabled' do
+ it 'includes ability to read build' do
+ expect(policies).to include :read_build
+ end
+ end
+
+ context 'when public builds are disabled' do
+ include_context 'public pipelines disabled'
+
+ it 'does not include ability to read build' do
+ expect(policies).to include :read_build
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 645e36683bc..f197fadebab 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -67,7 +67,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not return project builds' do
+ it 'does not return project builds' do
expect(response).to have_http_status(401)
end
end
@@ -86,7 +86,7 @@ describe API::Builds, api: true do
context 'when commit exists in repository' do
context 'when user is authorized' do
- context 'when pipeline has builds' do
+ context 'when pipeline has jobs' do
before do
create(:ci_pipeline, project: project, sha: project.commit.id)
create(:ci_build, pipeline: pipeline)
@@ -95,7 +95,7 @@ describe API::Builds, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
end
- it 'returns project builds for specific commit' do
+ it 'returns project jobs 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
@@ -111,7 +111,7 @@ describe API::Builds, api: true do
end
end
- context 'when pipeline has no builds' do
+ context 'when pipeline has no jobs' do
before do
branch_head = project.commit('feature').id
get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user)
@@ -133,7 +133,7 @@ describe API::Builds, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
end
- it 'does not return project builds' do
+ it 'does not return project jobs' do
expect(response).to have_http_status(401)
expect(json_response.except('message')).to be_empty
end
@@ -147,7 +147,7 @@ describe API::Builds, api: true do
end
context 'authorized user' do
- it 'returns specific build data' do
+ it 'returns specific job data' do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq('test')
end
@@ -165,7 +165,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'does not return specific build data' do
+ it 'does not return specific job data' do
expect(response).to have_http_status(401)
end
end
@@ -176,7 +176,7 @@ describe API::Builds, api: true do
get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
- context 'build with artifacts' do
+ context 'job with artifacts' do
let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
context 'authorized user' do
@@ -185,7 +185,7 @@ describe API::Builds, api: true do
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
- it 'returns specific build artifacts' do
+ it 'returns specific job artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include(download_headers)
end
@@ -194,13 +194,13 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'does not return specific build artifacts' do
+ it 'does not return specific job artifacts' do
expect(response).to have_http_status(401)
end
end
end
- it 'does not return build artifacts if not uploaded' do
+ it 'does not return job artifacts if not uploaded' do
expect(response).to have_http_status(404)
end
end
@@ -241,7 +241,7 @@ describe API::Builds, api: true do
end
end
- context 'non-existing build' do
+ context 'non-existing job' do
shared_examples 'not found' do
it { expect(response).to have_http_status(:not_found) }
end
@@ -254,7 +254,7 @@ describe API::Builds, api: true do
it_behaves_like 'not found'
end
- context 'has no such build' do
+ context 'has no such job' do
before do
get path_for_ref(pipeline.ref, 'NOBUILD')
end
@@ -263,7 +263,7 @@ describe API::Builds, api: true do
end
end
- context 'find proper build' do
+ context 'find proper job' do
shared_examples 'a valid file' do
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
@@ -311,7 +311,7 @@ describe API::Builds, api: true do
end
context 'authorized user' do
- it 'returns specific build trace' do
+ it 'returns specific job trace' do
expect(response).to have_http_status(200)
expect(response.body).to eq(build.trace)
end
@@ -320,7 +320,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'does not return specific build trace' do
+ it 'does not return specific job trace' do
expect(response).to have_http_status(401)
end
end
@@ -333,7 +333,7 @@ describe API::Builds, api: true do
context 'authorized user' do
context 'user with :update_build persmission' do
- it 'cancels running or pending build' do
+ it 'cancels running or pending job' do
expect(response).to have_http_status(201)
expect(project.builds.first.status).to eq('canceled')
end
@@ -342,7 +342,7 @@ describe API::Builds, api: true do
context 'user without :update_build permission' do
let(:api_user) { reporter.user }
- it 'does not cancel build' do
+ it 'does not cancel job' do
expect(response).to have_http_status(403)
end
end
@@ -351,7 +351,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'does not cancel build' do
+ it 'does not cancel job' do
expect(response).to have_http_status(401)
end
end
@@ -366,7 +366,7 @@ describe API::Builds, api: true do
context 'authorized user' do
context 'user with :update_build permission' do
- it 'retries non-running build' do
+ it 'retries non-running job' do
expect(response).to have_http_status(201)
expect(project.builds.first.status).to eq('canceled')
expect(json_response['status']).to eq('pending')
@@ -376,7 +376,7 @@ describe API::Builds, api: true do
context 'user without :update_build permission' do
let(:api_user) { reporter.user }
- it 'does not retry build' do
+ it 'does not retry job' do
expect(response).to have_http_status(403)
end
end
@@ -385,7 +385,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'does not retry build' do
+ it 'does not retry job' do
expect(response).to have_http_status(401)
end
end
@@ -396,23 +396,23 @@ describe API::Builds, api: true do
post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
end
- context 'build is erasable' do
+ context 'job is erasable' do
let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
- it 'erases build content' do
+ it 'erases job 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 'updates build' do
+ it 'updates job' do
expect(build.reload.erased_at).to be_truthy
expect(build.reload.erased_by).to eq user
end
end
- context 'build is not erasable' do
+ context 'job is not erasable' do
let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
it 'responds with forbidden' do
@@ -452,20 +452,20 @@ describe API::Builds, api: true do
post api("/projects/#{project.id}/builds/#{build.id}/play", user)
end
- context 'on an playable build' do
+ context 'on an playable job' do
let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
- it 'plays the build' do
+ it 'plays the job' do
expect(response).to have_http_status 200
expect(json_response['user']['id']).to eq(user.id)
expect(json_response['id']).to eq(build.id)
end
end
- context 'on a non-playable build' do
+ context 'on a non-playable job' do
it 'returns a status code 400, Bad Request' do
expect(response).to have_http_status 400
- expect(response.body).to match("Unplayable Build")
+ expect(response.body).to match("Unplayable Job")
end
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index edbf0140583..a027c23bb88 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -176,6 +176,9 @@ describe API::Groups, api: true do
expect(json_response['visibility_level']).to eq(group1.visibility_level)
expect(json_response['avatar_url']).to eq(group1.avatar_url)
expect(json_response['web_url']).to eq(group1.web_url)
+ expect(json_response['request_access_enabled']).to eq(group1.request_access_enabled)
+ expect(json_response['full_name']).to eq(group1.full_name)
+ expect(json_response['full_path']).to eq(group1.full_path)
expect(json_response['projects']).to be_an Array
expect(json_response['projects'].length).to eq(2)
expect(json_response['shared_projects']).to be_an Array
@@ -323,7 +326,7 @@ describe API::Groups, api: true do
expect(response).to have_http_status(404)
end
- it "should only return projects to which user has access" do
+ it "only returns projects to which user has access" do
project3.team << [user3, :developer]
get api("/groups/#{group1.id}/projects", user3)
@@ -335,7 +338,7 @@ describe API::Groups, 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}/projects", admin)
expect(response).to have_http_status(200)
@@ -343,7 +346,7 @@ describe API::Groups, api: true do
expect(json_response.first['name']).to eq(project2.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", admin)
expect(response).to have_http_status(404)
@@ -351,7 +354,7 @@ describe API::Groups, api: true do
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}/projects", admin)
expect(response).to have_http_status(200)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 01032c0929b..45d5ae267c5 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe 'GET /projects/:project_id/snippets/:id' do
@@ -22,7 +23,7 @@ describe API::ProjectSnippets, api: true do
let(:user) { create(:user) }
it 'returns all snippets available to team member' do
- project.team << [user, :developer]
+ project.add_developer(user)
public_snippet = create(:project_snippet, :public, project: project)
internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project)
@@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do
title: 'Test Title',
file_name: 'test.rb',
code: 'puts "hello world"',
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ visibility_level: Snippet::PUBLIC
}
end
@@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def create_snippet(project, snippet_params = {})
+ project.add_developer(user)
+
+ post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the project is private' do
+ let(:private_project) { create(:project_empty_repo, :private) }
+
+ context 'when the snippet is public' do
+ it 'creates the snippet' do
+ expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+ end
+
+ context 'when the project is public' do
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to have_http_status(400)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a1db81ce18c..753dde0ca3a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -459,7 +459,7 @@ describe API::Projects, api: true do
before { project }
before { admin }
- 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/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
expect(response).to have_http_status(201)
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index f6fb6ea5506..6b9a739b439 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -80,7 +80,7 @@ describe API::Snippets, api: true do
title: 'Test Title',
file_name: 'test.rb',
content: 'puts "hello world"',
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ visibility_level: Snippet::PUBLIC
}
end
@@ -101,6 +101,36 @@ describe API::Snippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def create_snippet(snippet_params = {})
+ post api('/snippets', user), params.merge(snippet_params)
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to have_http_status(400)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
end
describe 'PUT /snippets/:id' do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5bf5bf0739e..8692f9da976 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -305,6 +305,13 @@ describe API::Users, api: true do
expect(user.reload.bio).to eq('new test bio')
end
+ it "updates user with new password and forces reset on next login" do
+ put api("/users/#{user.id}", admin), password: '12345678'
+
+ expect(response).to have_http_status(200)
+ expect(user.reload.password_expires_at).to be <= Time.now
+ end
+
it "updates user with organization" do
put api("/users/#{user.id}", admin), { organization: 'GitLab' }
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 8dbe5f0b025..d85afdeab42 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -288,7 +288,7 @@ describe Ci::API::Builds do
expect(build.reload.trace).to eq 'BUILD TRACE'
end
- context 'build has been erased' do
+ context 'job has been erased' do
let(:build) { create(:ci_build, runner_id: runner.id, erased_at: Time.now) }
it 'responds with forbidden' do
@@ -458,7 +458,7 @@ describe Ci::API::Builds do
before { build.run! }
describe "POST /builds/:id/artifacts/authorize" do
- context "should authorize posting artifact to running build" do
+ context "authorizes posting artifact to running build" do
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
@@ -492,7 +492,7 @@ describe Ci::API::Builds do
end
end
- context "should fail to post too large artifact" do
+ context "fails to post too large artifact" do
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 77549db2927..96889abee79 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe 'project routing' do
before do
- allow(Project).to receive(:find_with_namespace).and_return(false)
- allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq').and_return(true)
+ allow(Project).to receive(:find_by_full_path).and_return(false)
+ allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq').and_return(true)
end
# Shared examples for a resource inside a Project
@@ -86,13 +86,13 @@ describe 'project routing' do
end
context 'name with dot' do
- before { allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq.keys').and_return(true) }
+ before { allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq.keys').and_return(true) }
it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') }
end
context 'with nested group' do
- before { allow(Project).to receive(:find_with_namespace).with('gitlab/subgroup/gitlabhq').and_return(true) }
+ before { allow(Project).to receive(:find_by_full_path).with('gitlab/subgroup/gitlabhq').and_return(true) }
it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') }
end
diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb
index f0551c78671..e3b1dd93dc2 100644
--- a/spec/serializers/analytics_build_serializer_spec.rb
+++ b/spec/serializers/analytics_build_serializer_spec.rb
@@ -1,17 +1,13 @@
require 'spec_helper'
describe AnalyticsBuildSerializer do
- let(:serializer) do
- described_class
- .new.represent(resource)
- end
-
- let(:json) { serializer.as_json }
let(:resource) { create(:ci_build) }
+ subject { described_class.new.represent(resource) }
+
context 'when there is a single object provided' do
it 'contains important elements of analyticsBuild' do
- expect(json)
+ expect(subject)
.to include(:name, :branch, :short_sha, :date, :total_time, :url, :author)
end
end
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 6afbb2df35c..2f08958a783 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -1,14 +1,13 @@
require 'spec_helper'
describe AnalyticsIssueSerializer do
- let(:serializer) do
+ subject do
described_class
.new(project: project, entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:json) { serializer.as_json }
let(:project) { create(:project) }
let(:resource) do
{
@@ -23,7 +22,7 @@ describe AnalyticsIssueSerializer do
context 'when there is a single object provided' do
it 'contains important elements of the issue' do
- expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author)
+ expect(subject).to include(:title, :iid, :created_at, :total_time, :url, :author)
end
end
end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index cdfae27193f..62067cc0ef2 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -1,14 +1,13 @@
require 'spec_helper'
describe AnalyticsMergeRequestSerializer do
- let(:serializer) do
+ subject do
described_class
.new(project: project, entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:json) { serializer.as_json }
let(:project) { create(:project) }
let(:resource) do
{
@@ -24,7 +23,7 @@ describe AnalyticsMergeRequestSerializer do
context 'when there is a single object provided' do
it 'contains important elements of the merge request' do
- expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author, :state)
+ expect(subject).to include(:title, :iid, :created_at, :total_time, :url, :author, :state)
end
end
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index f9951826683..be6aa7c65c3 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -1,13 +1,13 @@
require 'spec_helper'
describe AnalyticsStageSerializer do
- let(:serializer) do
- described_class
- .new.represent(resource)
+ subject do
+ described_class.new.represent(resource)
end
- let(:json) { serializer.as_json }
- let(:resource) { Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {}) }
+ let(:resource) do
+ Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {})
+ end
before do
allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
@@ -15,10 +15,10 @@ describe AnalyticsStageSerializer do
end
it 'it generates payload for single object' do
- expect(json).to be_kind_of Hash
+ expect(subject).to be_kind_of Hash
end
it 'contains important elements of AnalyticsStage' do
- expect(json).to include(:title, :description, :value)
+ expect(subject).to include(:title, :description, :value)
end
end
diff --git a/spec/serializers/analytics_summary_serializer_spec.rb b/spec/serializers/analytics_summary_serializer_spec.rb
index 7a84c8b0b40..5d7a94c2d02 100644
--- a/spec/serializers/analytics_summary_serializer_spec.rb
+++ b/spec/serializers/analytics_summary_serializer_spec.rb
@@ -1,29 +1,28 @@
require 'spec_helper'
describe AnalyticsSummarySerializer do
- let(:serializer) do
- described_class
- .new.represent(resource)
+ subject do
+ described_class.new.represent(resource)
end
- let(:json) { serializer.as_json }
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
+
let(:resource) do
- Gitlab::CycleAnalytics::Summary::Issue.new(project: double,
- from: 1.day.ago,
- current_user: user)
+ Gitlab::CycleAnalytics::Summary::Issue
+ .new(project: double, from: 1.day.ago, current_user: user)
end
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue).to receive(:value).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::Summary::Issue)
+ .to receive(:value).and_return(1.12)
end
it 'it generates payload for single object' do
- expect(json).to be_kind_of Hash
+ expect(subject).to be_kind_of Hash
end
it 'contains important elements of AnalyticsStage' do
- expect(json).to include(:title, :value)
+ expect(subject).to include(:title, :value)
end
end
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index b7ed4eb0239..3c37660885d 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -1,16 +1,15 @@
require 'spec_helper'
describe EnvironmentSerializer do
- let(:serializer) do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ let(:json) do
described_class
.new(user: user, project: project)
.represent(resource)
end
- let(:json) { serializer.as_json }
- let(:user) { create(:user) }
- let(:project) { create(:project) }
-
context 'when there is a single object provided' do
before do
create(:ci_build, :manual, name: 'manual1',
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 3a32cb394dd..7cbf131e41e 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -7,11 +7,7 @@ describe PipelineSerializer do
described_class.new(user: user)
end
- let(:entity) do
- serializer.represent(resource)
- end
-
- subject { entity.as_json }
+ subject { serializer.represent(resource) }
describe '#represent' do
context 'when used without pagination' do
diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb
index 3760f19aaa2..0a7fc58523f 100644
--- a/spec/services/compare_service_spec.rb
+++ b/spec/services/compare_service_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe CompareService, services: true do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:service) { described_class.new }
+ let(:service) { described_class.new(project, 'feature') }
describe '#execute' do
context 'compare with base, like feature...fix' do
- subject { service.execute(project, 'feature', project, 'fix', straight: false) }
+ subject { service.execute(project, 'fix', straight: false) }
it { expect(subject.diffs.size).to eq(1) }
end
context 'straight compare, like feature..fix' do
- subject { service.execute(project, 'feature', project, 'fix', straight: true) }
+ subject { service.execute(project, 'fix', straight: true) }
it { expect(subject.diffs.size).to eq(3) }
end
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index b7dc99ed887..f2c2009bcbf 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -9,7 +9,7 @@ describe EventCreateService, services: true do
it { expect(service.open_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count }
end
end
@@ -19,7 +19,7 @@ describe EventCreateService, services: true do
it { expect(service.close_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count }
end
end
@@ -29,7 +29,7 @@ describe EventCreateService, services: true do
it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
end
end
diff --git a/spec/services/files/update_service_spec.rb b/spec/services/files/update_service_spec.rb
index d3c37c7820f..35e6e139238 100644
--- a/spec/services/files/update_service_spec.rb
+++ b/spec/services/files/update_service_spec.rb
@@ -6,7 +6,10 @@ describe Files::UpdateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:file_path) { 'files/ruby/popen.rb' }
- let(:new_contents) { "New Content" }
+ let(:new_contents) { 'New Content' }
+ let(:target_branch) { project.default_branch }
+ let(:last_commit_sha) { nil }
+
let(:commit_params) do
{
file_path: file_path,
@@ -14,9 +17,9 @@ describe Files::UpdateService do
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,
+ start_project: project,
+ start_branch: project.default_branch,
+ target_branch: target_branch
}
end
@@ -54,18 +57,6 @@ describe Files::UpdateService do
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
@@ -80,5 +71,15 @@ describe Files::UpdateService do
expect(results.data).to eq(new_contents)
end
end
+
+ context 'when target branch is different than source branch' do
+ let(:target_branch) { "#{project.default_branch}-new" }
+
+ it 'fires hooks only once' do
+ expect(GitHooksService).to receive(:new).once.and_call_original
+
+ subject.execute
+ end
+ end
end
end
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
index 41b0968b8b4..3318dfb22b6 100644
--- a/spec/services/git_hooks_service_spec.rb
+++ b/spec/services/git_hooks_service_spec.rb
@@ -21,7 +21,7 @@ describe GitHooksService, services: true do
hook = double(trigger: [true, nil])
expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook)
- expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq([true, nil])
+ service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }
end
end
diff --git a/spec/services/labels/promote_service_spec.rb b/spec/services/labels/promote_service_spec.rb
new file mode 100644
index 00000000000..4b90ad19640
--- /dev/null
+++ b/spec/services/labels/promote_service_spec.rb
@@ -0,0 +1,187 @@
+require 'spec_helper'
+
+describe Labels::PromoteService, services: true do
+ describe '#execute' do
+ let!(:user) { create(:user) }
+
+ context 'project without group' do
+ let!(:project_1) { create(:empty_project) }
+
+ let!(:project_label_1_1) { create(:label, project: project_1) }
+
+ subject(:service) { described_class.new(project_1, user) }
+
+ it 'fails on project without group' do
+ expect(service.execute(project_label_1_1)).to be_falsey
+ end
+ end
+
+ context 'project with group' do
+ let!(:promoted_label_name) { "Promoted Label" }
+ let!(:untouched_label_name) { "Untouched Label" }
+ let!(:promoted_description) { "Promoted Description" }
+ let!(:promoted_color) { "#0000FF" }
+ let!(:label_2_1_priority) { 1 }
+ let!(:label_3_1_priority) { 2 }
+
+ let!(:group_1) { create(:group) }
+ let!(:group_2) { create(:group) }
+
+ let!(:project_1) { create(:empty_project, namespace: group_1) }
+ let!(:project_2) { create(:empty_project, namespace: group_1) }
+ let!(:project_3) { create(:empty_project, namespace: group_1) }
+ let!(:project_4) { create(:empty_project, namespace: group_2) }
+
+ # Labels/issues can't be lazily created so we might as well eager initialize
+ # all other objects too since we use them inside
+ let!(:project_label_1_1) { create(:label, project: project_1, name: promoted_label_name, color: promoted_color, description: promoted_description) }
+ let!(:project_label_1_2) { create(:label, project: project_1, name: untouched_label_name) }
+ let!(:project_label_2_1) { create(:label, project: project_2, priority: label_2_1_priority, name: promoted_label_name, color: "#FF0000") }
+ let!(:project_label_3_1) { create(:label, project: project_3, priority: label_3_1_priority, name: promoted_label_name) }
+ let!(:project_label_3_2) { create(:label, project: project_3, priority: 1, name: untouched_label_name) }
+ let!(:project_label_4_1) { create(:label, project: project_4, name: promoted_label_name) }
+
+ let!(:issue_1_1) { create(:labeled_issue, project: project_1, labels: [project_label_1_1, project_label_1_2]) }
+ let!(:issue_1_2) { create(:labeled_issue, project: project_1, labels: [project_label_1_2]) }
+ let!(:issue_2_1) { create(:labeled_issue, project: project_2, labels: [project_label_2_1]) }
+ let!(:issue_4_1) { create(:labeled_issue, project: project_4, labels: [project_label_4_1]) }
+
+ let!(:merge_3_1) { create(:labeled_merge_request, source_project: project_3, target_project: project_3, labels: [project_label_3_1, project_label_3_2]) }
+
+ let!(:issue_board_2_1) { create(:board, project: project_2) }
+ let!(:issue_board_list_2_1) { create(:list, board: issue_board_2_1, label: project_label_2_1) }
+
+ let(:new_label) { group_1.labels.find_by(title: promoted_label_name) }
+
+ subject(:service) { described_class.new(project_1, user) }
+
+ it 'fails on group label' do
+ group_label = create(:group_label, group: group_1)
+
+ expect(service.execute(group_label)).to be_falsey
+ end
+
+ it 'is truthy on success' do
+ expect(service.execute(project_label_1_1)).to be_truthy
+ end
+
+ it 'recreates the label as a group label' do
+ expect { service.execute(project_label_1_1) }.
+ to change(project_1.labels, :count).by(-1).
+ and change(group_1.labels, :count).by(1)
+ expect(new_label).not_to be_nil
+ end
+
+ it 'copies title, description and color' do
+ service.execute(project_label_1_1)
+
+ expect(new_label.title).to eq(promoted_label_name)
+ expect(new_label.description).to eq(promoted_description)
+ expect(new_label.color).to eq(promoted_color)
+ end
+
+ it 'merges labels with the same name in group' do
+ expect { service.execute(project_label_1_1) }.to change(project_2.labels, :count).by(-1).and \
+ change(project_3.labels, :count).by(-1)
+ end
+
+ it 'recreates priorities' do
+ service.execute(project_label_1_1)
+
+ expect(new_label.priority(project_1)).to be_nil
+ expect(new_label.priority(project_2)).to eq(label_2_1_priority)
+ expect(new_label.priority(project_3)).to eq(label_3_1_priority)
+ end
+
+ it 'does not touch project out of promoted group' do
+ service.execute(project_label_1_1)
+ project_4_new_label = project_4.labels.find_by(title: promoted_label_name)
+
+ expect(project_4_new_label).not_to be_nil
+ expect(project_4_new_label.id).to eq(project_label_4_1.id)
+ end
+
+ it 'does not touch out of group priority' do
+ service.execute(project_label_1_1)
+
+ expect(new_label.priority(project_4)).to be_nil
+ end
+
+ it 'relinks issue with the promoted label' do
+ service.execute(project_label_1_1)
+ issue_label = issue_1_1.labels.find_by(title: promoted_label_name)
+
+ expect(issue_label).not_to be_nil
+ expect(issue_label.id).to eq(new_label.id)
+ end
+
+ it 'does not remove untouched labels from issue' do
+ expect { service.execute(project_label_1_1) }.not_to change(issue_1_1.labels, :count)
+ end
+
+ it 'does not relink untouched label in issue' do
+ service.execute(project_label_1_1)
+ issue_label = issue_1_2.labels.find_by(title: untouched_label_name)
+
+ expect(issue_label).not_to be_nil
+ expect(issue_label.id).to eq(project_label_1_2.id)
+ end
+
+ it 'relinks issues with merged labels' do
+ service.execute(project_label_1_1)
+ issue_label = issue_2_1.labels.find_by(title: promoted_label_name)
+
+ expect(issue_label).not_to be_nil
+ expect(issue_label.id).to eq(new_label.id)
+ end
+
+ it 'does not relink issues from other group' do
+ service.execute(project_label_1_1)
+ issue_label = issue_4_1.labels.find_by(title: promoted_label_name)
+
+ expect(issue_label).not_to be_nil
+ expect(issue_label.id).to eq(project_label_4_1.id)
+ end
+
+ it 'updates merge request' do
+ service.execute(project_label_1_1)
+ merge_label = merge_3_1.labels.find_by(title: promoted_label_name)
+
+ expect(merge_label).not_to be_nil
+ expect(merge_label.id).to eq(new_label.id)
+ end
+
+ it 'updates board lists' do
+ service.execute(project_label_1_1)
+ list = issue_board_2_1.lists.find_by(label: new_label)
+
+ expect(list).not_to be_nil
+ end
+
+ # In case someone adds a new relation to Label.rb and forgets to relink it
+ # and the database doesn't have foreign key constraints
+ it 'relinks all relations' do
+ service.execute(project_label_1_1)
+
+ Label.reflect_on_all_associations.each do |association|
+ expect(project_label_1_1.send(association.name).any?).to be_falsey
+ end
+ end
+
+ context 'with invalid group label' do
+ before do
+ allow(service).to receive(:clone_label_to_group_label).and_wrap_original do |m, *args|
+ label = m.call(*args)
+ allow(label).to receive(:valid?).and_return(false)
+
+ label
+ end
+ end
+
+ it 'raises an exception' do
+ expect { service.execute(project_label_1_1) }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 5f6a7716beb..d55a7657c0e 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do
it { expect(@merge_request).to be_valid }
it { expect(@merge_request).to be_closed }
- it 'should execute hooks with close action' do
+ it 'executes hooks with close action' do
expect(service).to have_received(:execute_hooks).
with(@merge_request, 'close')
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 314ea670a71..2cc21acab7b 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -89,7 +89,7 @@ describe MergeRequests::RefreshService, services: true do
# Merge master -> feature branch
author = { email: 'test@gitlab.com', time: Time.now, name: "Me" }
commit_options = { message: 'Test message', committer: author, author: author }
- @project.repository.merge(@user, @merge_request, commit_options)
+ @project.repository.merge(@user, @merge_request.diff_head_sha, @merge_request, commit_options)
commit = @project.repository.commit('feature')
service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
reload_mrs
diff --git a/spec/services/merge_requests/resolve_service_spec.rb b/spec/services/merge_requests/resolve_service_spec.rb
index 388abb6a0df..a0e51681725 100644
--- a/spec/services/merge_requests/resolve_service_spec.rb
+++ b/spec/services/merge_requests/resolve_service_spec.rb
@@ -66,7 +66,13 @@ describe MergeRequests::ResolveService do
context 'when the source project is a fork and does not contain the HEAD of the target branch' do
let!(:target_head) do
- project.repository.commit_file(user, 'new-file-in-target', '', 'Add new file in target', 'conflict-start', false)
+ project.repository.commit_file(
+ user,
+ 'new-file-in-target',
+ '',
+ message: 'Add new file in target',
+ branch_name: 'conflict-start',
+ update: false)
end
before do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 309985a5d90..7cf2cd9968f 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -443,6 +443,8 @@ describe NotificationService, services: true do
before do
build_team(issue.project)
+ build_group(issue.project)
+
add_users_with_subscription(issue.project, issue)
reset_delivered_emails!
update_custom_notification(:new_issue, @u_guest_custom, project)
@@ -459,6 +461,8 @@ describe NotificationService, services: true do
should_email(@u_guest_custom)
should_email(@u_custom_global)
should_email(@u_participant_mentioned)
+ should_email(@g_global_watcher)
+ should_email(@g_watcher)
should_not_email(@u_mentioned)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -1218,6 +1222,22 @@ describe NotificationService, services: true do
project.add_master(@u_custom_global)
end
+ # Users in the project's group but not part of project's team
+ # with different notification settings
+ def build_group(project)
+ group = create(:group, :public)
+ project.group = group
+
+ # Group member: global=disabled, group=watch
+ @g_watcher = create_user_with_notification(:watch, 'group_watcher', project.group)
+ @g_watcher.notification_settings_for(nil).disabled!
+
+ # Group member: global=watch, group=global
+ @g_global_watcher = create_global_setting_for(create(:user), :watch)
+ group.add_users([@g_watcher, @g_global_watcher], :master)
+ group
+ end
+
def create_global_setting_for(user, level)
setting = user.global_notification_setting
setting.level = level
@@ -1226,9 +1246,9 @@ describe NotificationService, services: true do
user
end
- def create_user_with_notification(level, username)
+ def create_user_with_notification(level, username, resource = project)
user = create(:user, username: username)
- setting = user.notification_settings_for(project)
+ setting = user.notification_settings_for(resource)
setting.level = level
setting.save
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index bd89c4a7c11..bed1031e40a 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -41,6 +41,25 @@ describe 'Search::GlobalService', services: true do
results = context.execute
expect(results.objects('projects')).to match_array [found_project]
end
+
+ context 'nested group' do
+ let!(:nested_group) { create(:group, :nested) }
+ let!(:project) { create(:project, namespace: nested_group) }
+
+ before { project.add_master(user) }
+
+ it 'returns result from nested group' do
+ context = Search::GlobalService.new(user, search: project.path)
+ results = context.execute
+ expect(results.objects('projects')).to match_array [project]
+ end
+
+ it 'returns result from descendants when search inside group' do
+ context = Search::GlobalService.new(user, search: project.path, group_id: nested_group.parent)
+ results = context.execute
+ expect(results.objects('projects')).to match_array [project]
+ end
+ end
end
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 9f5a0ac4ec6..bd7269045e1 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -245,6 +245,8 @@ describe SystemNoteService, services: true do
end
describe '.change_title' do
+ let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum') }
+
subject { described_class.change_title(noteable, project, author, 'Old title') }
context 'when noteable responds to `title`' do
@@ -252,7 +254,7 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
expect(subject.note).
- to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**"
+ to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**"
end
end
end
diff --git a/spec/support/cycle_analytics_helpers.rb b/spec/support/cycle_analytics_helpers.rb
index 75c95d70951..6ed55289ed9 100644
--- a/spec/support/cycle_analytics_helpers.rb
+++ b/spec/support/cycle_analytics_helpers.rb
@@ -35,7 +35,13 @@ module CycleAnalyticsHelpers
project.repository.add_branch(user, source_branch, 'master')
end
- sha = project.repository.commit_file(user, random_git_name, "content", "commit message", source_branch, false)
+ sha = project.repository.commit_file(
+ user,
+ random_git_name,
+ 'content',
+ message: 'commit message',
+ branch_name: source_branch,
+ update: false)
project.repository.commit(sha)
opts = {
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 10b90b40ba7..19b32c84d81 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -63,22 +63,20 @@ module CycleAnalyticsHelpers
# test case.
allow(self).to receive(:project) { other_project }
- 5.times do
- data = data_fn[self]
- start_time = Time.now
- end_time = rand(1..10).days.from_now
-
- start_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(start_time) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ start_time = Time.now
+ end_time = rand(1..10).days.from_now
- end_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(end_time) { condition_fn[self, data] }
- end
+ start_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(start_time) { condition_fn[self, data] }
+ end
- Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+ end_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(end_time) { condition_fn[self, data] }
end
+ Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
@@ -114,17 +112,15 @@ module CycleAnalyticsHelpers
context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do
- 5.times do
- data = data_fn[self]
- end_time = rand(1..10).days.from_now
-
- end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
- Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ end_time = rand(1..10).days.from_now
- Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+ end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
+ Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
end
+ Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
expect(subject[phase].median).to be_nil
end
end
@@ -133,17 +129,15 @@ module CycleAnalyticsHelpers
context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do
- 5.times do
- data = data_fn[self]
- start_time = Time.now
-
- start_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(start_time) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ start_time = Time.now
- post_fn[self, data] if post_fn
+ start_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(start_time) { condition_fn[self, data] }
end
+ post_fn[self, data] if post_fn
+
expect(subject[phase].median).to be_nil
end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 90f1a9c8798..b87232a350b 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -36,7 +36,8 @@ module TestEnv
'conflict-non-utf8' => 'd0a293c',
'conflict-too-large' => '39fa04f',
'deleted-image-test' => '6c17798',
- 'wip' => 'b9238ee'
+ 'wip' => 'b9238ee',
+ 'csv' => '3dd0896'
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
index 80fc8c48fed..8d1cff7a261 100644
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do
Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
end
- it 'should run the task without errors' do
+ it 'runs the task without errors' do
expect { run_rake_task }.not_to raise_error
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 44870cfcfb3..b6f6e7b7a2b 100644
--- a/spec/views/projects/builds/show.html.haml_spec.rb
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -15,7 +15,7 @@ describe 'projects/builds/show', :view do
allow(view).to receive(:can?).and_return(true)
end
- describe 'build information in header' do
+ describe 'job information in header' do
let(:build) do
create(:ci_build, :success, environment: 'staging')
end
@@ -28,11 +28,11 @@ describe 'projects/builds/show', :view do
expect(rendered).to have_css('.ci-status.ci-success', text: 'passed')
end
- it 'does not render a link to the build' do
+ it 'does not render a link to the job' do
expect(rendered).not_to have_link('passed')
end
- it 'shows build id' do
+ it 'shows job id' do
expect(rendered).to have_css('.js-build-id', text: build.id)
end
@@ -45,8 +45,8 @@ describe 'projects/builds/show', :view do
end
end
- describe 'environment info in build view' do
- context 'build with latest deployment' do
+ describe 'environment info in job view' do
+ context 'job with latest deployment' do
let(:build) do
create(:ci_build, :success, environment: 'staging')
end
@@ -57,7 +57,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'This build is the most recent deployment'
+ expected_text = 'This job is the most recent deployment'
render
expect(rendered).to have_css(
@@ -65,7 +65,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'build with outdated deployment' do
+ context 'job with outdated deployment' do
let(:build) do
create(:ci_build, :success, environment: 'staging', pipeline: pipeline)
end
@@ -87,7 +87,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'This build is an out-of-date deployment ' \
+ expected_text = 'This job is an out-of-date deployment ' \
"to staging.\nView the most recent deployment ##{second_deployment.iid}."
render
@@ -95,7 +95,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'build failed to deploy' do
+ context 'job failed to deploy' do
let(:build) do
create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
end
@@ -105,7 +105,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'The deployment of this build to staging did not succeed.'
+ expected_text = 'The deployment of this job to staging did not succeed.'
render
expect(rendered).to have_css(
@@ -113,7 +113,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'build will deploy' do
+ context 'job will deploy' do
let(:build) do
create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
end
@@ -124,7 +124,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'This build is creating a deployment to staging'
+ expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
@@ -137,7 +137,7 @@ describe 'projects/builds/show', :view do
end
it 'shows that deployment will be overwritten' do
- expected_text = 'This build is creating a deployment to staging'
+ expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
@@ -150,7 +150,7 @@ describe 'projects/builds/show', :view do
context 'when environment does not exist' do
it 'shows deployment message' do
- expected_text = 'This build is creating a deployment to staging'
+ expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
@@ -161,7 +161,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'build that failed to deploy and environment has not been created' do
+ context 'job that failed to deploy and environment has not been created' do
let(:build) do
create(:ci_build, :failed, environment: 'staging', pipeline: pipeline)
end
@@ -171,7 +171,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'The deployment of this build to staging did not succeed'
+ expected_text = 'The deployment of this job to staging did not succeed'
render
expect(rendered).to have_css(
@@ -179,7 +179,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'build that will deploy and environment has not been created' do
+ context 'job that will deploy and environment has not been created' do
let(:build) do
create(:ci_build, :running, environment: 'staging', pipeline: pipeline)
end
@@ -189,7 +189,7 @@ describe 'projects/builds/show', :view do
end
it 'shows deployment message' do
- expected_text = 'This build is creating a deployment to staging'
+ expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
@@ -200,7 +200,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'when build is running' do
+ context 'when job is running' do
before do
build.run!
render
@@ -211,7 +211,7 @@ describe 'projects/builds/show', :view do
end
end
- context 'when build is not running' do
+ context 'when job is not running' do
before do
build.success!
render
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index e471a68a49a..5ef8cf1105b 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -107,7 +107,8 @@ describe GitGarbageCollectWorker do
tree: old_commit.tree,
parents: [old_commit],
)
- project.repository.update_ref!(
+ GitOperationService.new(nil, project.repository).send(
+ :update_ref,
"refs/heads/#{SecureRandom.hex(6)}",
new_commit_sha,
Gitlab::Git::BLANK_SHA
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 984acdade36..5919b99a6ed 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -74,7 +74,7 @@ describe PostReceive do
context "webhook" do
it "fetches the correct project" do
- expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project)
+ expect(Project).to receive(:find_by_full_path).with(project.path_with_namespace).and_return(project)
PostReceive.new.perform(pwd(project), key_id, base64_changes)
end
@@ -89,7 +89,7 @@ describe PostReceive do
end
it "asks the project to trigger all hooks" do
- allow(Project).to receive(:find_with_namespace).and_return(project)
+ allow(Project).to receive(:find_by_full_path).and_return(project)
expect(project).to receive(:execute_hooks).twice
expect(project).to receive(:execute_services).twice
@@ -97,7 +97,7 @@ describe PostReceive do
end
it "enqueues a UpdateMergeRequestsWorker job" do
- allow(Project).to receive(:find_with_namespace).and_return(project)
+ allow(Project).to receive(:find_by_full_path).and_return(project)
expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
PostReceive.new.perform(pwd(project), key_id, base64_changes)
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 1b910d9b91e..1f4c39eb64a 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -8,14 +8,14 @@ describe ProjectDestroyWorker do
describe "#perform" do
it "deletes the project" do
- subject.perform(project.id, project.owner, {})
+ subject.perform(project.id, project.owner.id, {})
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey
end
it "deletes the project but skips repo deletion" do
- subject.perform(project.id, project.owner, { "skip_repo" => true })
+ subject.perform(project.id, project.owner.id, { "skip_repo" => true })
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_truthy
diff --git a/vendor/assets/javascripts/jquery.ba-resize.js b/vendor/assets/javascripts/jquery.ba-resize.js
deleted file mode 100644
index 1f41d379153..00000000000
--- a/vendor/assets/javascripts/jquery.ba-resize.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/*!
- * jQuery resize event - v1.1 - 3/14/2010
- * http://benalman.com/projects/jquery-resize-plugin/
- *
- * Copyright (c) 2010 "Cowboy" Ben Alman
- * Dual licensed under the MIT and GPL licenses.
- * http://benalman.com/about/license/
- */
-
-// Script: jQuery resize event
-//
-// *Version: 1.1, Last updated: 3/14/2010*
-//
-// Project Home - http://benalman.com/projects/jquery-resize-plugin/
-// GitHub - http://github.com/cowboy/jquery-resize/
-// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
-// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
-//
-// About: License
-//
-// Copyright (c) 2010 "Cowboy" Ben Alman,
-// Dual licensed under the MIT and GPL licenses.
-// http://benalman.com/about/license/
-//
-// About: Examples
-//
-// This working example, complete with fully commented code, illustrates a few
-// ways in which this plugin can be used.
-//
-// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
-//
-// About: Support and Testing
-//
-// Information about what version or versions of jQuery this plugin has been
-// tested with, what browsers it has been tested in, and where the unit tests
-// reside (so you can test it yourself).
-//
-// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
-// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
-// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/
-//
-// About: Release History
-//
-// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
-// immediately after bind in some circumstances. Also changed $.fn.data
-// to $.data to improve performance.
-// 1.0 - (2/10/2010) Initial release
-
-(function($,window,undefined){
- '$:nomunge'; // Used by YUI compressor.
-
- // A jQuery object containing all non-window elements to which the resize
- // event is bound.
- var elems = $([]),
-
- // Extend $.resize if it already exists, otherwise create it.
- jq_resize = $.resize = $.extend( $.resize, {} ),
-
- timeout_id,
-
- // Reused strings.
- str_setTimeout = 'setTimeout',
- str_resize = 'resize',
- str_data = str_resize + '-special-event',
- str_delay = 'delay',
- str_throttle = 'throttleWindow';
-
- // Property: jQuery.resize.delay
- //
- // The numeric interval (in milliseconds) at which the resize event polling
- // loop executes. Defaults to 250.
-
- jq_resize[ str_delay ] = 250;
-
- // Property: jQuery.resize.throttleWindow
- //
- // Throttle the native window object resize event to fire no more than once
- // every <jQuery.resize.delay> milliseconds. Defaults to true.
- //
- // Because the window object has its own resize event, it doesn't need to be
- // provided by this plugin, and its execution can be left entirely up to the
- // browser. However, since certain browsers fire the resize event continuously
- // while others do not, enabling this will throttle the window resize event,
- // making event behavior consistent across all elements in all browsers.
- //
- // While setting this property to false will disable window object resize
- // event throttling, please note that this property must be changed before any
- // window object resize event callbacks are bound.
-
- jq_resize[ str_throttle ] = true;
-
- // Event: resize event
- //
- // Fired when an element's width or height changes. Because browsers only
- // provide this event for the window element, for other elements a polling
- // loop is initialized, running every <jQuery.resize.delay> milliseconds
- // to see if elements' dimensions have changed. You may bind with either
- // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
- //
- // Usage:
- //
- // > jQuery('selector').bind( 'resize', function(e) {
- // > // element's width or height has changed!
- // > ...
- // > });
- //
- // Additional Notes:
- //
- // * The polling loop is not created until at least one callback is actually
- // bound to the 'resize' event, and this single polling loop is shared
- // across all elements.
- //
- // Double firing issue in jQuery 1.3.2:
- //
- // While this plugin works in jQuery 1.3.2, if an element's event callbacks
- // are manually triggered via .trigger( 'resize' ) or .resize() those
- // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
- // events system. This is not an issue when using jQuery 1.4+.
- //
- // > // While this works in jQuery 1.4+
- // > $(elem).css({ width: new_w, height: new_h }).resize();
- // >
- // > // In jQuery 1.3.2, you need to do this:
- // > var elem = $(elem);
- // > elem.css({ width: new_w, height: new_h });
- // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
- // > elem.resize();
-
- $.event.special[ str_resize ] = {
-
- // Called only when the first 'resize' event callback is bound per element.
- setup: function() {
- // Since window has its own native 'resize' event, return false so that
- // jQuery will bind the event using DOM methods. Since only 'window'
- // objects have a .setTimeout method, this should be a sufficient test.
- // Unless, of course, we're throttling the 'resize' event for window.
- if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-
- var elem = $(this);
-
- // Add this element to the list of internal elements to monitor.
- elems = elems.add( elem );
-
- // Initialize data store on the element.
- $.data( this, str_data, { w: elem.width(), h: elem.height() } );
-
- // If this is the first element added, start the polling loop.
- if ( elems.length === 1 ) {
- loopy();
- }
- },
-
- // Called only when the last 'resize' event callback is unbound per element.
- teardown: function() {
- // Since window has its own native 'resize' event, return false so that
- // jQuery will unbind the event using DOM methods. Since only 'window'
- // objects have a .setTimeout method, this should be a sufficient test.
- // Unless, of course, we're throttling the 'resize' event for window.
- if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-
- var elem = $(this);
-
- // Remove this element from the list of internal elements to monitor.
- elems = elems.not( elem );
-
- // Remove any data stored on the element.
- elem.removeData( str_data );
-
- // If this is the last element removed, stop the polling loop.
- if ( !elems.length ) {
- clearTimeout( timeout_id );
- }
- },
-
- // Called every time a 'resize' event callback is bound per element (new in
- // jQuery 1.4).
- add: function( handleObj ) {
- // Since window has its own native 'resize' event, return false so that
- // jQuery doesn't modify the event object. Unless, of course, we're
- // throttling the 'resize' event for window.
- if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
-
- var old_handler;
-
- // The new_handler function is executed every time the event is triggered.
- // This is used to update the internal element data store with the width
- // and height when the event is triggered manually, to avoid double-firing
- // of the event callback. See the "Double firing issue in jQuery 1.3.2"
- // comments above for more information.
-
- function new_handler( e, w, h ) {
- var elem = $(this),
- data = $.data( this, str_data );
-
- // If called from the polling loop, w and h will be passed in as
- // arguments. If called manually, via .trigger( 'resize' ) or .resize(),
- // those values will need to be computed.
- data.w = w !== undefined ? w : elem.width();
- data.h = h !== undefined ? h : elem.height();
-
- old_handler.apply( this, arguments );
- };
-
- // This may seem a little complicated, but it normalizes the special event
- // .add method between jQuery 1.4/1.4.1 and 1.4.2+
- if ( $.isFunction( handleObj ) ) {
- // 1.4, 1.4.1
- old_handler = handleObj;
- return new_handler;
- } else {
- // 1.4.2+
- old_handler = handleObj.handler;
- handleObj.handler = new_handler;
- }
- }
-
- };
-
- function loopy() {
-
- // Start the polling loop, asynchronously.
- timeout_id = window[ str_setTimeout ](function(){
-
- // Iterate over all elements to which the 'resize' event is bound.
- elems.each(function(){
- var elem = $(this),
- width = elem.width(),
- height = elem.height(),
- data = $.data( this, str_data );
-
- // If element size has changed since the last time, update the element
- // data store and trigger the 'resize' event.
- if ( width !== data.w || height !== data.h ) {
- elem.trigger( str_resize, [ data.w = width, data.h = height ] );
- }
-
- });
-
- // Loop.
- loopy();
-
- }, jq_resize[ str_delay ] );
-
- };
-
-})(jQuery,this);
diff --git a/vendor/assets/javascripts/xterm/fit.js b/vendor/assets/javascripts/xterm/fit.js
index 7e24fd9b36e..55438452cad 100644
--- a/vendor/assets/javascripts/xterm/fit.js
+++ b/vendor/assets/javascripts/xterm/fit.js
@@ -16,12 +16,12 @@
/*
* CommonJS environment
*/
- module.exports = fit(require('../../xterm'));
+ module.exports = fit(require('./xterm'));
} else if (typeof define == 'function') {
/*
* Require.js is available
*/
- define(['../../xterm'], fit);
+ define(['./xterm'], fit);
} else {
/*
* Plain browser environment