summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2017-09-28 21:38:06 +0800
committerLin Jen-Shin <godfat@godfat.org>2017-09-28 21:38:06 +0800
commite9060da6f0aee35d1d6b7a9158a102c4b197a965 (patch)
treecb4235d6eabd6a9470f7b4b01673adf4f0fcdad6
parent028712fb843881f5c51d84e6b1edf77da2bc76ca (diff)
parent576425f029aae35d2489b058595dd40ae2ae6e2c (diff)
downloadgitlab-ce-e9060da6f0aee35d1d6b7a9158a102c4b197a965.tar.gz
Merge remote-tracking branch 'upstream/master' into gem/sm/bump-google-api-client-gem-from-0-8-6-to-0-13-6
* upstream/master: (130 commits) Handle error when fetching ref for MR with deleted source branch refactor emails service fix update service refactor users update service refactor keys controller refactor some controllers to make them EE friendly fix specs fix users update service update initializers refactor services to match EE signature Change recommended MySQL version to 5.6 Clarify artifact download via the API only accepts branch or tag name for ref Clean merge_jid whenever necessary on the merge process Rolling back change to n+1 detection Breadcrumbs receives padding when double lined karma spec fixes Add EEP to headings Added some Gitaly docs to `docs/development` Check orientation of profile image Fixed missing namespaces on navigation translations ...
-rw-r--r--.eslintignore2
-rw-r--r--CHANGELOG.md7
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock8
-rw-r--r--[-rwxr-xr-x]app/assets/images/favicon-blue.icobin5430 -> 5430 bytes
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js6
-rw-r--r--app/assets/javascripts/cycle_analytics/components/limit_warning_component.js17
-rw-r--r--app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue26
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.js51
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.vue47
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_component.vue53
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_issue_component.js52
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js53
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue56
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_production_component.js52
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.js62
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue62
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js53
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue55
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.js49
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue54
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.js25
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.vue29
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js103
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js35
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js5
-rw-r--r--app/assets/javascripts/files_comment_button.js6
-rw-r--r--app/assets/javascripts/fly_out_nav.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js30
-rw-r--r--app/assets/javascripts/line_highlighter.js283
-rw-r--r--app/assets/javascripts/locale/index.js5
-rw-r--r--app/assets/javascripts/main.js5
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/profile/gl_crop.js3
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue14
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js2
-rw-r--r--app/assets/javascripts/right_sidebar.js2
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss8
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss12
-rw-r--r--app/assets/stylesheets/framework/mixins.scss38
-rw-r--r--app/assets/stylesheets/framework/nav.scss34
-rw-r--r--app/assets/stylesheets/framework/new-nav.scss (renamed from app/assets/stylesheets/new_nav.scss)79
-rw-r--r--app/assets/stylesheets/framework/new-sidebar.scss (renamed from app/assets/stylesheets/new_sidebar.scss)7
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss78
-rw-r--r--app/assets/stylesheets/pages/boards.scss26
-rw-r--r--app/assets/stylesheets/pages/convdev_index.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss19
-rw-r--r--app/assets/stylesheets/pages/issuable.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss8
-rw-r--r--app/assets/stylesheets/pages/profile.scss4
-rw-r--r--app/assets/stylesheets/pages/repo.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss78
-rw-r--r--app/assets/stylesheets/pages/status.scss6
-rw-r--r--app/assets/stylesheets/test.scss6
-rw-r--r--app/controllers/admin/applications_controller.rb10
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/concerns/issuable_collections.rb29
-rw-r--r--app/controllers/confirmations_controller.rb6
-rw-r--r--app/controllers/oauth/applications_controller.rb10
-rw-r--r--app/controllers/profiles/avatars_controller.rb2
-rw-r--r--app/controllers/profiles/emails_controller.rb4
-rw-r--r--app/controllers/profiles/keys_controller.rb8
-rw-r--r--app/controllers/profiles/notifications_controller.rb2
-rw-r--r--app/controllers/profiles/passwords_controller.rb6
-rw-r--r--app/controllers/profiles/preferences_controller.rb2
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb6
-rw-r--r--app/controllers/profiles_controller.rb10
-rw-r--r--app/controllers/sessions_controller.rb2
-rw-r--r--app/finders/issuable_finder.rb22
-rw-r--r--app/helpers/avatars_helper.rb25
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/helpers/sorting_helper.rb304
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/commit.rb11
-rw-r--r--app/models/concerns/issuable.rb24
-rw-r--r--app/models/concerns/sortable.rb15
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/repository.rb25
-rw-r--r--app/models/user.rb10
-rw-r--r--app/services/ci/create_pipeline_service.rb150
-rw-r--r--app/services/emails/base_service.rb5
-rw-r--r--app/services/emails/destroy_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb19
-rw-r--r--app/services/users/update_service.rb19
-rw-r--r--app/views/admin/background_jobs/show.html.haml1
-rw-r--r--app/views/admin/cohorts/index.html.haml1
-rw-r--r--app/views/admin/conversational_development_index/show.html.haml2
-rw-r--r--app/views/admin/dashboard/_head.html.haml37
-rw-r--r--app/views/admin/dashboard/index.html.haml1
-rw-r--r--app/views/admin/groups/index.html.haml1
-rw-r--r--app/views/admin/health_check/show.html.haml1
-rw-r--r--app/views/admin/jobs/index.html.haml1
-rw-r--r--app/views/admin/logs/show.html.haml1
-rw-r--r--app/views/admin/monitoring/_head.html.haml25
-rw-r--r--app/views/admin/projects/index.html.haml1
-rw-r--r--app/views/admin/requests_profiles/index.html.haml1
-rw-r--r--app/views/admin/runners/index.html.haml1
-rw-r--r--app/views/admin/system_info/show.html.haml1
-rw-r--r--app/views/admin/users/index.html.haml1
-rw-r--r--app/views/dashboard/projects/_nav.html.haml6
-rw-r--r--app/views/dashboard/projects/index.html.haml3
-rw-r--r--app/views/groups/_head.html.haml17
-rw-r--r--app/views/groups/_head_issues.html.haml19
-rw-r--r--app/views/groups/_settings_head.html.haml19
-rw-r--r--app/views/groups/activity.html.haml1
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/issues.html.haml1
-rw-r--r--app/views/groups/labels/index.html.haml3
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/groups/projects.html.haml1
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml1
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/groups/subgroups.html.haml1
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/projects/_head.html.haml17
-rw-r--r--app/views/projects/activity.html.haml2
-rw-r--r--app/views/projects/artifacts/browse.html.haml3
-rw-r--r--app/views/projects/artifacts/file.html.haml1
-rw-r--r--app/views/projects/blame/show.html.haml1
-rw-r--r--app/views/projects/blob/edit.html.haml1
-rw-r--r--app/views/projects/blob/show.html.haml1
-rw-r--r--app/views/projects/branches/index.html.haml1
-rw-r--r--app/views/projects/commit/show.html.haml1
-rw-r--r--app/views/projects/commits/_head.html.haml36
-rw-r--r--app/views/projects/commits/show.html.haml3
-rw-r--r--app/views/projects/compare/index.html.haml1
-rw-r--r--app/views/projects/compare/show.html.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/empty.html.haml1
-rw-r--r--app/views/projects/environments/edit.html.haml1
-rw-r--r--app/views/projects/environments/folder.html.haml1
-rw-r--r--app/views/projects/environments/index.html.haml1
-rw-r--r--app/views/projects/environments/metrics.html.haml2
-rw-r--r--app/views/projects/environments/new.html.haml1
-rw-r--r--app/views/projects/environments/show.html.haml1
-rw-r--r--app/views/projects/environments/terminal.html.haml1
-rw-r--r--app/views/projects/find_file/show.html.haml1
-rw-r--r--app/views/projects/graphs/charts.html.haml1
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/hook_logs/show.html.haml2
-rw-r--r--app/views/projects/hooks/edit.html.haml2
-rw-r--r--app/views/projects/issues/_head.html.haml33
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/jobs/index.html.haml1
-rw-r--r--app/views/projects/jobs/show.html.haml1
-rw-r--r--app/views/projects/labels/edit.html.haml1
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/labels/new.html.haml1
-rw-r--r--app/views/projects/merge_requests/_head.html.haml21
-rw-r--r--app/views/projects/merge_requests/index.html.haml3
-rw-r--r--app/views/projects/milestones/edit.html.haml1
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/new.html.haml1
-rw-r--r--app/views/projects/milestones/show.html.haml1
-rw-r--r--app/views/projects/network/show.html.haml1
-rw-r--r--app/views/projects/notes/_actions.html.haml3
-rw-r--r--app/views/projects/pages/show.html.haml1
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml2
-rw-r--r--app/views/projects/pipelines/_head.html.haml34
-rw-r--r--app/views/projects/pipelines/charts.html.haml1
-rw-r--r--app/views/projects/pipelines/index.html.haml1
-rw-r--r--app/views/projects/pipelines/show.html.haml1
-rw-r--r--app/views/projects/pipelines_settings/_show.html.haml3
-rw-r--r--app/views/projects/releases/edit.html.haml1
-rw-r--r--app/views/projects/services/edit.html.haml1
-rw-r--r--app/views/projects/settings/_head.html.haml30
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/projects/settings/integrations/show.html.haml1
-rw-r--r--app/views/projects/settings/members/show.html.haml1
-rw-r--r--app/views/projects/settings/repository/show.html.haml2
-rw-r--r--app/views/projects/show.html.haml1
-rw-r--r--app/views/projects/tags/index.html.haml1
-rw-r--r--app/views/projects/tags/show.html.haml1
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/shared/_auto_devops_callout.html.haml10
-rw-r--r--app/views/shared/_mr_head.html.haml4
-rw-r--r--app/views/shared/_nav_scroll.html.haml4
-rw-r--r--app/views/shared/_sort_dropdown.html.haml42
-rw-r--r--app/views/shared/boards/_show.html.haml10
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_canceled.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_created.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_failed.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_manual.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_pending.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_running.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_skipped.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_success.svg0
-rw-r--r--[-rwxr-xr-x]app/views/shared/icons/_icon_status_warning.svg0
-rw-r--r--app/views/shared/issuable/_user_dropdown_item.html.haml2
-rw-r--r--changelogs/unreleased/34371-cycle-analitcs-global.yml5
-rw-r--r--changelogs/unreleased/3523-i18n-autodevops.yml5
-rw-r--r--changelogs/unreleased/37467-helper-method-from-users-endpoint-overrides-api-helper-method.yml5
-rw-r--r--changelogs/unreleased/38319-nomethoderror-undefined-method-sha-for-nil-nilclass.yml5
-rw-r--r--changelogs/unreleased/38476-improve-merge-jid-cleanup-on-merge-process.yml5
-rw-r--r--changelogs/unreleased/breadcrumbs-line-height-padding.yml5
-rw-r--r--changelogs/unreleased/commit-side-by-side-comment.yml5
-rw-r--r--changelogs/unreleased/dm-bitbucket-import-truncated-shas.yml6
-rw-r--r--changelogs/unreleased/dm-simple-project-avatar-url.yml5
-rw-r--r--changelogs/unreleased/docs-28814-clarify-artifacts-ref.yml5
-rw-r--r--changelogs/unreleased/docs-38152-bump-recommended-mysql-version.yml5
-rw-r--r--changelogs/unreleased/expose-last-pipeline-details-in-api-for-single-commit.yml5
-rw-r--r--changelogs/unreleased/improve_sorting_list.yml5
-rw-r--r--changelogs/unreleased/mr-side-by-side-breadcrumbs-container.yml5
-rw-r--r--changelogs/unreleased/project-page-clearer.yml5
-rw-r--r--changelogs/unreleased/remove-temporary-ci-index.yml5
-rw-r--r--changelogs/unreleased/rotated_profile_image.yml5
-rw-r--r--changelogs/unreleased/zj-repo-gitaly.yml5
-rw-r--r--config/application.rb2
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--db/migrate/20170828135939_migrate_user_external_mail_data.rb2
-rw-r--r--db/migrate/20170919211300_remove_temporary_ci_builds_index.rb27
-rw-r--r--db/post_migrate/20170828170502_post_deploy_migrate_user_external_mail_data.rb2
-rw-r--r--db/schema.rb1
-rw-r--r--doc/api/commits.md6
-rw-r--r--doc/api/jobs.md2
-rw-r--r--doc/ci/yaml/README.md5
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/background_migrations.md29
-rw-r--r--doc/development/fe_guide/index.md28
-rw-r--r--doc/development/gitaly.md54
-rw-r--r--doc/development/licensing.md3
-rw-r--r--doc/development/testing.md20
-rw-r--r--doc/install/database_mysql.md6
-rw-r--r--doc/install/kubernetes/gitlab_omnibus.md33
-rw-r--r--doc/install/requirements.md4
-rw-r--r--doc/integration/azure.md3
-rw-r--r--[-rwxr-xr-x]doc/university/training/gitlab_flow.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/index.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/additional_resources.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/agile_git.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/bisect.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/cherry_picking.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/env_setup.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/explore_gitlab.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/feature_branching.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/getting_started.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/git_add.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/git_intro.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/git_log.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/gitlab_flow.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/merge_conflicts.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/merge_requests.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/rollback_commits.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/stash.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/subtree.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/tags.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/topics/unstage.md0
-rw-r--r--[-rwxr-xr-x]doc/university/training/user_training.md0
-rw-r--r--doc/user/project/integrations/img/kubernetes_configuration.pngbin113827 -> 14407 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/integrations/img/webhook_logs.pngbin24066 -> 24066 bytes
-rw-r--r--doc/user/project/integrations/kubernetes.md134
-rw-r--r--doc/user/project/integrations/prometheus_library/cloudwatch.md5
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md4
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md10
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md5
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md5
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/button_close_issue.pngbin15508 -> 15508 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/closing_and_related_issues.pngbin6395 -> 6395 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/confidential_issues_create.pngbin8185 -> 8185 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/confidential_issues_search_guest.pngbin8593 -> 8593 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/confidential_issues_search_master.pngbin13228 -> 13228 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/due_dates_create.pngbin6992 -> 6992 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/due_dates_edit_sidebar.pngbin1700 -> 1700 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/due_dates_issues_index_page.pngbin19302 -> 19302 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/due_dates_todos.pngbin4799 -> 4799 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/issue_board.pngbin58645 -> 58645 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/issue_template.pngbin28061 -> 28061 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/mention_in_issue.pngbin3738 -> 3738 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/mention_in_merge_request.pngbin3944 -> 3944 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/merge_request_closes_issue.pngbin19423 -> 19423 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/new_issue.pngbin31727 -> 31727 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/new_issue_from_issue_board.pngbin137175 -> 137175 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/new_issue_from_open_issue.pngbin20628 -> 20628 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/new_issue_from_projects_dashboard.pngbin29865 -> 29865 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/new_issue_from_tracker_list.pngbin24345 -> 24345 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/sidebar_confidential_issue.pngbin10210 -> 10210 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/issues/img/sidebar_not_confidential_issue.pngbin8163 -> 8163 bytes
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md22
-rw-r--r--[-rwxr-xr-x]doc/user/project/repository/img/contributors_graph.pngbin31670 -> 31670 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/repository/img/repo_graph.pngbin52317 -> 52317 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/settings/img/general_settings.pngbin35871 -> 35871 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/settings/img/merge_requests_settings.pngbin52029 -> 52029 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/issues_any_assignee.pngbin90455 -> 90455 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/issues_assigned_to_you.pngbin49079 -> 49079 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/issues_author.pngbin55217 -> 55217 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/issues_mrs_shortcut.pngbin34115 -> 34115 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/left_menu_bar.pngbin37433 -> 37433 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/project_search.pngbin41900 -> 41900 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/search_issues_board.pngbin82113 -> 82113 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/search/img/sort_projects.pngbin59495 -> 59495 bytes
-rw-r--r--features/project/issues/issues.feature18
-rw-r--r--features/project/merge_requests.feature16
-rw-r--r--features/steps/project/issues/issues.rb2
-rw-r--r--features/steps/project/merge_requests.rb2
-rw-r--r--features/steps/shared/issuable.rb12
-rw-r--r--lib/api/entities.rb12
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/users.rb14
-rw-r--r--lib/banzai/filter/sanitization_filter.rb3
-rw-r--r--lib/github/client.rb3
-rw-r--r--lib/github/import.rb29
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb9
-rw-r--r--lib/gitlab/ci/pipeline/chain/base.rb27
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb29
-rw-r--r--lib/gitlab/ci/pipeline/chain/helpers.rb25
-rw-r--r--lib/gitlab/ci/pipeline/chain/sequence.rb36
-rw-r--r--lib/gitlab/ci/pipeline/chain/skip.rb33
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb54
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/config.rb35
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/repository.rb30
-rw-r--r--lib/gitlab/ci/pipeline/duration.rb143
-rw-r--r--lib/gitlab/ci/pipeline_duration.rb141
-rw-r--r--lib/gitlab/diff/diff_refs.rb22
-rw-r--r--lib/gitlab/diff/position.rb11
-rw-r--r--lib/gitlab/git/repository.rb31
-rw-r--r--lib/gitlab/gitaly_client.rb8
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb20
-rw-r--r--lib/gitlab/ldap/access.rb2
-rw-r--r--lib/gitlab/markdown/pipeline.rb32
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/system_check/app/git_version_check.rb2
-rw-r--r--lib/system_check/app/ruby_version_check.rb2
-rw-r--r--locale/gitlab.pot212
-rwxr-xr-xscripts/lint-doc.sh18
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb8
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb8
-rw-r--r--spec/features/dashboard/projects_spec.rb19
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb38
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb16
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues_spec.rb52
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb4
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb40
-rw-r--r--spec/features/projects/commit/diff_notes_spec.rb36
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/commit/detail.json11
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json16
-rw-r--r--spec/helpers/avatars_helper_spec.rb102
-rw-r--r--spec/helpers/projects_helper_spec.rb12
-rw-r--r--spec/javascripts/cycle_analytics/limit_warning_component_spec.js2
-rw-r--r--spec/javascripts/fly_out_nav_spec.js6
-rw-r--r--spec/javascripts/lib/utils/sticky_spec.js75
-rw-r--r--spec/javascripts/line_highlighter_spec.js18
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js23
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb8
-rw-r--r--spec/lib/github/client_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb66
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb142
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb126
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb60
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb (renamed from spec/lib/gitlab/ci/pipeline_duration_spec.rb)6
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb55
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb38
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb10
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb78
-rw-r--r--spec/lib/system_check/base_check_spec.rb17
-rw-r--r--spec/models/ci/runner_spec.rb69
-rw-r--r--spec/policies/project_policy_spec.rb133
-rw-r--r--spec/requests/api/commits_spec.rb5
-rw-r--r--spec/requests/api/environments_spec.rb1
-rw-r--r--spec/requests/api/projects_spec.rb1
-rw-r--r--spec/requests/api/users_spec.rb9
-rw-r--r--spec/requests/api/v3/projects_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb120
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb23
-rw-r--r--spec/services/users/update_service_spec.rb8
-rw-r--r--spec/spec_helper.rb12
-rw-r--r--spec/support/test_env.rb3
-rw-r--r--spec/tasks/gitlab/task_helpers_spec.rb20
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml.rb17
-rw-r--r--spec/workers/post_receive_spec.rb13
386 files changed, 3726 insertions, 2508 deletions
diff --git a/.eslintignore b/.eslintignore
index d6ce39636bd..1623b996213 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -8,4 +8,4 @@
karma.config.js
webpack.config.js
svg.config.js
-/app/assets/javascripts/locale/**/*.js
+/app/assets/javascripts/locale/**/app.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c671f8d53a..d08e42b3b65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.0.2 (2017-09-27)
+
+- [FIXED] Notes will not show an empty bubble when the author isn't a member. !14450
+- [FIXED] Some checks in `rake gitlab:check` were failling with 'undefined method `run_command`'. !14469
+- [FIXED] Make locked setting of Runner to not affect jobs scheduling. !14483
+- [FIXED] Re-allow `name` attribute on user-provided anchor HTML.
+
## 10.0.1 (2017-09-23)
- [FIXED] Fix duplicate key errors in PostDeployMigrateUserExternalMailData migration.
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index b3d91f9cfc0..da902181863 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.9.0
+5.9.2
diff --git a/Gemfile b/Gemfile
index 460d9a05292..fb66b176ad7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -26,7 +26,7 @@ gem 'doorkeeper', '~> 4.2.0'
gem 'doorkeeper-openid_connect', '~> 1.1.0'
gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
-gem 'omniauth-azure-oauth2', '~> 0.0.6'
+gem 'omniauth-azure-oauth2', '~> 0.0.9'
gem 'omniauth-cas3', '~> 1.1.4'
gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index a13ab3e3305..a11190069ff 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -512,10 +512,10 @@ GEM
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1)
omniauth-oauth2 (~> 1.3, >= 1.3.1)
- omniauth-azure-oauth2 (0.0.6)
+ omniauth-azure-oauth2 (0.0.9)
jwt (~> 1.0)
omniauth (~> 1.0)
- omniauth-oauth2 (~> 1.1)
+ omniauth-oauth2 (~> 1.4)
omniauth-cas3 (1.1.4)
addressable (~> 2.3)
nokogiri (~> 1.7, >= 1.7.1)
@@ -541,7 +541,7 @@ GEM
omniauth-oauth (1.1.0)
oauth
omniauth (~> 1.0)
- omniauth-oauth2 (1.3.1)
+ omniauth-oauth2 (1.4.0)
oauth2 (~> 1.0)
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
@@ -1077,7 +1077,7 @@ DEPENDENCIES
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.1)
- omniauth-azure-oauth2 (~> 0.0.6)
+ omniauth-azure-oauth2 (~> 0.0.9)
omniauth-cas3 (~> 1.1.4)
omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1)
diff --git a/app/assets/images/favicon-blue.ico b/app/assets/images/favicon-blue.ico
index 156fcf07588..156fcf07588 100755..100644
--- a/app/assets/images/favicon-blue.ico
+++ b/app/assets/images/favicon-blue.ico
Binary files differ
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index ea00efe4b46..815248f38ee 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -77,9 +77,6 @@ $(() => {
});
Store.rootPath = this.boardsEndpoint;
- this.filterManager = new FilteredSearchBoards(Store.filter, true);
- this.filterManager.setup();
-
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
},
@@ -87,6 +84,9 @@ $(() => {
eventHub.$off('updateTokens', this.updateTokens);
},
mounted () {
+ this.filterManager = new FilteredSearchBoards(Store.filter, true);
+ this.filterManager.setup();
+
Store.disabled = this.disabled;
gl.boardService.all()
.then(response => response.json())
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js
deleted file mode 100644
index 8d3d34f836f..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export default {
- props: {
- count: {
- type: Number,
- required: true,
- },
- },
- template: `
- <span v-if="count === 50" class="events-info pull-right">
- <i class="fa fa-warning has-tooltip"
- aria-hidden="true"
- :title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
- data-placement="top"></i>
- {{ n__('Showing %d event', 'Showing %d events', 50) }}
- </span>
- `,
-};
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
new file mode 100644
index 00000000000..6e94ba929b2
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
@@ -0,0 +1,26 @@
+<script>
+ import tooltip from '../../vue_shared/directives/tooltip';
+
+ export default {
+ props: {
+ count: {
+ type: Number,
+ required: true,
+ },
+ },
+ directives: {
+ tooltip,
+ },
+ };
+</script>
+<template>
+ <span v-if="count === 50" class="events-info pull-right">
+ <i
+ class="fa fa-warning"
+ v-tooltip
+ aria-hidden="true"
+ :title="n__('Limited to showing %d event at most', 'Limited to showing %d events at most', 50)"
+ data-placement="top"></i>
+ {{ n__('Showing %d event', 'Showing %d events', 50) }}
+ </span>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
deleted file mode 100644
index 7c32a38fbe7..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* eslint-disable no-param-reassign */
-
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageCodeComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="mergeRequest in items" class="stage-event-item">
- <div class="item-details">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
- <h5 class="item-title merge-merquest-title">
- <a :href="mergeRequest.url">
- {{ mergeRequest.title }}
- </a>
- </h5>
- <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
- &middot;
- <span>
- {{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
- </span>
- <span>
- {{ s__('ByAuthor|by') }}
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="mergeRequest.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
new file mode 100644
index 00000000000..e4d62b649e5
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
@@ -0,0 +1,47 @@
+<script>
+ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ components: {
+ userAvatarImage,
+ },
+ };
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li v-for="mergeRequest in items" class="stage-event-item">
+ <div class="item-details">
+ <!-- FIXME: Pass an alt attribute here for accessibility -->
+ <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
+ <h5 class="item-title merge-merquest-title">
+ <a :href="mergeRequest.url">
+ {{ mergeRequest.title }}
+ </a>
+ </h5>
+ <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+ &middot;
+ <span>
+ {{ s__('OpenedNDaysAgo|Opened') }}
+ <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ </span>
+ <span>
+ {{ s__('ByAuthor|by') }}
+ <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="mergeRequest.totalTime"></total-time>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
new file mode 100644
index 00000000000..ab730af8f5b
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
@@ -0,0 +1,53 @@
+<script>
+ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ components: {
+ userAvatarImage,
+ },
+ };
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li
+ v-for="(issue, i) in items"
+ :key="i"
+ class="stage-event-item">
+ <div class="item-details">
+ <!-- FIXME: Pass an alt attribute here for accessibility -->
+ <user-avatar-image :img-src="issue.author.avatarUrl"/>
+ <h5 class="item-title issue-title">
+ <a class="issue-title" :href="issue.url">
+ {{ issue.title }}
+ </a>
+ </h5>
+ <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
+ &middot;
+ <span>
+ {{ s__('OpenedNDaysAgo|Opened') }}
+ <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
+ </span>
+ <span>
+ {{ s__('ByAuthor|by') }}
+ <a :href="issue.author.webUrl" class="issue-author-link">
+ {{ issue.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="issue.totalTime"/>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
+
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
deleted file mode 100644
index 5f4a0ac8590..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageIssueComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="issue in items" class="stage-event-item">
- <div class="item-details">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="issue.author.avatarUrl"/>
- <h5 class="item-title issue-title">
- <a class="issue-title" :href="issue.url">
- {{ issue.title }}
- </a>
- </h5>
- <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
- &middot;
- <span>
- {{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
- </span>
- <span>
- {{ s__('ByAuthor|by') }}
- <a :href="issue.author.webUrl" class="issue-author-link">
- {{ issue.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="issue.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
deleted file mode 100644
index 11fee5410d9..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-import iconCommit from '../svg/icon_commit.svg';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StagePlanComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- data() {
- return { iconCommit };
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="commit in items" class="stage-event-item">
- <div class="item-details item-conmmit-component">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="commit.author.avatarUrl"/>
- <h5 class="item-title commit-title">
- <a :href="commit.commitUrl">
- {{ commit.title }}
- </a>
- </h5>
- <span>
- {{ s__('FirstPushedBy|First') }}
- <span class="commit-icon">${iconCommit}</span>
- <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
- {{ s__('FirstPushedBy|pushed by') }}
- <a :href="commit.author.webUrl" class="commit-author-link">
- {{ commit.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="commit.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
new file mode 100644
index 00000000000..152c086a606
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
@@ -0,0 +1,56 @@
+<script>
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import iconCommit from '../svg/icon_commit.svg';
+
+export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ components: {
+ userAvatarImage,
+ },
+ computed: {
+ iconCommit() {
+ return iconCommit;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li
+ v-for="(commit, i) in items"
+ :key="i"
+ class="stage-event-item">
+ <div class="item-details item-conmmit-component">
+ <!-- FIXME: Pass an alt attribute here for accessibility -->
+ <user-avatar-image :img-src="commit.author.avatarUrl"/>
+ <h5 class="item-title commit-title">
+ <a :href="commit.commitUrl">
+ {{ commit.title }}
+ </a>
+ </h5>
+ <span>
+ {{ s__('FirstPushedBy|First') }}
+ <span class="commit-icon" v-html="iconCommit"></span>
+ <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
+ {{ s__('FirstPushedBy|pushed by') }}
+ <a :href="commit.author.webUrl" class="commit-author-link">
+ {{ commit.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="commit.totalTime" />
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
+
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
deleted file mode 100644
index b7ba9360f70..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
+++ /dev/null
@@ -1,52 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageProductionComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="issue in items" class="stage-event-item">
- <div class="item-details">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="issue.author.avatarUrl"/>
- <h5 class="item-title issue-title">
- <a class="issue-title" :href="issue.url">
- {{ issue.title }}
- </a>
- </h5>
- <a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
- &middot;
- <span>
- {{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
- </span>
- <span>
- {{ s__('ByAuthor|by') }}
- <a :href="issue.author.webUrl" class="issue-author-link">
- {{ issue.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="issue.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
deleted file mode 100644
index f41a0d0e4ff..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageReviewComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- components: {
- userAvatarImage,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="mergeRequest in items" class="stage-event-item">
- <div class="item-details">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
- <h5 class="item-title merge-merquest-title">
- <a :href="mergeRequest.url">
- {{ mergeRequest.title }}
- </a>
- </h5>
- <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
- &middot;
- <span>
- {{ s__('OpenedNDaysAgo|Opened') }}
- <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
- </span>
- <span>
- {{ s__('ByAuthor|by') }}
- <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
- </span>
- <template v-if="mergeRequest.state === 'closed'">
- <span class="merge-request-state">
- <i class="fa fa-ban"></i>
- {{ mergeRequest.state.toUpperCase() }}
- </span>
- </template>
- <template v-else>
- <span class="merge-request-branch" v-if="mergeRequest.branch">
- <i class= "fa fa-code-fork"></i>
- <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
- </span>
- </template>
- </div>
- <div class="item-time">
- <total-time :time="mergeRequest.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
new file mode 100644
index 00000000000..9e66b690404
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -0,0 +1,62 @@
+<script>
+ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ components: {
+ userAvatarImage,
+ },
+ };
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li
+ v-for="(mergeRequest, i) in items"
+ :key="i"
+ class="stage-event-item">
+ <div class="item-details">
+ <!-- FIXME: Pass an alt attribute here for accessibility -->
+ <user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
+ <h5 class="item-title merge-merquest-title">
+ <a :href="mergeRequest.url">
+ {{ mergeRequest.title }}
+ </a>
+ </h5>
+ <a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
+ &middot;
+ <span>
+ {{ s__('OpenedNDaysAgo|Opened') }}
+ <a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
+ </span>
+ <span>
+ {{ s__('ByAuthor|by') }}
+ <a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
+ </span>
+ <template v-if="mergeRequest.state === 'closed'">
+ <span class="merge-request-state">
+ <i class="fa fa-ban"></i>
+ {{ mergeRequest.state.toUpperCase() }}
+ </span>
+ </template>
+ <template v-else>
+ <span class="merge-request-branch" v-if="mergeRequest.branch">
+ <i class= "fa fa-code-fork"></i>
+ <a :href="mergeRequest.branch.url">{{ mergeRequest.branch.name }}</a>
+ </span>
+ </template>
+ </div>
+ <div class="item-time">
+ <total-time :time="mergeRequest.totalTime"/>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
deleted file mode 100644
index d7c906c9d39..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
-import iconBranch from '../svg/icon_branch.svg';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageStagingComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- data() {
- return { iconBranch };
- },
- components: {
- userAvatarImage,
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="build in items" class="stage-event-item item-build-component">
- <div class="item-details">
- <!-- FIXME: Pass an alt attribute here for accessibility -->
- <user-avatar-image :img-src="build.author.avatarUrl"/>
- <h5 class="item-title">
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
- <span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
- </h5>
- <span>
- <a :href="build.url" class="build-date">{{ build.date }}</a>
- {{ s__('ByAuthor|by') }}
- <a :href="build.author.webUrl" class="issue-author-link">
- {{ build.author.name }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="build.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
new file mode 100644
index 00000000000..2787b5ea47b
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -0,0 +1,55 @@
+<script>
+ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+ import iconBranch from '../svg/icon_branch.svg';
+
+ export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ components: {
+ userAvatarImage,
+ },
+ computed: {
+ iconBranch() {
+ return iconBranch;
+ },
+ },
+ };
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li
+ v-for="(build, i) in items"
+ class="stage-event-item item-build-component"
+ :key="i">
+ <div class="item-details">
+ <!-- FIXME: Pass an alt attribute here for accessibility -->
+ <user-avatar-image :img-src="build.author.avatarUrl"/>
+ <h5 class="item-title">
+ <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <i class="fa fa-code-fork"></i>
+ <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
+ <span class="icon-branch" v-html="iconBranch"></span>
+ <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
+ </h5>
+ <span>
+ <a :href="build.url" class="build-date">{{ build.date }}</a>
+ {{ s__('ByAuthor|by') }}
+ <a :href="build.author.webUrl" class="issue-author-link">
+ {{ build.author.name }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="build.totalTime"/>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
deleted file mode 100644
index 78cc97eea0b..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/* eslint-disable no-param-reassign */
-import Vue from 'vue';
-import iconBuildStatus from '../svg/icon_build_status.svg';
-import iconBranch from '../svg/icon_branch.svg';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.StageTestComponent = Vue.extend({
- props: {
- items: Array,
- stage: Object,
- },
- data() {
- return { iconBuildStatus, iconBranch };
- },
- template: `
- <div>
- <div class="events-description">
- {{ stage.description }}
- <limit-warning :count="items.length" />
- </div>
- <ul class="stage-event-list">
- <li v-for="build in items" class="stage-event-item item-build-component">
- <div class="item-details">
- <h5 class="item-title">
- <span class="icon-build-status">${iconBuildStatus}</span>
- <a :href="build.url" class="item-build-name">{{ build.name }}</a>
- &middot;
- <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
- <i class="fa fa-code-fork"></i>
- <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
- <span class="icon-branch">${iconBranch}</span>
- <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
- </h5>
- <span>
- <a :href="build.url" class="issue-date">
- {{ build.date }}
- </a>
- </span>
- </div>
- <div class="item-time">
- <total-time :time="build.totalTime"></total-time>
- </div>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
new file mode 100644
index 00000000000..9c3d39ce011
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -0,0 +1,54 @@
+<script>
+import iconBuildStatus from '../svg/icon_build_status.svg';
+import iconBranch from '../svg/icon_branch.svg';
+
+export default {
+ props: {
+ items: Array,
+ stage: Object,
+ },
+ computed: {
+ iconBuildStatus() {
+ return iconBuildStatus;
+ },
+ iconBranch() {
+ return iconBranch;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="events-description">
+ {{ stage.description }}
+ <limit-warning :count="items.length" />
+ </div>
+ <ul class="stage-event-list">
+ <li
+ v-for="(build, i) in items"
+ :key="i"
+ class="stage-event-item item-build-component">
+ <div class="item-details">
+ <h5 class="item-title">
+ <span class="icon-build-status" v-html="iconBuildStatus"></span>
+ <a :href="build.url" class="item-build-name">{{ build.name }}</a>
+ &middot;
+ <a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
+ <i class="fa fa-code-fork"></i>
+ <a :href="build.branch.url" class="ref-name">{{ build.branch.name }}</a>
+ <span class="icon-branch" v-html="iconBranch"></span>
+ <a :href="build.commitUrl" class="commit-sha">{{ build.shortSha }}</a>
+ </h5>
+ <span>
+ <a :href="build.url" class="issue-date">
+ {{ build.date }}
+ </a>
+ </span>
+ </div>
+ <div class="item-time">
+ <total-time :time="build.totalTime"/>
+ </div>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
deleted file mode 100644
index d5e6167b2a8..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/* eslint-disable no-param-reassign */
-
-import Vue from 'vue';
-
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
-global.cycleAnalytics.TotalTimeComponent = Vue.extend({
- props: {
- time: Object,
- },
- template: `
- <span class="total-time">
- <template v-if="Object.keys(time).length">
- <template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
- <template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
- <template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
- <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
- </template>
- <template v-else>
- --
- </template>
- </span>
- `,
-});
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
new file mode 100644
index 00000000000..9941b997b3f
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
@@ -0,0 +1,29 @@
+<script>
+ export default {
+ props: {
+ time: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ hasData() {
+ return Object.keys(this.time).length;
+ },
+ },
+ };
+</script>
+<template>
+ <span class="total-time">
+ <template v-if="hasData">
+ <template v-if="time.days">{{ time.days }} <span>{{ n__('day', 'days', time.days) }}</span></template>
+ <template v-if="time.hours">{{ time.hours }} <span>{{ n__('Time|hr', 'Time|hrs', time.hours) }}</span></template>
+ <template v-if="time.mins && !time.days">{{ time.mins }} <span>{{ n__('Time|min', 'Time|mins', time.mins) }}</span></template>
+ <template v-if="time.seconds && hasDa === 1 || time.seconds === 0">{{ time.seconds }} <span>{{ s__('Time|s') }}</span></template>
+ </template>
+ <template v-else>
+ --
+ </template>
+ </span>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 6583e471a48..8002b0b23c9 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -3,60 +3,63 @@
import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate';
-import LimitWarningComponent from './components/limit_warning_component';
-import './components/stage_code_component';
-import './components/stage_issue_component';
-import './components/stage_plan_component';
-import './components/stage_production_component';
-import './components/stage_review_component';
-import './components/stage_staging_component';
-import './components/stage_test_component';
-import './components/total_time_component';
-import './cycle_analytics_service';
-import './cycle_analytics_store';
+import limitWarningComponent from './components/limit_warning_component.vue';
+import stageCodeComponent from './components/stage_code_component.vue';
+import stagePlanComponent from './components/stage_plan_component.vue';
+import stageComponent from './components/stage_component.vue';
+import stageReviewComponent from './components/stage_review_component.vue';
+import stageStagingComponent from './components/stage_staging_component.vue';
+import stageTestComponent from './components/stage_test_component.vue';
+import totalTime from './components/total_time_component.vue';
+import CycleAnalyticsService from './cycle_analytics_service';
+import CycleAnalyticsStore from './cycle_analytics_store';
Vue.use(Translate);
$(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
- const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
- const cycleAnalyticsStore = gl.cycleAnalytics.CycleAnalyticsStore;
- const cycleAnalyticsService = new gl.cycleAnalytics.CycleAnalyticsService({
- requestPath: cycleAnalyticsEl.dataset.requestPath,
- });
gl.cycleAnalyticsApp = new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
- data: {
- state: cycleAnalyticsStore.state,
- isLoading: false,
- isLoadingStage: false,
- isEmptyStage: false,
- hasError: false,
- startDate: 30,
- isOverviewDialogDismissed: Cookies.get(OVERVIEW_DIALOG_COOKIE),
+ data() {
+ const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
+ const cycleAnalyticsService = new CycleAnalyticsService({
+ requestPath: cycleAnalyticsEl.dataset.requestPath,
+ });
+
+ return {
+ store: CycleAnalyticsStore,
+ state: CycleAnalyticsStore.state,
+ isLoading: false,
+ isLoadingStage: false,
+ isEmptyStage: false,
+ hasError: false,
+ startDate: 30,
+ isOverviewDialogDismissed: Cookies.get(OVERVIEW_DIALOG_COOKIE),
+ service: cycleAnalyticsService,
+ };
},
computed: {
currentStage() {
- return cycleAnalyticsStore.currentActiveStage();
+ return this.store.currentActiveStage();
},
},
components: {
- 'stage-issue-component': gl.cycleAnalytics.StageIssueComponent,
- 'stage-plan-component': gl.cycleAnalytics.StagePlanComponent,
- 'stage-code-component': gl.cycleAnalytics.StageCodeComponent,
- 'stage-test-component': gl.cycleAnalytics.StageTestComponent,
- 'stage-review-component': gl.cycleAnalytics.StageReviewComponent,
- 'stage-staging-component': gl.cycleAnalytics.StageStagingComponent,
- 'stage-production-component': gl.cycleAnalytics.StageProductionComponent,
+ 'stage-issue-component': stageComponent,
+ 'stage-plan-component': stagePlanComponent,
+ 'stage-code-component': stageCodeComponent,
+ 'stage-test-component': stageTestComponent,
+ 'stage-review-component': stageReviewComponent,
+ 'stage-staging-component': stageStagingComponent,
+ 'stage-production-component': stageComponent,
},
created() {
this.fetchCycleAnalyticsData();
},
methods: {
handleError() {
- cycleAnalyticsStore.setErrorState(true);
+ this.store.setErrorState(true);
return new Flash('There was an error while fetching cycle analytics data.');
},
initDropdown() {
@@ -77,17 +80,17 @@ $(() => {
this.isLoading = true;
- cycleAnalyticsService
+ this.service
.fetchCycleAnalyticsData(fetchOptions)
- .done((response) => {
- cycleAnalyticsStore.setCycleAnalyticsData(response);
+ .then(resp => resp.json())
+ .then((response) => {
+ this.store.setCycleAnalyticsData(response);
this.selectDefaultStage();
this.initDropdown();
+ this.isLoading = false;
})
- .error(() => {
+ .catch(() => {
this.handleError();
- })
- .always(() => {
this.isLoading = false;
});
},
@@ -100,27 +103,27 @@ $(() => {
if (this.currentStage === stage) return;
if (!stage.isUserAllowed) {
- cycleAnalyticsStore.setActiveStage(stage);
+ this.store.setActiveStage(stage);
return;
}
this.isLoadingStage = true;
- cycleAnalyticsStore.setStageEvents([], stage);
- cycleAnalyticsStore.setActiveStage(stage);
+ this.store.setStageEvents([], stage);
+ this.store.setActiveStage(stage);
- cycleAnalyticsService
+ this.service
.fetchStageData({
stage,
startDate: this.startDate,
})
- .done((response) => {
+ .then(resp => resp.json())
+ .then((response) => {
this.isEmptyStage = !response.events.length;
- cycleAnalyticsStore.setStageEvents(response.events, stage);
+ this.store.setStageEvents(response.events, stage);
+ this.isLoadingStage = false;
})
- .error(() => {
+ .catch(() => {
this.isEmptyStage = true;
- })
- .always(() => {
this.isLoadingStage = false;
});
},
@@ -132,6 +135,6 @@ $(() => {
});
// Register global components
- Vue.component('limit-warning', LimitWarningComponent);
- Vue.component('total-time', gl.cycleAnalytics.TotalTimeComponent);
+ Vue.component('limit-warning', limitWarningComponent);
+ Vue.component('total-time', totalTime);
});
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 6504d7db2f2..f496c38208d 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -1,27 +1,16 @@
-/* eslint-disable no-param-reassign */
+import Vue from 'vue';
+import VueResource from 'vue-resource';
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
+Vue.use(VueResource);
-class CycleAnalyticsService {
+export default class CycleAnalyticsService {
constructor(options) {
this.requestPath = options.requestPath;
+ this.cycleAnalytics = Vue.resource(this.requestPath);
}
- fetchCycleAnalyticsData(options) {
- options = options || { startDate: 30 };
-
- return $.ajax({
- url: this.requestPath,
- method: 'GET',
- dataType: 'json',
- contentType: 'application/json',
- data: {
- cycle_analytics: {
- start_date: options.startDate,
- },
- },
- });
+ fetchCycleAnalyticsData(options = { startDate: 30 }) {
+ return this.cycleAnalytics.get({ cycle_analytics: { start_date: options.startDate } });
}
fetchStageData(options) {
@@ -30,12 +19,12 @@ class CycleAnalyticsService {
startDate,
} = options;
- return $.get(`${this.requestPath}/events/${stage.name}.json`, {
- cycle_analytics: {
- start_date: startDate,
+ return Vue.http.get(`${this.requestPath}/events/${stage.name}.json`, {
+ params: {
+ cycle_analytics: {
+ start_date: startDate,
+ },
},
});
}
}
-
-global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index 991f8c1f6fd..8bf9ae17de0 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
@@ -4,9 +4,6 @@ import { __ } from '../locale';
import '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
-const global = window.gl || (window.gl = {});
-global.cycleAnalytics = global.cycleAnalytics || {};
-
const EMPTY_STAGE_TEXTS = {
issue: __('The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.'),
plan: __('The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.'),
@@ -17,7 +14,7 @@ const EMPTY_STAGE_TEXTS = {
production: __('The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.'),
};
-global.cycleAnalytics.CycleAnalyticsStore = {
+export default {
state: {
summary: '',
stats: '',
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index d02e4cd5876..a00d29a845a 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -7,6 +7,8 @@
* causes reflows, visit https://gist.github.com/paulirish/5d52fb081b3570c81e3a
*/
+import Cookies from 'js-cookie';
+
const LINE_NUMBER_CLASS = 'diff-line-num';
const UNFOLDABLE_LINE_CLASS = 'js-unfold';
const NO_COMMENT_CLASS = 'no-comment-btn';
@@ -27,9 +29,7 @@ export default {
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('can-create-note') === '';
}
- if (typeof notes !== 'undefined' && !this.isParallelView) {
- this.isParallelView = notes.isParallelView && notes.isParallelView();
- }
+ this.isParallelView = Cookies.get('diff_view') === 'parallel';
if (this.userCanCreateNote) {
$diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 157280d66e3..98837c3b2a0 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -34,7 +34,7 @@ export const canShowActiveSubItems = (el) => {
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
export const getHideSubItemsInterval = () => {
- if (!currentOpenMenu) return 0;
+ if (!currentOpenMenu || !mousePos.length) return 0;
const currentMousePos = mousePos[mousePos.length - 1];
const prevMousePos = mousePos[0];
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index 283c0ec0410..64db42701ce 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -1,14 +1,34 @@
-export const isSticky = (el, scrollY, stickyTop) => {
+export const createPlaceholder = () => {
+ const placeholder = document.createElement('div');
+ placeholder.classList.add('sticky-placeholder');
+
+ return placeholder;
+};
+
+export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
const top = Math.floor(el.offsetTop - scrollY);
- if (top <= stickyTop) {
+ if (top <= stickyTop && !el.classList.contains('is-stuck')) {
+ const placeholder = insertPlaceholder ? createPlaceholder() : null;
+ const heightBefore = el.offsetHeight;
+
el.classList.add('is-stuck');
- } else {
+
+ if (insertPlaceholder) {
+ el.parentNode.insertBefore(placeholder, el.nextElementSibling);
+
+ placeholder.style.height = `${heightBefore - el.offsetHeight}px`;
+ }
+ } else if (top > stickyTop && el.classList.contains('is-stuck')) {
el.classList.remove('is-stuck');
+
+ if (insertPlaceholder && el.nextElementSibling && el.nextElementSibling.classList.contains('sticky-placeholder')) {
+ el.nextElementSibling.remove();
+ }
}
};
-export default (el) => {
+export default (el, insertPlaceholder = true) => {
if (!el) return;
const computedStyle = window.getComputedStyle(el);
@@ -17,7 +37,7 @@ export default (el) => {
const stickyTop = parseInt(computedStyle.top, 10);
- document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop), {
+ document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
passive: true,
});
};
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 7400c22543f..a16d00b5cef 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -28,148 +28,149 @@
// </div>
// </div>
//
-(function() {
- this.LineHighlighter = (function() {
- // CSS class applied to highlighted lines
- LineHighlighter.prototype.highlightClass = 'hll';
-
- // Internal copy of location.hash so we're not dependent on `location` in tests
- LineHighlighter.prototype._hash = '';
-
- function LineHighlighter(hash) {
- if (hash == null) {
- // Initialize a LineHighlighter object
- //
- // hash - String URL hash for dependency injection in tests
- hash = location.hash;
- }
- this.setHash = this.setHash.bind(this);
- this.highlightLine = this.highlightLine.bind(this);
- this.clickHandler = this.clickHandler.bind(this);
- this.highlightHash = this.highlightHash.bind(this);
- this._hash = hash;
- this.bindEvents();
- this.highlightHash();
- }
- LineHighlighter.prototype.bindEvents = function() {
- const $fileHolder = $('.file-holder');
- $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
- $fileHolder.on('highlight:line', this.highlightHash);
- };
-
- LineHighlighter.prototype.highlightHash = function() {
- var range;
- if (this._hash !== '') {
- range = this.hashToRange(this._hash);
- if (range[0]) {
- this.highlightRange(range);
- $.scrollTo("#L" + range[0], {
- // Scroll to the first highlighted line on initial load
- // Offset -50 for the sticky top bar, and another -100 for some context
- offset: -150
- });
- }
- }
- };
-
- LineHighlighter.prototype.clickHandler = function(event) {
- var current, lineNumber, range;
- event.preventDefault();
- this.clearHighlight();
- lineNumber = $(event.target).closest('a').data('line-number');
- current = this.hashToRange(this._hash);
- if (!(current[0] && event.shiftKey)) {
- // If there's no current selection, or there is but Shift wasn't held,
- // treat this like a single-line selection.
- this.setHash(lineNumber);
- return this.highlightLine(lineNumber);
- } else if (event.shiftKey) {
- if (lineNumber < current[0]) {
- range = [lineNumber, current[0]];
- } else {
- range = [current[0], lineNumber];
- }
- this.setHash(range[0], range[1]);
- return this.highlightRange(range);
- }
- };
-
- LineHighlighter.prototype.clearHighlight = function() {
- return $("." + this.highlightClass).removeClass(this.highlightClass);
- // Unhighlight previously highlighted lines
- };
-
- // Convert a URL hash String into line numbers
- //
- // hash - Hash String
- //
- // Examples:
- //
- // hashToRange('#L5') # => [5, null]
- // hashToRange('#L5-15') # => [5, 15]
- // hashToRange('#foo') # => [null, null]
- //
- // Returns an Array
- LineHighlighter.prototype.hashToRange = function(hash) {
- var first, last, matches;
- // ?L(\d+)(?:-(\d+))?$/)
- matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
- if (matches && matches.length) {
- first = parseInt(matches[1], 10);
- last = matches[2] ? parseInt(matches[2], 10) : null;
- return [first, last];
- } else {
- return [null, null];
- }
- };
-
- // Highlight a single line
- //
- // lineNumber - Line number to highlight
- LineHighlighter.prototype.highlightLine = function(lineNumber) {
- return $("#LC" + lineNumber).addClass(this.highlightClass);
- };
-
- // Highlight all lines within a range
- //
- // range - Array containing the starting and ending line numbers
- LineHighlighter.prototype.highlightRange = function(range) {
- var i, lineNumber, ref, ref1, results;
- if (range[1]) {
- results = [];
- for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) {
- results.push(this.highlightLine(lineNumber));
- }
- return results;
- } else {
- return this.highlightLine(range[0]);
- }
- };
+const LineHighlighter = function(options = {}) {
+ options.highlightLineClass = options.highlightLineClass || 'hll';
+ options.fileHolderSelector = options.fileHolderSelector || '.file-holder';
+ options.scrollFileHolder = options.scrollFileHolder || false;
+ options.hash = options.hash || location.hash;
- // Set the URL hash string
- LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
- var hash;
- if (lastLineNumber) {
- hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ this.options = options;
+ this._hash = options.hash;
+ this.highlightLineClass = options.highlightLineClass;
+ this.setHash = this.setHash.bind(this);
+ this.highlightLine = this.highlightLine.bind(this);
+ this.clickHandler = this.clickHandler.bind(this);
+ this.highlightHash = this.highlightHash.bind(this);
+
+ this.bindEvents();
+ this.highlightHash();
+};
+
+LineHighlighter.prototype.bindEvents = function() {
+ const $fileHolder = $(this.options.fileHolderSelector);
+
+ $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
+ $fileHolder.on('highlight:line', this.highlightHash);
+};
+
+LineHighlighter.prototype.highlightHash = function() {
+ var range;
+
+ if (this._hash !== '') {
+ range = this.hashToRange(this._hash);
+
+ if (range[0]) {
+ this.highlightRange(range);
+ const lineSelector = `#L${range[0]}`;
+ const scrollOptions = {
+ // Scroll to the first highlighted line on initial load
+ // Offset -50 for the sticky top bar, and another -100 for some context
+ offset: -150
+ };
+ if (this.options.scrollFileHolder) {
+ $(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions);
} else {
- hash = "#L" + firstLineNumber;
+ $.scrollTo(lineSelector, scrollOptions);
}
- this._hash = hash;
- return this.__setLocationHash__(hash);
- };
-
- // Make the actual hash change in the browser
- //
- // This method is stubbed in tests.
- LineHighlighter.prototype.__setLocationHash__ = function(value) {
- return history.pushState({
- url: value
- // We're using pushState instead of assigning location.hash directly to
- // prevent the page from scrolling on the hashchange event
- }, document.title, value);
- };
-
- return LineHighlighter;
- })();
-}).call(window);
+ }
+ }
+};
+
+LineHighlighter.prototype.clickHandler = function(event) {
+ var current, lineNumber, range;
+ event.preventDefault();
+ this.clearHighlight();
+ lineNumber = $(event.target).closest('a').data('line-number');
+ current = this.hashToRange(this._hash);
+ if (!(current[0] && event.shiftKey)) {
+ // If there's no current selection, or there is but Shift wasn't held,
+ // treat this like a single-line selection.
+ this.setHash(lineNumber);
+ return this.highlightLine(lineNumber);
+ } else if (event.shiftKey) {
+ if (lineNumber < current[0]) {
+ range = [lineNumber, current[0]];
+ } else {
+ range = [current[0], lineNumber];
+ }
+ this.setHash(range[0], range[1]);
+ return this.highlightRange(range);
+ }
+};
+
+LineHighlighter.prototype.clearHighlight = function() {
+ return $("." + this.highlightLineClass).removeClass(this.highlightLineClass);
+};
+
+// Convert a URL hash String into line numbers
+//
+// hash - Hash String
+//
+// Examples:
+//
+// hashToRange('#L5') # => [5, null]
+// hashToRange('#L5-15') # => [5, 15]
+// hashToRange('#foo') # => [null, null]
+//
+// Returns an Array
+LineHighlighter.prototype.hashToRange = function(hash) {
+ var first, last, matches;
+ // ?L(\d+)(?:-(\d+))?$/)
+ matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
+ if (matches && matches.length) {
+ first = parseInt(matches[1], 10);
+ last = matches[2] ? parseInt(matches[2], 10) : null;
+ return [first, last];
+ } else {
+ return [null, null];
+ }
+};
+
+// Highlight a single line
+//
+// lineNumber - Line number to highlight
+LineHighlighter.prototype.highlightLine = function(lineNumber) {
+ return $("#LC" + lineNumber).addClass(this.highlightLineClass);
+};
+
+// Highlight all lines within a range
+//
+// range - Array containing the starting and ending line numbers
+LineHighlighter.prototype.highlightRange = function(range) {
+ var i, lineNumber, ref, ref1, results;
+ if (range[1]) {
+ results = [];
+ for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) {
+ results.push(this.highlightLine(lineNumber));
+ }
+ return results;
+ } else {
+ return this.highlightLine(range[0]);
+ }
+};
+
+// Set the URL hash string
+LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
+ var hash;
+ if (lastLineNumber) {
+ hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ } else {
+ hash = "#L" + firstLineNumber;
+ }
+ this._hash = hash;
+ return this.__setLocationHash__(hash);
+};
+
+// Make the actual hash change in the browser
+//
+// This method is stubbed in tests.
+LineHighlighter.prototype.__setLocationHash__ = function(value) {
+ return history.pushState({
+ url: value
+ // We're using pushState instead of assigning location.hash directly to
+ // prevent the page from scrolling on the hashchange event
+ }, document.title, value);
+};
+
+window.LineHighlighter = LineHighlighter;
diff --git a/app/assets/javascripts/locale/index.js b/app/assets/javascripts/locale/index.js
index 7ba676d6d20..6a5084efeb8 100644
--- a/app/assets/javascripts/locale/index.js
+++ b/app/assets/javascripts/locale/index.js
@@ -16,9 +16,8 @@ const locales = allLocales.reduce((d, obj) => {
return data;
}, {});
-let lang = document.querySelector('html').getAttribute('lang') || 'en';
-lang = lang.replace(/-/g, '_');
-
+const langAttribute = document.querySelector('html').getAttribute('lang');
+const lang = (langAttribute || 'en').replace(/-/g, '_');
const locale = new Jed(locales[lang]);
/**
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index ec001b9b31c..3b8e2c5b2f3 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -302,7 +302,10 @@ $(function () {
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded'));
+ $('.navbar-toggle').on('click', () => {
+ $('.header-content').toggleClass('menu-expanded');
+ gl.lazyLoader.loadCheck();
+ });
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 8ae127776e8..d3299c15720 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -352,7 +352,7 @@ import {
}
expandViewContainer() {
- const $wrapper = $('.content-wrapper .container-fluid');
+ const $wrapper = $('.content-wrapper .container-fluid').not('.breadcrumbs');
if (this.fixedLayoutPref === null) {
this.fixedLayoutPref = $wrapper.hasClass('container-limited');
}
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index 291ae24aa68..4bdda611cfc 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -73,7 +73,8 @@ import _ from 'underscore';
aspectRatio: 1,
modal: true,
scalable: false,
- rotatable: false,
+ rotatable: true,
+ checkOrientation: true,
zoomable: true,
dragMode: 'move',
guides: false,
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index 2200754cbef..0d9d132f766 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -1,23 +1,27 @@
<script>
+/* global LineHighlighter */
+
import Store from '../stores/repo_store';
export default {
data: () => Store,
- mounted() {
- this.highlightFile();
- },
computed: {
html() {
return this.activeFile.html;
},
},
-
methods: {
highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight();
},
},
-
+ mounted() {
+ this.highlightFile();
+ this.lineHighlighter = new LineHighlighter({
+ fileHolderSelector: '.blob-viewer-container',
+ scrollFileHolder: true,
+ });
+ },
watch: {
html() {
this.$nextTick(() => {
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
index 2bd8d7eea65..655e4e7605b 100644
--- a/app/assets/javascripts/repo/helpers/repo_helper.js
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -178,8 +178,8 @@ const RepoHelper = {
setFile(data, file) {
const newFile = data;
+ newFile.url = file.url || Service.url; // Grab the URL from service, happens on page refresh.
- newFile.url = file.url;
if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') {
newFile.tooLarge = true;
}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index a4eae135403..0c1ec276baf 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -43,6 +43,8 @@ import Cookies from 'js-cookie';
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.page-with-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
+
+ if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
if (!triggered) {
return Cookies.set("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'));
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 923d14f2c3d..74b846217bb 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -31,10 +31,12 @@
@import "framework/mobile";
@import "framework/modal";
@import "framework/nav";
+@import "framework/new-nav";
@import "framework/pagination";
@import "framework/panels";
@import "framework/selects";
@import "framework/sidebar";
+@import "framework/new-sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/timeline";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 5c68059f485..1d72a70f0f5 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -260,7 +260,7 @@
position: relative;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
- background-color: $blue-25;
+ background-color: $blue-50;
justify-content: center;
.dismiss-button {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 2bcd23a15e6..c0d8e6c328c 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -779,6 +779,14 @@
white-space: normal;
width: 100%;
+ &.dropdown-menu-user-link {
+ white-space: nowrap;
+
+ .dropdown-menu-user-username {
+ display: block;
+ }
+ }
+
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index f844d6f1d5a..0d1c04026b8 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -6,7 +6,7 @@
// Header
header.navbar-gitlab-new {
- background: linear-gradient(to right, $color-900, $color-800);
+ background-color: $color-900;
.navbar-collapse {
color: $color-200;
@@ -201,7 +201,7 @@ body {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
header.navbar-gitlab-new {
- background: $theme-gray-100;
+ background-color: $theme-gray-100;
box-shadow: 0 2px 0 0 $border-color;
.logo-text svg {
@@ -242,10 +242,10 @@ body {
&:hover {
background-color: $white-light;
- box-shadow: inset 0 0 0 1px $blue-100;
+ box-shadow: inset 0 0 0 1px $blue-200;
.location-badge {
- box-shadow: inset 0 0 0 1px $blue-100;
+ box-shadow: inset 0 0 0 1px $blue-200;
}
}
}
@@ -254,6 +254,10 @@ body {
.search-icon {
color: $theme-gray-200;
}
+
+ .search-input {
+ color: $gl-text-color;
+ }
}
.location-badge {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index d40b65bb2cc..2fee2164190 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -142,5 +142,41 @@
}
@mixin green-status-color {
- @include status-color($green-50, $green-500, $green-700);
+ @include status-color($green-100, $green-500, $green-700);
+}
+
+@mixin fade($gradient-direction, $gradient-color) {
+ visibility: hidden;
+ opacity: 0;
+ z-index: 2;
+ position: absolute;
+ bottom: 12px;
+ width: 43px;
+ height: 30px;
+ transition-duration: .3s;
+ -webkit-transform: translateZ(0);
+ background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
+
+ &.scrolling {
+ visibility: visible;
+ opacity: 1;
+ transition-duration: .3s;
+ }
+
+ .fa {
+ position: relative;
+ top: 5px;
+ font-size: 18px;
+ }
+}
+
+@mixin scrolling-links() {
+ overflow-x: auto;
+ overflow-y: hidden;
+ -webkit-overflow-scrolling: touch;
+ display: flex;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 2f7717760ec..f8777d1fd9d 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -1,38 +1,4 @@
-@mixin fade($gradient-direction, $gradient-color) {
- visibility: hidden;
- opacity: 0;
- z-index: 2;
- position: absolute;
- bottom: 12px;
- width: 43px;
- height: 30px;
- transition-duration: .3s;
- -webkit-transform: translateZ(0);
- background: linear-gradient(to $gradient-direction, $gradient-color 45%, rgba($gradient-color, 0.4));
-
- &.scrolling {
- visibility: visible;
- opacity: 1;
- transition-duration: .3s;
- }
-
- .fa {
- position: relative;
- top: 5px;
- font-size: 18px;
- }
-}
-@mixin scrolling-links() {
- overflow-x: auto;
- overflow-y: hidden;
- -webkit-overflow-scrolling: touch;
- display: flex;
-
- &::-webkit-scrollbar {
- display: none;
- }
-}
.nav-links {
display: flex;
diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/framework/new-nav.scss
index 8c5bafac637..81022d4af2a 100644
--- a/app/assets/stylesheets/new_nav.scss
+++ b/app/assets/stylesheets/framework/new-nav.scss
@@ -295,75 +295,6 @@ header.navbar-gitlab-new {
margin-top: 4px;
}
-.search {
- margin: 4px 8px 0;
-
- form {
- height: 32px;
- border: 0;
- border-radius: $border-radius-default;
- transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
-
- &:hover {
- box-shadow: none;
- }
- }
-
- &.search-active form {
- box-shadow: none;
-
- .search-input {
- color: $gl-text-color;
- transition: color ease-in-out 0.15s;
- }
-
- .search-input::placeholder {
- color: $gl-text-color-tertiary;
- }
-
- .search-input-wrap {
- .search-icon,
- .clear-icon {
- color: $gl-text-color-tertiary;
- transition: color ease-in-out 0.15s;
- }
- }
- }
-
- .search-input {
- color: $white-light;
- background: none;
- transition: color ease-in-out 0.15s;
- }
-
- .search-input::placeholder {
- transition: color ease-in-out 0.15s;
- }
-
- .location-badge {
- font-size: 12px;
- margin: -4px 4px -4px -4px;
- line-height: 25px;
- padding: 4px 8px;
- border-radius: 2px 0 0 2px;
- height: 32px;
- transition: border-color ease-in-out 0.15s;
- }
-
- &.search-active {
- .location-badge {
- background-color: $nav-badge-bg;
- border-color: $border-color;
- }
-
- .search-input-wrap {
- .clear-icon {
- color: $white-light;
- }
- }
- }
-}
-
.breadcrumbs {
display: flex;
min-height: 48px;
@@ -375,6 +306,8 @@ header.navbar-gitlab-new {
display: flex;
width: 100%;
position: relative;
+ padding-top: $gl-padding / 2;
+ padding-bottom: $gl-padding / 2;
align-items: center;
border-bottom: 1px solid $border-color;
}
@@ -386,11 +319,6 @@ header.navbar-gitlab-new {
align-self: center;
color: $gl-text-color-secondary;
- @media (max-width: $screen-xs-max) {
- padding-left: 17px;
- border-left: 1px solid $gl-text-color-quaternary;
- }
-
.avatar-tile {
margin-right: 4px;
border: 1px solid $border-color;
@@ -420,6 +348,7 @@ header.navbar-gitlab-new {
display: flex;
align-items: center;
position: relative;
+ padding: 2px 0;
&:not(:last-child) {
margin-right: 20px;
@@ -455,7 +384,7 @@ header.navbar-gitlab-new {
margin: 0;
font-size: 12px;
font-weight: 600;
- line-height: 1;
+ line-height: 16px;
a {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/framework/new-sidebar.scss
index 9c404b7e542..3f1cb97aebc 100644
--- a/app/assets/stylesheets/new_sidebar.scss
+++ b/app/assets/stylesheets/framework/new-sidebar.scss
@@ -461,6 +461,13 @@ $new-sidebar-collapsed-width: 50px;
font-size: 18px;
}
}
+
+ @media (max-width: $screen-xs-max) {
+ + .breadcrumbs-links {
+ padding-left: 17px;
+ border-left: 1px solid $gl-text-color-quaternary;
+ }
+ }
}
@media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 4c35e3a9c3c..3ea77eb7a43 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -137,7 +137,7 @@ $well-border: #eee;
//##
$code-color: $red-600;
-$code-bg: lighten($red-50, 2%);
+$code-bg: lighten($red-100, 2%);
$kbd-color: $white-light;
$kbd-bg: #333;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index a3da9fd44e8..e8bb42f4a8c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -6,6 +6,8 @@ $gutter_width: 290px;
$gutter_inner_width: 250px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px;
+$default-transition-duration: .15s;
+$right-sidebar-transition-duration: .3s;
/*
* Color schema
@@ -27,46 +29,45 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
-$green-25: #f6fcf8;
-$green-50: #e4f5eb;
-$green-100: #bae6cc;
-$green-200: #8dd5aa;
-$green-300: #5fc488;
-$green-400: #3cb76f;
+$green-50: #f1fdf6;
+$green-100: #dcf5e7;
+$green-200: #b3e6c8;
+$green-300: #75d09b;
+$green-400: #37b96d;
$green-500: #1aaa55;
$green-600: #168f48;
$green-700: #12753a;
$green-800: #0e5a2d;
$green-900: #0a4020;
+$green-950: #072b15;
-$blue-25: #f6fafd;
-$blue-50: #e4eff9;
-$blue-100: #bcd7f1;
-$blue-200: #8fbce8;
-$blue-300: #62a1df;
-$blue-400: #418cd8;
+$blue-50: #f6fafe;
+$blue-100: #e4f0fb;
+$blue-200: #b8d6f4;
+$blue-300: #73afea;
+$blue-400: #2e87e0;
$blue-500: #1f78d1;
$blue-600: #1b69b6;
$blue-700: #17599c;
$blue-800: #134a81;
$blue-900: #0f3b66;
+$blue-950: #0a2744;
-$orange-25: #fffcf8;
-$orange-50: #fff2e1;
-$orange-100: #fedfb3;
-$orange-200: #feca81;
-$orange-300: #fdb44f;
-$orange-400: #fca429;
+$orange-50: #fffaf4;
+$orange-100: #fff1de;
+$orange-200: #fed69f;
+$orange-300: #fdbc60;
+$orange-400: #fca121;
$orange-500: #fc9403;
$orange-600: #de7e00;
$orange-700: #c26700;
-$orange-800: #a35100;
-$orange-900: #853b00;
+$orange-800: #a35200;
+$orange-900: #853c00;
+$orange-950: #592800;
-$red-25: #fef7f6;
-$red-50: #fbe7e4;
-$red-100: #f4c4bc;
-$red-200: #ed9d90;
+$red-50: #fef6f5;
+$red-100: #fbe5e1;
+$red-200: #f2b4a9;
$red-300: #e67664;
$red-400: #e05842;
$red-500: #db3b21;
@@ -74,6 +75,7 @@ $red-600: #c0341d;
$red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
+$red-950: #4b140b;
// GitLab themes
@@ -184,8 +186,8 @@ $list-text-disabled-color: $gl-text-color-tertiary;
$list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
-$list-warning-row-bg: $orange-50;
-$list-warning-row-border: $orange-100;
+$list-warning-row-bg: $orange-100;
+$list-warning-row-border: $orange-200;
$list-warning-row-color: $orange-700;
/*
@@ -214,8 +216,8 @@ $gl-sidebar-padding: 22px;
/*
* Misc
*/
-$row-hover: $blue-25;
-$row-hover-border: $blue-100;
+$row-hover: $blue-50;
+$row-hover-border: $blue-200;
$progress-color: #c0392b;
$header-height: 50px;
$new-navbar-height: 40px;
@@ -265,8 +267,8 @@ $time-color: #999;
$project-member-show-color: #aaa;
$gl-promo-color: #aaa;
$error-bg: $red-400;
-$warning-message-bg: $orange-50;
-$warning-message-border: $orange-100;
+$warning-message-bg: $orange-100;
+$warning-message-border: $orange-200;
$warning-message-color: $orange-700;
$control-group-descr-color: #666;
$table-permission-x-bg: #d9edf7;
@@ -451,17 +453,17 @@ $builds-trace-bg: #111;
/*
* Callout
*/
-$callout-danger-bg: $red-50;
-$callout-danger-border: $red-100;
+$callout-danger-bg: $red-100;
+$callout-danger-border: $red-200;
$callout-danger-color: $red-700;
-$callout-warning-bg: $orange-50;
-$callout-warning-border: $orange-100;
+$callout-warning-bg: $orange-100;
+$callout-warning-border: $orange-200;
$callout-warning-color: $orange-700;
-$callout-info-bg: $blue-50;
-$callout-info-border: $blue-100;
+$callout-info-bg: $blue-100;
+$callout-info-border: $blue-200;
$callout-info-color: $blue-700;
-$callout-success-bg: $green-50;
-$callout-success-border: $green-100;
+$callout-success-bg: $green-100;
+$callout-success-border: $green-200;
$callout-success-color: $green-700;
/*
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 700be173039..3305a482a0d 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -55,6 +55,15 @@
.boards-app {
position: relative;
+
+ @media (min-width: $screen-sm-min) {
+ transition: width $right-sidebar-transition-duration;
+ width: 100%;
+
+ &.is-compact {
+ width: calc(100% - #{$gutter_width});
+ }
+ }
}
.boards-app-loading {
@@ -78,11 +87,6 @@
height: calc(100vh - 222px);
// scss-lint:enable DuplicateProperty
min-height: 475px;
- transition: width .2s;
-
- &.is-compact {
- width: calc(100% - 290px);
- }
}
}
@@ -412,14 +416,6 @@
.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar,
.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
- position: absolute;
-
- &.right-sidebar {
- top: 0;
- bottom: 0;
- height: 100%;
- }
-
.issuable-sidebar-header {
position: relative;
}
@@ -457,8 +453,8 @@
.right-sidebar.right-sidebar-expanded {
&.boards-sidebar-slide-enter-active,
&.boards-sidebar-slide-leave-active {
- transition: width .2s,
- padding .2s;
+ transition: width $right-sidebar-transition-duration,
+ padding $right-sidebar-transition-duration;
}
&.boards-sidebar-slide-enter,
diff --git a/app/assets/stylesheets/pages/convdev_index.scss b/app/assets/stylesheets/pages/convdev_index.scss
index 16702442f50..fb1899284fd 100644
--- a/app/assets/stylesheets/pages/convdev_index.scss
+++ b/app/assets/stylesheets/pages/convdev_index.scss
@@ -83,7 +83,7 @@ $space-between-cards: 8px;
border-top-color: $color-low-score;
.card-score-big {
- background-color: $red-25;
+ background-color: $red-50;
}
}
@@ -91,7 +91,7 @@ $space-between-cards: 8px;
border-top-color: $color-average-score;
.card-score-big {
- background-color: $orange-25;
+ background-color: $orange-50;
}
}
@@ -99,7 +99,7 @@ $space-between-cards: 8px;
border-top-color: $color-high-score;
.card-score-big {
- background-color: $green-25;
+ background-color: $green-50;
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 951580ea1fe..e4bd783c8bc 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -451,7 +451,7 @@
}
.files {
- margin-top: -1px;
+ margin-top: 1px;
.diff-file:last-child {
margin-bottom: 0;
@@ -535,7 +535,6 @@
}
.diff-notes-collapse {
- position: relative;
width: 19px;
height: 19px;
padding: 0;
@@ -543,11 +542,7 @@
z-index: 100;
svg {
- position: absolute;
- left: 50%;
- top: 50%;
- margin-left: -5.5px;
- margin-top: -5.5px;
+ vertical-align: text-top;
}
path {
@@ -586,11 +581,6 @@
top: 76px;
}
- + .files,
- + .alert {
- margin-top: 1px;
- }
-
&:not(.is-stuck) .diff-stats-additions-deletions-collapsed {
display: none;
}
@@ -605,11 +595,6 @@
.inline-parallel-buttons {
display: none;
}
-
- + .files,
- + .alert {
- margin-top: 32px;
- }
}
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index d01ee4b033c..7eb28354e6d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -7,7 +7,7 @@
.is-confidential {
color: $orange-600;
- background-color: $orange-50;
+ background-color: $orange-100;
border-radius: $border-radius-default;
padding: 5px;
margin: 0 3px 0 -4px;
@@ -223,14 +223,14 @@
top: $new-navbar-height;
bottom: 0;
right: 0;
- transition: width .3s;
+ transition: width $right-sidebar-transition-duration;
background: $gray-light;
z-index: 200;
overflow: hidden;
.issuable-sidebar {
width: calc(100% + 100px);
- height: calc(100% - #{$new-navbar-height});
+ height: 100%;
overflow-y: scroll;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index 35cefd449f1..dbf3e2b763c 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -255,7 +255,7 @@ $colors: (
&.saved {
.editor {
- border-top: solid 2px $green-200;
+ border-top: solid 2px $green-300;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 5d7c85b16ef..be4db597689 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -103,7 +103,7 @@
.confidential-issue-warning {
color: $orange-600;
- background-color: $orange-50;
+ background-color: $orange-100;
border-radius: $border-radius-default $border-radius-default 0 0;
border: 1px solid $border-gray-normal;
border-bottom: none;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 9d03a042aa3..086dd528579 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -644,20 +644,20 @@ button.mini-pipeline-graph-dropdown-toggle {
// Dropdown button animation in mini pipeline graph
&.ci-status-icon-success {
- @include mini-pipeline-graph-color($green-50, $green-500, $green-600);
+ @include mini-pipeline-graph-color($green-100, $green-500, $green-600);
}
&.ci-status-icon-failed {
- @include mini-pipeline-graph-color($red-50, $red-500, $red-600);
+ @include mini-pipeline-graph-color($red-100, $red-500, $red-600);
}
&.ci-status-icon-pending,
&.ci-status-icon-success_with_warnings {
- @include mini-pipeline-graph-color($orange-50, $orange-500, $orange-600);
+ @include mini-pipeline-graph-color($orange-100, $orange-500, $orange-600);
}
&.ci-status-icon-running {
- @include mini-pipeline-graph-color($blue-50, $blue-400, $blue-600);
+ @include mini-pipeline-graph-color($blue-100, $blue-400, $blue-600);
}
&.ci-status-icon-canceled,
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index c5d6ff66dd6..67abe6e88ed 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -291,7 +291,7 @@ table.u2f-registrations {
.bordered-box {
border: 1px solid $blue-300;
border-radius: $border-radius-default;
- background-color: $blue-25;
+ background-color: $blue-50;
position: relative;
display: flex;
justify-content: center;
@@ -379,7 +379,7 @@ table.u2f-registrations {
.nav-wip {
border: 1px solid $blue-500;
- background: $blue-25;
+ background: $blue-50;
padding: $gl-padding;
margin-bottom: $gl-padding;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 4d4d92f9494..c36fe25f74d 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -54,6 +54,10 @@
border-radius: $border-radius-default;
color: $almost-black;
+ .code.white pre .hll {
+ background-color: $well-light-border !important;
+ }
+
.tree-content-holder {
display: flex;
min-height: 300px;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 13dd7b5a780..2fa710a05b5 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -28,9 +28,7 @@ input[type="checkbox"]:hover {
}
.search {
- margin-right: 10px;
- margin-left: 10px;
- margin-top: ($header-height - 35) / 2;
+ margin: 4px 8px 0;
form {
@extend .form-control;
@@ -38,15 +36,23 @@ input[type="checkbox"]:hover {
padding: 4px;
width: $search-input-width;
line-height: 24px;
+ height: 32px;
+ border: 0;
+ border-radius: $border-radius-default;
+ transition: border-color ease-in-out $default-transition-duration, background-color ease-in-out $default-transition-duration;
&:hover {
- border-color: lighten($dropdown-input-focus-border, 20%);
- box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
+ box-shadow: none;
}
}
- .location-text {
- font-style: normal;
+ .location-badge {
+ font-size: 12px;
+ margin: -4px 4px -4px -4px;
+ line-height: 25px;
+ padding: 4px 8px;
+ border-radius: $border-radius-default 0 0 $border-radius-default;
+ transition: border-color ease-in-out $default-transition-duration;
}
.search-input {
@@ -56,41 +62,26 @@ input[type="checkbox"]:hover {
margin-left: 5px;
line-height: 25px;
width: 98%;
+ color: $white-light;
+ background: none;
+ transition: color ease-in-out $default-transition-duration;
}
- .location-badge {
- line-height: 25px;
- padding: 0 5px;
- border-radius: $border-radius-default;
- font-size: 14px;
- font-style: normal;
- color: $note-disabled-comment-color;
- display: inline-block;
- background-color: $gray-normal;
- vertical-align: top;
- cursor: default;
+ .search-input::placeholder {
+ transition: color ease-in-out $default-transition-duration;
}
.search-input-container {
- display: -webkit-flex;
display: flex;
position: relative;
}
.search-input-wrap {
- // Fallback if flexbox is not supported
- display: inline-block;
- }
-
- .search-input-wrap {
- width: 100%;
-
.search-icon,
.clear-icon {
position: absolute;
right: 5px;
top: 0;
- color: $location-icon-color;
&::before {
font-family: FontAwesome;
@@ -101,7 +92,7 @@ input[type="checkbox"]:hover {
.search-icon {
@extend .fa-search;
- transition: color 0.15s;
+ transition: color $default-transition-duration;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -148,21 +139,32 @@ input[type="checkbox"]:hover {
form {
@extend .form-control:focus;
border-color: $dropdown-input-focus-border;
- box-shadow: 0 0 4px $search-input-focus-shadow-color;
- }
+ box-shadow: none;
+
+ .search-input-wrap {
+ .search-icon,
+ .clear-icon {
+ color: $gl-text-color-tertiary;
+ transition: color ease-in-out $default-transition-duration;
+ }
+ }
- .location-badge {
- transition: all 0.15s;
- background-color: $location-badge-active-bg;
- color: $white-light;
- }
+ .search-input {
+ color: $gl-text-color;
+ transition: color ease-in-out $default-transition-duration;
+ }
- .search-input-wrap {
- i {
- color: $layout-link-gray;
+ .search-input::placeholder {
+ color: $gl-text-color-tertiary;
}
}
+ .location-badge {
+ transition: all $default-transition-duration;
+ background-color: $nav-badge-bg;
+ border-color: $border-color;
+ }
+
.dropdown-menu {
transition-duration: 100ms, 75ms;
transition-delay: 75ms, 100ms;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 36f622db136..25c80e1f950 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -18,7 +18,7 @@
}
&.ci-failed {
- @include status-color($red-50, $red-500, $red-600);
+ @include status-color($red-100, $red-500, $red-600);
}
&.ci-success {
@@ -39,12 +39,12 @@
&.ci-pending,
&.ci-failed_with_warnings,
&.ci-success_with_warnings {
- @include status-color($orange-50, $orange-500, $orange-700);
+ @include status-color($orange-100, $orange-500, $orange-700);
}
&.ci-info,
&.ci-running {
- @include status-color($blue-50, $blue-500, $blue-600);
+ @include status-color($blue-100, $blue-500, $blue-600);
}
&.ci-created,
diff --git a/app/assets/stylesheets/test.scss b/app/assets/stylesheets/test.scss
index 7d9f3da79c5..06733b7f1a9 100644
--- a/app/assets/stylesheets/test.scss
+++ b/app/assets/stylesheets/test.scss
@@ -15,3 +15,9 @@
-ms-animation: none !important;
animation: none !important;
}
+
+// Disable sticky changes bar for tests
+.diff-files-changed {
+ position: relative !important;
+ top: 0 !important;
+}
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 16590e66d61..fb6d8c0bb81 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -22,8 +22,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
@application = Doorkeeper::Application.new(application_params)
if @application.save
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to admin_application_url(@application)
+ redirect_to_admin_page
else
render :new
end
@@ -42,6 +41,13 @@ class Admin::ApplicationsController < Admin::ApplicationController
redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.'
end
+ protected
+
+ def redirect_to_admin_page
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to admin_application_url(@application)
+ end
+
private
def set_application
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index cbcef70e957..676a7203c7d 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -128,7 +128,7 @@ class Admin::UsersController < Admin::ApplicationController
end
respond_to do |format|
- result = Users::UpdateService.new(user, user_params_with_pass).execute do |user|
+ result = Users::UpdateService.new(current_user, user_params_with_pass.merge(user: user)).execute do |user|
user.skip_reconfirmation!
end
@@ -155,7 +155,7 @@ class Admin::UsersController < Admin::ApplicationController
def remove_email
email = user.emails.find(params[:email_id])
- success = Emails::DestroyService.new(user, email: email.email).execute
+ success = Emails::DestroyService.new(current_user, user: user, email: email.email).execute
respond_to do |format|
if success
@@ -219,7 +219,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def update_user(&block)
- result = Users::UpdateService.new(user).execute(&block)
+ result = Users::UpdateService.new(current_user, user: user).execute(&block)
result[:status] == :success
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 0d0e53d4b76..3181f517087 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -106,7 +106,7 @@ module IssuableCollections
# @filter_params[:authorized_only] = true
end
- @filter_params
+ @filter_params.permit(IssuableFinder::VALID_PARAMS)
end
def set_default_state
@@ -117,19 +117,32 @@ module IssuableCollections
key = 'issuable_sort'
cookies[key] = params[:sort] if params[:sort].present?
-
- # id_desc and id_asc are old values for these two.
- cookies[key] = sort_value_recently_created if cookies[key] == 'id_desc'
- cookies[key] = sort_value_oldest_created if cookies[key] == 'id_asc'
-
+ cookies[key] = update_cookie_value(cookies[key])
params[:sort] = cookies[key]
end
def default_sort_order
case params[:state]
- when 'opened', 'all' then sort_value_recently_created
+ when 'opened', 'all' then sort_value_created_date
when 'merged', 'closed' then sort_value_recently_updated
- else sort_value_recently_created
+ else sort_value_created_date
+ end
+ end
+
+ # Update old values to the actual ones.
+ def update_cookie_value(value)
+ case value
+ when 'id_asc' then sort_value_oldest_created
+ when 'id_desc' then sort_value_recently_created
+ when 'created_asc' then sort_value_created_date
+ when 'created_desc' then sort_value_created_date
+ when 'due_date_asc' then sort_value_due_date
+ when 'due_date_desc' then sort_value_due_date
+ when 'milestone_due_asc' then sort_value_milestone
+ when 'milestone_due_desc' then sort_value_milestone
+ when 'downvotes_asc' then sort_value_popularity
+ when 'downvotes_desc' then sort_value_popularity
+ else value
end
end
end
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 306afb65f10..10d2665c06a 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -12,10 +12,14 @@ class ConfirmationsController < Devise::ConfirmationsController
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
- after_sign_in_path_for(resource)
+ after_sign_in(resource)
else
flash[:notice] += " Please sign in."
new_session_path(resource_name)
end
end
+
+ def after_sign_in(resource)
+ after_sign_in_path_for(resource)
+ end
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 2ae4785b12c..b02e64a132b 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -21,14 +21,20 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
@application.owner = current_user
if @application.save
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to oauth_application_url(@application)
+ redirect_to_oauth_application_page
else
set_index_vars
render :index
end
end
+ protected
+
+ def redirect_to_oauth_application_page
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+ redirect_to oauth_application_url(@application)
+ end
+
private
def verify_user_oauth_applications_enabled
diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb
index 408650aac54..39b9f8a84d1 100644
--- a/app/controllers/profiles/avatars_controller.rb
+++ b/app/controllers/profiles/avatars_controller.rb
@@ -2,7 +2,7 @@ class Profiles::AvatarsController < Profiles::ApplicationController
def destroy
@user = current_user
- Users::UpdateService.new(@user).execute { |user| user.remove_avatar! }
+ Users::UpdateService.new(current_user, user: @user).execute { |user| user.remove_avatar! }
redirect_to profile_path, status: 302
end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index ddb67d1c4d1..97db84b92d4 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -5,7 +5,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
end
def create
- @email = Emails::CreateService.new(current_user, email_params).execute
+ @email = Emails::CreateService.new(current_user, email_params.merge(user: current_user)).execute
if @email.errors.blank?
NotificationService.new.new_email(@email)
@@ -19,7 +19,7 @@ class Profiles::EmailsController < Profiles::ApplicationController
def destroy
@email = current_user.emails.find(params[:id])
- Emails::DestroyService.new(current_user, email: @email.email).execute
+ Emails::DestroyService.new(current_user, user: current_user, email: @email.email).execute
respond_to do |format|
format.html { redirect_to profile_emails_url, status: 302 }
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 89d6d7f1b52..069e6a810f2 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -14,7 +14,7 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = Keys::CreateService.new(current_user, key_params).execute
if @key.persisted?
- redirect_to profile_key_path(@key)
+ redirect_to_profile_key_path
else
@keys = current_user.keys.select(&:persisted?)
render :index
@@ -50,6 +50,12 @@ class Profiles::KeysController < Profiles::ApplicationController
end
end
+ protected
+
+ def redirect_to_profile_key_path
+ redirect_to profile_key_path(@key)
+ end
+
private
def key_params
diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb
index 960b7512602..8a38ba65d4c 100644
--- a/app/controllers/profiles/notifications_controller.rb
+++ b/app/controllers/profiles/notifications_controller.rb
@@ -7,7 +7,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController
end
def update
- result = Users::UpdateService.new(current_user, user_params).execute
+ result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
if result[:status] == :success
flash[:notice] = "Notification settings saved"
diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb
index 7beb52dd8e8..dcfcb855ab5 100644
--- a/app/controllers/profiles/passwords_controller.rb
+++ b/app/controllers/profiles/passwords_controller.rb
@@ -21,10 +21,10 @@ class Profiles::PasswordsController < Profiles::ApplicationController
password_automatically_set: false
}
- result = Users::UpdateService.new(@user, password_attributes).execute
+ result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
- Users::UpdateService.new(@user, password_expires_at: nil).execute
+ Users::UpdateService.new(current_user, user: @user, password_expires_at: nil).execute
redirect_to root_path, notice: 'Password successfully changed'
else
@@ -46,7 +46,7 @@ class Profiles::PasswordsController < Profiles::ApplicationController
return
end
- result = Users::UpdateService.new(@user, password_attributes).execute
+ result = Users::UpdateService.new(current_user, password_attributes.merge(user: @user)).execute
if result[:status] == :success
flash[:notice] = "Password was successfully updated. Please login with it"
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index cce2a847b53..ed0f98179eb 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -6,7 +6,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
def update
begin
- result = Users::UpdateService.new(user, preferences_params).execute
+ result = Users::UpdateService.new(current_user, preferences_params.merge(user: user)).execute
if result[:status] == :success
flash[:notice] = 'Preferences saved.'
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 1a4f77639e7..aa9789f8a0f 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
current_user.otp_grace_period_started_at = Time.current
end
- Users::UpdateService.new(current_user).execute!
+ Users::UpdateService.new(current_user, user: current_user).execute!
if two_factor_authentication_required? && !current_user.two_factor_enabled?
two_factor_authentication_reason(
@@ -41,7 +41,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
if current_user.validate_and_consume_otp!(params[:pin_code])
- Users::UpdateService.new(current_user, otp_required_for_login: true).execute! do |user|
+ Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
@codes = user.generate_otp_backup_codes!
end
@@ -70,7 +70,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def codes
- Users::UpdateService.new(current_user).execute! do |user|
+ Users::UpdateService.new(current_user, user: current_user).execute! do |user|
@codes = user.generate_otp_backup_codes!
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index d83824fef06..5d87037f012 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -10,7 +10,7 @@ class ProfilesController < Profiles::ApplicationController
def update
respond_to do |format|
- result = Users::UpdateService.new(@user, user_params).execute
+ result = Users::UpdateService.new(current_user, user_params.merge(user: @user)).execute
if result[:status] == :success
message = "Profile was successfully updated"
@@ -25,7 +25,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_private_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_authentication_token!
end
@@ -35,7 +35,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_incoming_email_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_incoming_email_token!
end
@@ -45,7 +45,7 @@ class ProfilesController < Profiles::ApplicationController
end
def reset_rss_token
- Users::UpdateService.new(@user).execute! do |user|
+ Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_rss_token!
end
@@ -61,7 +61,7 @@ class ProfilesController < Profiles::ApplicationController
end
def update_username
- result = Users::UpdateService.new(@user, username: user_params[:username]).execute
+ result = Users::UpdateService.new(current_user, user: @user, username: user_params[:username]).execute
options = if result[:status] == :success
{ notice: "Username successfully changed" }
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index be6491d042c..fe3bb117410 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -55,7 +55,7 @@ class SessionsController < Devise::SessionsController
return unless user && user.require_password_creation?
- Users::UpdateService.new(user).execute do |user|
+ Users::UpdateService.new(current_user, user: user).execute do |user|
@token = user.generate_reset_token
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0a2e3c709d9..24c07f3dc70 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -25,6 +25,28 @@ class IssuableFinder
NONE = '0'.freeze
+ SCALAR_PARAMS = %i[
+ assignee_id
+ assignee_username
+ author_id
+ author_username
+ authorized_only
+ due_date
+ group_id
+ iids
+ label_name
+ milestone_title
+ non_archived
+ project_id
+ scope
+ search
+ sort
+ state
+ ].freeze
+ ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
+
+ VALID_PARAMS = (SCALAR_PARAMS + [ARRAY_PARAMS]).freeze
+
attr_accessor :current_user, :params
def initialize(current_user, params = {})
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index a4c226a6aad..be11d453898 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -13,22 +13,29 @@ module AvatarsHelper
user_name = options[:user].try(:name) || options[:user_name]
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip]
- data_attributes = {}
+ data_attributes = options[:data] || {}
css_class = %W[avatar s#{avatar_size}].push(*options[:css_class])
if has_tooltip
css_class.push('has-tooltip')
- data_attributes = { container: 'body' }
+ data_attributes[:container] = 'body'
end
- image_tag(
- avatar_url,
+ if options[:lazy]
+ css_class << 'lazy'
+ data_attributes[:src] = avatar_url
+ avatar_url = LazyImageTagHelper.placeholder_image
+ end
+
+ image_options = {
+ alt: "#{user_name}'s avatar",
+ src: avatar_url,
+ data: data_attributes,
class: css_class,
- alt: "#{user_name}'s avatar",
- title: user_name,
- data: data_attributes,
- lazy: true
- )
+ title: user_name
+ }
+
+ tag(:img, image_options)
end
def user_avatar(options = {})
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 62ac208f16a..7112c6ee470 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -79,6 +79,6 @@ module BoardsHelper
end
def boards_link_text
- _("Board")
+ s_("IssueBoards|Board")
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index ddeff490d3a..21fb17e06d6 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -239,8 +239,8 @@ module ProjectsHelper
end
end
- def has_projects_or_name?(projects, params)
- !!(params[:name] || any_projects?(projects))
+ def show_projects?(projects, params)
+ !!(params[:personal] || params[:name] || any_projects?(projects))
end
private
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index c4a73bedbcd..1b542ed2a96 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -1,34 +1,38 @@
module SortingHelper
def sort_options_hash
{
- sort_value_name => sort_title_name,
- sort_value_name_desc => sort_title_name_desc,
- sort_value_recently_updated => sort_title_recently_updated,
- sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_created_date => sort_title_created_date,
+ sort_value_downvotes => sort_title_downvotes,
+ sort_value_due_date => sort_title_due_date,
+ sort_value_due_date_later => sort_title_due_date_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_label_priority => sort_title_label_priority,
+ sort_value_largest_group => sort_title_largest_group,
+ sort_value_largest_repo => sort_title_largest_repo,
+ sort_value_milestone => sort_title_milestone,
+ sort_value_milestone_later => sort_title_milestone_later,
+ sort_value_milestone_soon => sort_title_milestone_soon,
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_oldest_signin => sort_title_oldest_signin,
+ sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created,
- sort_value_oldest_created => sort_title_oldest_created,
- sort_value_milestone_soon => sort_title_milestone_soon,
- sort_value_milestone_later => sort_title_milestone_later,
- sort_value_due_date_soon => sort_title_due_date_soon,
- sort_value_due_date_later => sort_title_due_date_later,
- sort_value_largest_repo => sort_title_largest_repo,
- sort_value_largest_group => sort_title_largest_group,
- sort_value_recently_signin => sort_title_recently_signin,
- sort_value_oldest_signin => sort_title_oldest_signin,
- sort_value_downvotes => sort_title_downvotes,
- sort_value_upvotes => sort_title_upvotes,
- sort_value_priority => sort_title_priority,
- sort_value_label_priority => sort_title_label_priority
+ sort_value_recently_signin => sort_title_recently_signin,
+ sort_value_recently_updated => sort_title_recently_updated,
+ sort_value_popularity => sort_title_popularity,
+ sort_value_priority => sort_title_priority,
+ sort_value_upvotes => sort_title_upvotes
}
end
def projects_sort_options_hash
options = {
- sort_value_name => sort_title_name,
- sort_value_latest_activity => sort_title_latest_activity,
- sort_value_oldest_activity => sort_title_oldest_activity,
- sort_value_recently_created => sort_title_recently_created,
- sort_value_oldest_created => sort_title_oldest_created
+ sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_name => sort_title_name,
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created
}
if current_controller?('admin/projects')
@@ -40,160 +44,174 @@ module SortingHelper
def member_sort_options_hash
{
- sort_value_access_level_asc => sort_title_access_level_asc,
+ sort_value_access_level_asc => sort_title_access_level_asc,
sort_value_access_level_desc => sort_title_access_level_desc,
- sort_value_last_joined => sort_title_last_joined,
- sort_value_oldest_joined => sort_title_oldest_joined,
- sort_value_name => sort_title_name_asc,
- sort_value_name_desc => sort_title_name_desc,
- sort_value_recently_signin => sort_title_recently_signin,
- sort_value_oldest_signin => sort_title_oldest_signin
+ sort_value_last_joined => sort_title_last_joined,
+ sort_value_name => sort_title_name_asc,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_oldest_joined => sort_title_oldest_joined,
+ sort_value_oldest_signin => sort_title_oldest_signin,
+ sort_value_recently_signin => sort_title_recently_signin
}
end
def milestone_sort_options_hash
{
- sort_value_name => sort_title_name_asc,
- sort_value_name_desc => sort_title_name_desc,
- sort_value_due_date_soon => sort_title_due_date_soon,
- sort_value_due_date_later => sort_title_due_date_later,
- sort_value_start_date_soon => sort_title_start_date_soon,
- sort_value_start_date_later => sort_title_start_date_later
+ sort_value_name => sort_title_name_asc,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_due_date_later => sort_title_due_date_later,
+ sort_value_due_date_soon => sort_title_due_date_soon,
+ sort_value_start_date_later => sort_title_start_date_later,
+ sort_value_start_date_soon => sort_title_start_date_soon
}
end
def branches_sort_options_hash
{
- sort_value_name => sort_title_name,
- sort_value_recently_updated => sort_title_recently_updated,
- sort_value_oldest_updated => sort_title_oldest_updated
+ sort_value_name => sort_title_name,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_updated => sort_title_recently_updated
}
end
def tags_sort_options_hash
{
- sort_value_name => sort_title_name,
- sort_value_recently_updated => sort_title_recently_updated,
- sort_value_oldest_updated => sort_title_oldest_updated
+ sort_value_name => sort_title_name,
+ sort_value_oldest_updated => sort_title_oldest_updated,
+ sort_value_recently_updated => sort_title_recently_updated
}
end
- def sort_title_priority
- s_('SortOptions|Priority')
+ def sortable_item(item, path, sorted_by)
+ link_to item, path, class: sorted_by == item ? 'is-active' : ''
end
- def sort_title_label_priority
- s_('SortOptions|Label priority')
+ # Titles.
+ def sort_title_access_level_asc
+ s_('SortOptions|Access level, ascending')
end
- def sort_title_oldest_updated
- s_('SortOptions|Oldest updated')
+ def sort_title_access_level_desc
+ s_('SortOptions|Access level, descending')
end
- def sort_title_recently_updated
- s_('SortOptions|Last updated')
+ def sort_title_created_date
+ s_('SortOptions|Created date')
end
- def sort_title_oldest_activity
- s_('SortOptions|Oldest updated')
+ def sort_title_downvotes
+ s_('SortOptions|Least popular')
end
- def sort_title_latest_activity
- s_('SortOptions|Last updated')
+ def sort_title_due_date
+ s_('SortOptions|Due date')
end
- def sort_title_oldest_created
- s_('SortOptions|Oldest created')
+ def sort_title_due_date_later
+ s_('SortOptions|Due later')
end
- def sort_title_recently_created
- s_('SortOptions|Last created')
+ def sort_title_due_date_soon
+ s_('SortOptions|Due soon')
end
- def sort_title_milestone_soon
- s_('SortOptions|Milestone due soon')
+ def sort_title_label_priority
+ s_('SortOptions|Label priority')
end
- def sort_title_milestone_later
- s_('SortOptions|Milestone due later')
+ def sort_title_largest_group
+ s_('SortOptions|Largest group')
end
- def sort_title_due_date_soon
- s_('SortOptions|Due soon')
+ def sort_title_largest_repo
+ s_('SortOptions|Largest repository')
end
- def sort_title_due_date_later
- s_('SortOptions|Due later')
+ def sort_title_last_joined
+ s_('SortOptions|Last joined')
end
- def sort_title_start_date_soon
- s_('SortOptions|Start soon')
+ def sort_title_latest_activity
+ s_('SortOptions|Last updated')
end
- def sort_title_start_date_later
- s_('SortOptions|Start later')
+ def sort_title_milestone
+ s_('SortOptions|Milestone')
+ end
+
+ def sort_title_milestone_later
+ s_('SortOptions|Milestone due later')
+ end
+
+ def sort_title_milestone_soon
+ s_('SortOptions|Milestone due soon')
end
def sort_title_name
s_('SortOptions|Name')
end
- def sort_title_largest_repo
- s_('SortOptions|Largest repository')
+ def sort_title_name_asc
+ s_('SortOptions|Name, ascending')
end
- def sort_title_largest_group
- s_('SortOptions|Largest group')
+ def sort_title_name_desc
+ s_('SortOptions|Name, descending')
end
- def sort_title_recently_signin
- s_('SortOptions|Recent sign in')
+ def sort_title_oldest_activity
+ s_('SortOptions|Oldest updated')
end
- def sort_title_oldest_signin
- s_('SortOptions|Oldest sign in')
+ def sort_title_oldest_created
+ s_('SortOptions|Oldest created')
end
- def sort_title_downvotes
- s_('SortOptions|Least popular')
+ def sort_title_oldest_joined
+ s_('SortOptions|Oldest joined')
end
- def sort_title_upvotes
- s_('SortOptions|Most popular')
+ def sort_title_oldest_signin
+ s_('SortOptions|Oldest sign in')
end
- def sort_title_last_joined
- s_('SortOptions|Last joined')
+ def sort_title_oldest_updated
+ s_('SortOptions|Oldest updated')
end
- def sort_title_oldest_joined
- s_('SortOptions|Oldest joined')
+ def sort_title_popularity
+ s_('SortOptions|Popularity')
end
- def sort_title_access_level_asc
- s_('SortOptions|Access level, ascending')
+ def sort_title_priority
+ s_('SortOptions|Priority')
end
- def sort_title_access_level_desc
- s_('SortOptions|Access level, descending')
+ def sort_title_recently_created
+ s_('SortOptions|Last created')
end
- def sort_title_name_asc
- s_('SortOptions|Name, ascending')
+ def sort_title_recently_signin
+ s_('SortOptions|Recent sign in')
end
- def sort_title_name_desc
- s_('SortOptions|Name, descending')
+ def sort_title_recently_updated
+ s_('SortOptions|Last updated')
end
- def sort_value_last_joined
- 'last_joined'
+ def sort_title_start_date_later
+ s_('SortOptions|Start later')
end
- def sort_value_oldest_joined
- 'oldest_joined'
+ def sort_title_start_date_soon
+ s_('SortOptions|Start soon')
end
+ def sort_title_upvotes
+ s_('SortOptions|Most popular')
+ end
+
+ # Values.
def sort_value_access_level_asc
'access_level_asc'
end
@@ -202,88 +220,112 @@ module SortingHelper
'access_level_desc'
end
- def sort_value_name_desc
- 'name_desc'
+ def sort_value_created_date
+ 'created_date'
end
- def sort_value_priority
- 'priority'
+ def sort_value_downvotes
+ 'downvotes_desc'
+ end
+
+ def sort_value_due_date
+ 'due_date'
+ end
+
+ def sort_value_due_date_later
+ 'due_date_desc'
+ end
+
+ def sort_value_due_date_soon
+ 'due_date_asc'
end
def sort_value_label_priority
'label_priority'
end
- def sort_value_oldest_updated
- 'updated_asc'
+ def sort_value_largest_group
+ 'storage_size_desc'
end
- def sort_value_recently_updated
- 'updated_desc'
+ def sort_value_largest_repo
+ 'storage_size_desc'
end
- def sort_value_oldest_activity
- 'latest_activity_asc'
+ def sort_value_last_joined
+ 'last_joined'
end
def sort_value_latest_activity
'latest_activity_desc'
end
- def sort_value_oldest_created
- 'created_asc'
+ def sort_value_milestone
+ 'milestone'
end
- def sort_value_recently_created
- 'created_desc'
+ def sort_value_milestone_later
+ 'milestone_due_desc'
end
def sort_value_milestone_soon
'milestone_due_asc'
end
- def sort_value_milestone_later
- 'milestone_due_desc'
+ def sort_value_name
+ 'name_asc'
end
- def sort_value_due_date_soon
- 'due_date_asc'
+ def sort_value_name_desc
+ 'name_desc'
end
- def sort_value_due_date_later
- 'due_date_desc'
+ def sort_value_oldest_activity
+ 'latest_activity_asc'
end
- def sort_value_start_date_soon
- 'start_date_asc'
+ def sort_value_oldest_created
+ 'created_asc'
end
- def sort_value_start_date_later
- 'start_date_desc'
+ def sort_value_oldest_signin
+ 'oldest_sign_in'
end
- def sort_value_name
- 'name_asc'
+ def sort_value_oldest_joined
+ 'oldest_joined'
end
- def sort_value_largest_repo
- 'storage_size_desc'
+ def sort_value_oldest_updated
+ 'updated_asc'
end
- def sort_value_largest_group
- 'storage_size_desc'
+ def sort_value_popularity
+ 'popularity'
+ end
+
+ def sort_value_priority
+ 'priority'
+ end
+
+ def sort_value_recently_created
+ 'created_desc'
end
def sort_value_recently_signin
'recent_sign_in'
end
- def sort_value_oldest_signin
- 'oldest_sign_in'
+ def sort_value_recently_updated
+ 'updated_desc'
end
- def sort_value_downvotes
- 'downvotes_desc'
+ def sort_value_start_date_later
+ 'start_date_desc'
+ end
+
+ def sort_value_start_date_soon
+ 'start_date_asc'
end
def sort_value_upvotes
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index acaa028eaa2..3d5acc00f8f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -434,7 +434,7 @@ module Ci
def update_duration
return unless started_at
- self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self)
+ self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
end
def execute_hooks
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index a0d07902ba2..c6509f89117 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -174,7 +174,7 @@ module Ci
end
def assignable_for?(project)
- !locked? || projects.exists?(id: project.id)
+ is_shared? || projects.exists?(id: project.id)
end
def accepting_tags?(build)
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2ae8890c1b3..6dba154a6ea 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -25,8 +25,8 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000
- # The SHA can be between 7 and 40 hex characters.
- COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
+ MIN_SHA_LENGTH = 7
+ COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
@@ -53,7 +53,7 @@ class Commit
# Truncate sha to 8 characters
def truncate_sha(sha)
- sha[0..7]
+ sha[0..MIN_SHA_LENGTH]
end
def max_diff_options
@@ -100,7 +100,7 @@ class Commit
def self.reference_pattern
@reference_pattern ||= %r{
(?:#{Project.reference_pattern}#{reference_prefix})?
- (?<commit>\h{7,40})
+ (?<commit>#{COMMIT_SHA_PATTERN})
}x
end
@@ -216,9 +216,8 @@ class Commit
@raw.respond_to?(method, include_private) || super
end
- # Truncate sha to 8 characters
def short_id
- @raw.short_id(7)
+ @raw.short_id(MIN_SHA_LENGTH)
end
def diff_refs
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 265f6e48540..fc30d008dea 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -143,16 +143,18 @@ module Issuable
end
def sort(method, excluded_labels: [])
- sorted = case method.to_s
- when 'milestone_due_asc' then order_milestone_due_asc
- when 'milestone_due_desc' then order_milestone_due_desc
- when 'downvotes_desc' then order_downvotes_desc
- when 'upvotes_desc' then order_upvotes_desc
- when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
- when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
- else
- order_by(method)
- end
+ sorted =
+ case method.to_s
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'milestone' then order_milestone_due_asc
+ when 'milestone_due_asc' then order_milestone_due_asc
+ when 'milestone_due_desc' then order_milestone_due_desc
+ when 'popularity' then order_upvotes_desc
+ when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
+ when 'upvotes_desc' then order_upvotes_desc
+ else order_by(method)
+ end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
@@ -214,7 +216,7 @@ module Issuable
def grouping_columns(sort)
grouping_columns = [arel_table[:id]]
- if %w(milestone_due_desc milestone_due_asc).include?(sort)
+ if %w(milestone_due_desc milestone_due_asc milestone).include?(sort)
milestone_table = Milestone.arel_table
grouping_columns << milestone_table[:id]
grouping_columns << milestone_table[:due_date]
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index db3cd257584..cefa5c13c5f 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -19,14 +19,15 @@ module Sortable
module ClassMethods
def order_by(method)
case method.to_s
- when 'name_asc' then order_name_asc
- when 'name_desc' then order_name_desc
- when 'updated_asc' then order_updated_asc
- when 'updated_desc' then order_updated_desc
- when 'created_asc' then order_created_asc
+ when 'created_asc' then order_created_asc
+ when 'created_date' then order_created_desc
when 'created_desc' then order_created_desc
- when 'id_desc' then order_id_desc
- when 'id_asc' then order_id_asc
+ when 'id_asc' then order_id_asc
+ when 'id_desc' then order_id_desc
+ when 'name_asc' then order_name_asc
+ when 'name_desc' then order_name_desc
+ when 'updated_asc' then order_updated_asc
+ when 'updated_desc' then order_updated_desc
else
all
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 92a454300af..155c5d972b7 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -116,7 +116,8 @@ class Issue < ActiveRecord::Base
def self.sort(method, excluded_labels: [])
case method.to_s
- when 'due_date_asc' then order_due_date_asc
+ when 'due_date' then order_due_date_asc
+ when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
else
super
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 90cede9d3d4..f0de2697dfc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -489,13 +489,7 @@ class Repository
def exists?
return false unless full_path
- Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
- if enabled
- raw_repository.exists?
- else
- refs_directory_exists?
- end
- end
+ raw_repository.exists?
end
cache_method :exists?
@@ -534,8 +528,11 @@ class Repository
cache_method :tag_count, fallback: 0
def avatar
- if tree = file_on_head(:avatar)
- tree.path
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ if tree = file_on_head(:avatar)
+ tree.path
+ end
end
end
cache_method :avatar
@@ -1060,12 +1057,6 @@ class Repository
blob.data
end
- def refs_directory_exists?
- circuit_breaker.perform do
- File.exist?(File.join(path_to_repo, 'refs'))
- end
- end
-
def cache
# TODO: should we use UUIDs here? We could move repositories without clearing this cache
@cache ||= RepositoryCache.new(full_path, @project.id)
@@ -1117,10 +1108,6 @@ class Repository
Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, false))
end
- def circuit_breaker
- @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(project.repository_storage)
- end
-
def find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
ref ||= root_ref
diff --git a/app/models/user.rb b/app/models/user.rb
index 09c9b3250eb..103ac78783f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -60,7 +60,7 @@ class User < ActiveRecord::Base
lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
return unless lease.try_obtain
- Users::UpdateService.new(self).execute(validate: false)
+ Users::UpdateService.new(self, user: self).execute(validate: false)
end
attr_accessor :force_random_password
@@ -526,8 +526,8 @@ class User < ActiveRecord::Base
def update_emails_with_primary_email
primary_email_record = emails.find_by(email: email)
if primary_email_record
- Emails::DestroyService.new(self, email: email).execute
- Emails::CreateService.new(self, email: email_was).execute
+ Emails::DestroyService.new(self, user: self, email: email).execute
+ Emails::CreateService.new(self, user: self, email: email_was).execute
end
end
@@ -1000,7 +1000,7 @@ class User < ActiveRecord::Base
if attempts_exceeded?
lock_access! unless access_locked?
else
- Users::UpdateService.new(self).execute(validate: false)
+ Users::UpdateService.new(self, user: self).execute(validate: false)
end
end
@@ -1186,7 +1186,7 @@ class User < ActiveRecord::Base
&creation_block
)
- Users::UpdateService.new(user).execute(validate: false)
+ Users::UpdateService.new(user, user: user).execute(validate: false)
user
ensure
Gitlab::ExclusiveLease.cancel(lease_key, uuid)
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index d20de9b16a4..31a712ccc1b 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -2,110 +2,55 @@ module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
- def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil)
+ SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
+ Gitlab::Ci::Pipeline::Chain::Validate::Repository,
+ Gitlab::Ci::Pipeline::Chain::Validate::Config,
+ Gitlab::Ci::Pipeline::Chain::Skip,
+ Gitlab::Ci::Pipeline::Chain::Create].freeze
+
+ def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, &block)
@pipeline = Ci::Pipeline.new(
source: source,
project: project,
ref: ref,
sha: sha,
before_sha: before_sha,
- tag: tag?,
+ tag: tag_exists?,
trigger_requests: Array(trigger_request),
user: current_user,
pipeline_schedule: schedule,
protected: project.protected_for?(ref)
)
- result = validate_project_and_git_items ||
- validate_pipeline(ignore_skip_ci: ignore_skip_ci,
- save_on_errors: save_on_errors)
+ command = OpenStruct.new(ignore_skip_ci: ignore_skip_ci,
+ save_incompleted: save_on_errors,
+ seeds_block: block,
+ project: project,
+ current_user: current_user)
- return result if result
+ sequence = Gitlab::Ci::Pipeline::Chain::Sequence
+ .new(pipeline, command, SEQUENCE)
- begin
- Ci::Pipeline.transaction do
- pipeline.save!
+ sequence.build! do |pipeline, sequence|
+ update_merge_requests_head_pipeline if pipeline.persisted?
- yield(pipeline) if block_given?
+ if sequence.complete?
+ cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
+ pipeline_created_counter.increment(source: source)
- Ci::CreatePipelineStagesService
- .new(project, current_user)
- .execute(pipeline)
+ pipeline.process!
end
- rescue ActiveRecord::RecordInvalid => e
- return error("Failed to persist the pipeline: #{e}")
end
-
- update_merge_requests_head_pipeline
-
- cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
-
- pipeline_created_counter.increment(source: source)
-
- pipeline.tap(&:process!)
end
private
- def validate_project_and_git_items
- unless project.builds_enabled?
- return error('Pipeline is disabled')
- end
-
- unless allowed_to_trigger_pipeline?
- if can?(current_user, :create_pipeline, project)
- return error("Insufficient permissions for protected ref '#{ref}'")
- else
- return error('Insufficient permissions to create a new pipeline')
- end
- end
-
- unless branch? || tag?
- return error('Reference not found')
- end
-
- unless commit
- return error('Commit not found')
- end
- end
-
- def validate_pipeline(ignore_skip_ci:, save_on_errors:)
- unless pipeline.config_processor
- unless pipeline.ci_yaml_file
- return error("Missing #{pipeline.ci_yaml_file_path} file")
- end
- return error(pipeline.yaml_errors, save: save_on_errors)
- end
-
- if !ignore_skip_ci && skip_ci?
- pipeline.skip if save_on_errors
- return pipeline
- end
-
- unless pipeline.has_stage_seeds?
- return error('No stages / jobs for this pipeline.')
- end
- end
-
- def allowed_to_trigger_pipeline?
- if current_user
- allowed_to_create?
- else # legacy triggers don't have a corresponding user
- !project.protected_for?(ref)
- end
+ def commit
+ @commit ||= project.commit(origin_sha || origin_ref)
end
- def allowed_to_create?
- return unless can?(current_user, :create_pipeline, project)
-
- access = Gitlab::UserAccess.new(current_user, project: project)
- if branch?
- access.can_update_branch?(ref)
- elsif tag?
- access.can_create_tag?(ref)
- else
- true # Allow it for now and we'll reject when we check ref existence
- end
+ def sha
+ commit.try(:id)
end
def update_merge_requests_head_pipeline
@@ -115,11 +60,6 @@ module Ci
.update_all(head_pipeline_id: @pipeline.id)
end
- def skip_ci?
- return false unless pipeline.git_commit_message
- pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i
- end
-
def cancel_pending_pipelines
Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
cancelables.find_each do |cancelable|
@@ -136,14 +76,6 @@ module Ci
.created_or_pending
end
- def commit
- @commit ||= project.commit(origin_sha || origin_ref)
- end
-
- def sha
- commit.try(:id)
- end
-
def before_sha
params[:checkout_sha] || params[:before] || Gitlab::Git::BLANK_SHA
end
@@ -156,41 +88,17 @@ module Ci
params[:ref]
end
- def branch?
- return @is_branch if defined?(@is_branch)
-
- @is_branch =
- project.repository.ref_exists?(Gitlab::Git::BRANCH_REF_PREFIX + ref)
- end
-
- def tag?
- return @is_tag if defined?(@is_tag)
-
- @is_tag =
- project.repository.ref_exists?(Gitlab::Git::TAG_REF_PREFIX + ref)
+ def tag_exists?
+ project.repository.tag_exists?(ref)
end
def ref
@ref ||= Gitlab::Git.ref_name(origin_ref)
end
- def valid_sha?
- origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
- end
-
- def error(message, save: false)
- pipeline.tap do
- pipeline.errors.add(:base, message)
-
- if save
- pipeline.drop
- update_merge_requests_head_pipeline
- end
- end
- end
-
def pipeline_created_counter
- @pipeline_created_counter ||= Gitlab::Metrics.counter(:pipelines_created_total, "Counter of pipelines created")
+ @pipeline_created_counter ||= Gitlab::Metrics
+ .counter(:pipelines_created_total, "Counter of pipelines created")
end
end
end
diff --git a/app/services/emails/base_service.rb b/app/services/emails/base_service.rb
index ace49889097..7f591c89411 100644
--- a/app/services/emails/base_service.rb
+++ b/app/services/emails/base_service.rb
@@ -1,7 +1,8 @@
module Emails
class BaseService
- def initialize(user, opts)
- @user = user
+ def initialize(current_user, opts)
+ @current_user = current_user
+ @user = opts.delete(:user)
@email = opts[:email]
end
end
diff --git a/app/services/emails/destroy_service.rb b/app/services/emails/destroy_service.rb
index d586b9dfe0c..44011cc36c8 100644
--- a/app/services/emails/destroy_service.rb
+++ b/app/services/emails/destroy_service.rb
@@ -1,13 +1,13 @@
module Emails
class DestroyService < ::Emails::BaseService
def execute
- Email.find_by_email!(@email).destroy && update_secondary_emails!
+ update_secondary_emails! if Email.find_by_email!(@email).destroy
end
private
def update_secondary_emails!
- result = ::Users::UpdateService.new(@user).execute do |user|
+ result = ::Users::UpdateService.new(@current_user, user: @user).execute do |user|
user.update_secondary_emails!
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 07cbd8f92a9..bf26859dd6d 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -14,13 +14,13 @@ module MergeRequests
@merge_request = merge_request
unless @merge_request.mergeable?
- return log_merge_error('Merge request is not mergeable', save_message_on_model: true)
+ return handle_merge_error(log_message: 'Merge request is not mergeable', save_message_on_model: true)
end
@source = find_merge_source
unless @source
- return log_merge_error('No source for merge', save_message_on_model: true)
+ return handle_merge_error(log_message: 'No source for merge', save_message_on_model: true)
end
merge_request.in_locked_state do
@@ -31,8 +31,7 @@ module MergeRequests
end
end
rescue MergeError => e
- clean_merge_jid
- log_merge_error(e.message, save_message_on_model: true)
+ handle_merge_error(log_message: e.message, save_message_on_model: true)
end
private
@@ -74,10 +73,16 @@ module MergeRequests
@merge_request.force_remove_source_branch? ? @merge_request.author : current_user
end
- def log_merge_error(message, save_message_on_model: false)
- Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{message}")
+ # Logs merge error message and cleans `MergeRequest#merge_jid`.
+ #
+ def handle_merge_error(log_message:, save_message_on_model: false)
+ Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
- @merge_request.update(merge_error: message) if save_message_on_model
+ if save_message_on_model
+ @merge_request.update(merge_error: log_message, merge_jid: nil)
+ else
+ clean_merge_jid
+ end
end
def merge_request_info
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index 6188b8a4349..15ca1a55a5b 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -2,22 +2,21 @@ module Users
class UpdateService < BaseService
include NewUserNotifier
- def initialize(user, params = {})
- @user = user
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @user = params.delete(:user)
@params = params.dup
end
def execute(validate: true, &block)
yield(@user) if block_given?
- assign_attributes(&block)
-
user_exists = @user.persisted?
- if @user.save(validate: validate)
- notify_new_user(@user, nil) unless user_exists
+ assign_attributes(&block)
- success
+ if @user.save(validate: validate)
+ notify_success(user_exists)
else
error(@user.errors.full_messages.uniq.join('. '))
end
@@ -33,6 +32,12 @@ module Users
private
+ def notify_success(user_exists)
+ notify_new_user(@user, nil) unless user_exists
+
+ success
+ end
+
def assign_attributes(&block)
if @user.user_synced_attributes_metadata
params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index e5842bd1ea0..3ef8f2a3acb 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Background Jobs"
-= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title Background Jobs
diff --git a/app/views/admin/cohorts/index.html.haml b/app/views/admin/cohorts/index.html.haml
index bff53da1d9a..5e9a8c083af 100644
--- a/app/views/admin/cohorts/index.html.haml
+++ b/app/views/admin/cohorts/index.html.haml
@@ -1,6 +1,5 @@
- breadcrumb_title "Cohorts"
- @no_container = true
-= render "admin/dashboard/head"
%div{ class: container_class }
- if @cohorts
diff --git a/app/views/admin/conversational_development_index/show.html.haml b/app/views/admin/conversational_development_index/show.html.haml
index 833d4c612f8..30dd87f0463 100644
--- a/app/views/admin/conversational_development_index/show.html.haml
+++ b/app/views/admin/conversational_development_index/show.html.haml
@@ -1,8 +1,6 @@
- @no_container = true
- page_title 'ConvDev Index'
-= render 'admin/monitoring/head'
-
.container
- if show_callout?('convdev_intro_callout_dismissed')
= render 'callout'
diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml
deleted file mode 100644
index c2151710884..00000000000
--- a/app/views/admin/dashboard/_head.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview' do
- %span
- Overview
- = nav_link(controller: [:admin, :projects]) do
- = link_to admin_projects_path, title: 'Projects' do
- %span
- Projects
- = nav_link(controller: :users) do
- = link_to admin_users_path, title: 'Users' do
- %span
- Users
- = nav_link(controller: :groups) do
- = link_to admin_groups_path, title: 'Groups' do
- %span
- Groups
- = nav_link path: 'builds#index' do
- = link_to admin_jobs_path, title: 'Jobs' do
- %span
- Jobs
- = nav_link path: ['runners#index', 'runners#show'] do
- = link_to admin_runners_path, title: 'Runners' do
- %span
- Runners
- = nav_link path: 'cohorts#index' do
- = link_to admin_cohorts_path, title: 'Cohorts' do
- %span
- Cohorts
- = nav_link(controller: :conversational_development_index) do
- = link_to admin_conversational_development_index_path, title: 'ConvDev Index' do
- %span
- ConvDev Index
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index d212c7ca965..2f0143c7eff 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- breadcrumb_title "Dashboard"
-= render "admin/dashboard/head"
%div{ class: container_class }
.admin-dashboard.prepend-top-default
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index e5f380c78e2..535251fef5e 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Groups"
-= render "admin/dashboard/head"
%div{ class: container_class }
.top-area
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index 517db50b97f..10a3bed0a4f 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- page_title _('Health Check')
- no_errors = @errors.blank? && @failing_storage_statuses.blank?
-= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title= page_title
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index aa6e9db3900..0310498ae54 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -1,6 +1,5 @@
- breadcrumb_title "Jobs"
- @no_container = true
-= render "admin/dashboard/head"
%div{ class: container_class }
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index ee87f25a225..78757b6384f 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Logs"
-= render 'admin/monitoring/head'
%div{ class: container_class }
%ul.nav-links.log-tabs
diff --git a/app/views/admin/monitoring/_head.html.haml b/app/views/admin/monitoring/_head.html.haml
deleted file mode 100644
index b3530915068..00000000000
--- a/app/views/admin/monitoring/_head.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: :system_info) do
- = link_to admin_system_info_path, title: 'System Info' do
- %span
- System Info
- = nav_link(controller: :background_jobs) do
- = link_to admin_background_jobs_path, title: 'Background Jobs' do
- %span
- Background Jobs
- = nav_link(controller: :logs) do
- = link_to admin_logs_path, title: 'Logs' do
- %span
- Logs
- = nav_link(controller: :health_check) do
- = link_to admin_health_check_path, title: 'Health Check' do
- %span
- Health Check
- = nav_link(controller: :requests_profiles) do
- = link_to admin_requests_profiles_path, title: 'Requests Profiles' do
- %span
- Requests Profiles
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 3301f55b8a8..3f202fbf4fe 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -2,7 +2,6 @@
- page_title "Projects"
- params[:visibility_level] ||= []
-= render "admin/dashboard/head"
%div{ class: container_class }
.top-area
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index b7db18b2d32..cb02a750490 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title 'Requests Profiles'
-= render 'admin/monitoring/head'
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 6793ce557c4..43cea1358cc 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,6 +1,5 @@
- breadcrumb_title "Runners"
- @no_container = true
-= render "admin/dashboard/head"
%div{ class: container_class }
.bs-callout
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index fd0281e4961..6bf979a937e 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "System Info"
-= render 'admin/monitoring/head'
%div{ class: container_class }
.prepend-top-default
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 5516134d8a0..38ce1564eff 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Users"
-= render "admin/dashboard/head"
%div{ class: container_class }
.prepend-top-default
diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml
new file mode 100644
index 00000000000..3701e1c0578
--- /dev/null
+++ b/app/views/dashboard/projects/_nav.html.haml
@@ -0,0 +1,6 @@
+.top-area
+ %ul.nav-links
+ = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
+ = link_to s_('DashboardProjects|All'), dashboard_projects_path
+ = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
+ = link_to s_('DashboardProjects|Personal'), filter_projects_path(personal: true)
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index a4dc49d2120..57a4da353fe 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -10,8 +10,9 @@
= render "projects/last_push"
%div{ class: container_class }
- - if has_projects_or_name?(@projects, params)
+ - if show_projects?(@projects, params)
= render 'dashboard/projects_head'
+ = render 'nav'
= render 'projects'
- else
= render "zero_authorized_projects"
diff --git a/app/views/groups/_head.html.haml b/app/views/groups/_head.html.haml
deleted file mode 100644
index 0f63774fb9b..00000000000
--- a/app/views/groups/_head.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: container_class }
- = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: 'Group Home' do
- %span
- Home
-
- = nav_link(path: 'groups#activity') do
- = link_to activity_group_path(@group), title: 'Activity' do
- %span
- Activity
-
-.hidden-xs
- = render "projects/last_push"
diff --git a/app/views/groups/_head_issues.html.haml b/app/views/groups/_head_issues.html.haml
deleted file mode 100644
index d554bc23743..00000000000
--- a/app/views/groups/_head_issues.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: container_class }
- = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
- = link_to issues_group_path(@group), title: 'List' do
- %span
- List
-
- = nav_link(path: 'labels#index') do
- = link_to group_labels_path(@group), title: 'Labels' do
- %span
- Labels
-
- = nav_link(path: 'milestones#index') do
- = link_to group_milestones_path(@group), title: 'Milestones' do
- %span
- Milestones
diff --git a/app/views/groups/_settings_head.html.haml b/app/views/groups/_settings_head.html.haml
deleted file mode 100644
index 623d233a46a..00000000000
--- a/app/views/groups/_settings_head.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: container_class }
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'General' do
- %span
- General
-
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
- %span
- Projects
-
- = nav_link(controller: :ci_cd) do
- = link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
- %span
- Pipelines
diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml
index 3969e56f937..cb7dab26332 100644
--- a/app/views/groups/activity.html.haml
+++ b/app/views/groups/activity.html.haml
@@ -2,7 +2,6 @@
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
- page_title "Activity"
-= render 'groups/head'
%section.activities
= render 'activities'
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 0d3308833b7..15606dd30fd 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title "General Settings"
-= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
Group settings
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 7f411927429..07e64d9aeaf 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,6 +1,5 @@
- page_title "Issues"
- group_issues_exists = group_issues(@group).exists?
-= render "head_issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 89165096fe2..d10efdad53b 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,8 +1,5 @@
- page_title 'Labels'
-= render "groups/head_issues"
-
-
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index ed582e521c4..cb4fc69d5b8 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -1,7 +1,5 @@
- page_title "Milestones"
-= render "groups/head_issues"
-
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index 7f3f2f707f7..8d2bc810a7d 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title "Projects"
-= render "groups/settings_head"
.panel.panel-default.prepend-top-default
.panel-heading
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index 9f9ae01e7c5..472da2a6a72 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
-= render "groups/settings_head"
= render 'ci/variables/index'
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index f4f76887422..3ca63f9c3e0 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -4,7 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
-= render 'groups/head'
= render 'groups/home_panel'
.groups-header{ class: container_class }
diff --git a/app/views/groups/subgroups.html.haml b/app/views/groups/subgroups.html.haml
index 7abc84412c6..869b3b243c6 100644
--- a/app/views/groups/subgroups.html.haml
+++ b/app/views/groups/subgroups.html.haml
@@ -1,7 +1,6 @@
- breadcrumb_title "Details"
- @no_container = true
-= render 'head'
= render 'groups/home_panel'
.groups-header{ class: container_class }
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index e6a10e500a4..e3a9e99250e 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -32,10 +32,6 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- // TODO: Combine these 2 stylesheets into application.scss
- = stylesheet_link_tag "new_nav", media: "all"
- = stylesheet_link_tag "new_sidebar", media: "all"
-
= Gon::Base.render_data
- if content_for?(:library_javascripts)
diff --git a/app/views/projects/_head.html.haml b/app/views/projects/_head.html.haml
deleted file mode 100644
index dba84838a52..00000000000
--- a/app/views/projects/_head.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: container_class }
- = nav_link(path: 'projects#show') do
- = link_to project_path(@project), title: _('Project home'), class: 'shortcuts-project' do
- %span= _('Home')
-
- = nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
- %span= _('Activity')
-
- - if can?(current_user, :read_cycle_analytics, @project)
- = nav_link(path: 'cycle_analytics#show') do
- = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
- %span= _('Cycle Analytics')
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index f80dadb8037..d0ab39033cf 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -2,8 +2,6 @@
- page_title _("Activity")
-= render "projects/head"
-
= render 'projects/last_push'
= render 'projects/activity'
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 4cc3218d967..fe02cbcbf95 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,10 +1,9 @@
- breadcrumb_title _('Artifacts')
- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
-= render "projects/pipelines/head"
= render "projects/jobs/header", show_controls: false
-- add_to_breadcrumbs(_('Jobs'), project_jobs_path(@project))
+- add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project))
- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
.tree-holder
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index b85bbcb980e..2942d618a42 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -1,5 +1,4 @@
- page_title @path, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
-= render "projects/pipelines/head"
= render "projects/jobs/header", show_controls: false
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 60ac202bde0..e45861ac08d 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- project_duration = age_map_duration(@blame_groups, @project)
- page_title "Blame", @blob.path, @ref
-= render "projects/commits/head"
%div{ class: container_class }
#blob-content-holder.tree-holder
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 992fe7f717f..626cbc9e41d 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -4,7 +4,6 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
= page_specific_javascript_bundle_tag('blob')
-= render "projects/commits/head"
%div{ class: container_class }
- if @conflict
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 240e62d5ac5..c4712bf3736 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -2,7 +2,6 @@
- @no_container = true
- page_title @blob.path, @ref
-= render "projects/commits/head"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'blob'
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ea6e7e9db6c..7d9645d79e6 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title _('Branches')
-= render "projects/commits/head"
%div{ class: container_class }
.top-area.adjust
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 717de85c5d2..abb292f8f27 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -6,7 +6,6 @@
- @content_class = limited_container_width
- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
- page_description @commit.description
-= render "projects/commits/head"
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
deleted file mode 100644
index e1549baef89..00000000000
--- a/app/views/projects/commits/_head.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
- = link_to project_tree_path(@project) do
- #{ _('Files') }
-
- = nav_link(controller: [:commit, :commits]) do
- = link_to project_commits_path(@project, current_ref) do
- #{ _('Commits') }
-
- = nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
- #{ _('Branches') }
-
- = nav_link(controller: [:tags, :releases]) do
- = link_to project_tags_path(@project) do
- #{ _('Tags') }
-
- = nav_link(path: 'graphs#show') do
- = link_to project_graph_path(@project, current_ref) do
- #{ _('Contributors') }
-
- = nav_link(controller: %w(network)) do
- = link_to project_network_path(@project, current_ref) do
- #{ s_('ProjectNetworkGraph|Graph') }
-
- = nav_link(controller: :compare) do
- = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
- #{ _('Compare') }
-
- = nav_link(path: 'graphs#charts') do
- = link_to charts_project_graph_path(@project, current_ref) do
- #{ _('Charts') }
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index e873b931683..ef305120525 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -5,9 +5,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-= content_for :sub_nav do
- = render "head"
-
.js-project-commits-show{ 'data-commits-limit' => @limit }
%div{ class: container_class }
.tree-holder
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 1ce3ad0c0fd..3ad0166e9cd 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- breadcrumb_title "Compare Revisions"
- page_title "Compare"
-= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 7cc42455394..f87f1d476f5 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- add_to_breadcrumbs "Compare Revisions", project_compare_index_path(@project)
- page_title "#{params[:from]}...#{params[:to]}"
-= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block.no-bottom-space
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 8d008be5aae..c06e9f323af 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -4,8 +4,6 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics')
-= render "projects/head"
-
#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
.landing.content-block{ "v-if" => "!isOverviewDialogDismissed" }
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 0a3045604f4..8ae4fd94146 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -3,8 +3,6 @@
- @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test?
-= render "projects/settings/head"
-
.project-edit-container
%section.settings.general-settings
.settings-header
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index d5b83b53ebb..3f3ce10419f 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -3,7 +3,6 @@
= render partial: 'flash_messages', locals: { project: @project }
-= render "projects/head"
= render "home_panel"
.row-content-block.second-block.center
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
index 3871165763c..d6ff3f729b4 100644
--- a/app/views/projects/environments/edit.html.haml
+++ b/app/views/projects/environments/edit.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Edit", @environment.name, "Environments"
-= render "projects/pipelines/head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index f7e3733ba0b..1bcc955ddc8 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Environments"
-= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index acc80b49dd0..2e85f608823 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- page_title "Environments"
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
-= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 4a65b46f029..e0aedcac5e1 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -4,7 +4,6 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'common_d3'
= webpack_bundle_tag 'monitoring'
-= render "projects/pipelines/head"
.prometheus-container{ class: container_class }
.top-area
@@ -21,4 +20,3 @@
"empty-unable-to-connect-svg-path": image_path('illustrations/monitoring/unable_to_connect'),
"additional-metrics": additional_metrics_project_environment_path(@project, @environment, format: :json),
"has-metrics": "#{@environment.has_metrics?}", deployment_endpoint: project_environment_deployments_path(@project, @environment, format: :json) } }
-
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 88f43a1e7e4..62b08e85e22 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- breadcrumb_title "Environments"
- page_title 'New Environment'
-= render "projects/pipelines/head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index c35d1b5aaee..d7859c9fbeb 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -2,7 +2,6 @@
- add_to_breadcrumbs "Environments", project_environments_path(@project)
- breadcrumb_title @environment.name
- page_title "Environments"
-= render "projects/pipelines/head"
%div{ class: container_class }
.row.top-area.adjust
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index 464135b5ac7..a073a164f11 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Terminal for environment", @environment.name
-= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm/xterm"
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
index 021575160ea..a3467eb6f05 100644
--- a/app/views/projects/find_file/show.html.haml
+++ b/app/views/projects/find_file/show.html.haml
@@ -1,5 +1,4 @@
- page_title "Find File", @ref
-= render "projects/commits/head"
.file-finder-holder.tree-holder.clearfix.js-file-finder{ 'data-file-find-url': "#{escape_javascript(project_files_path(@project, @ref, @options.merge(format: :json)))}", 'data-find-tree-url': escape_javascript(project_tree_path(@project, @ref)), 'data-blob-url-template': escape_javascript(project_blob_path(@project, @id || @commit.id)) }
.nav-block
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index f0ef647ddb3..ffb9238a65a 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -4,7 +4,6 @@
= webpack_bundle_tag('common_d3')
= webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_charts')
-= render "projects/commits/head"
.repo-charts{ class: container_class }
%h4.sub-header
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 08b38428b50..70156c03e3c 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -5,8 +5,6 @@
= webpack_bundle_tag('graphs')
= webpack_bundle_tag('graphs_show')
-= render 'projects/commits/head'
-
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
.sub-header-block
.tree-ref-holder
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
index ab5a7b117d7..1cf4105bd27 100644
--- a/app/views/projects/hook_logs/show.html.haml
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -1,5 +1,3 @@
-= render 'projects/settings/head'
-
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index c8c17d2d828..b1219f019d7 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -1,5 +1,4 @@
- page_title 'Integrations'
-= render 'projects/settings/head'
.row.prepend-top-default
.col-lg-3
@@ -19,4 +18,3 @@
%hr
= render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
-
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
deleted file mode 100644
index e9f21594a71..00000000000
--- a/app/views/projects/issues/_head.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- - if project_nav_tab?(:issues) && !current_controller?(:merge_requests)
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), title: 'Issues' do
- %span
- List
-
- = nav_link(controller: :boards) do
- = link_to project_boards_path(@project), title: 'Board' do
- %span
- Board
-
- - if project_nav_tab?(:merge_requests) && current_controller?(:merge_requests)
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
- %span
- Merge Requests
-
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: 'Labels' do
- %span
- Labels
-
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to project_milestones_path(@project), title: 'Milestones' do
- %span
- Milestones
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index e72c94695bc..bfaf024428d 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -3,8 +3,6 @@
- page_title "Issues"
- new_issue_email = @project.new_issue_address(current_user)
-= content_for :sub_nav do
- = render "projects/issues/head"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 8604c7d3ea4..4a238b99b58 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Jobs"
-= render "projects/pipelines/head"
%div{ class: container_class }
.top-area
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 975c08c06e6..ce0e3872240 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -2,7 +2,6 @@
- add_to_breadcrumbs "Jobs", project_jobs_path(@project)
- breadcrumb_title "##{@build.id}"
- page_title "#{@build.name} (##{@build.id})", "Jobs"
-= render "projects/pipelines/head"
%div{ class: container_class }
.build-page.js-build-page
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index 84b0b65d1c0..b8ee4305142 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Edit", @label.name, "Labels"
-= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 10d07ce8e45..80e4dce1a80 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -3,8 +3,6 @@
- hide_class = ''
- can_admin_label = can?(current_user, :admin_label, @project)
-= render "shared/mr_head"
-
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }
.top-area.adjust
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index 562b6fb8d8c..02f59f30a39 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- breadcrumb_title "Labels"
- page_title "New Label"
-= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml
deleted file mode 100644
index 1e505222887..00000000000
--- a/app/views/projects/merge_requests/_head.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- = nav_link(controller: :merge_requests) do
- = link_to project_merge_requests_path(@project), title: 'Merge Requests' do
- %span
- List
-
- - if project_nav_tab? :labels
- = nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: 'Labels' do
- %span
- Labels
-
- - if project_nav_tab? :milestones
- = nav_link(controller: :milestones) do
- = link_to project_milestones_path(@project), title: 'Milestones' do
- %span
- Milestones
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 2c53891a92d..6b8dcb3e60b 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -4,9 +4,6 @@
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests"
-- unless @project.issues_enabled?
- = content_for :sub_nav do
- = render "projects/merge_requests/head"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index 1e66c6079e3..af3f25c6a30 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Edit", @milestone.title, "Milestones"
-= render "shared/mr_head"
%div{ class: container_class }
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index f3abecdd302..fcbf7cb802b 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,8 +1,6 @@
- @no_container = true
- page_title 'Milestones'
-= render "shared/mr_head"
-
%div{ class: container_class }
.top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 84ffbc0a926..c301f517013 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- breadcrumb_title "Milestones"
- page_title "New Milestone"
-= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1f5f18801ad..a5153df1159 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -3,7 +3,6 @@
- breadcrumb_title @milestone.title
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
-= render "shared/mr_head"
%div{ class: container_class }
.detail-page-header.milestone-page-header
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index e29cb277389..8a19497c55b 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -2,7 +2,6 @@
- page_title "Graph", @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('network')
-= render "projects/commits/head"
= render "head"
%div{ class: container_class }
.project-network
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index de76832331a..ab87b5e0005 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -1,7 +1,8 @@
+- access = note_max_access_for_user(note)
- if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR)
%span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project. Handle with care.") }
= issuable_first_contribution_icon
-- if access = note_max_access_for_user(note)
+- if access.nonzero?
%span.note-role.note-role-access= Gitlab::Access.human_access(access)
- if note.resolvable?
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 098b0ef56ef..04e647c0dc6 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,5 +1,4 @@
- page_title 'Pages'
-= render "projects/settings/head"
%h3.page_title
Pages
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 2b081786b6a..4fbdd1dd5e4 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -7,8 +7,6 @@
- @no_container = true
- page_title _("Pipeline Schedules")
-= render "projects/pipelines/head"
-
%div{ class: container_class }
#pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } }
.top-area
diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml
deleted file mode 100644
index ee2f236cec4..00000000000
--- a/app/views/projects/pipelines/_head.html.haml
+++ /dev/null
@@ -1,34 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: (container_class) }
- - if project_nav_tab? :pipelines
- = nav_link(path: ['pipelines#index', 'pipelines#show']) do
- = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do
- %span
- Pipelines
-
- - if project_nav_tab? :builds
- = nav_link(controller: [:jobs, :artifacts]) do
- = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do
- %span
- Jobs
-
- - if project_nav_tab? :pipelines
- = nav_link(controller: :pipeline_schedules) do
- = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do
- %span
- Schedules
-
- - if project_nav_tab? :environments
- = nav_link(controller: :environments) do
- = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
- %span
- Environments
-
- - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
- = nav_link(path: 'pipelines#charts') do
- = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do
- %span
- Charts
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 487ac87186d..ba55bc23add 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -4,7 +4,6 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
-= render 'head'
%div{ class: container_class }
.sub-header-block
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4f53efcf791..a10a7c23924 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title "Pipelines"
-= render "projects/pipelines/head"
%div{ 'class' => container_class }
- if show_auto_devops_callout?(@project)
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 7cc9fe79afd..2174154b207 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -2,7 +2,6 @@
- add_to_breadcrumbs "Pipelines", project_pipelines_path(@project)
- breadcrumb_title "##{@pipeline.id}"
- page_title "Pipeline"
-= render "projects/pipelines/head"
.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
- if @commit
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml
index 21d01242c0e..77211099830 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/pipelines_settings/_show.html.haml
@@ -26,7 +26,8 @@
%strong Disable Auto DevOps
%br
%span.descr
- An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continious Integration and Delivery.
+ An explicit <code>.gitlab-ci.yml</code> needs to be specified before you can begin using Continuous Integration and Delivery.
+
.radio
= form.label :enabled_nil do
= form.radio_button :enabled, ''
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index c786298e341..4d962f9433f 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -2,7 +2,6 @@
- add_to_breadcrumbs "Tags", project_tags_path(@project)
- breadcrumb_title @tag.name
- page_title "Edit", @tag.name, "Tags"
-= render "projects/commits/head"
%div{ class: container_class }
.sub-header-block.no-bottom-space
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index 3e2a24a4c32..25770df1c90 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -2,5 +2,4 @@
- page_title @service.title, "Services"
- add_to_breadcrumbs("Settings", edit_project_path(@project))
-= render "projects/settings/head"
= render 'form'
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
deleted file mode 100644
index 7d24c6a9122..00000000000
--- a/app/views/projects/settings/_head.html.haml
+++ /dev/null
@@ -1,30 +0,0 @@
-= content_for :sub_nav do
- .scrolling-tabs-container.sub-nav-scroll
- = render 'shared/nav_scroll'
- .nav-links.sub-nav.scrolling-tabs
- %ul{ class: container_class }
- - can_edit = can?(current_user, :admin_project, @project)
- - if can_edit
- = nav_link(controller: :projects) do
- = link_to edit_project_path(@project), title: 'General' do
- %span
- General
- - if can_edit
- = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
- = link_to project_settings_integrations_path(@project), title: 'Integrations' do
- %span
- Integrations
- = nav_link(controller: :repository) do
- = link_to project_settings_repository_path(@project), title: 'Repository' do
- %span
- Repository
- - if @project.feature_available?(:builds, current_user)
- = nav_link(controller: :ci_cd) do
- = link_to project_settings_ci_cd_path(@project), title: 'Pipelines' do
- %span
- Pipelines
- - if @project.pages_available?
- = nav_link(controller: :pages) do
- = link_to project_pages_path(@project), title: 'Pages' do
- %span
- Pages
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 47c056d097a..62455d0d40d 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -2,8 +2,6 @@
- page_title "CI / CD Settings"
- page_title "CI / CD"
-= render "projects/settings/head"
-
- expanded = Rails.env.test?
%section.settings#js-general-pipeline-settings
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
index 933daa7f549..2f1a548e119 100644
--- a/app/views/projects/settings/integrations/show.html.haml
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -1,6 +1,5 @@
- @content_class = "limit-container-width" unless fluid_layout
- breadcrumb_title "Integrations Settings"
- page_title 'Integrations'
-= render "projects/settings/head"
= render 'projects/hooks/index'
= render 'projects/services/index'
diff --git a/app/views/projects/settings/members/show.html.haml b/app/views/projects/settings/members/show.html.haml
index 1e7695ac397..ea2cd36b212 100644
--- a/app/views/projects/settings/members/show.html.haml
+++ b/app/views/projects/settings/members/show.html.haml
@@ -1,6 +1,5 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title "Members"
-= render "projects/settings/head"
= render "projects/project_members/index"
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index 6d4af72b8ea..517d51993d2 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -2,8 +2,6 @@
- page_title "Repository"
- @content_class = "limit-container-width" unless fluid_layout
-= render "projects/settings/head"
-
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('deploy_keys')
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index d8f5114f4b5..705a4607ad2 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -7,7 +7,6 @@
= render partial: 'flash_messages', locals: { project: @project }
-= render "projects/head"
= render "projects/last_push"
= render "home_panel"
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index a6fe02fcae0..27d58d4c0e8 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -2,7 +2,6 @@
- @sort ||= sort_value_recently_updated
- page_title "Tags"
- add_to_breadcrumbs("Repository", project_tree_path(@project))
-= render "projects/commits/head"
.flex-list{ class: container_class }
.top-area.adjust
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 5d6eb4f4026..43aa2b27af6 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -2,7 +2,6 @@
- add_to_breadcrumbs "Tags", project_tags_path(@project)
- breadcrumb_title @tag.name
- page_title @tag.name, "Tags"
-= render "projects/commits/head"
%div{ class: container_class }
.top-area.multi-line
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index d84a1fd7ee1..f819f2addaa 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -11,8 +11,6 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'repo'
-= render "projects/commits/head"
-
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if show_auto_devops_callout?(@project)
= render 'shared/auto_devops_callout'
diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml
index 2f09c2fec87..7c633175a06 100644
--- a/app/views/shared/_auto_devops_callout.html.haml
+++ b/app/views/shared/_auto_devops_callout.html.haml
@@ -6,10 +6,10 @@
.svg-container
= custom_icon('icon_autodevops')
.user-callout-copy
- %h4= _('Auto DevOps (Beta)')
- %p= _('Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
+ %h4= s_('AutoDevOps|Auto DevOps (Beta)')
+ %p= s_('AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.')
%p
- #{s_('AutoDevOps|Learn more in the')}
- = link_to _('Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
+ - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer')
+ = s_('AutoDevOps|Learn more in the %{link_to_documentation}').html_safe % { link_to_documentation: link }
- = link_to _('Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout'
+ = link_to s_('AutoDevOps|Enable in settings'), project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings'), class: 'btn btn-primary js-close-callout'
diff --git a/app/views/shared/_mr_head.html.haml b/app/views/shared/_mr_head.html.haml
deleted file mode 100644
index e7355ae2eea..00000000000
--- a/app/views/shared/_mr_head.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- if @project.issues_enabled?
- = render "projects/issues/head"
-- else
- = render "projects/merge_requests/head"
diff --git a/app/views/shared/_nav_scroll.html.haml b/app/views/shared/_nav_scroll.html.haml
deleted file mode 100644
index 61646f150c1..00000000000
--- a/app/views/shared/_nav_scroll.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-.fade-left
- = icon('angle-left')
-.fade-right
- = icon('angle-right')
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 785a500e44e..7ff5e679f17 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -1,36 +1,16 @@
+- sorted_by = sort_options_hash[@sort]
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
.dropdown.inline.prepend-left-10
- %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } }
- - if @sort.present?
- = sort_options_hash[@sort]
- - else
- = sort_title_recently_created
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
+ = sorted_by
= icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
+ %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable.dropdown-menu-sort
%li
- = link_to page_filter_path(sort: sort_value_priority, label: true) do
- = sort_title_priority
- = link_to page_filter_path(sort: sort_value_label_priority, label: true) do
- = sort_title_label_priority
- = link_to page_filter_path(sort: sort_value_recently_created, label: true) do
- = sort_title_recently_created
- = link_to page_filter_path(sort: sort_value_oldest_created, label: true) do
- = sort_title_oldest_created
- = link_to page_filter_path(sort: sort_value_recently_updated, label: true) do
- = sort_title_recently_updated
- = link_to page_filter_path(sort: sort_value_oldest_updated, label: true) do
- = sort_title_oldest_updated
- = link_to page_filter_path(sort: sort_value_milestone_soon, label: true) do
- = sort_title_milestone_soon
- = link_to page_filter_path(sort: sort_value_milestone_later, label: true) do
- = sort_title_milestone_later
- - if viewing_issues
- = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
- = sort_title_due_date_soon
- = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
- = sort_title_due_date_later
- = link_to page_filter_path(sort: sort_value_upvotes, label: true) do
- = sort_title_upvotes
- = link_to page_filter_path(sort: sort_value_downvotes, label: true) do
- = sort_title_downvotes
+ = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sorted_by)
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
+ = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sorted_by)
+ = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone, label: true), sorted_by)
+ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date, label: true), sorted_by) if viewing_issues
+ = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity, label: true), sorted_by)
+ = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority, label: true), sorted_by)
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 722890a01e5..ee8ad8e3999 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -12,13 +12,11 @@
%script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
-= render "projects/issues/head"
+#board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
+ .hidden-xs.hidden-sm
+ = render 'shared/issuable/search_bar', type: :boards
-.hidden-xs.hidden-sm
- = render 'shared/issuable/search_bar', type: :boards
-
-#board-app.boards-app{ "v-cloak" => true, data: board_data }
- .boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
+ .boards-list
.boards-app-loading.text-center{ "v-if" => "loading" }
= icon("spinner spin")
%board{ "v-cloak" => true,
diff --git a/app/views/shared/icons/_icon_status_canceled.svg b/app/views/shared/icons/_icon_status_canceled.svg
index bd5d04e1cd7..bd5d04e1cd7 100755..100644
--- a/app/views/shared/icons/_icon_status_canceled.svg
+++ b/app/views/shared/icons/_icon_status_canceled.svg
diff --git a/app/views/shared/icons/_icon_status_created.svg b/app/views/shared/icons/_icon_status_created.svg
index 326ad04e017..326ad04e017 100755..100644
--- a/app/views/shared/icons/_icon_status_created.svg
+++ b/app/views/shared/icons/_icon_status_created.svg
diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg
index 64da5aa31fc..64da5aa31fc 100755..100644
--- a/app/views/shared/icons/_icon_status_failed.svg
+++ b/app/views/shared/icons/_icon_status_failed.svg
diff --git a/app/views/shared/icons/_icon_status_manual.svg b/app/views/shared/icons/_icon_status_manual.svg
index c98839f51a9..c98839f51a9 100755..100644
--- a/app/views/shared/icons/_icon_status_manual.svg
+++ b/app/views/shared/icons/_icon_status_manual.svg
diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg
index 02d5da407e3..02d5da407e3 100755..100644
--- a/app/views/shared/icons/_icon_status_pending.svg
+++ b/app/views/shared/icons/_icon_status_pending.svg
diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg
index 532f4fee33c..532f4fee33c 100755..100644
--- a/app/views/shared/icons/_icon_status_running.svg
+++ b/app/views/shared/icons/_icon_status_running.svg
diff --git a/app/views/shared/icons/_icon_status_skipped.svg b/app/views/shared/icons/_icon_status_skipped.svg
index a9ba29c922c..a9ba29c922c 100755..100644
--- a/app/views/shared/icons/_icon_status_skipped.svg
+++ b/app/views/shared/icons/_icon_status_skipped.svg
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
index eed5006bebe..eed5006bebe 100755..100644
--- a/app/views/shared/icons/_icon_status_success.svg
+++ b/app/views/shared/icons/_icon_status_success.svg
diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg
index cb785635b7e..cb785635b7e 100755..100644
--- a/app/views/shared/icons/_icon_status_warning.svg
+++ b/app/views/shared/icons/_icon_status_warning.svg
diff --git a/app/views/shared/issuable/_user_dropdown_item.html.haml b/app/views/shared/issuable/_user_dropdown_item.html.haml
index 48d04678d47..4a3547e9e70 100644
--- a/app/views/shared/issuable/_user_dropdown_item.html.haml
+++ b/app/views/shared/issuable/_user_dropdown_item.html.haml
@@ -4,7 +4,7 @@
%li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) }
%button.btn.btn-link.dropdown-user{ type: :button }
.avatar-container.s40
- = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40, has_tooltip: false).gsub('/images/{{avatar_url}}','{{avatar_url}}').html_safe
+ = user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40, has_tooltip: false)
.dropdown-user-details
%span
= user.name
diff --git a/changelogs/unreleased/34371-cycle-analitcs-global.yml b/changelogs/unreleased/34371-cycle-analitcs-global.yml
new file mode 100644
index 00000000000..5e9f0a85e9a
--- /dev/null
+++ b/changelogs/unreleased/34371-cycle-analitcs-global.yml
@@ -0,0 +1,5 @@
+---
+title: Removes cycle analytics service and store from global namespace
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/3523-i18n-autodevops.yml b/changelogs/unreleased/3523-i18n-autodevops.yml
new file mode 100644
index 00000000000..10cb22b42a0
--- /dev/null
+++ b/changelogs/unreleased/3523-i18n-autodevops.yml
@@ -0,0 +1,5 @@
+---
+title: Improves i18n for Auto Devops callout
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/37467-helper-method-from-users-endpoint-overrides-api-helper-method.yml b/changelogs/unreleased/37467-helper-method-from-users-endpoint-overrides-api-helper-method.yml
new file mode 100644
index 00000000000..1984ec6e81c
--- /dev/null
+++ b/changelogs/unreleased/37467-helper-method-from-users-endpoint-overrides-api-helper-method.yml
@@ -0,0 +1,5 @@
+---
+title: find_user Users helper method no longer overrides find_user API helper method.
+merge_request: 14418
+author:
+type: fixed
diff --git a/changelogs/unreleased/38319-nomethoderror-undefined-method-sha-for-nil-nilclass.yml b/changelogs/unreleased/38319-nomethoderror-undefined-method-sha-for-nil-nilclass.yml
new file mode 100644
index 00000000000..f3c39827590
--- /dev/null
+++ b/changelogs/unreleased/38319-nomethoderror-undefined-method-sha-for-nil-nilclass.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error on merged merge requests when GitLab is restored from a backup
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/38476-improve-merge-jid-cleanup-on-merge-process.yml b/changelogs/unreleased/38476-improve-merge-jid-cleanup-on-merge-process.yml
new file mode 100644
index 00000000000..43dec51029b
--- /dev/null
+++ b/changelogs/unreleased/38476-improve-merge-jid-cleanup-on-merge-process.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust MRs being stuck on "process of being merged" for more than 2 hours
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/breadcrumbs-line-height-padding.yml b/changelogs/unreleased/breadcrumbs-line-height-padding.yml
new file mode 100644
index 00000000000..3ac56c8b593
--- /dev/null
+++ b/changelogs/unreleased/breadcrumbs-line-height-padding.yml
@@ -0,0 +1,5 @@
+---
+title: breadcrumbs receives padding when double lined
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/commit-side-by-side-comment.yml b/changelogs/unreleased/commit-side-by-side-comment.yml
new file mode 100644
index 00000000000..f9bea285a77
--- /dev/null
+++ b/changelogs/unreleased/commit-side-by-side-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed commenting on side-by-side commit diff
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-bitbucket-import-truncated-shas.yml b/changelogs/unreleased/dm-bitbucket-import-truncated-shas.yml
new file mode 100644
index 00000000000..057407b78d9
--- /dev/null
+++ b/changelogs/unreleased/dm-bitbucket-import-truncated-shas.yml
@@ -0,0 +1,6 @@
+---
+title: Fix bug that caused merge requests with diff notes imported from Bitbucket
+ to raise errors
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-simple-project-avatar-url.yml b/changelogs/unreleased/dm-simple-project-avatar-url.yml
new file mode 100644
index 00000000000..e517345f5d2
--- /dev/null
+++ b/changelogs/unreleased/dm-simple-project-avatar-url.yml
@@ -0,0 +1,5 @@
+---
+title: Expose avatar_url when requesting list of projects from API with simple=true
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/docs-28814-clarify-artifacts-ref.yml b/changelogs/unreleased/docs-28814-clarify-artifacts-ref.yml
new file mode 100644
index 00000000000..3cdcff8caaf
--- /dev/null
+++ b/changelogs/unreleased/docs-28814-clarify-artifacts-ref.yml
@@ -0,0 +1,5 @@
+---
+title: Clarify artifact download via the API only accepts branch or tag name for ref
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/docs-38152-bump-recommended-mysql-version.yml b/changelogs/unreleased/docs-38152-bump-recommended-mysql-version.yml
new file mode 100644
index 00000000000..eea679d0814
--- /dev/null
+++ b/changelogs/unreleased/docs-38152-bump-recommended-mysql-version.yml
@@ -0,0 +1,5 @@
+---
+title: Change recommended MySQL version to 5.6
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/expose-last-pipeline-details-in-api-for-single-commit.yml b/changelogs/unreleased/expose-last-pipeline-details-in-api-for-single-commit.yml
new file mode 100644
index 00000000000..d16e052cd92
--- /dev/null
+++ b/changelogs/unreleased/expose-last-pipeline-details-in-api-for-single-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Expose last pipeline details in API response when getting a single commit
+merge_request: 13521
+author: Mehdi Lahmam (@mehlah)
+type: added
diff --git a/changelogs/unreleased/improve_sorting_list.yml b/changelogs/unreleased/improve_sorting_list.yml
new file mode 100644
index 00000000000..a3730e23ed1
--- /dev/null
+++ b/changelogs/unreleased/improve_sorting_list.yml
@@ -0,0 +1,5 @@
+---
+title: Improve list of sorting options
+merge_request: 14320
+author: Vitaliy @blackst0ne Klachkov
+type: added
diff --git a/changelogs/unreleased/mr-side-by-side-breadcrumbs-container.yml b/changelogs/unreleased/mr-side-by-side-breadcrumbs-container.yml
new file mode 100644
index 00000000000..39b636bdfda
--- /dev/null
+++ b/changelogs/unreleased/mr-side-by-side-breadcrumbs-container.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed breadcrumbs container expanding in side-by-side diff view
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/project-page-clearer.yml b/changelogs/unreleased/project-page-clearer.yml
new file mode 100644
index 00000000000..7db01373360
--- /dev/null
+++ b/changelogs/unreleased/project-page-clearer.yml
@@ -0,0 +1,5 @@
+---
+title: Added tabs to dashboard/projects to easily switch to personal projects
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/remove-temporary-ci-index.yml b/changelogs/unreleased/remove-temporary-ci-index.yml
new file mode 100644
index 00000000000..a319f7fff7f
--- /dev/null
+++ b/changelogs/unreleased/remove-temporary-ci-index.yml
@@ -0,0 +1,5 @@
+---
+title: Remove an index on ci_builds meant to be only temporary
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/rotated_profile_image.yml b/changelogs/unreleased/rotated_profile_image.yml
new file mode 100644
index 00000000000..1e221e47379
--- /dev/null
+++ b/changelogs/unreleased/rotated_profile_image.yml
@@ -0,0 +1,5 @@
+---
+title: Fix profile image orientation based on EXIF data gvieira37
+merge_request: 14461
+author: gvieira37
+type: fixed
diff --git a/changelogs/unreleased/zj-repo-gitaly.yml b/changelogs/unreleased/zj-repo-gitaly.yml
new file mode 100644
index 00000000000..634f6ba1b8b
--- /dev/null
+++ b/changelogs/unreleased/zj-repo-gitaly.yml
@@ -0,0 +1,5 @@
+---
+title: Gitaly RepositoryExists remains opt-in for all method calls
+merge_request:
+author:
+type: fixed
diff --git a/config/application.rb b/config/application.rb
index da9bb25c8b9..30117b6a98e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -105,8 +105,6 @@ module Gitlab
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css"
- config.assets.precompile << "new_nav.css"
- config.assets.precompile << "new_sidebar.css"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 94429ee91a9..27c1ecc7b23 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -499,9 +499,7 @@ Settings.backup['upload']['storage_class'] ||= nil
# Git
#
Settings['git'] ||= Settingslogic.new({})
-Settings.git['max_size'] ||= 20971520 # 20.megabytes
-Settings.git['bin_path'] ||= '/usr/bin/git'
-Settings.git['timeout'] ||= 10
+Settings.git['bin_path'] ||= '/usr/bin/git'
# Important: keep the satellites.path setting until GitLab 9.0 at
# least. This setting is fed to 'rm -rf' in
diff --git a/db/migrate/20170828135939_migrate_user_external_mail_data.rb b/db/migrate/20170828135939_migrate_user_external_mail_data.rb
index 395181a3b22..f7ac87374b6 100644
--- a/db/migrate/20170828135939_migrate_user_external_mail_data.rb
+++ b/db/migrate/20170828135939_migrate_user_external_mail_data.rb
@@ -33,7 +33,7 @@ class MigrateUserExternalMailData < ActiveRecord::Migration
SELECT true
FROM user_synced_attributes_metadata
WHERE user_id = users.id
- AND provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL)
+ AND (provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL))
)
AND id BETWEEN #{start_id} AND #{end_id}
EOF
diff --git a/db/migrate/20170919211300_remove_temporary_ci_builds_index.rb b/db/migrate/20170919211300_remove_temporary_ci_builds_index.rb
new file mode 100644
index 00000000000..b2009b282e9
--- /dev/null
+++ b/db/migrate/20170919211300_remove_temporary_ci_builds_index.rb
@@ -0,0 +1,27 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveTemporaryCiBuildsIndex < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # To use create/remove index concurrently
+ disable_ddl_transaction!
+
+ def up
+ return unless index_exists?(:ci_builds, :id, name: 'index_for_ci_builds_retried_migration')
+ remove_concurrent_index(:ci_builds, :id, name: "index_for_ci_builds_retried_migration")
+ end
+
+ def down
+ # this was a temporary index for a migration that was never
+ # present previously so this probably shouldn't be here but it's
+ # easier to test the drop if we have a way to create it.
+ add_concurrent_index("ci_builds", ["id"],
+ name: "index_for_ci_builds_retried_migration",
+ where: "(retried IS NULL)",
+ using: :btree)
+ end
+end
diff --git a/db/post_migrate/20170828170502_post_deploy_migrate_user_external_mail_data.rb b/db/post_migrate/20170828170502_post_deploy_migrate_user_external_mail_data.rb
index a475b242921..fd1437b07f5 100644
--- a/db/post_migrate/20170828170502_post_deploy_migrate_user_external_mail_data.rb
+++ b/db/post_migrate/20170828170502_post_deploy_migrate_user_external_mail_data.rb
@@ -33,7 +33,7 @@ class PostDeployMigrateUserExternalMailData < ActiveRecord::Migration
SELECT true
FROM user_synced_attributes_metadata
WHERE user_id = users.id
- AND provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL)
+ AND (provider = users.email_provider OR (provider IS NULL AND users.email_provider IS NULL))
)
AND id BETWEEN #{start_id} AND #{end_id}
EOF
diff --git a/db/schema.rb b/db/schema.rb
index 80ef91ec95d..330336e8e61 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -256,7 +256,6 @@ ActiveRecord::Schema.define(version: 20170921115009) do
add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
- add_index "ci_builds", ["id"], name: "index_for_ci_builds_retried_migration", where: "(retried IS NULL)", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 2a78553782f..5a4a8d888b3 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -181,6 +181,12 @@ Example response:
"parent_ids": [
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
],
+ "last_pipeline" : {
+ "id": 8,
+ "ref": "master",
+ "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0"
+ "status": "created"
+ }
"stats": {
"additions": 15,
"deletions": 10,
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index d60c7c12881..e7060e154f4 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -336,7 +336,7 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|-------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `ref_name` | string | yes | The ref from a repository |
+| `ref_name` | string | yes | The ref from a repository (can only be branch or tag name, not HEAD or SHA) |
| `job` | string | yes | The name of the job |
Example request:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index aad81843299..38bd0450a09 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1570,6 +1570,11 @@ Read more on [GitLab Pages user documentation](../../user/project/pages/index.md
Each instance of GitLab CI has an embedded debug tool called Lint.
You can find the link under `/ci/lint` of your gitlab instance.
+## Using reserved keywords
+
+If you get validation error when using specific values (e.g., `true` or `false`),
+try to quote them, or change them to a different form (e.g., `/bin/true`).
+
## Skipping jobs
If your commit message contains `[ci skip]` or `[skip ci]`, using any
diff --git a/doc/development/README.md b/doc/development/README.md
index 3096d9f25f0..1448a4c0414 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -44,6 +44,7 @@
- [Building a package for testing purposes](build_test_package.md)
- [Manage feature flags](feature_flags.md)
- [View sent emails or preview mailers](emails.md)
+- [Working with Gitaly](gitaly.md)
## Databases
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index f83a60e49e8..5452b0e7a2f 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -215,14 +215,29 @@ same time will ensure that both existing and new data is migrated.
In the next release we can remove the `after_commit` hooks and related code. We
will also need to add a post-deployment migration that consumes any remaining
-jobs. Such a migration would look like this:
+jobs and manually run on any un-migrated rows. Such a migration would look like
+this:
```ruby
class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration
disable_ddl_transaction!
+ class Service < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'services'
+ end
+
def up
+ # This must be included
Gitlab::BackgroundMigration.steal('ExtractServicesUrl')
+
+ # This should be included, but can be skipped - see below
+ Service.where(url: nil).each_batch(of: 50) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ Gitlab::BackgroundMigration::ExtractServicesUrl.new.perform(*range)
+ end
end
def down
@@ -230,6 +245,15 @@ class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration
end
```
+The final step runs for any un-migrated rows after all of the jobs have been
+processed. This is in case a Sidekiq process running the background migrations
+received SIGKILL, leading to the jobs being lost. (See
+[more reliable Sidekiq queue][reliable-sidekiq] for more information.)
+
+If the application does not depend on the data being 100% migrated (for
+instance, the data is advisory, and not mission-critical), then this final step
+can be skipped.
+
This migration will then process any jobs for the ExtractServicesUrl migration
and continue once all jobs have been processed. Once done you can safely remove
the `services.properties` column.
@@ -254,6 +278,9 @@ for more details.
1. Make sure that background migration jobs are idempotent.
1. Make sure that tests you write are not false positives.
+1. Make sure that if the data being migrated is critical and cannot be lost, the
+ clean-up migration also checks the final state of the data before completing.
[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
+[reliable-sidekiq]: https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index d84801f91d4..031b12a8e91 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -29,34 +29,6 @@ For our currently-supported browsers, see our [requirements][requirements].
## Development Process
-When you are assigned an issue please follow the next steps:
-
-### Divide a big feature into small Merge Requests
-1. Big Merge Request are painful to review. In order to make this process easier we
-must break a big feature into smaller ones and create a Merge Request for each step.
-1. First step is to create a branch from `master`, let's call it `new-feature`. This branch
-will be the recipient of all the smaller Merge Requests. Only this one will be merged to master.
-1. Don't do any work on this one, let's keep it synced with master.
-1. Create a new branch from `new-feature`, let's call it `new-feature-step-1`. We advise you
-to clearly identify which step the branch represents.
-1. Do the first part of the modifications in this branch. The target branch of this Merge Request
-should be `new-feature`.
-1. Once `new-feature-step-1` gets merged into `new-feature` we can continue our work. Create a new
-branch from `new-feature`, let's call it `new-feature-step-2` and repeat the process done before.
-
-```shell
-master
-└─ new-feature
- ├─ new-feature-step-1
- ├─ new-feature-step-2
- └─ new-feature-step-3
-```
-
-**Tips**
-- Make sure `new-feature` branch is always synced with `master`: merge master frequently.
-- Do the same for the feature branch you have opened. This can be accomplished by merging `master` into `new-feature` and `new-feature` into `new-feature-step-*`
-- Avoid rewriting history.
-
### Share your work early
1. Before writing code guarantee your vision of the architecture is aligned with
GitLab's architecture.
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
new file mode 100644
index 00000000000..f0be3a6b141
--- /dev/null
+++ b/doc/development/gitaly.md
@@ -0,0 +1,54 @@
+# GitLab Developers Guide to Working with Gitaly
+
+[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
+Workhorse and GitLab-Shell. All Rugged operations in GitLab CE/EE are currently being phased out to
+be replaced by Gitaly API calls.
+
+Visit the [Gitaly Migration Board](https://gitlab.com/gitlab-org/gitaly/boards/331341) for current
+status of the migration.
+
+## Feature Flags
+
+Gitaly makes heavy use of [feature flags](feature_flags.md).
+
+Each Rugged-to-Gitaly migration goes through a [series of phases](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/MIGRATION_PROCESS.md):
+* **Opt-In**: by default the Rugged implementation is used.
+ * Production instances can choose to enable the Gitaly endpoint by enabling the feature flag.
+ * For testing purposes, you may wish to enable all feature flags by default. This can be done by exporting the following
+ environment variable: `GITALY_FEATURE_DEFAULT_ON=1`.
+ * On developer instances (ie, when `Rails.env.development?` is true), the Gitaly endpoint
+ is enabled by default, but can be _disabled_ using feature flags.
+* **Opt-Out**: by default, the Gitaly endpoint is used, but the feature can be explicitly disabled using the feature flag.
+* **Madatory**: The migration is complete and cannot be disabled. The old codepath is removed.
+
+### Enabling and Disabling Feature
+
+In the Rails console, type:
+
+```ruby
+Feature.enable(:gitaly_feature_name)
+Feature.disable(:gitaly_feature_name)
+```
+
+Where `gitaly_feature_name` is the name of the Gitaly feature. This can be determined by finding the appropriate
+`gitaly_migrate` code block, for example:
+
+```ruby
+gitaly_migrate(:tag_names) do
+...
+end
+```
+
+Since Gitaly features are always prefixed with `gitaly_`, the name of the feature flag in this case would be `gitaly_tag_names`.
+
+## Gitaly-Related Test Failures
+
+If your test-suite is failing with Gitaly issues, as a first step, try running:
+
+```shell
+rm -rf tmp/tests/gitaly
+```
+
+---
+
+[Return to Development documentation](README.md)
diff --git a/doc/development/licensing.md b/doc/development/licensing.md
index 9a5811d8474..a75cdf22f40 100644
--- a/doc/development/licensing.md
+++ b/doc/development/licensing.md
@@ -65,6 +65,7 @@ Libraries with the following licenses are unacceptable for use:
- [GNU AGPLv3][AGPLv3]: AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
- [Facebook BSD + PATENTS][Facebook]: is a 3-clause BSD license with a patent grant that has been deemed [Category X][x-list] by the Apache foundation.
+- [WTFPL][WTFPL]: is a public domain dedication [rejected by the OSI (3.2)][WTFPL-OSI]. Also has a strong language which is not in accordance with our diversity policy.
## Requesting Approval for Licenses
@@ -108,3 +109,5 @@ Gems which are included only in the "development" or "test" groups by Bundler ar
[x-list]: https://www.apache.org/legal/resolved.html#category-x
[Acceptable-Licenses]: #acceptable-licenses
[Unacceptable-Licenses]: #unacceptable-licenses
+[WTFPL]: https://wtfpl.net
+[WTFPL-OSI]: https://opensource.org/minutes20090304
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 83269303005..c9f14b5fb35 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -493,24 +493,24 @@ Here are some things to keep in mind regarding test performance:
Our current CI parallelization setup is as follows:
-1. The `knapsack` job in the prepare stage that is supposed to ensure we have a
- `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file:
+1. The `retrieve-tests-metadata` job in the `prepare` stage ensures that we have
+ a `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file:
- The `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file is fetched
from S3, if it's not here we initialize the file with `{}`.
-1. Each `rspec x y` job are run with `knapsack rspec` and should have an evenly
- distributed share of tests:
+1. Each `rspec-pg x y`/`rspec-mysql x y` job is run with `knapsack rspec` and
+ should have an evenly distributed share of tests:
- It works because the jobs have access to the
`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` since the "artifacts
from all previous stages are passed by default". [^1]
- - the jobs set their own report path to
+ - The jobs set their own report path to
`KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`.
- - if knapsack is doing its job, test files that are run should be listed under
+ - If knapsack is doing its job, test files that are run should be listed under
`Report specs`, not under `Leftover specs`.
-1. The `update-knapsack` job takes all the
+1. The `update-tests-metadata` job takes all the
`knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json`
- files from the `rspec x y` jobs and merge them all together into a single
- `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file that is then
- uploaded to S3.
+ files from the `rspec-pg x y`/`rspec-mysql x y`jobs and merge them all together
+ into a single `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file that
+ is then uploaded to S3.
After that, the next pipeline will use the up-to-date
`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file. The same strategy
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index 5c128f54a76..f9ba1508705 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -1,11 +1,12 @@
# Database MySQL
>**Note:**
-We do not recommend using MySQL due to various issues. For example, case
+- We do not recommend using MySQL due to various issues. For example, case
[(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html)
and [problems](https://bugs.mysql.com/bug.php?id=65830) that
[suggested](https://bugs.mysql.com/bug.php?id=50909)
[fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164).
+- We recommend using MySQL version 5.6 or later. Please see the following [issue][ce-38152].
## Initial database setup
@@ -13,7 +14,7 @@ and [problems](https://bugs.mysql.com/bug.php?id=65830) that
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
-# Ensure you have MySQL version 5.5.14 or later
+# Ensure you have MySQL version 5.6 or later
mysql --version
# Pick a MySQL root password (can be anything), type it and press enter
@@ -293,3 +294,4 @@ Details can be found in the [PostgreSQL][postgres-text-type] and
[postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html
[mysql-text-types]: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html
+[ce-38152]: https://gitlab.com/gitlab-org/gitlab-ce/issues/38152
diff --git a/doc/install/kubernetes/gitlab_omnibus.md b/doc/install/kubernetes/gitlab_omnibus.md
index 19e2a257c94..8c110a37380 100644
--- a/doc/install/kubernetes/gitlab_omnibus.md
+++ b/doc/install/kubernetes/gitlab_omnibus.md
@@ -148,13 +148,36 @@ helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,g
## Updating GitLab using the Helm Chart
+>**Note**: If you are upgrading from a previous version to 0.1.35 or above, you will need to change the access mode values for GitLab's storage. To do this, set the following in `values.yaml` or on the CLI:
+```
+gitlabDataAccessMode=ReadWriteMany
+gitlabRegistryAccessMode=ReadWriteMany
+gitlabConfigAccessMode=ReadWriteMany
+```
+
Once your GitLab Chart is installed, configuration changes and chart updates
-should we done using `helm upgrade`:
+should be done using `helm upgrade`:
+
+```bash
+helm upgrade -f values.yaml gitlab gitlab/gitlab-omnibus
+```
+
+## Upgrading from CE to EE using the Helm Chart
+
+If you have installed the Community Edition using this chart, upgrading to Enterprise Edition is easy.
+
+If you are using a `values.yaml` file to specify the configuration options, edit the file and set `gitlab=ee`. If you would like to run a specific version of GitLab EE, set `gitlabEEImage` to be the desired GitLab [docker image](https://hub.docker.com/r/gitlab/gitlab-ee/tags/). Then you can use `helm upgrade` to update your GitLab instance to EE:
```bash
helm upgrade -f values.yaml gitlab gitlab/gitlab-omnibus
```
+You can also upgrade and specify these options via the command line:
+
+```bash
+helm upgrade gitlab --set gitlab=ee,gitlabEEImage=gitlab/gitlab-ee:9.5.5-ee.0 gitlab/gitlab-omnibus
+```
+
## Uninstalling GitLab using the Helm Chart
To uninstall the GitLab Chart, run the following:
@@ -163,5 +186,13 @@ To uninstall the GitLab Chart, run the following:
helm delete gitlab
```
+## Troubleshooting
+
+### Storage errors when updating `gitlab-omnibus` versions prior to 0.1.35
+
+Users upgrading `gitlab-omnibus` from a version prior to 0.1.35, may see an error like: `Error: UPGRADE FAILED: PersistentVolumeClaim "gitlab-gitlab-config-storage" is invalid: spec: Forbidden: field is immutable after creation`.
+
+This is due to a change in the access mode for GitLab storage in version 0.1.35. To successfully upgrade, the access mode flags must be set to `ReadWriteMany` as detailed in the [update section](#updating-gitlab-using-the-helm-chart).
+
[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index f672b358096..17fe80fa93d 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -82,11 +82,11 @@ errors during usage.
We recommend having at least 2GB of swap on your server, even if you currently have
enough available RAM. Having swap will help reduce the chance of errors occurring
-if your available memory changes. We also recommend [configuring the kernels swappiness setting](https://askubuntu.com/a/103916)
+if your available memory changes. We also recommend [configuring the kernel's swappiness setting](https://askubuntu.com/a/103916)
to a low value like `10` to make the most of your RAM while still having the swap
available when needed.
-Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
+Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those.
## Database
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
index 5e3e9f5ab77..f3c9c498634 100644
--- a/doc/integration/azure.md
+++ b/doc/integration/azure.md
@@ -74,6 +74,9 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
tenant_id: "TENANT ID" } }
```
+ The `base_azure_url` is optional and can be added for different locales;
+ e.g. `base_azure_url: "https://login.microsoftonline.de"`.
+
1. Replace 'CLIENT ID', 'CLIENT SECRET' and 'TENANT ID' with the values you got above.
1. Save the configuration file.
diff --git a/doc/university/training/gitlab_flow.md b/doc/university/training/gitlab_flow.md
index a7db1f2e069..a7db1f2e069 100755..100644
--- a/doc/university/training/gitlab_flow.md
+++ b/doc/university/training/gitlab_flow.md
diff --git a/doc/university/training/index.md b/doc/university/training/index.md
index 03179ff5a77..03179ff5a77 100755..100644
--- a/doc/university/training/index.md
+++ b/doc/university/training/index.md
diff --git a/doc/university/training/topics/additional_resources.md b/doc/university/training/topics/additional_resources.md
index 3ed601625cf..3ed601625cf 100755..100644
--- a/doc/university/training/topics/additional_resources.md
+++ b/doc/university/training/topics/additional_resources.md
diff --git a/doc/university/training/topics/agile_git.md b/doc/university/training/topics/agile_git.md
index e6e4fea9b51..e6e4fea9b51 100755..100644
--- a/doc/university/training/topics/agile_git.md
+++ b/doc/university/training/topics/agile_git.md
diff --git a/doc/university/training/topics/bisect.md b/doc/university/training/topics/bisect.md
index a60c4365e0c..a60c4365e0c 100755..100644
--- a/doc/university/training/topics/bisect.md
+++ b/doc/university/training/topics/bisect.md
diff --git a/doc/university/training/topics/cherry_picking.md b/doc/university/training/topics/cherry_picking.md
index af7a70a2818..af7a70a2818 100755..100644
--- a/doc/university/training/topics/cherry_picking.md
+++ b/doc/university/training/topics/cherry_picking.md
diff --git a/doc/university/training/topics/env_setup.md b/doc/university/training/topics/env_setup.md
index 8149379b36f..8149379b36f 100755..100644
--- a/doc/university/training/topics/env_setup.md
+++ b/doc/university/training/topics/env_setup.md
diff --git a/doc/university/training/topics/explore_gitlab.md b/doc/university/training/topics/explore_gitlab.md
index b65457728c0..b65457728c0 100755..100644
--- a/doc/university/training/topics/explore_gitlab.md
+++ b/doc/university/training/topics/explore_gitlab.md
diff --git a/doc/university/training/topics/feature_branching.md b/doc/university/training/topics/feature_branching.md
index 4b34406ea75..4b34406ea75 100755..100644
--- a/doc/university/training/topics/feature_branching.md
+++ b/doc/university/training/topics/feature_branching.md
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index ec7bb2631aa..ec7bb2631aa 100755..100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index 9ffb4b9c859..9ffb4b9c859 100755..100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
diff --git a/doc/university/training/topics/git_intro.md b/doc/university/training/topics/git_intro.md
index ca1ff29d93b..ca1ff29d93b 100755..100644
--- a/doc/university/training/topics/git_intro.md
+++ b/doc/university/training/topics/git_intro.md
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 32ebceff491..32ebceff491 100755..100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
diff --git a/doc/university/training/topics/gitlab_flow.md b/doc/university/training/topics/gitlab_flow.md
index 8e5d3baf959..8e5d3baf959 100755..100644
--- a/doc/university/training/topics/gitlab_flow.md
+++ b/doc/university/training/topics/gitlab_flow.md
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index 77807b3e7ef..77807b3e7ef 100755..100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
diff --git a/doc/university/training/topics/merge_requests.md b/doc/university/training/topics/merge_requests.md
index 5b446f02f63..5b446f02f63 100755..100644
--- a/doc/university/training/topics/merge_requests.md
+++ b/doc/university/training/topics/merge_requests.md
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index cf647284604..cf647284604 100755..100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index c1bdda32645..c1bdda32645 100755..100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
diff --git a/doc/university/training/topics/subtree.md b/doc/university/training/topics/subtree.md
index 5d869af64c1..5d869af64c1 100755..100644
--- a/doc/university/training/topics/subtree.md
+++ b/doc/university/training/topics/subtree.md
diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md
index e9607b5a875..e9607b5a875 100755..100644
--- a/doc/university/training/topics/tags.md
+++ b/doc/university/training/topics/tags.md
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index 17dbb64b9e6..17dbb64b9e6 100755..100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index 9e38df26b6a..9e38df26b6a 100755..100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
diff --git a/doc/user/project/integrations/img/kubernetes_configuration.png b/doc/user/project/integrations/img/kubernetes_configuration.png
index 349a2dc8456..e535e2b8d46 100644
--- a/doc/user/project/integrations/img/kubernetes_configuration.png
+++ b/doc/user/project/integrations/img/kubernetes_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/webhook_logs.png b/doc/user/project/integrations/img/webhook_logs.png
index 917068d9398..917068d9398 100755..100644
--- a/doc/user/project/integrations/img/webhook_logs.png
+++ b/doc/user/project/integrations/img/webhook_logs.png
Binary files differ
diff --git a/doc/user/project/integrations/kubernetes.md b/doc/user/project/integrations/kubernetes.md
index f4000523938..e9738b683f9 100644
--- a/doc/user/project/integrations/kubernetes.md
+++ b/doc/user/project/integrations/kubernetes.md
@@ -1,3 +1,7 @@
+---
+last_updated: 2017-09-25
+---
+
# GitLab Kubernetes / OpenShift integration
GitLab can be configured to interact with Kubernetes, or other systems using the
@@ -6,62 +10,114 @@ Kubernetes API (such as OpenShift).
Each project can be configured to connect to a different Kubernetes cluster, see
the [configuration](#configuration) section.
-If you have a single cluster that you want to use for all your projects,
-you can pre-fill the settings page with a default template. To configure the
-template, see the [Services Templates](services_templates.md) document.
-
## Configuration
Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
-of your project and select the **Kubernetes** service to configure it.
+of your project and select the **Kubernetes** service to configure it. Fill in
+all the needed parameters, check the "Active" checkbox and hit **Save changes**
+for the changes to take effect.
![Kubernetes configuration settings](img/kubernetes_configuration.png)
-The Kubernetes service takes the following arguments:
-
-1. API URL
-1. Custom CA bundle
-1. Kubernetes namespace
-1. Service token
-
-The API URL is the URL that GitLab uses to access the Kubernetes API. Kubernetes
-exposes several APIs - we want the "base" URL that is common to all of them,
-e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
-
-GitLab authenticates against Kubernetes using service tokens, which are
-scoped to a particular `namespace`. If you don't have a service token yet,
-you can follow the
-[Kubernetes documentation](http://kubernetes.io/docs/user-guide/service-accounts/)
-to create one. You can also view or create service tokens in the
-[Kubernetes dashboard](http://kubernetes.io/docs/user-guide/ui/) - visit
-`Config -> Secrets`.
-
-Fill in the service token and namespace according to the values you just got.
-If the API is using a self-signed TLS certificate, you'll also need to include
-the `ca.crt` contents as the `Custom CA bundle`.
+The Kubernetes service takes the following parameters:
+
+- **API URL** -
+ It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
+ exposes several APIs, we want the "base" URL that is common to all of them,
+ e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
+- **CA certificate** (optional) -
+ If the API is using a self-signed TLS certificate, you'll also need to include
+ the `ca.crt` contents here.
+- **Project namespace** (optional) - The following apply:
+ - By default you don't have to fill it in; by leaving it blank, GitLab will
+ create one for you.
+ - Each project should have a unique namespace.
+ - The project namespace is not necessarily the namespace of the secret, if
+ you're using a secret with broader permissions, like the secret from `default`.
+ - You should **not** use `default` as the project namespace.
+ - If you or someone created a secret specifically for the project, usually
+ with limited permissions, the secret's namespace and project namespace may
+ be the same.
+- **Token** -
+ GitLab authenticates against Kubernetes using service tokens, which are
+ scoped to a particular `namespace`. If you don't have a service token yet,
+ you can follow the
+ [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
+ to create one. You can also view or create service tokens in the
+ [Kubernetes dashboard](https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#config)
+ (under **Config > Secrets**).
+
+TIP: **Tip:**
+If you have a single cluster that you want to use for all your projects,
+you can pre-fill the settings page with a default template. To configure the
+template, see [Services Templates](services_templates.md).
## Deployment variables
-The Kubernetes service exposes following
+The Kubernetes service exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-variables) in the
-GitLab CI build environment:
+GitLab CI/CD build environment:
-- `KUBE_URL` - equal to the API URL
-- `KUBE_TOKEN`
+- `KUBE_URL` - Equal to the API URL.
+- `KUBE_TOKEN` - The Kubernetes token.
- `KUBE_NAMESPACE` - The Kubernetes namespace is auto-generated if not specified.
The default value is `<project_name>-<project_id>`. You can overwrite it to
use different one if needed, otherwise the `KUBE_NAMESPACE` variable will
receive the default value.
-- `KUBE_CA_PEM_FILE` - only present if a custom CA bundle was specified. Path
+- `KUBE_CA_PEM_FILE` - Only present if a custom CA bundle was specified. Path
to a file containing PEM data.
-- `KUBE_CA_PEM` (deprecated)- only if a custom CA bundle was specified. Raw PEM data.
-- `KUBECONFIG` - Path to a file containing kubeconfig for this deployment. CA bundle would be embedded if specified.
+- `KUBE_CA_PEM` (deprecated) - Only if a custom CA bundle was specified. Raw PEM data.
+- `KUBECONFIG` - Path to a file containing `kubeconfig` for this deployment.
+ CA bundle would be embedded if specified.
+
+## What you can get with the Kubernetes integration
+
+Here's what you can do with GitLab if you enable the Kubernetes integration.
+
+### Deploy Boards (EEP)
+
+> Available in [GitLab Enterprise Edition Premium][ee].
-## Web terminals
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
+displaying the status of the pods in the deployment. Developers and other
+teammates can view the progress and status of a rollout, pod by pod, in the
+workflow they already use without any need to access Kubernetes.
->**NOTE:**
-Added in GitLab 8.15. You must be the project owner or have `master` permissions
-to use terminals. Support is currently limited to the first container in the
+[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+
+### Canary Deployments (EEP)
+
+> Available in [GitLab Enterprise Edition Premium][ee].
+
+Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+and visualize your canary deployments right inside the Deploy Board, without
+the need to leave GitLab.
+
+[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+
+### Kubernetes monitoring
+
+Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
+[NGINX ingress](./prometheus_library/nginx.md) is also supported.
+
+[> Read more about Kubernetes monitoring](prometheus_library/kubernetes.md)
+
+### Auto DevOps
+
+Auto DevOps automatically detects, builds, tests, deploys, and monitors your
+applications.
+
+To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
+you will need the Kubernetes project integration enabled.
+
+[> Read more about Auto DevOps](../../../topics/autodevops/index.md)
+
+### Web terminals
+
+NOTE: **Note:**
+Introduced in GitLab 8.15. You must be the project owner or have `master` permissions
+to use terminals. Support is limited to the first container in the
first pod of your environment.
When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
@@ -70,3 +126,5 @@ Docker and Kubernetes, so you get a new shell session within your existing
containers. To use this integration, you should deploy to Kubernetes using
the deployment variables above, ensuring any pods you create are labelled with
`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
+
+[ee]: https://about.gitlab.com/gitlab-ee/
diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md
index cc5cee36d28..34a0b97a171 100644
--- a/doc/user/project/integrations/prometheus_library/cloudwatch.md
+++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md
@@ -1,8 +1,13 @@
# Monitoring AWS Resources
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring AWS resources, starting with the [Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). This is provided by leveraging the official [Cloudwatch exporter](https://github.com/prometheus/cloudwatch_exporter), which translates [Cloudwatch metrics](https://aws.amazon.com/cloudwatch/) into a Prometheus readable form.
+## Requirements
+
+The [Prometheus service](../prometheus/index.md) must be enabled.
+
## Metrics supported
| Name | Query |
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
index d4b5911a91c..518018e5839 100644
--- a/doc/user/project/integrations/prometheus_library/haproxy.md
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -3,6 +3,10 @@
GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
+## Requirements
+
+The [Prometheus service](../prometheus/index.md) must be enabled.
+
## Metrics supported
| Name | Query |
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 4d39ae0c4fa..518683965e8 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -1,7 +1,13 @@
# Monitoring Kubernetes
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935) in GitLab 9.0
-GitLab has support for automatically detecting and monitoring Kubernetes metrics. Kubernetes exposes Node level metrics out of the box via the built-in [Prometheus metrics support in cAdvisor](https://github.com/google/cadvisor). No additional services or exporters are needed.
+GitLab has support for automatically detecting and monitoring Kubernetes metrics.
+
+## Requirements
+
+The [Prometheus](../prometheus.md) and [Kubernetes](../kubernetes.md)
+integration services must be enabled.
## Metrics supported
@@ -23,4 +29,4 @@ Prometheus server up and running. You have two options here:
In order to isolate and only display relevant metrics for a given environment
however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
-If you are using [GitLab Auto-Deploy][../../../ci/autodeploy/index.md] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
+If you are using [GitLab Auto-Deploy](../../../../ci/autodeploy/index.md) and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md
index bab22f9a384..7fb8369d3c1 100644
--- a/doc/user/project/integrations/prometheus_library/nginx.md
+++ b/doc/user/project/integrations/prometheus_library/nginx.md
@@ -1,8 +1,13 @@
# Monitoring NGINX
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring NGINX. This is provided by leveraging the [NGINX VTS exporter](https://github.com/hnlq715/nginx-vts-exporter), which translates [VTS statistics](https://github.com/vozlt/nginx-module-vts) into a Prometheus readable form.
+## Requirements
+
+The [Prometheus service](../prometheus/index.md) must be enabled.
+
## Metrics supported
| Name | Query |
diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
index 17a47cfa646..e6f13d0630b 100644
--- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md
+++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md
@@ -1,8 +1,13 @@
# Monitoring NGINX Ingress Controller
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438) in GitLab 9.5
GitLab has support for automatically detecting and monitoring the Kubernetes NGINX ingress controller. This is provided by leveraging the built in Prometheus metrics included in [version 0.9.0](https://github.com/kubernetes/ingress/blob/master/controllers/nginx/Changelog.md#09-beta1) of the ingress.
+## Requirements
+
+The [Prometheus service](../prometheus/index.md) must be enabled.
+
## Metrics supported
| Name | Query |
diff --git a/doc/user/project/issues/img/button_close_issue.png b/doc/user/project/issues/img/button_close_issue.png
index 8fb2e23f58a..8fb2e23f58a 100755..100644
--- a/doc/user/project/issues/img/button_close_issue.png
+++ b/doc/user/project/issues/img/button_close_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/closing_and_related_issues.png b/doc/user/project/issues/img/closing_and_related_issues.png
index c6543e85fdb..c6543e85fdb 100755..100644
--- a/doc/user/project/issues/img/closing_and_related_issues.png
+++ b/doc/user/project/issues/img/closing_and_related_issues.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_issues_create.png b/doc/user/project/issues/img/confidential_issues_create.png
index 0a141eb39f8..0a141eb39f8 100755..100644
--- a/doc/user/project/issues/img/confidential_issues_create.png
+++ b/doc/user/project/issues/img/confidential_issues_create.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_issues_search_guest.png b/doc/user/project/issues/img/confidential_issues_search_guest.png
index dc1b4ba8ad7..dc1b4ba8ad7 100755..100644
--- a/doc/user/project/issues/img/confidential_issues_search_guest.png
+++ b/doc/user/project/issues/img/confidential_issues_search_guest.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_issues_search_master.png b/doc/user/project/issues/img/confidential_issues_search_master.png
index fc01f4da9db..fc01f4da9db 100755..100644
--- a/doc/user/project/issues/img/confidential_issues_search_master.png
+++ b/doc/user/project/issues/img/confidential_issues_search_master.png
Binary files differ
diff --git a/doc/user/project/issues/img/due_dates_create.png b/doc/user/project/issues/img/due_dates_create.png
index ece35d44213..ece35d44213 100755..100644
--- a/doc/user/project/issues/img/due_dates_create.png
+++ b/doc/user/project/issues/img/due_dates_create.png
Binary files differ
diff --git a/doc/user/project/issues/img/due_dates_edit_sidebar.png b/doc/user/project/issues/img/due_dates_edit_sidebar.png
index d1c7d1eb7e9..d1c7d1eb7e9 100755..100644
--- a/doc/user/project/issues/img/due_dates_edit_sidebar.png
+++ b/doc/user/project/issues/img/due_dates_edit_sidebar.png
Binary files differ
diff --git a/doc/user/project/issues/img/due_dates_issues_index_page.png b/doc/user/project/issues/img/due_dates_issues_index_page.png
index 94679436b32..94679436b32 100755..100644
--- a/doc/user/project/issues/img/due_dates_issues_index_page.png
+++ b/doc/user/project/issues/img/due_dates_issues_index_page.png
Binary files differ
diff --git a/doc/user/project/issues/img/due_dates_todos.png b/doc/user/project/issues/img/due_dates_todos.png
index 4c124c97f67..4c124c97f67 100755..100644
--- a/doc/user/project/issues/img/due_dates_todos.png
+++ b/doc/user/project/issues/img/due_dates_todos.png
Binary files differ
diff --git a/doc/user/project/issues/img/issue_board.png b/doc/user/project/issues/img/issue_board.png
index 1759b28a9ef..1759b28a9ef 100755..100644
--- a/doc/user/project/issues/img/issue_board.png
+++ b/doc/user/project/issues/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/issues/img/issue_template.png b/doc/user/project/issues/img/issue_template.png
index c63229a4af2..c63229a4af2 100755..100644
--- a/doc/user/project/issues/img/issue_template.png
+++ b/doc/user/project/issues/img/issue_template.png
Binary files differ
diff --git a/doc/user/project/issues/img/mention_in_issue.png b/doc/user/project/issues/img/mention_in_issue.png
index c762a812138..c762a812138 100755..100644
--- a/doc/user/project/issues/img/mention_in_issue.png
+++ b/doc/user/project/issues/img/mention_in_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/mention_in_merge_request.png b/doc/user/project/issues/img/mention_in_merge_request.png
index 681e086d6e0..681e086d6e0 100755..100644
--- a/doc/user/project/issues/img/mention_in_merge_request.png
+++ b/doc/user/project/issues/img/mention_in_merge_request.png
Binary files differ
diff --git a/doc/user/project/issues/img/merge_request_closes_issue.png b/doc/user/project/issues/img/merge_request_closes_issue.png
index 6fd27738843..6fd27738843 100755..100644
--- a/doc/user/project/issues/img/merge_request_closes_issue.png
+++ b/doc/user/project/issues/img/merge_request_closes_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/new_issue.png b/doc/user/project/issues/img/new_issue.png
index e72ac49d6b9..e72ac49d6b9 100755..100644
--- a/doc/user/project/issues/img/new_issue.png
+++ b/doc/user/project/issues/img/new_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/new_issue_from_issue_board.png b/doc/user/project/issues/img/new_issue_from_issue_board.png
index 9c2b3ff50fa..9c2b3ff50fa 100755..100644
--- a/doc/user/project/issues/img/new_issue_from_issue_board.png
+++ b/doc/user/project/issues/img/new_issue_from_issue_board.png
Binary files differ
diff --git a/doc/user/project/issues/img/new_issue_from_open_issue.png b/doc/user/project/issues/img/new_issue_from_open_issue.png
index 2aed5372830..2aed5372830 100755..100644
--- a/doc/user/project/issues/img/new_issue_from_open_issue.png
+++ b/doc/user/project/issues/img/new_issue_from_open_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png b/doc/user/project/issues/img/new_issue_from_projects_dashboard.png
index cddf36b7457..cddf36b7457 100755..100644
--- a/doc/user/project/issues/img/new_issue_from_projects_dashboard.png
+++ b/doc/user/project/issues/img/new_issue_from_projects_dashboard.png
Binary files differ
diff --git a/doc/user/project/issues/img/new_issue_from_tracker_list.png b/doc/user/project/issues/img/new_issue_from_tracker_list.png
index 7e5413f0b7d..7e5413f0b7d 100755..100644
--- a/doc/user/project/issues/img/new_issue_from_tracker_list.png
+++ b/doc/user/project/issues/img/new_issue_from_tracker_list.png
Binary files differ
diff --git a/doc/user/project/issues/img/sidebar_confidential_issue.png b/doc/user/project/issues/img/sidebar_confidential_issue.png
index d99a1ca756e..d99a1ca756e 100755..100644
--- a/doc/user/project/issues/img/sidebar_confidential_issue.png
+++ b/doc/user/project/issues/img/sidebar_confidential_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/sidebar_not_confidential_issue.png b/doc/user/project/issues/img/sidebar_not_confidential_issue.png
index 2e6cbbc5b3a..2e6cbbc5b3a 100755..100644
--- a/doc/user/project/issues/img/sidebar_not_confidential_issue.png
+++ b/doc/user/project/issues/img/sidebar_not_confidential_issue.png
Binary files differ
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index dfe43c6b691..29e04a0ccf0 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -113,25 +113,25 @@ started:
1. Use the following command to list the private GPG key you just created:
```
- gpg --list-secret-keys --keyid-format 0xLONG mr@robot.sh
+ gpg --list-secret-keys --keyid-format LONG mr@robot.sh
```
Replace `mr@robot.sh` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
- `0x30F2B65B9246B6CA`:
+ `30F2B65B9246B6CA`:
```
- sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC]
+ sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
uid [ultimate] Mr. Robot <mr@robot.sh>
- ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E]
+ ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
```
1. Export the public key of that ID (replace your key ID from the previous step):
```
- gpg --armor --export 0x30F2B65B9246B6CA
+ gpg --armor --export 30F2B65B9246B6CA
```
1. Finally, copy the public key and [add it in your profile settings](#adding-a-gpg-key-to-your-account)
@@ -167,28 +167,28 @@ key to use.
1. Use the following command to list the private GPG key you just created:
```
- gpg --list-secret-keys --keyid-format 0xLONG mr@robot.sh
+ gpg --list-secret-keys --keyid-format LONG mr@robot.sh
```
Replace `mr@robot.sh` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
- `0x30F2B65B9246B6CA`:
+ `30F2B65B9246B6CA`:
```
- sec rsa4096/0x30F2B65B9246B6CA 2017-08-18 [SC]
+ sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
uid [ultimate] Mr. Robot <mr@robot.sh>
- ssb rsa4096/0xB7ABC0813E4028C0 2017-08-18 [E]
+ ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
```
1. Tell Git to use that key to sign the commits:
```
- git config --global user.signingkey 0x30F2B65B9246B6CA
+ git config --global user.signingkey 30F2B65B9246B6CA
```
- Replace `0x30F2B65B9246B6CA` with your GPG key ID.
+ Replace `30F2B65B9246B6CA` with your GPG key ID.
## Signing commits
diff --git a/doc/user/project/repository/img/contributors_graph.png b/doc/user/project/repository/img/contributors_graph.png
index c31da7aa1ff..c31da7aa1ff 100755..100644
--- a/doc/user/project/repository/img/contributors_graph.png
+++ b/doc/user/project/repository/img/contributors_graph.png
Binary files differ
diff --git a/doc/user/project/repository/img/repo_graph.png b/doc/user/project/repository/img/repo_graph.png
index 28da8ad9589..28da8ad9589 100755..100644
--- a/doc/user/project/repository/img/repo_graph.png
+++ b/doc/user/project/repository/img/repo_graph.png
Binary files differ
diff --git a/doc/user/project/settings/img/general_settings.png b/doc/user/project/settings/img/general_settings.png
index 96f5b84871f..96f5b84871f 100755..100644
--- a/doc/user/project/settings/img/general_settings.png
+++ b/doc/user/project/settings/img/general_settings.png
Binary files differ
diff --git a/doc/user/project/settings/img/merge_requests_settings.png b/doc/user/project/settings/img/merge_requests_settings.png
index b1f2dfa7376..b1f2dfa7376 100755..100644
--- a/doc/user/project/settings/img/merge_requests_settings.png
+++ b/doc/user/project/settings/img/merge_requests_settings.png
Binary files differ
diff --git a/doc/user/search/img/issues_any_assignee.png b/doc/user/search/img/issues_any_assignee.png
index 2f902bcc66c..2f902bcc66c 100755..100644
--- a/doc/user/search/img/issues_any_assignee.png
+++ b/doc/user/search/img/issues_any_assignee.png
Binary files differ
diff --git a/doc/user/search/img/issues_assigned_to_you.png b/doc/user/search/img/issues_assigned_to_you.png
index 36c670eedd5..36c670eedd5 100755..100644
--- a/doc/user/search/img/issues_assigned_to_you.png
+++ b/doc/user/search/img/issues_assigned_to_you.png
Binary files differ
diff --git a/doc/user/search/img/issues_author.png b/doc/user/search/img/issues_author.png
index 792f9746db6..792f9746db6 100755..100644
--- a/doc/user/search/img/issues_author.png
+++ b/doc/user/search/img/issues_author.png
Binary files differ
diff --git a/doc/user/search/img/issues_mrs_shortcut.png b/doc/user/search/img/issues_mrs_shortcut.png
index 6380b337b54..6380b337b54 100755..100644
--- a/doc/user/search/img/issues_mrs_shortcut.png
+++ b/doc/user/search/img/issues_mrs_shortcut.png
Binary files differ
diff --git a/doc/user/search/img/left_menu_bar.png b/doc/user/search/img/left_menu_bar.png
index d68a71cba8e..d68a71cba8e 100755..100644
--- a/doc/user/search/img/left_menu_bar.png
+++ b/doc/user/search/img/left_menu_bar.png
Binary files differ
diff --git a/doc/user/search/img/project_search.png b/doc/user/search/img/project_search.png
index 3150b40de29..3150b40de29 100755..100644
--- a/doc/user/search/img/project_search.png
+++ b/doc/user/search/img/project_search.png
Binary files differ
diff --git a/doc/user/search/img/search_issues_board.png b/doc/user/search/img/search_issues_board.png
index 84048ae6a02..84048ae6a02 100755..100644
--- a/doc/user/search/img/search_issues_board.png
+++ b/doc/user/search/img/search_issues_board.png
Binary files differ
diff --git a/doc/user/search/img/sort_projects.png b/doc/user/search/img/sort_projects.png
index 9bf2770b299..9bf2770b299 100755..100644
--- a/doc/user/search/img/sort_projects.png
+++ b/doc/user/search/img/sort_projects.png
Binary files differ
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 4f905674d8c..d6cfa524a3a 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -51,36 +51,34 @@ Feature: Project Issues
@javascript
Scenario: Visiting Issues after being sorted the list
Given I visit project "Shop" issues page
- And I sort the list by "Oldest updated"
+ And I sort the list by "Last updated"
And I visit my project's home page
And I visit project "Shop" issues page
- Then The list should be sorted by "Oldest updated"
+ Then The list should be sorted by "Last updated"
@javascript
Scenario: Visiting Merge Requests after being sorted the list
Given project "Shop" has a "Bugfix MR" merge request open
And I visit project "Shop" issues page
- And I sort the list by "Oldest updated"
+ And I sort the list by "Last updated"
And I visit project "Shop" merge requests page
- Then The list should be sorted by "Oldest updated"
+ Then The list should be sorted by "Last updated"
@javascript
Scenario: Visiting Merge Requests from a differente Project after sorting
Given project "Shop" has a "Bugfix MR" merge request open
And I visit project "Shop" merge requests page
- And I sort the list by "Oldest updated"
+ And I sort the list by "Last updated"
And I visit dashboard merge requests page
- Then The list should be sorted by "Oldest updated"
+ Then The list should be sorted by "Last updated"
@javascript
Scenario: Sort issues by upvotes/downvotes
Given project "Shop" have "Bugfix" open issue
And issue "Release 0.4" have 2 upvotes and 1 downvote
And issue "Tweet control" have 1 upvote and 2 downvotes
- And I sort the list by "Most popular"
- Then The list should be sorted by "Most popular"
- And I sort the list by "Least popular"
- Then The list should be sorted by "Least popular"
+ And I sort the list by "Popularity"
+ Then The list should be sorted by "Popularity"
# Markdown
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 0ebeded7fc5..349fa2663a7 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -91,28 +91,26 @@ Feature: Project Merge Requests
@javascript
Scenario: Visiting Merge Requests after being sorted the list
Given I visit project "Shop" merge requests page
- And I sort the list by "Oldest updated"
+ And I sort the list by "Last updated"
And I visit my project's home page
And I visit project "Shop" merge requests page
- Then The list should be sorted by "Oldest updated"
+ Then The list should be sorted by "Last updated"
@javascript
Scenario: Visiting Merge Requests from a different Project after sorting
Given I visit project "Shop" merge requests page
- And I sort the list by "Oldest updated"
+ And I sort the list by "Last updated"
And I visit dashboard merge requests page
- Then The list should be sorted by "Oldest updated"
+ Then The list should be sorted by "Last updated"
@javascript
- Scenario: Sort merge requests by upvotes/downvotes
+ Scenario: Sort merge requests by upvotes
Given project "Shop" have "Bug NS-05" open merge request with diffs inside
And project "Shop" have "Bug NS-06" open merge request
And merge request "Bug NS-04" have 2 upvotes and 1 downvote
And merge request "Bug NS-06" have 1 upvote and 2 downvotes
- And I sort the list by "Most popular"
- Then The list should be sorted by "Most popular"
- And I sort the list by "Least popular"
- Then The list should be sorted by "Least popular"
+ And I sort the list by "Popularity"
+ Then The list should be sorted by "Popularity"
@javascript
Scenario: I comment on a merge request diff
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index b9460f5b534..2c3ef2efd52 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -223,7 +223,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
- step 'The list should be sorted by "Most popular"' do
+ step 'The list should be sorted by "Popularity"' do
page.within '.issues-list' do
page.within 'li.issue:nth-child(1)' do
expect(page).to have_content 'Release 0.4'
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 0d49a4ab90d..dde918e3d41 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -222,7 +222,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
end
- step 'The list should be sorted by "Most popular"' do
+ step 'The list should be sorted by "Popularity"' do
page.within '.mr-list' do
page.within 'li.merge-request:nth-child(1)' do
expect(page).to have_content 'Bug NS-04'
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 7c842ba88fb..714985f2051 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -109,10 +109,10 @@ module SharedIssuable
edit_issuable
end
- step 'I sort the list by "Oldest updated"' do
+ step 'I sort the list by "Last updated"' do
find('button.dropdown-toggle').click
page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
- click_link "Oldest updated"
+ click_link "Last updated"
end
end
@@ -124,16 +124,16 @@ module SharedIssuable
end
end
- step 'I sort the list by "Most popular"' do
+ step 'I sort the list by "Popularity"' do
find('button.dropdown-toggle').click
page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do
- click_link 'Most popular'
+ click_link 'Popularity'
end
end
- step 'The list should be sorted by "Oldest updated"' do
- expect(find('.issues-filters')).to have_content('Oldest updated')
+ step 'The list should be sorted by "Last updated"' do
+ expect(find('.issues-filters')).to have_content('Last updated')
end
step 'I click link "Next" in the sidebar' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 71253f72533..7f4736a08cb 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -89,6 +89,9 @@ module API
expose :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :name, :name_with_namespace
expose :path, :path_with_namespace
+ expose :avatar_url do |project, options|
+ project.avatar_url(only_path: false)
+ end
expose :star_count, :forks_count
expose :created_at, :last_activity_at
end
@@ -146,9 +149,7 @@ module API
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
- expose :avatar_url do |user, options|
- user.avatar_url(only_path: false)
- end
+
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds, as: :public_jobs
@@ -193,8 +194,8 @@ module API
class Group < Grape::Entity
expose :id, :name, :path, :description, :visibility
expose :lfs_enabled?, as: :lfs_enabled
- expose :avatar_url do |user, options|
- user.avatar_url(only_path: false)
+ expose :avatar_url do |group, options|
+ group.avatar_url(only_path: false)
end
expose :web_url
expose :request_access_enabled
@@ -234,6 +235,7 @@ module API
class RepoCommitDetail < RepoCommit
expose :stats, using: Entities::RepoCommitStats
expose :status
+ expose :last_pipeline, using: 'API::Entities::PipelineBasic'
end
class RepoBranch < Grape::Entity
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index c0fef56378f..a0557a609ca 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -136,7 +136,7 @@ module API
codes = nil
- ::Users::UpdateService.new(user).execute! do |user|
+ ::Users::UpdateService.new(current_user, user: user).execute! do |user|
codes = user.generate_otp_backup_codes!
end
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index bcc0833aa5c..0266bf2f717 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -35,7 +35,7 @@ module API
new_notification_email = params.delete(:notification_email)
if new_notification_email
- ::Users::UpdateService.new(current_user, notification_email: new_notification_email).execute
+ ::Users::UpdateService.new(current_user, user: current_user, notification_email: new_notification_email).execute
end
notification_setting.update(declared_params(include_missing: false))
diff --git a/lib/api/users.rb b/lib/api/users.rb
index bdebda58d3f..99024d1f0ad 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -11,7 +11,7 @@ module API
end
helpers do
- def find_user(params)
+ def find_user_by_id(params)
id = params[:user_id] || params[:id]
User.find_by(id: id) || not_found!('User')
end
@@ -166,7 +166,7 @@ module API
user_params[:password_expires_at] = Time.now if user_params[:password].present?
- result = ::Users::UpdateService.new(user, user_params.except(:extern_uid, :provider)).execute
+ result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute
if result[:status] == :success
present user, with: Entities::UserPublic
@@ -326,7 +326,7 @@ module API
user = User.find_by(id: params.delete(:id))
not_found!('User') unless user
- email = Emails::CreateService.new(user, declared_params(include_missing: false)).execute
+ email = Emails::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute
if email.errors.blank?
NotificationService.new.new_email(email)
@@ -367,7 +367,7 @@ module API
not_found!('Email') unless email
destroy_conditionally!(email) do |email|
- Emails::DestroyService.new(current_user, email: email.email).execute
+ Emails::DestroyService.new(current_user, user: user, email: email.email).execute
end
user.update_secondary_emails!
@@ -430,7 +430,7 @@ module API
resource :impersonation_tokens do
helpers do
def finder(options = {})
- user = find_user(params)
+ user = find_user_by_id(params)
PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
end
@@ -672,7 +672,7 @@ module API
requires :email, type: String, desc: 'The new email'
end
post "emails" do
- email = Emails::CreateService.new(current_user, declared_params).execute
+ email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute
if email.errors.blank?
NotificationService.new.new_email(email)
@@ -691,7 +691,7 @@ module API
not_found!('Email') unless email
destroy_conditionally!(email) do |email|
- Emails::DestroyService.new(current_user, email: email.email).execute
+ Emails::DestroyService.new(current_user, user: current_user, email: email.email).execute
end
current_user.update_secondary_emails!
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 9923ec4e870..88b17e12576 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -45,8 +45,9 @@ module Banzai
whitelist[:elements].push('abbr')
whitelist[:attributes]['abbr'] = %w(title)
- # Disallow `name` attribute globally
+ # Disallow `name` attribute globally, allow on `a`
whitelist[:attributes][:all].delete('name')
+ whitelist[:attributes]['a'].push('name')
# Allow any protocol in `a` elements...
whitelist[:protocols].delete('a')
diff --git a/lib/github/client.rb b/lib/github/client.rb
index 9c476df7d46..29bd9c1f39e 100644
--- a/lib/github/client.rb
+++ b/lib/github/client.rb
@@ -1,6 +1,7 @@
module Github
class Client
TIMEOUT = 60
+ DEFAULT_PER_PAGE = 100
attr_reader :connection, :rate_limit
@@ -20,7 +21,7 @@ module Github
exceed, reset_in = rate_limit.get
sleep reset_in if exceed
- Github::Response.new(connection.get(url, query))
+ Github::Response.new(connection.get(url, { per_page: DEFAULT_PER_PAGE }.merge(query)))
end
private
diff --git a/lib/github/import.rb b/lib/github/import.rb
index 9354e142d3d..f5f62dc8b6f 100644
--- a/lib/github/import.rb
+++ b/lib/github/import.rb
@@ -202,13 +202,8 @@ module Github
merge_request.save!(validate: false)
merge_request.merge_request_diffs.create
- # Fetch review comments
review_comments_url = "/repos/#{repo}/pulls/#{pull_request.iid}/comments"
fetch_comments(merge_request, :review_comment, review_comments_url, LegacyDiffNote)
-
- # Fetch comments
- comments_url = "/repos/#{repo}/issues/#{pull_request.iid}/comments"
- fetch_comments(merge_request, :comment, comments_url)
rescue => e
error(:pull_request, pull_request.url, e.message)
ensure
@@ -241,12 +236,17 @@ module Github
# for both features, like manipulating assignees, labels
# and milestones, are provided within the Issues API.
if representation.pull_request?
- return unless representation.has_labels?
+ return unless representation.has_labels? || representation.has_comments?
merge_request = MergeRequest.find_by!(target_project_id: project.id, iid: representation.iid)
- merge_request.update_attribute(:label_ids, label_ids(representation.labels))
+
+ if representation.has_labels?
+ merge_request.update_attribute(:label_ids, label_ids(representation.labels))
+ end
+
+ fetch_comments_conditionally(merge_request, representation)
else
- return if Issue.where(iid: representation.iid, project_id: project.id).exists?
+ return if Issue.exists?(iid: representation.iid, project_id: project.id)
author_id = user_id(representation.author, project.creator_id)
issue = Issue.new
@@ -263,17 +263,20 @@ module Github
issue.updated_at = representation.updated_at
issue.save!(validate: false)
- # Fetch comments
- if representation.has_comments?
- comments_url = "/repos/#{repo}/issues/#{issue.iid}/comments"
- fetch_comments(issue, :comment, comments_url)
- end
+ fetch_comments_conditionally(issue, representation)
end
rescue => e
error(:issue, representation.url, e.message)
end
end
+ def fetch_comments_conditionally(issuable, representation)
+ if representation.has_comments?
+ comments_url = "/repos/#{repo}/issues/#{issuable.iid}/comments"
+ fetch_comments(issuable, :comment, comments_url)
+ end
+ end
+
def fetch_comments(noteable, type, url, klass = Note)
while url
comments = Github::Client.new(options).get(url)
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 28bbf3b384e..d1979bb7ed3 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -149,16 +149,21 @@ module Gitlab
description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
description += pull_request.description
+ source_branch_sha = pull_request.source_branch_sha
+ target_branch_sha = pull_request.target_branch_sha
+ source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
+ target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
+
merge_request = project.merge_requests.create!(
iid: pull_request.iid,
title: pull_request.title,
description: description,
source_project: project,
source_branch: pull_request.source_branch_name,
- source_branch_sha: pull_request.source_branch_sha,
+ source_branch_sha: source_branch_sha,
target_project: project,
target_branch: pull_request.target_branch_name,
- target_branch_sha: pull_request.target_branch_sha,
+ target_branch_sha: target_branch_sha,
state: pull_request.state,
author_id: gitlab_user_id(project, pull_request.author),
assignee_id: nil,
diff --git a/lib/gitlab/ci/pipeline/chain/base.rb b/lib/gitlab/ci/pipeline/chain/base.rb
new file mode 100644
index 00000000000..8d82e1b288d
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/base.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Base
+ attr_reader :pipeline, :project, :current_user
+
+ def initialize(pipeline, command)
+ @pipeline = pipeline
+ @command = command
+
+ @project = command.project
+ @current_user = command.current_user
+ end
+
+ def perform!
+ raise NotImplementedError
+ end
+
+ def break?
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
new file mode 100644
index 00000000000..d5e17a123df
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Create < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ ::Ci::Pipeline.transaction do
+ pipeline.save!
+
+ @command.seeds_block&.call(pipeline)
+
+ ::Ci::CreatePipelineStagesService
+ .new(project, current_user)
+ .execute(pipeline)
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ error("Failed to persist the pipeline: #{e}")
+ end
+
+ def break?
+ !pipeline.persisted?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb
new file mode 100644
index 00000000000..02d81286f21
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/helpers.rb
@@ -0,0 +1,25 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Helpers
+ def branch_exists?
+ return @is_branch if defined?(@is_branch)
+
+ @is_branch = project.repository.branch_exists?(pipeline.ref)
+ end
+
+ def tag_exists?
+ return @is_tag if defined?(@is_tag)
+
+ @is_tag = project.repository.tag_exists?(pipeline.ref)
+ end
+
+ def error(message)
+ pipeline.errors.add(:base, message)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/sequence.rb b/lib/gitlab/ci/pipeline/chain/sequence.rb
new file mode 100644
index 00000000000..015f2988327
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/sequence.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Sequence
+ def initialize(pipeline, command, sequence)
+ @pipeline = pipeline
+ @completed = []
+
+ @sequence = sequence.map do |chain|
+ chain.new(pipeline, command)
+ end
+ end
+
+ def build!
+ @sequence.each do |step|
+ step.perform!
+
+ break if step.break?
+
+ @completed << step
+ end
+
+ @pipeline.tap do
+ yield @pipeline, self if block_given?
+ end
+ end
+
+ def complete?
+ @completed.size == @sequence.size
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb
new file mode 100644
index 00000000000..9a72de87bab
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/skip.rb
@@ -0,0 +1,33 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ class Skip < Chain::Base
+ SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i
+
+ def perform!
+ if skipped?
+ @pipeline.skip if @command.save_incompleted
+ end
+ end
+
+ def skipped?
+ !@command.ignore_skip_ci && commit_message_skips_ci?
+ end
+
+ def break?
+ skipped?
+ end
+
+ private
+
+ def commit_message_skips_ci?
+ return false unless @pipeline.git_commit_message
+
+ @skipped ||= !!(@pipeline.git_commit_message =~ SKIP_PATTERN)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
new file mode 100644
index 00000000000..4913a604079
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -0,0 +1,54 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Validate
+ class Abilities < Chain::Base
+ include Gitlab::Allowable
+ include Chain::Helpers
+
+ def perform!
+ unless project.builds_enabled?
+ return error('Pipelines are disabled!')
+ end
+
+ unless allowed_to_trigger_pipeline?
+ if can?(current_user, :create_pipeline, project)
+ return error("Insufficient permissions for protected ref '#{pipeline.ref}'")
+ else
+ return error('Insufficient permissions to create a new pipeline')
+ end
+ end
+ end
+
+ def break?
+ @pipeline.errors.any?
+ end
+
+ def allowed_to_trigger_pipeline?
+ if current_user
+ allowed_to_create?
+ else # legacy triggers don't have a corresponding user
+ !project.protected_for?(@pipeline.ref)
+ end
+ end
+
+ def allowed_to_create?
+ return unless can?(current_user, :create_pipeline, project)
+
+ access = Gitlab::UserAccess.new(current_user, project: project)
+
+ if branch_exists?
+ access.can_update_branch?(@pipeline.ref)
+ elsif tag_exists?
+ access.can_create_tag?(@pipeline.ref)
+ else
+ true # Allow it for now and we'll reject when we check ref existence
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/config.rb b/lib/gitlab/ci/pipeline/chain/validate/config.rb
new file mode 100644
index 00000000000..489bcd79655
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/validate/config.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Validate
+ class Config < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ unless @pipeline.config_processor
+ unless @pipeline.ci_yaml_file
+ return error("Missing #{@pipeline.ci_yaml_file_path} file")
+ end
+
+ if @command.save_incompleted && @pipeline.has_yaml_errors?
+ @pipeline.drop
+ end
+
+ return error(@pipeline.yaml_errors)
+ end
+
+ unless @pipeline.has_stage_seeds?
+ return error('No stages / jobs for this pipeline.')
+ end
+ end
+
+ def break?
+ @pipeline.errors.any? || @pipeline.persisted?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
new file mode 100644
index 00000000000..70a4cfdbdea
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Validate
+ class Repository < Chain::Base
+ include Chain::Helpers
+
+ def perform!
+ unless branch_exists? || tag_exists?
+ return error('Reference not found')
+ end
+
+ ## TODO, we check commit in the service, that is why
+ # there is no repository access here.
+ #
+ unless pipeline.sha
+ return error('Commit not found')
+ end
+ end
+
+ def break?
+ @pipeline.errors.any?
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/duration.rb b/lib/gitlab/ci/pipeline/duration.rb
new file mode 100644
index 00000000000..469fc094cc8
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/duration.rb
@@ -0,0 +1,143 @@
+module Gitlab
+ module Ci
+ module Pipeline
+ # # Introduction - total running time
+ #
+ # The problem this module is trying to solve is finding the total running
+ # time amongst all the jobs, excluding retries and pending (queue) time.
+ # We could reduce this problem down to finding the union of periods.
+ #
+ # So each job would be represented as a `Period`, which consists of
+ # `Period#first` as when the job started and `Period#last` as when the
+ # job was finished. A simple example here would be:
+ #
+ # * A (1, 3)
+ # * B (2, 4)
+ # * C (6, 7)
+ #
+ # Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
+ # C begins from 6, and ends to 7. Visually it could be viewed as:
+ #
+ # 0 1 2 3 4 5 6 7
+ # AAAAAAA
+ # BBBBBBB
+ # CCCC
+ #
+ # The union of A, B, and C would be (1, 4) and (6, 7), therefore the
+ # total running time should be:
+ #
+ # (4 - 1) + (7 - 6) => 4
+ #
+ # # The Algorithm
+ #
+ # The algorithm used here for union would be described as follow.
+ # First we make sure that all periods are sorted by `Period#first`.
+ # Then we try to merge periods by iterating through the first period
+ # to the last period. The goal would be merging all overlapped periods
+ # so that in the end all the periods are discrete. When all periods
+ # are discrete, we're free to just sum all the periods to get real
+ # running time.
+ #
+ # Here we begin from A, and compare it to B. We could find that
+ # before A ends, B already started. That is `B.first <= A.last`
+ # that is `2 <= 3` which means A and B are overlapping!
+ #
+ # When we found that two periods are overlapping, we would need to merge
+ # them into a new period and disregard the old periods. To make a new
+ # period, we take `A.first` as the new first because remember? we sorted
+ # them, so `A.first` must be smaller or equal to `B.first`. And we take
+ # `[A.last, B.last].max` as the new last because we want whoever ended
+ # later. This could be broken into two cases:
+ #
+ # 0 1 2 3 4
+ # AAAAAAA
+ # BBBBBBB
+ #
+ # Or:
+ #
+ # 0 1 2 3 4
+ # AAAAAAAAAA
+ # BBBB
+ #
+ # So that we need to take whoever ends later. Back to our example,
+ # after merging and discard A and B it could be visually viewed as:
+ #
+ # 0 1 2 3 4 5 6 7
+ # DDDDDDDDDD
+ # CCCC
+ #
+ # Now we could go on and compare the newly created D and the old C.
+ # We could figure out that D and C are not overlapping by checking
+ # `C.first <= D.last` is `false`. Therefore we need to keep both C
+ # and D. The example would end here because there are no more jobs.
+ #
+ # After having the union of all periods, we just need to sum the length
+ # of all periods to get total time.
+ #
+ # (4 - 1) + (7 - 6) => 4
+ #
+ # That is 4 is the answer in the example.
+ module Duration
+ extend self
+
+ Period = Struct.new(:first, :last) do
+ def duration
+ last - first
+ end
+ end
+
+ def from_pipeline(pipeline)
+ status = %w[success failed running canceled]
+ builds = pipeline.builds.latest
+ .where(status: status).where.not(started_at: nil).order(:started_at)
+
+ from_builds(builds)
+ end
+
+ def from_builds(builds)
+ now = Time.now
+
+ periods = builds.map do |b|
+ Period.new(b.started_at, b.finished_at || now)
+ end
+
+ from_periods(periods)
+ end
+
+ # periods should be sorted by `first`
+ def from_periods(periods)
+ process_duration(process_periods(periods))
+ end
+
+ private
+
+ def process_periods(periods)
+ return periods if periods.empty?
+
+ periods.drop(1).inject([periods.first]) do |result, current|
+ previous = result.last
+
+ if overlap?(previous, current)
+ result[-1] = merge(previous, current)
+ result
+ else
+ result << current
+ end
+ end
+ end
+
+ def overlap?(previous, current)
+ current.first <= previous.last
+ end
+
+ def merge(previous, current)
+ Period.new(previous.first, [previous.last, current.last].max)
+ end
+
+ def process_duration(periods)
+ periods.sum(&:duration)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb
deleted file mode 100644
index 3208cc2bef6..00000000000
--- a/lib/gitlab/ci/pipeline_duration.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-module Gitlab
- module Ci
- # # Introduction - total running time
- #
- # The problem this module is trying to solve is finding the total running
- # time amongst all the jobs, excluding retries and pending (queue) time.
- # We could reduce this problem down to finding the union of periods.
- #
- # So each job would be represented as a `Period`, which consists of
- # `Period#first` as when the job started and `Period#last` as when the
- # job was finished. A simple example here would be:
- #
- # * A (1, 3)
- # * B (2, 4)
- # * C (6, 7)
- #
- # Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
- # C begins from 6, and ends to 7. Visually it could be viewed as:
- #
- # 0 1 2 3 4 5 6 7
- # AAAAAAA
- # BBBBBBB
- # CCCC
- #
- # The union of A, B, and C would be (1, 4) and (6, 7), therefore the
- # total running time should be:
- #
- # (4 - 1) + (7 - 6) => 4
- #
- # # The Algorithm
- #
- # The algorithm used here for union would be described as follow.
- # First we make sure that all periods are sorted by `Period#first`.
- # Then we try to merge periods by iterating through the first period
- # to the last period. The goal would be merging all overlapped periods
- # so that in the end all the periods are discrete. When all periods
- # are discrete, we're free to just sum all the periods to get real
- # running time.
- #
- # Here we begin from A, and compare it to B. We could find that
- # before A ends, B already started. That is `B.first <= A.last`
- # that is `2 <= 3` which means A and B are overlapping!
- #
- # When we found that two periods are overlapping, we would need to merge
- # them into a new period and disregard the old periods. To make a new
- # period, we take `A.first` as the new first because remember? we sorted
- # them, so `A.first` must be smaller or equal to `B.first`. And we take
- # `[A.last, B.last].max` as the new last because we want whoever ended
- # later. This could be broken into two cases:
- #
- # 0 1 2 3 4
- # AAAAAAA
- # BBBBBBB
- #
- # Or:
- #
- # 0 1 2 3 4
- # AAAAAAAAAA
- # BBBB
- #
- # So that we need to take whoever ends later. Back to our example,
- # after merging and discard A and B it could be visually viewed as:
- #
- # 0 1 2 3 4 5 6 7
- # DDDDDDDDDD
- # CCCC
- #
- # Now we could go on and compare the newly created D and the old C.
- # We could figure out that D and C are not overlapping by checking
- # `C.first <= D.last` is `false`. Therefore we need to keep both C
- # and D. The example would end here because there are no more jobs.
- #
- # After having the union of all periods, we just need to sum the length
- # of all periods to get total time.
- #
- # (4 - 1) + (7 - 6) => 4
- #
- # That is 4 is the answer in the example.
- module PipelineDuration
- extend self
-
- Period = Struct.new(:first, :last) do
- def duration
- last - first
- end
- end
-
- def from_pipeline(pipeline)
- status = %w[success failed running canceled]
- builds = pipeline.builds.latest
- .where(status: status).where.not(started_at: nil).order(:started_at)
-
- from_builds(builds)
- end
-
- def from_builds(builds)
- now = Time.now
-
- periods = builds.map do |b|
- Period.new(b.started_at, b.finished_at || now)
- end
-
- from_periods(periods)
- end
-
- # periods should be sorted by `first`
- def from_periods(periods)
- process_duration(process_periods(periods))
- end
-
- private
-
- def process_periods(periods)
- return periods if periods.empty?
-
- periods.drop(1).inject([periods.first]) do |result, current|
- previous = result.last
-
- if overlap?(previous, current)
- result[-1] = merge(previous, current)
- result
- else
- result << current
- end
- end
- end
-
- def overlap?(previous, current)
- current.first <= previous.last
- end
-
- def merge(previous, current)
- Period.new(previous.first, [previous.last, current.last].max)
- end
-
- def process_duration(periods)
- periods.sum(&:duration)
- end
- end
- end
-end
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index 371cbe04b9b..c98eefbce25 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -13,9 +13,9 @@ module Gitlab
def ==(other)
other.is_a?(self.class) &&
- base_sha == other.base_sha &&
- start_sha == other.start_sha &&
- head_sha == other.head_sha
+ shas_equal?(base_sha, other.base_sha) &&
+ shas_equal?(start_sha, other.start_sha) &&
+ shas_equal?(head_sha, other.head_sha)
end
alias_method :eql?, :==
@@ -47,6 +47,22 @@ module Gitlab
CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
end
end
+
+ private
+
+ def shas_equal?(sha1, sha2)
+ return true if sha1 == sha2
+ return false if sha1.nil? || sha2.nil?
+ return false unless sha1.class == sha2.class
+
+ length = [sha1.length, sha2.length].min
+
+ # If either of the shas is below the minimum length, we cannot be sure
+ # that they actually refer to the same commit because of hash collision.
+ return false if length < Commit::MIN_SHA_LENGTH
+
+ sha1[0, length] == sha2[0, length]
+ end
end
end
end
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index f80afb20f0c..b8db3adef0a 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -49,12 +49,13 @@ module Gitlab
coder['attributes'] = self.to_h
end
- def key
- @key ||= [base_sha, start_sha, head_sha, Digest::SHA1.hexdigest(old_path || ""), Digest::SHA1.hexdigest(new_path || ""), old_line, new_line]
- end
-
def ==(other)
- other.is_a?(self.class) && key == other.key
+ other.is_a?(self.class) &&
+ other.diff_refs == diff_refs &&
+ other.old_path == old_path &&
+ other.new_path == new_path &&
+ other.old_line == old_line &&
+ other.new_line == new_line
end
def to_h
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 10ba29acbd1..6baff362dad 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -73,8 +73,6 @@ module Gitlab
delegate :empty?,
to: :rugged
- delegate :exists?, to: :gitaly_repository_client
-
def ==(other)
path == other.path
end
@@ -102,6 +100,18 @@ module Gitlab
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
end
+ def exists?
+ Gitlab::GitalyClient.migrate(:repository_exists) do |enabled|
+ if enabled
+ gitaly_repository_client.exists?
+ else
+ circuit_breaker.perform do
+ File.exist?(File.join(@path, 'refs'))
+ end
+ end
+ end
+ end
+
# Returns an Array of branch names
# sorted by name ASC
def branch_names
@@ -386,7 +396,13 @@ module Gitlab
options[:limit] ||= 0
options[:offset] ||= 0
- raw_log(options).map { |c| Commit.decorate(self, c) }
+ gitaly_migrate(:find_commits) do |is_enabled|
+ if is_enabled
+ gitaly_commit_client.find_commits(options)
+ else
+ raw_log(options).map { |c| Commit.decorate(self, c) }
+ end
+ end
end
# Used in gitaly-ruby
@@ -931,7 +947,11 @@ module Gitlab
if start_repository == self
yield commit(start_branch_name)
else
- sha = start_repository.commit(start_branch_name).sha
+ start_commit = start_repository.commit(start_branch_name)
+
+ return yield nil unless start_commit
+
+ sha = start_commit.sha
if branch_commit = commit(sha)
yield branch_commit
@@ -960,8 +980,9 @@ module Gitlab
with_repo_branch_commit(source_repository, source_branch) do |commit|
if commit
write_ref(local_ref, commit.sha)
+ true
else
- raise Rugged::ReferenceError, 'source repository is empty'
+ false
end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index cbd9ff406de..955d2307f88 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -228,10 +228,18 @@ module Gitlab
path.read.chomp
end
+ def self.timestamp(t)
+ Google::Protobuf::Timestamp.new(seconds: t.to_i)
+ end
+
def self.encode(s)
s.dup.force_encoding(Encoding::ASCII_8BIT)
end
+ def self.encode_repeated(a)
+ Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| self.encode(s) } )
+ end
+
# Count a stack. Used for n+1 detection
def self.count_stack
return unless RequestStore.active?
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index cf3a3554552..36da63fd586 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -230,6 +230,26 @@ module Gitlab
GitalyClient.call(@repository.storage, :commit_service, :commit_stats, request)
end
+ def find_commits(options)
+ request = Gitaly::FindCommitsRequest.new(
+ repository: @gitaly_repo,
+ limit: options[:limit],
+ offset: options[:offset],
+ follow: options[:follow],
+ skip_merges: options[:skip_merges],
+ disable_walk: options[:disable_walk]
+ )
+ request.after = GitalyClient.timestamp(options[:after]) if options[:after]
+ request.before = GitalyClient.timestamp(options[:before]) if options[:before]
+ request.revision = GitalyClient.encode(options[:ref]) if options[:ref]
+
+ request.paths = GitalyClient.encode_repeated(Array(options[:path])) if options[:path].present?
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :find_commits, request)
+
+ consume_commits_response(response)
+ end
+
private
def call_commit_diff(request_params, options = {})
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index fb68627dedf..e60ceba27c8 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -16,7 +16,7 @@ module Gitlab
def self.allowed?(user)
self.open(user) do |access|
if access.allowed?
- Users::UpdateService.new(user, last_credential_check_at: Time.now).execute
+ Users::UpdateService.new(user, user: user, last_credential_check_at: Time.now).execute
true
else
diff --git a/lib/gitlab/markdown/pipeline.rb b/lib/gitlab/markdown/pipeline.rb
deleted file mode 100644
index 306923902e0..00000000000
--- a/lib/gitlab/markdown/pipeline.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module Gitlab
- module Markdown
- class Pipeline
- def self.[](name)
- name ||= :full
- const_get("#{name.to_s.camelize}Pipeline")
- end
-
- def self.filters
- []
- end
-
- def self.transform_context(context)
- context
- end
-
- def self.html_pipeline
- @html_pipeline ||= HTML::Pipeline.new(filters)
- end
-
- class << self
- %i(call to_document to_html).each do |meth|
- define_method(meth) do |text, context|
- context = transform_context(context)
-
- html_pipeline.__send__(meth, text, context) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 7704bf715e4..e06d4dc45f7 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -32,7 +32,7 @@ module Gitlab
block_after_save = needs_blocking?
- Users::UpdateService.new(gl_user).execute!
+ Users::UpdateService.new(gl_user, user: gl_user).execute!
gl_user.block if block_after_save
diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb
index c388682dfb4..6ee8c8874ec 100644
--- a/lib/system_check/app/git_version_check.rb
+++ b/lib/system_check/app/git_version_check.rb
@@ -9,7 +9,7 @@ module SystemCheck
end
def self.current_version
- @current_version ||= Gitlab::VersionInfo.parse(run_command(%W(#{Gitlab.config.git.bin_path} --version)))
+ @current_version ||= Gitlab::VersionInfo.parse(Gitlab::TaskHelpers.run_command(%W(#{Gitlab.config.git.bin_path} --version)))
end
def check?
diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb
index fd82f5f8a4a..08a2c495bd4 100644
--- a/lib/system_check/app/ruby_version_check.rb
+++ b/lib/system_check/app/ruby_version_check.rb
@@ -9,7 +9,7 @@ module SystemCheck
end
def self.current_version
- @current_version ||= Gitlab::VersionInfo.parse(run_command(%w(ruby --version)))
+ @current_version ||= Gitlab::VersionInfo.parse(Gitlab::TaskHelpers.run_command(%w(ruby --version)))
end
def check?
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bc476a706cb..600432be3bf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-09-21 14:20+0530\n"
-"PO-Revision-Date: 2017-09-21 14:20+0530\n"
+"POT-Creation-Date: 2017-09-27 15:43+0100\n"
+"PO-Revision-Date: 2017-09-27 15:43+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -31,6 +31,9 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr ""
+msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
+msgstr ""
+
msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
msgstr ""
@@ -131,28 +134,28 @@ msgstr ""
msgid "Authentication Log"
msgstr ""
-msgid "Auto DevOps (Beta)"
+msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly."
msgstr ""
-msgid "Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
+msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
msgstr ""
-msgid "Auto DevOps documentation"
+msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly."
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly."
+msgid "AutoDevOps|Auto DevOps (Beta)"
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
+msgid "AutoDevOps|Auto DevOps can be activated for this project. It will automatically build, test, and deploy your application based on a predefined CI/CD configuration."
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need the %{kubernetes} to work correctly."
+msgid "AutoDevOps|Auto DevOps documentation"
msgstr ""
-msgid "AutoDevOps|Learn more in the"
+msgid "AutoDevOps|Enable in settings"
msgstr ""
-msgid "Board"
+msgid "AutoDevOps|Learn more in the %{link_to_documentation}"
msgstr ""
msgid "Branch"
@@ -172,12 +175,81 @@ msgstr ""
msgid "Branches"
msgstr ""
+msgid "Branches|Cant find HEAD commit for this branch"
+msgstr ""
+
+msgid "Branches|Compare"
+msgstr ""
+
+msgid "Branches|Delete all branches that are merged into '%{default_branch}'"
+msgstr ""
+
+msgid "Branches|Delete branch"
+msgstr ""
+
+msgid "Branches|Delete merged branches"
+msgstr ""
+
+msgid "Branches|Delete protected branch"
+msgstr ""
+
+msgid "Branches|Delete protected branch '%{branch_name}'?"
+msgstr ""
+
+msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?"
+msgstr ""
+
+msgid "Branches|Filter by branch name"
+msgstr ""
+
+msgid "Branches|Merged into %{default_branch}"
+msgstr ""
+
+msgid "Branches|New branch"
+msgstr ""
+
+msgid "Branches|No branches to show"
+msgstr ""
+
+msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered."
+msgstr ""
+
+msgid "Branches|Only a project master or owner can delete a protected branch"
+msgstr ""
+
+msgid "Branches|Protected branches can be managed in %{project_settings_link}"
+msgstr ""
+
+msgid "Branches|Sort by"
+msgstr ""
+
+msgid "Branches|The default branch cannot be deleted"
+msgstr ""
+
msgid "Branches|This branch hasn’t been merged into %{default_branch}."
msgstr ""
msgid "Branches|To avoid data loss, consider merging this branch before deleting it."
msgstr ""
+msgid "Branches|To confirm, type %{branch_name_confirmation}:"
+msgstr ""
+
+msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}."
+msgstr ""
+
+msgid "Branches|merged"
+msgstr ""
+
+msgid "Branches|project settings"
+msgstr ""
+
+msgid "Branches|protected"
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -199,6 +271,9 @@ msgstr ""
msgid "CI configuration"
msgstr ""
+msgid "CICD|Jobs"
+msgstr ""
+
msgid "Cancel"
msgstr ""
@@ -402,6 +477,12 @@ msgstr ""
msgid "CycleAnalyticsStage|Test"
msgstr ""
+msgid "DashboardProjects|All"
+msgstr ""
+
+msgid "DashboardProjects|Personal"
+msgstr ""
+
msgid "Define a custom pattern with cron syntax"
msgstr ""
@@ -467,9 +548,6 @@ msgstr ""
msgid "Emails"
msgstr ""
-msgid "Enable in settings"
-msgstr ""
-
msgid "EventFilterBy|Filter by all"
msgstr ""
@@ -568,7 +646,7 @@ msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}."
msgstr ""
-msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner."
+msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
msgstr ""
msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
@@ -595,9 +673,6 @@ msgstr ""
msgid "HealthCheck|Unhealthy"
msgstr ""
-msgid "Home"
-msgstr ""
-
msgid "Housekeeping successfully started"
msgstr ""
@@ -616,10 +691,10 @@ msgstr ""
msgid "Issue events"
msgstr ""
-msgid "Issues"
+msgid "IssueBoards|Board"
msgstr ""
-msgid "Jobs"
+msgid "Issues"
msgstr ""
msgid "LFSStatus|Disabled"
@@ -680,6 +755,9 @@ msgstr ""
msgid "Merge events"
msgstr ""
+msgid "Merge request"
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -955,9 +1033,6 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
-msgid "Project home"
-msgstr ""
-
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -1128,6 +1203,93 @@ msgstr[1] ""
msgid "Snippets"
msgstr ""
+msgid "SortOptions|Access level, ascending"
+msgstr ""
+
+msgid "SortOptions|Access level, descending"
+msgstr ""
+
+msgid "SortOptions|Created date"
+msgstr ""
+
+msgid "SortOptions|Due date"
+msgstr ""
+
+msgid "SortOptions|Due later"
+msgstr ""
+
+msgid "SortOptions|Due soon"
+msgstr ""
+
+msgid "SortOptions|Label priority"
+msgstr ""
+
+msgid "SortOptions|Largest group"
+msgstr ""
+
+msgid "SortOptions|Largest repository"
+msgstr ""
+
+msgid "SortOptions|Last created"
+msgstr ""
+
+msgid "SortOptions|Last joined"
+msgstr ""
+
+msgid "SortOptions|Last updated"
+msgstr ""
+
+msgid "SortOptions|Least popular"
+msgstr ""
+
+msgid "SortOptions|Milestone"
+msgstr ""
+
+msgid "SortOptions|Milestone due later"
+msgstr ""
+
+msgid "SortOptions|Milestone due soon"
+msgstr ""
+
+msgid "SortOptions|Most popular"
+msgstr ""
+
+msgid "SortOptions|Name"
+msgstr ""
+
+msgid "SortOptions|Name, ascending"
+msgstr ""
+
+msgid "SortOptions|Name, descending"
+msgstr ""
+
+msgid "SortOptions|Oldest created"
+msgstr ""
+
+msgid "SortOptions|Oldest joined"
+msgstr ""
+
+msgid "SortOptions|Oldest sign in"
+msgstr ""
+
+msgid "SortOptions|Oldest updated"
+msgstr ""
+
+msgid "SortOptions|Popularity"
+msgstr ""
+
+msgid "SortOptions|Priority"
+msgstr ""
+
+msgid "SortOptions|Recent sign in"
+msgstr ""
+
+msgid "SortOptions|Start later"
+msgstr ""
+
+msgid "SortOptions|Start soon"
+msgstr ""
+
msgid "Source code"
msgstr ""
@@ -1398,9 +1560,15 @@ msgstr ""
msgid "Use your global notification setting"
msgstr ""
+msgid "View file @ "
+msgstr ""
+
msgid "View open merge request"
msgstr ""
+msgid "View replaced file @ "
+msgstr ""
+
msgid "VisibilityLevel|Internal"
msgstr ""
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index 54c1ef3dfdd..e5242fee32b 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -3,15 +3,19 @@
cd "$(dirname "$0")/.."
# Use long options (e.g. --header instead of -H) for curl examples in documentation.
-grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/
+echo 'Checking for curl short options...'
+grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/ >/dev/null 2>&1
if [ $? == 0 ]
then
- echo '✖ ERROR: Short options should not be used in documentation!' >&2
+ echo '✖ ERROR: Short options for curl should not be used in documentation!
+ Use long options (e.g., --header instead of -H):' >&2
+ grep --extended-regexp --recursive --color=auto 'curl (.+ )?-[^- ].*' doc/
exit 1
fi
# Ensure that the CHANGELOG.md does not contain duplicate versions
DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^## .+' CHANGELOG.md | sed -E 's| \(.+\)||' | sort -r | uniq -d)
+echo 'Checking for CHANGELOG.md duplicate entries...'
if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ]
then
echo '✖ ERROR: Duplicate versions in CHANGELOG.md:' >&2
@@ -19,5 +23,15 @@ then
exit 1
fi
+# Make sure no files in doc/ are executable
+EXEC_PERM_COUNT=$(find doc/ app/ -type f -perm 755 | wc -l)
+echo 'Checking for executable permissions...'
+if [ "${EXEC_PERM_COUNT}" -ne 0 ]
+then
+ echo '✖ ERROR: Executable permissions should not be used in documentation! Use `chmod 644` to the files in question:' >&2
+ find doc/ app/ -type f -perm 755
+ exit 1
+fi
+
echo "✔ Linting passed"
exit 0
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index facb67ae787..8759950e013 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -90,17 +90,17 @@ feature 'Dashboard Issues filtering', :js do
context 'sorting' do
it 'shows sorted issues' do
- sorting_by('Oldest updated')
+ sorting_by('Created date')
visit_issues
- expect(find('.issues-filters')).to have_content('Oldest updated')
+ expect(find('.issues-filters')).to have_content('Created date')
end
it 'keeps sorting issues after visiting Projects Issues page' do
- sorting_by('Oldest updated')
+ sorting_by('Created date')
visit project_issues_path(project)
- expect(find('.issues-filters')).to have_content('Oldest updated')
+ expect(find('.issues-filters')).to have_content('Created date')
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index b4992dd54a1..8204828b5b9 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -112,19 +112,19 @@ feature 'Dashboard Merge Requests' do
end
it 'shows sorted merge requests' do
- sorting_by('Oldest updated')
+ sorting_by('Created date')
visit merge_requests_dashboard_path(assignee_id: current_user.id)
- expect(find('.issues-filters')).to have_content('Oldest updated')
+ expect(find('.issues-filters')).to have_content('Created date')
end
it 'keeps sorting merge requests after visiting Projects MR page' do
- sorting_by('Oldest updated')
+ sorting_by('Created date')
visit project_merge_requests_path(project)
- expect(find('.issues-filters')).to have_content('Oldest updated')
+ expect(find('.issues-filters')).to have_content('Created date')
end
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 9a7b8e3ba6b..4da95ccc169 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -50,6 +50,25 @@ feature 'Dashboard Projects' do
end
end
+ context 'when on Your projects tab' do
+ it 'shows all projects by default' do
+ visit dashboard_projects_path
+
+ expect(page).to have_content(project.name)
+ end
+
+ it 'shows personal projects on personal projects tab', :js do
+ project3 = create(:project, namespace: user.namespace)
+
+ visit dashboard_projects_path
+
+ click_link 'Personal'
+
+ expect(page).not_to have_content(project.name)
+ expect(page).to have_content(project3.name)
+ end
+ end
+
context 'when on Starred projects tab' do
it 'shows only starred projects' do
user.toggle_star(project2)
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
index b72b690110f..925d026ed61 100644
--- a/spec/features/issuables/default_sort_order_spec.rb
+++ b/spec/features/issuables/default_sort_order_spec.rb
@@ -40,10 +40,10 @@ describe 'Projects > Issuables > Default sort order' do
context 'in the "merge requests / open" tab', js: true do
let(:issuable_type) { :merge_request }
- it 'is "last created"' do
+ it 'is "created date"' do
visit_merge_requests_with_state(project, 'open')
- expect(selected_sort_order).to eq('last created')
+ expect(selected_sort_order).to eq('created date')
expect(first_merge_request).to include(last_created_issuable.title)
expect(last_merge_request).to include(first_created_issuable.title)
end
@@ -76,10 +76,10 @@ describe 'Projects > Issuables > Default sort order' do
context 'in the "merge requests / all" tab', js: true do
let(:issuable_type) { :merge_request }
- it 'is "last created"' do
+ it 'is "created date"' do
visit_merge_requests_with_state(project, 'all')
- expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(find('.issues-other-filters')).to have_content('Created date')
expect(first_merge_request).to include(last_created_issuable.title)
expect(last_merge_request).to include(first_created_issuable.title)
end
@@ -105,10 +105,10 @@ describe 'Projects > Issuables > Default sort order' do
context 'in the "issues" tab', js: true do
let(:issuable_type) { :issue }
- it 'is "last created"' do
+ it 'is "created date"' do
visit_issues project
- expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(find('.issues-other-filters')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -117,10 +117,10 @@ describe 'Projects > Issuables > Default sort order' do
context 'in the "issues / open" tab', js: true do
let(:issuable_type) { :issue }
- it 'is "last created"' do
+ it 'is "created date"' do
visit_issues_with_state(project, 'open')
- expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(find('.issues-other-filters')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -141,10 +141,10 @@ describe 'Projects > Issuables > Default sort order' do
context 'in the "issues / all" tab', js: true do
let(:issuable_type) { :issue }
- it 'is "last created"' do
+ it 'is "created date"' do
visit_issues_with_state(project, 'all')
- expect(find('.issues-other-filters')).to have_content('Last created')
+ expect(find('.issues-other-filters')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
@@ -157,26 +157,12 @@ describe 'Projects > Issuables > Default sort order' do
visit_issues(project, sort: 'id_desc')
end
- it 'shows the sort order as last created' do
- expect(find('.issues-other-filters')).to have_content('Last created')
+ it 'shows the sort order as created date' do
+ expect(find('.issues-other-filters')).to have_content('Created date')
expect(first_issue).to include(last_created_issuable.title)
expect(last_issue).to include(first_created_issuable.title)
end
end
-
- context 'when the sort in the URL is id_asc' do
- let(:issuable_type) { :issue }
-
- before do
- visit_issues(project, sort: 'id_asc')
- end
-
- it 'shows the sort order as oldest created' do
- expect(find('.issues-other-filters')).to have_content('Oldest created')
- expect(first_issue).to include(first_created_issuable.title)
- expect(last_issue).to include(last_created_issuable.title)
- end
- end
end
def selected_sort_order
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index b2229b44f99..a89dcdf41dc 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -405,7 +405,7 @@ feature 'Issues > Labels bulk assignment' do
end
def update_issues
- click_button 'Update all'
+ find('.update-selected-issues').trigger('click')
wait_for_requests
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 3ea6e1c8863..630d6a10c9c 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -405,20 +405,18 @@ describe 'Filter issues', js: true do
end
context 'sorting' do
- it 'sorts by oldest updated' do
- create(:issue,
+ it 'sorts by created date' do
+ new_issue = create(:issue,
title: '3 days ago',
project: project,
author: user,
- created_at: 3.days.ago,
- updated_at: 3.days.ago)
+ created_at: 3.days.ago)
- old_issue = create(:issue,
+ create(:issue,
title: '5 days ago',
project: project,
author: user,
- created_at: 5.days.ago,
- updated_at: 5.days.ago)
+ created_at: 5.days.ago)
input_filtered_search('days ago')
@@ -427,10 +425,10 @@ describe 'Filter issues', js: true do
sort_toggle = find('.filtered-search-wrapper .dropdown-toggle')
sort_toggle.click
- find('.filtered-search-wrapper .dropdown-menu li a', text: 'Oldest updated').click
+ find('.filtered-search-wrapper .dropdown-menu li a', text: 'Created date').click
wait_for_requests
- expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(old_issue.title)
+ expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(new_issue.title)
end
end
end
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index bcc6e9bab0f..1f57c110c11 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -118,7 +118,7 @@ feature 'Multiple issue updating from issues#index', :js do
end
def click_update_issues_button
- find('.update-selected-issues').click
+ find('.update-selected-issues').trigger('click')
wait_for_requests
end
end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 5c284a1fe5f..fb763c93c66 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -190,19 +190,12 @@ describe 'Issues' do
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
it 'sorts by newest' do
- visit project_issues_path(project, sort: sort_value_recently_created)
+ visit project_issues_path(project, sort: sort_value_created_date)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
- it 'sorts by oldest' do
- visit project_issues_path(project, sort: sort_value_oldest_created)
-
- expect(first_issue).to include('baz')
- expect(last_issue).to include('foo')
- end
-
it 'sorts by most recently updated' do
baz.updated_at = Time.now + 100
baz.save
@@ -211,36 +204,22 @@ describe 'Issues' do
expect(first_issue).to include('baz')
end
- it 'sorts by least recently updated' do
- baz.updated_at = Time.now - 100
- baz.save
- visit project_issues_path(project, sort: sort_value_oldest_updated)
-
- expect(first_issue).to include('baz')
- end
-
describe 'sorting by due date' do
before do
foo.update(due_date: 1.day.from_now)
bar.update(due_date: 6.days.from_now)
end
- it 'sorts by recently due date' do
- visit project_issues_path(project, sort: sort_value_due_date_soon)
+ it 'sorts by due date' do
+ visit project_issues_path(project, sort: sort_value_due_date)
expect(first_issue).to include('foo')
end
- it 'sorts by least recently due date' do
- visit project_issues_path(project, sort: sort_value_due_date_later)
-
- expect(first_issue).to include('bar')
- end
-
- it 'sorts by least recently due date by excluding nil due dates' do
+ it 'sorts by due date by excluding nil due dates' do
bar.update(due_date: nil)
- visit project_issues_path(project, sort: sort_value_due_date_later)
+ visit project_issues_path(project, sort: sort_value_due_date)
expect(first_issue).to include('foo')
end
@@ -339,19 +318,12 @@ describe 'Issues' do
bar.save
end
- it 'sorts by recently due milestone' do
- visit project_issues_path(project, sort: sort_value_milestone_soon)
+ it 'sorts by milestone' do
+ visit project_issues_path(project, sort: sort_value_milestone)
expect(first_issue).to include('foo')
expect(last_issue).to include('baz')
end
-
- it 'sorts by least recently due milestone' do
- visit project_issues_path(project, sort: sort_value_milestone_later)
-
- expect(first_issue).to include('bar')
- expect(last_issue).to include('baz')
- end
end
describe 'combine filter and sort' do
@@ -365,13 +337,11 @@ describe 'Issues' do
end
it 'sorts with a filter applied' do
- visit project_issues_path(project,
- sort: sort_value_oldest_created,
- assignee_id: user2.id)
+ visit project_issues_path(project, sort: sort_value_created_date, assignee_id: user2.id)
- expect(first_issue).to include('bar')
- expect(last_issue).to include('foo')
- expect(page).not_to have_content 'baz'
+ expect(first_issue).to include('foo')
+ expect(last_issue).to include('bar')
+ expect(page).not_to have_content('baz')
end
end
end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index b51ae0890e4..16703bc1c01 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -277,9 +277,9 @@ describe 'Filter merge requests' do
expect_mr_list_count(2)
- click_button 'Last created'
+ click_button 'Created date'
page.within '.dropdown-menu-sort' do
- click_link 'Oldest created'
+ click_link 'Priority'
end
wait_for_requests
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index e6dc284cba7..9cb8a357309 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -127,7 +127,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
def click_update_merge_requests_button
- find('.update-selected-issues').click
+ find('.update-selected-issues').trigger('click')
wait_for_requests
end
end
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 20008b4e7f9..416a0f78a45 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -52,21 +52,13 @@ describe 'Projects > Merge requests > User lists merge requests' do
end
it 'sorts by newest' do
- visit_merge_requests(project, sort: sort_value_recently_created)
+ visit_merge_requests(project, sort: sort_value_created_date)
expect(first_merge_request).to include('fix')
expect(last_merge_request).to include('merge-test')
expect(count_merge_requests).to eq(3)
end
- it 'sorts by oldest' do
- visit_merge_requests(project, sort: sort_value_oldest_created)
-
- expect(first_merge_request).to include('merge-test')
- expect(last_merge_request).to include('fix')
- expect(count_merge_requests).to eq(3)
- end
-
it 'sorts by last updated' do
visit_merge_requests(project, sort: sort_value_recently_updated)
@@ -74,33 +66,19 @@ describe 'Projects > Merge requests > User lists merge requests' do
expect(count_merge_requests).to eq(3)
end
- it 'sorts by oldest updated' do
- visit_merge_requests(project, sort: sort_value_oldest_updated)
-
- expect(first_merge_request).to include('markdown')
- expect(count_merge_requests).to eq(3)
- end
-
- it 'sorts by milestone due soon' do
- visit_merge_requests(project, sort: sort_value_milestone_soon)
+ it 'sorts by milestone' do
+ visit_merge_requests(project, sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(3)
end
- it 'sorts by milestone due later' do
- visit_merge_requests(project, sort: sort_value_milestone_later)
-
- expect(first_merge_request).to include('markdown')
- expect(count_merge_requests).to eq(3)
- end
-
- it 'filters on one label and sorts by due soon' do
+ it 'filters on one label and sorts by due date' do
label = create(:label, project: project)
create(:label_link, label: label, target: @fix)
visit_merge_requests(project, label_name: [label.name],
- sort: sort_value_due_date_soon)
+ sort: sort_value_due_date)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
@@ -115,9 +93,9 @@ describe 'Projects > Merge requests > User lists merge requests' do
create(:label_link, label: label2, target: @fix)
end
- it 'sorts by due soon' do
+ it 'sorts by due date' do
visit_merge_requests(project, label_name: [label.name, label2.name],
- sort: sort_value_due_date_soon)
+ sort: sort_value_due_date)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
@@ -127,7 +105,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
it 'sorts by due soon' do
visit_merge_requests(project, label_name: [label.name, label2.name],
assignee_id: user.id,
- sort: sort_value_due_date_soon)
+ sort: sort_value_due_date)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
@@ -137,7 +115,7 @@ describe 'Projects > Merge requests > User lists merge requests' do
visit project_merge_requests_path(project,
label_name: [label.name, label2.name],
assignee_id: user.id,
- sort: sort_value_milestone_soon)
+ sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
end
diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb
new file mode 100644
index 00000000000..f0fe4e00acc
--- /dev/null
+++ b/spec/features/projects/commit/diff_notes_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+feature 'Commit diff', :js do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+
+ before do
+ project.add_master(user)
+ sign_in user
+ end
+
+ %w(inline parallel).each do |view|
+ context "#{view} view" do
+ before do
+ visit project_commit_path(project, sample_commit.id, view: view)
+ end
+
+ it "adds comment to diff" do
+ diff_line_num = first('.diff-line-num.new')
+
+ diff_line_num.trigger('mouseover')
+ diff_line_num.find('.js-add-diff-note-button').trigger('click')
+
+ page.within(first('.diff-viewer')) do
+ find('.js-note-text').set 'test comment'
+
+ click_button 'Comment'
+
+ expect(page).to have_content('test comment')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json
index b7b2535c204..88a3cad62f6 100644
--- a/spec/fixtures/api/schemas/public_api/v4/commit/detail.json
+++ b/spec/fixtures/api/schemas/public_api/v4/commit/detail.json
@@ -5,11 +5,18 @@
{
"required" : [
"stats",
- "status"
+ "status",
+ "last_pipeline"
],
"properties": {
"stats": { "$ref": "../commit_stats.json" },
- "status": { "type": ["string", "null"] }
+ "status": { "type": ["string", "null"] },
+ "last_pipeline": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "../pipeline/basic.json" }
+ ]
+ }
}
}
]
diff --git a/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
new file mode 100644
index 00000000000..0d127dc5297
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/pipeline/basic.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "required" : [
+ "id",
+ "sha",
+ "ref",
+ "status"
+ ],
+ "properties" : {
+ "id": { "type": "integer" },
+ "sha": { "type": "string" },
+ "ref": { "type": "string" },
+ "status": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 4632c679972..f44e7ef6843 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -26,12 +26,13 @@ describe AvatarsHelper do
subject { helper.user_avatar_without_link(options) }
it 'displays user avatar' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 has-tooltip lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: avatar_icon(user, 16) }
+ src: avatar_icon(user, 16),
+ data: { container: 'body' },
+ class: 'avatar s16 has-tooltip',
+ title: user.name
)
end
@@ -39,12 +40,13 @@ describe AvatarsHelper do
let(:options) { { user: user, css_class: '.cat-pics' } }
it 'uses provided css_class' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: "avatar s16 #{options[:css_class]} has-tooltip lazy",
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: avatar_icon(user, 16) }
+ src: avatar_icon(user, 16),
+ data: { container: 'body' },
+ class: "avatar s16 #{options[:css_class]} has-tooltip",
+ title: user.name
)
end
end
@@ -53,12 +55,13 @@ describe AvatarsHelper do
let(:options) { { user: user, size: 99 } }
it 'uses provided size' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: "avatar s#{options[:size]} has-tooltip lazy",
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: avatar_icon(user, options[:size]) }
+ src: avatar_icon(user, options[:size]),
+ data: { container: 'body' },
+ class: "avatar s#{options[:size]} has-tooltip",
+ title: user.name
)
end
end
@@ -67,12 +70,28 @@ describe AvatarsHelper do
let(:options) { { user: user, url: '/over/the/rainbow.png' } }
it 'uses provided url' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 has-tooltip lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: options[:url] }
+ src: options[:url],
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: user.name
+ )
+ end
+ end
+
+ context 'with lazy parameter' do
+ let(:options) { { user: user, lazy: true } }
+
+ it 'adds `lazy` class to class list, sets `data-src` with avatar URL and `src` with placeholder image' do
+ is_expected.to eq tag(
+ :img,
+ alt: "#{user.name}'s avatar",
+ src: LazyImageTagHelper.placeholder_image,
+ data: { container: 'body', src: avatar_icon(user, 16) },
+ class: "avatar s16 has-tooltip lazy",
+ title: user.name
)
end
end
@@ -82,12 +101,13 @@ describe AvatarsHelper do
let(:options) { { user: user, has_tooltip: true } }
it 'adds has-tooltip' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 has-tooltip lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: avatar_icon(user, 16) }
+ src: avatar_icon(user, 16),
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: user.name
)
end
end
@@ -96,12 +116,12 @@ describe AvatarsHelper do
let(:options) { { user: user, has_tooltip: false } }
it 'does not add has-tooltip or data container' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { src: avatar_icon(user, 16) }
+ src: avatar_icon(user, 16),
+ class: "avatar s16",
+ title: user.name
)
end
end
@@ -114,23 +134,25 @@ describe AvatarsHelper do
let(:options) { { user: user, user_name: 'Tinky Winky' } }
it 'prefers user parameter' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 has-tooltip lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{user.name}'s avatar",
- title: user.name,
- data: { container: 'body', src: avatar_icon(user, 16) }
+ src: avatar_icon(user, 16),
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: user.name
)
end
end
it 'uses user_name and user_email parameter if user is not present' do
- is_expected.to eq image_tag(
- LazyImageTagHelper.placeholder_image,
- class: 'avatar s16 has-tooltip lazy',
+ is_expected.to eq tag(
+ :img,
alt: "#{options[:user_name]}'s avatar",
- title: options[:user_name],
- data: { container: 'body', src: avatar_icon(options[:user_email], 16) }
+ src: avatar_icon(options[:user_email], 16),
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: options[:user_name]
)
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index a76c75e0c08..7ded95d01af 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -420,22 +420,26 @@ describe ProjectsHelper do
end
end
- describe '#has_projects_or_name?' do
+ describe '#show_projects' do
let(:projects) do
create(:project)
Project.all
end
it 'returns true when there are projects' do
- expect(helper.has_projects_or_name?(projects, {})).to eq(true)
+ expect(helper.show_projects?(projects, {})).to eq(true)
end
it 'returns true when there are no projects but a name is given' do
- expect(helper.has_projects_or_name?(Project.none, name: 'foo')).to eq(true)
+ expect(helper.show_projects?(Project.none, name: 'foo')).to eq(true)
+ end
+
+ it 'returns true when there are no projects but personal is present' do
+ expect(helper.show_projects?(Project.none, personal: 'true')).to eq(true)
end
it 'returns false when there are no projects and there is no name' do
- expect(helper.has_projects_or_name?(Project.none, {})).to eq(false)
+ expect(helper.show_projects?(Project.none, {})).to eq(false)
end
end
diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
index 2fb9eb0ca85..13e9fe00a00 100644
--- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
-import limitWarningComp from '~/cycle_analytics/components/limit_warning_component';
+import limitWarningComp from '~/cycle_analytics/components/limit_warning_component.vue';
Vue.use(Translate);
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index f4b4d7980a4..4f20e31f511 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -73,6 +73,12 @@ describe('Fly out sidebar navigation', () => {
).toBe(0);
});
+ it('returns 0 if mousePos is empty', () => {
+ expect(
+ getHideSubItemsInterval(),
+ ).toBe(0);
+ });
+
it('returns 0 when mouse above sub-items', () => {
showSubLevelItems(el);
documentMouseMove({
diff --git a/spec/javascripts/lib/utils/sticky_spec.js b/spec/javascripts/lib/utils/sticky_spec.js
index c3ee3ef9825..b87c836654d 100644
--- a/spec/javascripts/lib/utils/sticky_spec.js
+++ b/spec/javascripts/lib/utils/sticky_spec.js
@@ -1,52 +1,79 @@
import { isSticky } from '~/lib/utils/sticky';
describe('sticky', () => {
- const el = {
- offsetTop: 0,
- classList: {},
- };
+ let el;
beforeEach(() => {
- el.offsetTop = 0;
- el.classList.add = jasmine.createSpy('spy');
- el.classList.remove = jasmine.createSpy('spy');
+ document.body.innerHTML += `
+ <div class="parent">
+ <div id="js-sticky"></div>
+ </div>
+ `;
+
+ el = document.getElementById('js-sticky');
});
- describe('classList.remove', () => {
- it('does not call classList.remove when stuck', () => {
- isSticky(el, 0, 0);
+ afterEach(() => {
+ el.parentNode.remove();
+ });
+
+ describe('when stuck', () => {
+ it('does not remove is-stuck class', () => {
+ isSticky(el, 0, el.offsetTop);
+ isSticky(el, 0, el.offsetTop);
expect(
- el.classList.remove,
- ).not.toHaveBeenCalled();
+ el.classList.contains('is-stuck'),
+ ).toBeTruthy();
});
- it('calls classList.remove when not stuck', () => {
- el.offsetTop = 10;
- isSticky(el, 0, 0);
+ it('adds is-stuck class', () => {
+ isSticky(el, 0, el.offsetTop);
expect(
- el.classList.remove,
- ).toHaveBeenCalledWith('is-stuck');
+ el.classList.contains('is-stuck'),
+ ).toBeTruthy();
+ });
+
+ it('inserts placeholder element', () => {
+ isSticky(el, 0, el.offsetTop, true);
+
+ expect(
+ document.querySelector('.sticky-placeholder'),
+ ).not.toBeNull();
});
});
- describe('classList.add', () => {
- it('calls classList.add when stuck', () => {
+ describe('when not stuck', () => {
+ it('removes is-stuck class', () => {
+ spyOn(el.classList, 'remove').and.callThrough();
+
+ isSticky(el, 0, el.offsetTop);
isSticky(el, 0, 0);
expect(
- el.classList.add,
+ el.classList.remove,
).toHaveBeenCalledWith('is-stuck');
+ expect(
+ el.classList.contains('is-stuck'),
+ ).toBeFalsy();
});
- it('does not call classList.add when not stuck', () => {
- el.offsetTop = 10;
+ it('does not add is-stuck class', () => {
isSticky(el, 0, 0);
expect(
- el.classList.add,
- ).not.toHaveBeenCalled();
+ el.classList.contains('is-stuck'),
+ ).toBeFalsy();
+ });
+
+ it('removes placeholder', () => {
+ isSticky(el, 0, el.offsetTop, true);
+ isSticky(el, 0, 0, true);
+
+ expect(
+ document.querySelector('.sticky-placeholder'),
+ ).toBeNull();
});
});
});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index aee274641e8..645664a5219 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -18,19 +18,25 @@ import '~/line_highlighter';
beforeEach(function() {
loadFixtures('static/line_highlighter.html.raw');
this["class"] = new LineHighlighter();
- this.css = this["class"].highlightClass;
+ this.css = this["class"].highlightLineClass;
return this.spies = {
__setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {})
};
});
describe('behavior', function() {
it('highlights one line given in the URL hash', function() {
- new LineHighlighter('#L13');
+ new LineHighlighter({ hash: '#L13' });
return expect($('#LC13')).toHaveClass(this.css);
});
+ it('highlights one line given in the URL hash with given CSS class name', function() {
+ const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' });
+ expect(hiliter.highlightLineClass).toBe('hilite');
+ expect($('#LC13')).toHaveClass('hilite');
+ expect($('#LC13')).not.toHaveClass('hll');
+ });
it('highlights a range of lines given in the URL hash', function() {
var line, results;
- new LineHighlighter('#L5-25');
+ new LineHighlighter({ hash: '#L5-25' });
expect($("." + this.css).length).toBe(21);
results = [];
for (line = 5; line <= 25; line += 1) {
@@ -41,7 +47,7 @@ import '~/line_highlighter';
it('scrolls to the first highlighted line on initial load', function() {
var spy;
spy = spyOn($, 'scrollTo');
- new LineHighlighter('#L5-25');
+ new LineHighlighter({ hash: '#L5-25' });
return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything());
});
it('discards click events', function() {
@@ -50,10 +56,10 @@ import '~/line_highlighter';
clickLine(13);
return expect(spy).toHaveBeenPrevented();
});
- return it('handles garbage input from the hash', function() {
+ it('handles garbage input from the hash', function() {
var func;
func = function() {
- return new LineHighlighter('#blob-content-holder');
+ return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' });
};
return expect(func).not.toThrow();
});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index eadab738376..ccdbfcba692 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -416,5 +416,28 @@ import 'vendor/jquery.scrollTo';
});
});
});
+
+ describe('expandViewContainer', function () {
+ beforeEach(() => {
+ $('body').append('<div class="content-wrapper"><div class="container-fluid container-limited"></div></div>');
+ });
+
+ afterEach(() => {
+ $('.content-wrapper').remove();
+ });
+
+ it('removes container-limited from containers', function () {
+ this.class.expandViewContainer();
+
+ expect($('.content-wrapper')).not.toContainElement('.container-limited');
+ });
+
+ it('does remove container-limited from breadcrumbs', function () {
+ $('.container-limited').addClass('breadcrumbs');
+ this.class.expandViewContainer();
+
+ expect($('.content-wrapper')).toContainElement('.container-limited');
+ });
+ });
});
}).call(window);
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 01ceb21dfaa..5f41e28fece 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -47,9 +47,11 @@ describe Banzai::Filter::SanitizationFilter do
describe 'custom whitelist' do
it 'customizes the whitelist only once' do
instance = described_class.new('Foo')
+ control_count = instance.whitelist[:transformers].size
+
3.times { instance.whitelist }
- expect(instance.whitelist[:transformers].size).to eq 5
+ expect(instance.whitelist[:transformers].size).to eq control_count
end
it 'sanitizes `class` attribute from all elements' do
@@ -101,16 +103,18 @@ describe Banzai::Filter::SanitizationFilter do
expect(filter(act).to_html).to eq exp
end
- it 'disallows the `name` attribute globally' do
+ it 'disallows the `name` attribute globally, allows on `a`' do
html = <<~HTML
<img name="getElementById" src="">
<span name="foo" class="bar">Hi</span>
+ <a name="foo" class="bar">Bye</a>
HTML
doc = filter(html)
expect(doc.at_css('img')).not_to have_attribute('name')
expect(doc.at_css('span')).not_to have_attribute('name')
+ expect(doc.at_css('a')).to have_attribute('name')
end
it 'allows `summary` elements' do
diff --git a/spec/lib/github/client_spec.rb b/spec/lib/github/client_spec.rb
new file mode 100644
index 00000000000..b846096fe25
--- /dev/null
+++ b/spec/lib/github/client_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Github::Client do
+ let(:connection) { spy }
+ let(:rate_limit) { double(get: [false, 1]) }
+ let(:client) { described_class.new({}) }
+ let(:results) { double }
+ let(:response) { double }
+
+ before do
+ allow(Faraday).to receive(:new).and_return(connection)
+ allow(Github::RateLimit).to receive(:new).with(connection).and_return(rate_limit)
+ end
+
+ describe '#get' do
+ before do
+ allow(Github::Response).to receive(:new).with(results).and_return(response)
+ end
+
+ it 'uses a default per_page param' do
+ expect(connection).to receive(:get).with('/foo', per_page: 100).and_return(results)
+
+ expect(client.get('/foo')).to eq(response)
+ end
+
+ context 'with per_page given' do
+ it 'overwrites the default per_page' do
+ expect(connection).to receive(:get).with('/foo', per_page: 30).and_return(results)
+
+ expect(client.get('/foo', per_page: 30)).to eq(response)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
new file mode 100644
index 00000000000..f54e2326b06
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Create do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+
+ let(:pipeline) do
+ build(:ci_pipeline_with_one_job, project: project,
+ ref: 'master')
+ end
+
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ seeds_block: nil)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ before do
+ step.perform!
+ end
+
+ context 'when pipeline is ready to be saved' do
+ it 'saves a pipeline' do
+ expect(pipeline).to be_persisted
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to be false
+ end
+
+ it 'creates stages' do
+ expect(pipeline.reload.stages).to be_one
+ end
+ end
+
+ context 'when pipeline has validation errors' do
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, ref: nil)
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+
+ it 'appends validation error' do
+ expect(pipeline.errors.to_a)
+ .to include /Failed to persist the pipeline/
+ end
+ end
+
+ context 'when there is a seed block present' do
+ let(:seeds) { spy('pipeline seeds') }
+
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ seeds_block: seeds)
+ end
+
+ it 'executes the block' do
+ expect(seeds).to have_received(:call).with(pipeline)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
new file mode 100644
index 00000000000..e165e0fac2a
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Sequence do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+
+ let(:pipeline) { build_stubbed(:ci_pipeline) }
+ let(:command) { double('command' ) }
+ let(:first_step) { spy('first step') }
+ let(:second_step) { spy('second step') }
+ let(:sequence) { [first_step, second_step] }
+
+ subject do
+ described_class.new(pipeline, command, sequence)
+ end
+
+ context 'when one of steps breaks the chain' do
+ before do
+ allow(first_step).to receive(:break?).and_return(true)
+ end
+
+ it 'does not process the second step' do
+ subject.build! do |pipeline, sequence|
+ expect(sequence).not_to be_complete
+ end
+
+ expect(second_step).not_to have_received(:perform!)
+ end
+
+ it 'returns a pipeline object' do
+ expect(subject.build!).to eq pipeline
+ end
+ end
+
+ context 'when all chains are executed correctly' do
+ before do
+ sequence.each do |step|
+ allow(step).to receive(:break?).and_return(false)
+ end
+ end
+
+ it 'iterates through entire sequence' do
+ subject.build! do |pipeline, sequence|
+ expect(sequence).to be_complete
+ end
+
+ expect(first_step).to have_received(:perform!)
+ expect(second_step).to have_received(:perform!)
+ end
+
+ it 'returns a pipeline object' do
+ expect(subject.build!).to eq pipeline
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
new file mode 100644
index 00000000000..32bd5de829b
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -0,0 +1,85 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Skip do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ ignore_skip_ci: false,
+ save_incompleted: true)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ context 'when pipeline has been skipped by a user' do
+ before do
+ allow(pipeline).to receive(:git_commit_message)
+ .and_return('commit message [ci skip]')
+
+ step.perform!
+ end
+
+ it 'should break the chain' do
+ expect(step.break?).to be true
+ end
+
+ it 'skips the pipeline' do
+ expect(pipeline.reload).to be_skipped
+ end
+ end
+
+ context 'when pipeline has not been skipped' do
+ before do
+ step.perform!
+ end
+
+ it 'should not break the chain' do
+ expect(step.break?).to be false
+ end
+
+ it 'should not skip a pipeline chain' do
+ expect(pipeline.reload).not_to be_skipped
+ end
+ end
+
+ context 'when [ci skip] should be ignored' do
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ ignore_skip_ci: true)
+ end
+
+ it 'does not break the chain' do
+ step.perform!
+
+ expect(step.break?).to be false
+ end
+ end
+
+ context 'when pipeline should be skipped but not persisted' do
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ ignore_skip_ci: false,
+ save_incompleted: false)
+ end
+
+ before do
+ allow(pipeline).to receive(:git_commit_message)
+ .and_return('commit message [ci skip]')
+
+ step.perform!
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+
+ it 'does not skip pipeline' do
+ expect(pipeline.reload).not_to be_skipped
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
new file mode 100644
index 00000000000..0bbdd23f4d6
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -0,0 +1,142 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ let(:pipeline) do
+ build_stubbed(:ci_pipeline, ref: ref, project: project)
+ end
+
+ let(:command) do
+ double('command', project: project, current_user: user)
+ end
+
+ let(:step) { described_class.new(pipeline, command) }
+
+ let(:ref) { 'master' }
+
+ context 'when users has no ability to run a pipeline' do
+ before do
+ step.perform!
+ end
+
+ it 'adds an error about insufficient permissions' do
+ expect(pipeline.errors.to_a)
+ .to include /Insufficient permissions/
+ end
+
+ it 'breaks the pipeline builder chain' do
+ expect(step.break?).to eq true
+ end
+ end
+
+ context 'when user has ability to create a pipeline' do
+ before do
+ project.add_developer(user)
+
+ step.perform!
+ end
+
+ it 'does not invalidate the pipeline' do
+ expect(pipeline).to be_valid
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to eq false
+ end
+ end
+
+ describe '#allowed_to_create?' do
+ subject { step.allowed_to_create? }
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to merge' do
+ let!(:protected_branch) do
+ create(:protected_branch,
+ :developers_can_merge,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+
+ context 'when developers are allowed to create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :developers_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when the branch is protected' do
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the tag is protected' do
+ let(:ref) { 'v1.0.0' }
+
+ let!(:protected_tag) do
+ create(:protected_tag, project: project, name: ref)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when no one can create the tag' do
+ let!(:protected_tag) do
+ create(:protected_tag,
+ :no_one_can_create,
+ project: project,
+ name: ref)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ context 'when owner cannot create pipeline' do
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
new file mode 100644
index 00000000000..3740df88f42
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -0,0 +1,126 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
+ set(:project) { create(:project) }
+ set(:user) { create(:user) }
+
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ save_incompleted: true)
+ end
+
+ let!(:step) { described_class.new(pipeline, command) }
+
+ before do
+ step.perform!
+ end
+
+ context 'when pipeline has no YAML configuration' do
+ let(:pipeline) do
+ build_stubbed(:ci_pipeline, project: project)
+ end
+
+ it 'appends errors about missing configuration' do
+ expect(pipeline.errors.to_a)
+ .to include 'Missing .gitlab-ci.yml file'
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+ end
+
+ context 'when YAML configuration contains errors' do
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, config: 'invalid YAML')
+ end
+
+ it 'appends errors about YAML errors' do
+ expect(pipeline.errors.to_a)
+ .to include 'Invalid configuration format'
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+
+ context 'when saving incomplete pipeline is allowed' do
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ save_incompleted: true)
+ end
+
+ it 'fails the pipeline' do
+ expect(pipeline.reload).to be_failed
+ end
+ end
+
+ context 'when saving incomplete pipeline is not allowed' do
+ let(:command) do
+ double('command', project: project,
+ current_user: user,
+ save_incompleted: false)
+ end
+
+ it 'does not drop pipeline' do
+ expect(pipeline).not_to be_failed
+ expect(pipeline).not_to be_persisted
+ end
+ end
+ end
+
+ context 'when pipeline has no stages / jobs' do
+ let(:config) do
+ { rspec: {
+ script: 'ls',
+ only: ['something']
+ } }
+ end
+
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, config: config)
+ end
+
+ it 'appends an error about missing stages' do
+ expect(pipeline.errors.to_a)
+ .to include 'No stages / jobs for this pipeline.'
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+ end
+
+ context 'when pipeline contains configuration validation errors' do
+ let(:config) { { rspec: {} } }
+
+ let(:pipeline) do
+ build(:ci_pipeline, project: project, config: config)
+ end
+
+ it 'appends configuration validation errors to pipeline errors' do
+ expect(pipeline.errors.to_a)
+ .to include "jobs:rspec config can't be blank"
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+ end
+
+ context 'when pipeline is correct and complete' do
+ let(:pipeline) do
+ build(:ci_pipeline_with_one_job, project: project)
+ end
+
+ it 'does not invalidate the pipeline' do
+ expect(pipeline).to be_valid
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to be false
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
new file mode 100644
index 00000000000..bb356efe9ad
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+
+ let(:command) do
+ double('command', project: project, current_user: user)
+ end
+
+ let!(:step) { described_class.new(pipeline, command) }
+
+ before do
+ step.perform!
+ end
+
+ context 'when pipeline ref and sha exists' do
+ let(:pipeline) do
+ build_stubbed(:ci_pipeline, ref: 'master', sha: '123', project: project)
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to be false
+ end
+
+ it 'does not append pipeline errors' do
+ expect(pipeline.errors).to be_empty
+ end
+ end
+
+ context 'when pipeline ref does not exist' do
+ let(:pipeline) do
+ build_stubbed(:ci_pipeline, ref: 'something', project: project)
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+
+ it 'adds an error about missing ref' do
+ expect(pipeline.errors.to_a)
+ .to include 'Reference not found'
+ end
+ end
+
+ context 'when pipeline does not have SHA set' do
+ let(:pipeline) do
+ build_stubbed(:ci_pipeline, ref: 'master', sha: nil, project: project)
+ end
+
+ it 'breaks the chain' do
+ expect(step.break?).to be true
+ end
+
+ it 'adds an error about missing SHA' do
+ expect(pipeline.errors.to_a)
+ .to include 'Commit not found'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index b26728a843c..7c9836e2da6 100644
--- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Ci::PipelineDuration do
+describe Gitlab::Ci::Pipeline::Duration do
let(:calculated_duration) { calculate(data) }
shared_examples 'calculating duration' do
@@ -107,9 +107,9 @@ describe Gitlab::Ci::PipelineDuration do
def calculate(data)
periods = data.shuffle.map do |(first, last)|
- Gitlab::Ci::PipelineDuration::Period.new(first, last)
+ described_class::Period.new(first, last)
end
- Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first))
+ described_class.from_periods(periods.sort_by(&:first))
end
end
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index c73708d90a8..f9bfb4c469e 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -3,6 +3,61 @@ require 'spec_helper'
describe Gitlab::Diff::DiffRefs do
let(:project) { create(:project, :repository) }
+ describe '#==' do
+ let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
+ subject { commit.diff_refs }
+
+ context 'when shas are missing' do
+ let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: nil) }
+
+ it 'returns false' do
+ expect(subject).not_to eq(other)
+ end
+ end
+
+ context 'when shas are equal' do
+ let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: subject.head_sha) }
+
+ it 'returns true' do
+ expect(subject).to eq(other)
+ end
+ end
+
+ context 'when shas are unequal' do
+ let(:other) { described_class.new(base_sha: subject.base_sha, start_sha: subject.start_sha, head_sha: subject.head_sha.reverse) }
+
+ it 'returns false' do
+ expect(subject).not_to eq(other)
+ end
+ end
+
+ context 'when shas are truncated' do
+ context 'when sha prefixes are too short' do
+ let(:other) { described_class.new(base_sha: subject.base_sha[0, 4], start_sha: subject.start_sha[0, 4], head_sha: subject.head_sha[0, 4]) }
+
+ it 'returns false' do
+ expect(subject).not_to eq(other)
+ end
+ end
+
+ context 'when sha prefixes are equal' do
+ let(:other) { described_class.new(base_sha: subject.base_sha[0, 10], start_sha: subject.start_sha[0, 10], head_sha: subject.head_sha[0, 10]) }
+
+ it 'returns true' do
+ expect(subject).to eq(other)
+ end
+ end
+
+ context 'when sha prefixes are unequal' do
+ let(:other) { described_class.new(base_sha: subject.base_sha[0, 10], start_sha: subject.start_sha[0, 10], head_sha: subject.head_sha[0, 10].reverse) }
+
+ it 'returns false' do
+ expect(subject).not_to eq(other)
+ end
+ end
+ end
+ end
+
describe '#compare_in' do
context 'with diff refs for the initial commit' do
let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index d4a2a852c12..7798736a4dc 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -429,6 +429,44 @@ describe Gitlab::Diff::Position do
end
end
+ describe '#==' do
+ let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
+
+ subject do
+ described_class.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ context 'when positions are equal' do
+ let(:other) { described_class.new(subject.to_h) }
+
+ it 'returns true' do
+ expect(subject).to eq(other)
+ end
+ end
+
+ context 'when positions are equal, except for truncated shas' do
+ let(:other) { described_class.new(subject.to_h.merge(start_sha: subject.start_sha[0, 10])) }
+
+ it 'returns true' do
+ expect(subject).to eq(other)
+ end
+ end
+
+ context 'when positions are unequal' do
+ let(:other) { described_class.new(subject.to_h.merge(start_sha: subject.start_sha.reverse)) }
+
+ it 'returns false' do
+ expect(subject).not_to eq(other)
+ end
+ end
+ end
+
describe "#to_json" do
let(:hash) do
{
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 46e968cc398..a3dff6d0d4b 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -181,7 +181,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
- describe '.where' do
+ shared_examples '.where' do
context 'path is empty string' do
subject do
commits = described_class.where(
@@ -279,6 +279,14 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
+ describe '.where with gitaly' do
+ it_should_behave_like '.where'
+ end
+
+ describe '.where without gitaly', skip_gitaly_mock: true do
+ it_should_behave_like '.where'
+ end
+
describe '.between' do
subject do
commits = described_class.between(repository, SeedRepo::Commit::PARENT_ID, SeedRepo::Commit::ID)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 4fc26c625a5..5effaf2b043 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1332,6 +1332,84 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
+ describe '#with_repo_branch_commit' do
+ context 'when comparing with the same repository' do
+ let(:start_repository) { repository }
+
+ context 'when the branch exists' do
+ let(:start_branch_name) { 'master' }
+
+ it 'yields the commit' do
+ expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
+ .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
+ end
+ end
+
+ context 'when the branch does not exist' do
+ let(:start_branch_name) { 'definitely-not-master' }
+
+ it 'yields nil' do
+ expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
+ .to yield_with_args(nil)
+ end
+ end
+ end
+
+ context 'when comparing with another repository' do
+ let(:start_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+
+ context 'when the branch exists' do
+ let(:start_branch_name) { 'master' }
+
+ it 'yields the commit' do
+ expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
+ .to yield_with_args(an_instance_of(Gitlab::Git::Commit))
+ end
+ end
+
+ context 'when the branch does not exist' do
+ let(:start_branch_name) { 'definitely-not-master' }
+
+ it 'yields nil' do
+ expect { |b| repository.with_repo_branch_commit(start_repository, start_branch_name, &b) }
+ .to yield_with_args(nil)
+ end
+ end
+ end
+ end
+
+ describe '#fetch_source_branch' do
+ let(:local_ref) { 'refs/merge-requests/1/head' }
+
+ context 'when the branch exists' do
+ let(:source_branch) { 'master' }
+
+ it 'writes the ref' do
+ expect(repository).to receive(:write_ref).with(local_ref, /\h{40}/)
+
+ repository.fetch_source_branch(repository, source_branch, local_ref)
+ end
+
+ it 'returns true' do
+ expect(repository.fetch_source_branch(repository, source_branch, local_ref)).to eq(true)
+ end
+ end
+
+ context 'when the branch does not exist' do
+ let(:source_branch) { 'definitely-not-master' }
+
+ it 'does not write the ref' do
+ expect(repository).not_to receive(:write_ref)
+
+ repository.fetch_source_branch(repository, source_branch, local_ref)
+ end
+
+ it 'returns false' do
+ expect(repository.fetch_source_branch(repository, source_branch, local_ref)).to eq(false)
+ end
+ end
+ end
+
def create_remote_branch(repository, remote_name, branch_name, source_branch_name)
source_branch = repository.branches.find { |branch| branch.name == source_branch_name }
rugged = repository.rugged
diff --git a/spec/lib/system_check/base_check_spec.rb b/spec/lib/system_check/base_check_spec.rb
new file mode 100644
index 00000000000..faf8c99e772
--- /dev/null
+++ b/spec/lib/system_check/base_check_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe SystemCheck::BaseCheck do
+ context 'helpers on instance level' do
+ it 'responds to SystemCheck::Helpers methods' do
+ expect(subject).to respond_to :fix_and_rerun, :for_more_information, :see_installation_guide_section,
+ :finished_checking, :start_checking, :try_fixing_it, :sanitized_message, :should_sanitize?, :omnibus_gitlab?,
+ :sudo_gitlab
+ end
+
+ it 'responds to Gitlab::TaskHelpers methods' do
+ expect(subject).to respond_to :ask_to_continue, :os_name, :prompt, :run_and_match, :run_command,
+ :run_command!, :uid_for, :gid_for, :gitlab_user, :gitlab_user?, :warn_user_is_not_gitlab, :all_repos,
+ :repository_storage_paths_args, :user_home, :checkout_or_clone_version, :clone_repo, :checkout_version
+ end
+ end
+end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 2e686e515c5..584dfe9a5c1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -183,75 +183,42 @@ describe Ci::Runner do
end
end
- context 'when runner is locked' do
+ context 'when runner is shared' do
before do
- runner.locked = true
+ runner.is_shared = true
+ build.project.runners = []
end
- shared_examples 'locked build picker' do
- context 'when runner cannot pick untagged jobs' do
- before do
- runner.run_untagged = false
- end
+ it 'can handle builds' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
- it 'cannot handle builds without tags' do
- expect(runner.can_pick?(build)).to be_falsey
- end
+ context 'when runner is locked' do
+ before do
+ runner.locked = true
end
- context 'when having runner tags' do
- before do
- runner.tag_list = %w(bb cc)
- end
-
- it 'cannot handle it for builds without matching tags' do
- build.tag_list = ['aa']
-
- expect(runner.can_pick?(build)).to be_falsey
- end
+ it 'can handle builds' do
+ expect(runner.can_pick?(build)).to be_truthy
end
end
+ end
- context 'when serving the same project' do
- it 'can handle it' do
+ context 'when runner is not shared' do
+ context 'when runner is assigned to a project' do
+ it 'can handle builds' do
expect(runner.can_pick?(build)).to be_truthy
end
-
- it_behaves_like 'locked build picker'
-
- context 'when having runner tags' do
- before do
- runner.tag_list = %w(bb cc)
- build.tag_list = ['bb']
- end
-
- it 'can handle it for matching tags' do
- expect(runner.can_pick?(build)).to be_truthy
- end
- end
end
- context 'serving a different project' do
+ context 'when runner is not assigned to a project' do
before do
- runner.runner_projects.destroy_all
+ build.project.runners = []
end
- it 'cannot handle it' do
+ it 'cannot handle builds' do
expect(runner.can_pick?(build)).to be_falsey
end
-
- it_behaves_like 'locked build picker'
-
- context 'when having runner tags' do
- before do
- runner.tag_list = %w(bb cc)
- build.tag_list = ['bb']
- end
-
- it 'cannot handle it for matching tags' do
- expect(runner.can_pick?(build)).to be_falsey
- end
- end
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index c0cbdeed03d..f2593a1a75c 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe ProjectPolicy do
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
- let(:dev) { create(:user) }
- let(:master) { create(:user) }
- let(:owner) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:guest) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:master) { create(:user) }
+ set(:owner) { create(:user) }
+ set(:admin) { create(:admin) }
let(:project) { create(:project, :public, namespace: owner.namespace) }
- let(:guest_permissions) do
+ let(:base_guest_permissions) do
%i[
read_project read_board read_list read_wiki read_issue read_label
read_milestone read_project_snippet read_project_member
@@ -18,7 +18,7 @@ describe ProjectPolicy do
]
end
- let(:reporter_permissions) do
+ let(:base_reporter_permissions) do
%i[
download_code fork_project create_project_snippet update_issue
admin_issue admin_label admin_list read_commit_status read_build
@@ -41,7 +41,7 @@ describe ProjectPolicy do
]
end
- let(:master_permissions) do
+ let(:base_master_permissions) do
%i[
delete_protected_branch update_project_snippet update_environment
update_deployment admin_project_snippet
@@ -66,11 +66,20 @@ describe ProjectPolicy do
]
end
+ # Used in EE specs
+ let(:additional_guest_permissions) { [] }
+ let(:additional_reporter_permissions) { [] }
+ let(:additional_master_permissions) { [] }
+
+ let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
+ let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }
+ let(:master_permissions) { base_master_permissions + additional_master_permissions }
+
before do
- project.team << [guest, :guest]
- project.team << [master, :master]
- project.team << [dev, :developer]
- project.team << [reporter, :reporter]
+ project.add_guest(guest)
+ project.add_master(master)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
end
def expect_allowed(*permissions)
@@ -127,38 +136,41 @@ describe ProjectPolicy do
end
end
- context 'when a project has pending invites, and the current user is anonymous' do
- let(:group) { create(:group, :public) }
- let(:project) { create(:project, :public, namespace: group) }
- let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
- let(:anonymous_permissions) { guest_permissions - user_permissions }
+ shared_examples 'project policies as anonymous' do
+ context 'abilities for public projects' do
+ context 'when a project has pending invites' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, namespace: group) }
+ let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
- subject { described_class.new(nil, project) }
+ subject { described_class.new(nil, project) }
- before do
- create(:group_member, :invited, group: group)
- end
+ before do
+ create(:group_member, :invited, group: group)
+ end
- it 'does not grant owner access' do
- expect_allowed(*anonymous_permissions)
- expect_disallowed(*user_permissions)
+ it 'does not grant owner access' do
+ expect_allowed(*anonymous_permissions)
+ expect_disallowed(*user_permissions)
+ end
+ end
end
- end
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
- subject { described_class.new(current_user, project) }
-
- context 'with no user' do
- let(:current_user) { nil }
+ subject { described_class.new(nil, project) }
it { is_expected.to be_banned }
end
+ end
- context 'guests' do
- let(:current_user) { guest }
+ shared_examples 'project policies as guest' do
+ subject { described_class.new(guest, project) }
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
let(:reporter_public_build_permissions) do
reporter_permissions - [:read_build, :read_pipeline]
end
@@ -179,7 +191,7 @@ describe ProjectPolicy do
end
end
- context 'public builds disabled' do
+ context 'when public builds disabled' do
before do
project.update(public_builds: false)
end
@@ -192,8 +204,7 @@ describe ProjectPolicy do
context 'when builds are disabled' do
before do
- project.project_feature.update(
- builds_access_level: ProjectFeature::DISABLED)
+ project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
end
it do
@@ -202,9 +213,13 @@ describe ProjectPolicy do
end
end
end
+ end
+
+ shared_examples 'project policies as reporter' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
- context 'reporter' do
- let(:current_user) { reporter }
+ subject { described_class.new(reporter, project) }
it do
expect_allowed(*guest_permissions)
@@ -216,9 +231,13 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions)
end
end
+ end
- context 'developer' do
- let(:current_user) { dev }
+ shared_examples 'project policies as developer' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(developer, project) }
it do
expect_allowed(*guest_permissions)
@@ -229,9 +248,13 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions)
end
end
+ end
+
+ shared_examples 'project policies as master' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
- context 'master' do
- let(:current_user) { master }
+ subject { described_class.new(master, project) }
it do
expect_allowed(*guest_permissions)
@@ -242,9 +265,13 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions)
end
end
+ end
+
+ shared_examples 'project policies as owner' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
- context 'owner' do
- let(:current_user) { owner }
+ subject { described_class.new(owner, project) }
it do
expect_allowed(*guest_permissions)
@@ -255,9 +282,13 @@ describe ProjectPolicy do
expect_allowed(*owner_permissions)
end
end
+ end
- context 'admin' do
- let(:current_user) { admin }
+ shared_examples 'project policies as admin' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(admin, project) }
it do
expect_allowed(*guest_permissions)
@@ -269,4 +300,12 @@ describe ProjectPolicy do
end
end
end
+
+ it_behaves_like 'project policies as anonymous'
+ it_behaves_like 'project policies as guest'
+ it_behaves_like 'project policies as reporter'
+ it_behaves_like 'project policies as developer'
+ it_behaves_like 'project policies as master'
+ it_behaves_like 'project policies as owner'
+ it_behaves_like 'project policies as admin'
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index f663719d28c..94462b4572d 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -491,6 +491,7 @@ describe API::Commits do
expect(json_response['stats']['deletions']).to eq(commit.stats.deletions)
expect(json_response['stats']['total']).to eq(commit.stats.total)
expect(json_response['status']).to be_nil
+ expect(json_response['last_pipeline']).to be_nil
end
context 'when ref does not exist' do
@@ -573,6 +574,10 @@ describe API::Commits do
expect(response).to have_http_status(200)
expect(response).to match_response_schema('public_api/v4/commit/detail')
expect(json_response['status']).to eq('created')
+ expect(json_response['last_pipeline']['id']).to eq(pipeline.id)
+ expect(json_response['last_pipeline']['ref']).to eq(pipeline.ref)
+ expect(json_response['last_pipeline']['sha']).to eq(pipeline.sha)
+ expect(json_response['last_pipeline']['status']).to eq(pipeline.status)
end
context 'when pipeline succeeds' do
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 2361809e0e1..f8cd529a06c 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -20,6 +20,7 @@ describe API::Environments do
path path_with_namespace
star_count forks_count
created_at last_activity_at
+ avatar_url
)
get api("/projects/#{project.id}/environments", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 508df990952..18f6f7df1fa 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -193,6 +193,7 @@ describe API::Projects do
path path_with_namespace
star_count forks_count
created_at last_activity_at
+ avatar_url
)
get api('/projects?simple=true', user)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 5b306ec6cbf..c30188b1b41 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -125,6 +125,15 @@ describe API::Users do
end
context "when admin" do
+ context 'when sudo is defined' do
+ it 'does not return 500' do
+ admin_personal_access_token = create(:personal_access_token, user: admin).token
+ get api("/users?private_token=#{admin_personal_access_token}&sudo=#{user.id}", admin)
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+
it "returns an array of users" do
get api("/users", admin)
diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb
index cae2c3118da..e5282c3311f 100644
--- a/spec/requests/api/v3/projects_spec.rb
+++ b/spec/requests/api/v3/projects_spec.rb
@@ -89,6 +89,7 @@ describe API::V3::Projects do
path path_with_namespace
star_count forks_count
created_at last_activity_at
+ avatar_url
)
get v3_api('/projects?simple=true', user)
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 4c2ff08039c..eb6e683cc23 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -133,6 +133,26 @@ describe Ci::CreatePipelineService do
expect(merge_request.reload.head_pipeline).to eq head_pipeline
end
end
+
+ context 'when pipeline has been skipped' do
+ before do
+ allow_any_instance_of(Ci::Pipeline)
+ .to receive(:git_commit_message)
+ .and_return('some commit [ci skip]')
+ end
+
+ it 'updates merge request head pipeline' do
+ merge_request = create(:merge_request, source_branch: 'master',
+ target_branch: 'feature',
+ source_project: project)
+
+ head_pipeline = execute_service
+
+ expect(head_pipeline).to be_skipped
+ expect(head_pipeline).to be_persisted
+ expect(merge_request.reload.head_pipeline).to eq head_pipeline
+ end
+ end
end
context 'auto-cancel enabled' do
@@ -481,104 +501,4 @@ describe Ci::CreatePipelineService do
end
end
end
-
- describe '#allowed_to_create?' do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:ref) { 'master' }
-
- subject do
- described_class.new(project, user, ref: ref)
- .send(:allowed_to_create?)
- end
-
- context 'when user is a developer' do
- before do
- project.add_developer(user)
- end
-
- it { is_expected.to be_truthy }
-
- context 'when the branch is protected' do
- let!(:protected_branch) do
- create(:protected_branch, project: project, name: ref)
- end
-
- it { is_expected.to be_falsey }
-
- context 'when developers are allowed to merge' do
- let!(:protected_branch) do
- create(:protected_branch,
- :developers_can_merge,
- project: project,
- name: ref)
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'when the tag is protected' do
- let(:ref) { 'v1.0.0' }
-
- let!(:protected_tag) do
- create(:protected_tag, project: project, name: ref)
- end
-
- it { is_expected.to be_falsey }
-
- context 'when developers are allowed to create the tag' do
- let!(:protected_tag) do
- create(:protected_tag,
- :developers_can_create,
- project: project,
- name: ref)
- end
-
- it { is_expected.to be_truthy }
- end
- end
- end
-
- context 'when user is a master' do
- before do
- project.add_master(user)
- end
-
- it { is_expected.to be_truthy }
-
- context 'when the branch is protected' do
- let!(:protected_branch) do
- create(:protected_branch, project: project, name: ref)
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when the tag is protected' do
- let(:ref) { 'v1.0.0' }
-
- let!(:protected_tag) do
- create(:protected_tag, project: project, name: ref)
- end
-
- it { is_expected.to be_truthy }
-
- context 'when no one can create the tag' do
- let!(:protected_tag) do
- create(:protected_tag,
- :no_one_can_create,
- project: project,
- name: ref)
- end
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
- context 'when owner cannot create pipeline' do
- it { is_expected.to be_falsey }
- end
- end
end
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index 641d5538de8..75812c17309 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Emails::CreateService do
let(:user) { create(:user) }
- let(:opts) { { email: 'new@email.com' } }
+ let(:opts) { { email: 'new@email.com', user: user } }
subject(:service) { described_class.new(user, opts) }
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index 1f4294dd905..7726fc0ef81 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -4,7 +4,7 @@ describe Emails::DestroyService do
let!(:user) { create(:user) }
let!(:email) { create(:email, user: user) }
- subject(:service) { described_class.new(user, email: email.email) }
+ subject(:service) { described_class.new(user, user: user, email: email.email) }
describe '#execute' do
it 'removes an email' do
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index b60136064b7..80213d093f1 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -13,20 +13,21 @@ describe MergeRequests::MergeService do
describe '#execute' do
context 'MergeRequest#merge_jid' do
+ let(:service) do
+ described_class.new(project, user, commit_message: 'Awesome message')
+ end
+
before do
merge_request.update_column(:merge_jid, 'hash-123')
end
it 'is cleaned when no error is raised' do
- service = described_class.new(project, user, commit_message: 'Awesome message')
-
service.execute(merge_request)
expect(merge_request.reload.merge_jid).to be_nil
end
it 'is cleaned when expected error is raised' do
- service = described_class.new(project, user, commit_message: 'Awesome message')
allow(service).to receive(:commit).and_raise(described_class::MergeError)
service.execute(merge_request)
@@ -34,6 +35,22 @@ describe MergeRequests::MergeService do
expect(merge_request.reload.merge_jid).to be_nil
end
+ it 'is cleaned when merge request is not mergeable' do
+ allow(merge_request).to receive(:mergeable?).and_return(false)
+
+ service.execute(merge_request)
+
+ expect(merge_request.reload.merge_jid).to be_nil
+ end
+
+ it 'is cleaned when no source is found' do
+ allow(merge_request).to receive(:diff_head_sha).and_return(nil)
+
+ service.execute(merge_request)
+
+ expect(merge_request.reload.merge_jid).to be_nil
+ end
+
it 'is not cleaned when unexpected error is raised' do
service = described_class.new(project, user, commit_message: 'Awesome message')
allow(service).to receive(:commit).and_raise(StandardError)
diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb
index 6ee35a33b2d..f8d4a47b212 100644
--- a/spec/services/users/update_service_spec.rb
+++ b/spec/services/users/update_service_spec.rb
@@ -31,13 +31,13 @@ describe Users::UpdateService do
end
def update_user(user, opts)
- described_class.new(user, opts).execute
+ described_class.new(user, opts.merge(user: user)).execute
end
end
describe '#execute!' do
it 'updates the name' do
- service = described_class.new(user, name: 'New Name')
+ service = described_class.new(user, user: user, name: 'New Name')
expect(service).not_to receive(:notify_new_user)
result = service.execute!
@@ -55,7 +55,7 @@ describe Users::UpdateService do
it 'fires system hooks when a new user is saved' do
system_hook_service = spy(:system_hook_service)
user = build(:user)
- service = described_class.new(user, name: 'John Doe')
+ service = described_class.new(user, user: user, name: 'John Doe')
expect(service).to receive(:notify_new_user).and_call_original
expect(service).to receive(:system_hook_service).and_return(system_hook_service)
@@ -65,7 +65,7 @@ describe Users::UpdateService do
end
def update_user(user, opts)
- described_class.new(user, opts).execute!
+ described_class.new(user, opts.merge(user: user)).execute!
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 92735336572..dbf05b7f004 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -64,8 +64,16 @@ RSpec.configure do |config|
config.infer_spec_type_from_file_location!
- config.define_derived_metadata(file_path: %r{/spec/requests/(ci/)?api/}) do |metadata|
- metadata[:api] = true
+ config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
+ location = metadata[:location]
+
+ metadata[:api] = true if location =~ %r{/spec/requests/api/}
+
+ # do not overwrite type if it's already set
+ next if metadata.key?(:type)
+
+ match = location.match(%r{/spec/([^/]+)/})
+ metadata[:type] = match[1].singularize.to_sym if match
end
config.raise_errors_for_deprecations!
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 6e5b9700b54..b4e8b5ea67b 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -306,6 +306,9 @@ module TestEnv
ensure_component_dir_name_is_correct!(component, install_dir)
+ # On CI, once installed, components never need update
+ return if File.exist?(install_dir) && ENV['CI']
+
if component_needs_update?(install_dir, version)
# Cleanup the component entirely to ensure we start fresh
FileUtils.rm_rf(install_dir)
diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb
index d34617be474..fae5ec35c47 100644
--- a/spec/tasks/gitlab/task_helpers_spec.rb
+++ b/spec/tasks/gitlab/task_helpers_spec.rb
@@ -75,4 +75,24 @@ describe Gitlab::TaskHelpers do
subject.checkout_version(tag, clone_path)
end
end
+
+ describe '#run_command' do
+ it 'runs command and return the output' do
+ expect(subject.run_command(%w(echo it works!))).to eq("it works!\n")
+ end
+
+ it 'returns empty string when command doesnt exist' do
+ expect(subject.run_command(%w(nonexistentcommand with arguments))).to eq('')
+ end
+ end
+
+ describe '#run_command!' do
+ it 'runs command and return the output' do
+ expect(subject.run_command!(%w(echo it works!))).to eq("it works!\n")
+ end
+
+ it 'returns and exception when command exit with non zero code' do
+ expect { subject.run_command!(['bash', '-c', 'exit 1']) }.to raise_error Gitlab::TaskFailedError
+ end
+ end
end
diff --git a/spec/views/dashboard/projects/_nav.html.haml.rb b/spec/views/dashboard/projects/_nav.html.haml.rb
new file mode 100644
index 00000000000..f6a8ca13040
--- /dev/null
+++ b/spec/views/dashboard/projects/_nav.html.haml.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe 'dashboard/projects/_nav.html.haml' do
+ it 'highlights All tab by default' do
+ render
+
+ expect(rendered).to have_css('li.active a', text: 'All')
+ end
+
+ it 'highlights Personal tab personal param is present' do
+ controller.params[:personal] = true
+
+ render
+
+ expect(rendered).to have_css('li.active a', text: 'Personal')
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index d3707a3cc11..05eecf5f0bb 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -70,12 +70,15 @@ describe PostReceive do
context "creates a Ci::Pipeline for every change" do
before do
- allow_any_instance_of(Ci::CreatePipelineService).to receive(:commit) do
- OpenStruct.new(id: '123456')
- end
- allow_any_instance_of(Ci::CreatePipelineService).to receive(:branch?).and_return(true)
- allow_any_instance_of(Repository).to receive(:ref_exists?).and_return(true)
stub_ci_pipeline_to_return_yaml_file
+
+ # TODO, don't stub private methods
+ #
+ allow_any_instance_of(Ci::CreatePipelineService)
+ .to receive(:commit).and_return(OpenStruct.new(id: '123456'))
+
+ allow_any_instance_of(Repository)
+ .to receive(:branch_exists?).and_return(true)
end
it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }