summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFatih Acet <acetfatih@gmail.com>2018-06-26 16:48:25 +0200
committerFatih Acet <acetfatih@gmail.com>2018-06-26 16:48:25 +0200
commit13a8ad61b2cc37cf65f482bfa6855d8e2060a554 (patch)
treeec6bfe03a00cd03d5ee4841d31865e861e84fb45
parent4e734a9004f5241a33af8e962781acb33cbbadab (diff)
parentd374d39cc7272e92b4ed3485095b6f2c6351b1b8 (diff)
downloadgitlab-ce-13a8ad61b2cc37cf65f482bfa6855d8e2060a554.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into acet-mr-diffs-autosave
-rw-r--r--.gitignore2
-rw-r--r--.gitlab-ci.yml51
-rw-r--r--.nvmrc2
-rw-r--r--CHANGELOG.md38
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--Gemfile.rails5.lock12
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js16
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue (renamed from app/assets/javascripts/boards/components/modal/footer.js)56
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js4
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js4
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.js54
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue56
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.js46
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue49
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js73
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue72
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js19
-rw-r--r--app/assets/javascripts/gl_form.js15
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue6
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue2
-rw-r--r--app/assets/javascripts/init_notes.js4
-rw-r--r--app/assets/javascripts/notes.js15
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue2
-rw-r--r--app/assets/javascripts/pages/projects/init_form.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js2
-rw-r--r--app/assets/javascripts/pages/projects/tags/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js2
-rw-r--r--app/assets/javascripts/pages/snippets/form.js9
-rw-r--r--app/assets/javascripts/shared/milestones/form.js10
-rw-r--r--app/assets/javascripts/u2f/authenticate.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue9
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/pages/repo.scss42
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/groups_controller.rb4
-rw-r--r--app/controllers/admin/users_controller.rb4
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/finders/user_recent_events_finder.rb2
-rw-r--r--app/helpers/notes_helper.rb9
-rw-r--r--app/helpers/projects_helper.rb11
-rw-r--r--app/models/application_setting.rb13
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/concerns/sortable.rb4
-rw-r--r--app/models/merge_request.rb25
-rw-r--r--app/models/merge_request_diff.rb27
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_auto_devops.rb4
-rw-r--r--app/models/project_team.rb2
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/services/base_count_service.rb6
-rw-r--r--app/services/merge_requests/delete_non_latest_diffs_service.rb18
-rw-r--r--app/services/merge_requests/merge_request_diff_cache_service.rb17
-rw-r--r--app/services/merge_requests/post_merge_service.rb5
-rw-r--r--app/services/merge_requests/reload_diffs_service.rb43
-rw-r--r--app/services/projects/count_service.rb6
-rw-r--r--app/services/projects/open_issues_count_service.rb32
-rw-r--r--app/services/web_hook_service.rb2
-rw-r--r--app/views/admin/application_settings/show.html.haml3
-rw-r--r--app/views/admin/labels/_form.html.haml10
-rw-r--r--app/views/admin/labels/_label.html.haml4
-rw-r--r--app/views/admin/labels/edit.html.haml8
-rw-r--r--app/views/admin/labels/index.html.haml8
-rw-r--r--app/views/admin/labels/new.html.haml4
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml6
-rw-r--r--app/views/profiles/keys/_form.html.haml6
-rw-r--r--app/views/profiles/keys/index.html.haml9
-rw-r--r--app/views/projects/clusters/_gcp_signup_offer_banner.html.haml2
-rw-r--r--app/views/projects/deployments/_commit.html.haml2
-rw-r--r--app/views/projects/deployments/_deployment.html.haml8
-rw-r--r--app/views/projects/deployments/_rollback.haml4
-rw-r--r--app/views/projects/graphs/charts.html.haml2
-rw-r--r--app/views/projects/merge_requests/diffs/_diffs.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml4
-rw-r--r--app/views/shared/boards/components/_board.html.haml6
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml12
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml8
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml12
-rw-r--r--app/views/shared/notes/_form.html.haml2
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml1
-rw-r--r--app/views/u2f/_authenticate.html.haml3
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/delete_diff_files_worker.rb17
-rwxr-xr-xbin/changelog49
-rw-r--r--changelogs/unreleased/40005-u2f-unspported-browsers.yml5
-rw-r--r--changelogs/unreleased/45933-webide-fade-uneditable-area.yml5
-rw-r--r--changelogs/unreleased/46202-webide-file-states.yml5
-rw-r--r--changelogs/unreleased/46396-recognise-when-a-user-is-trying-to-validate-a-private-ssh-key-part-1.yml5
-rw-r--r--changelogs/unreleased/46571-webhooks-nil-password.yml5
-rw-r--r--changelogs/unreleased/46783-removed-omniauth-provider-causing-invalid-application-setting.yml5
-rw-r--r--changelogs/unreleased/47274-help-users-find-our-contributing-page.yml5
-rw-r--r--changelogs/unreleased/48269-wiki-page-returns-error-code-502.yml5
-rw-r--r--changelogs/unreleased/48339-sorting-by-name-on-explore-projects-page-renders-a-500-error-when-logged-in.yml5
-rw-r--r--changelogs/unreleased/6591-dont-load-omniauth-if-not-enabled.yml5
-rw-r--r--changelogs/unreleased/6598-notify-only-open-unmergeable-mr.yml5
-rw-r--r--changelogs/unreleased/add-missing-index-for-deployments.yml5
-rw-r--r--changelogs/unreleased/enforce-variable-value-to-be-a-string.yml5
-rw-r--r--changelogs/unreleased/existing-gcp-accounts.yml5
-rw-r--r--changelogs/unreleased/fix-alert-btn.yml5
-rw-r--r--changelogs/unreleased/issue_47729.yml5
-rw-r--r--changelogs/unreleased/osw-delete-non-latest-mr-diff-files-upon-merge.yml5
-rw-r--r--changelogs/unreleased/rails5-fix-mysql-arel-from.yml5
-rw-r--r--changelogs/unreleased/security-2682-fix-xss-for-markdown-toc.yml5
-rw-r--r--changelogs/unreleased/security-fj-bumping-sanitize-gem.yml5
-rw-r--r--changelogs/unreleased/security-html_escape_branch_name.yml5
-rw-r--r--changelogs/unreleased/security-html_escape_usernames.yml5
-rw-r--r--changelogs/unreleased/security-rd-do-not-show-internal-info-in-public-feed.yml5
-rw-r--r--changelogs/unreleased/update-pipeline-icon-in-web-ide-sidebar.yml5
-rw-r--r--config/application.rb17
-rw-r--r--config/initializers/active_record_data_types.rb2
-rw-r--r--config/initializers/devise.rb4
-rw-r--r--config/locales/doorkeeper.en.yml16
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--db/migrate/20180626125654_add_index_on_deployable_for_deployments.rb18
-rw-r--r--db/migrate/merge_request_diff_file_limits_to_mysql.rb2
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/job_traces.md2
-rw-r--r--doc/development/documentation/index.md39
-rw-r--r--doc/development/i18n/proofreader.md2
-rw-r--r--doc/development/what_requires_downtime.md33
-rw-r--r--doc/install/installation.md5
-rw-r--r--doc/integration/saml.md75
-rw-r--r--doc/update/10.8-to-11.0.md7
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md1
-rw-r--r--doc/user/project/img/group_issue_board.pngbin0 -> 163417 bytes
-rw-r--r--doc/user/project/img/issue_board.pngbin82592 -> 100684 bytes
-rw-r--r--doc/user/project/img/issue_board_add_list.pngbin17312 -> 6404 bytes
-rw-r--r--doc/user/project/img/issue_board_assignee_lists.pngbin0 -> 134674 bytes
-rw-r--r--doc/user/project/img/issue_board_creation.pngbin0 -> 108674 bytes
-rw-r--r--doc/user/project/img/issue_board_edit_button.pngbin0 -> 108168 bytes
-rw-r--r--doc/user/project/img/issue_board_focus_mode.gifbin0 -> 1043366 bytes
-rw-r--r--doc/user/project/img/issue_board_move_issue_card_list.pngbin36747 -> 13670 bytes
-rw-r--r--doc/user/project/img/issue_board_system_notes.pngbin4899 -> 4893 bytes
-rw-r--r--doc/user/project/img/issue_board_view_scope.pngbin0 -> 63542 bytes
-rw-r--r--doc/user/project/img/issue_board_welcome_message.pngbin26533 -> 13519 bytes
-rw-r--r--doc/user/project/img/issue_boards_add_issues_modal.pngbin29176 -> 12421 bytes
-rw-r--r--doc/user/project/img/issue_boards_multiple.pngbin0 -> 6092 bytes
-rw-r--r--doc/user/project/img/issue_boards_remove_issue.pngbin135168 -> 39357 bytes
-rw-r--r--doc/user/project/import/bitbucket.md4
-rw-r--r--doc/user/project/issue_board.md212
-rw-r--r--doc/user/project/issues/deleting_issues.md4
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--doc/workflow/todos.md2
-rw-r--r--lib/backup/repository.rb8
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb6
-rw-r--r--lib/banzai/filter/sanitization_filter.rb3
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb4
-rw-r--r--lib/gitlab/auth/saml/auth_hash.rb15
-rw-r--r--lib/gitlab/auth/saml/config.rb4
-rw-r--r--lib/gitlab/auth/saml/user.rb4
-rw-r--r--lib/gitlab/background_migration/cleanup_concurrent_rename.rb14
-rw-r--r--lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb52
-rw-r--r--lib/gitlab/background_migration/cleanup_concurrent_type_change.rb48
-rw-r--r--lib/gitlab/ci/variables/collection/item.rb3
-rw-r--r--lib/gitlab/database/median.rb8
-rw-r--r--lib/gitlab/database/migration_helpers.rb91
-rw-r--r--lib/gitlab/git/blob.rb113
-rw-r--r--lib/gitlab/git/remote_mirror.rb77
-rw-r--r--lib/gitlab/git/repository.rb65
-rw-r--r--lib/mysql_zero_date.rb18
-rw-r--r--package.json3
-rw-r--r--qa/qa/specs/features/repository/protected_branches_spec.rb24
-rw-r--r--scripts/frontend/postinstall.js22
-rwxr-xr-xscripts/trigger-build-docs2
-rw-r--r--spec/bin/changelog_spec.rb11
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb189
-rw-r--r--spec/controllers/projects_controller_spec.rb2
-rw-r--r--spec/dependencies/omniauth_saml_spec.rb22
-rw-r--r--spec/features/admin/admin_settings_spec.rb23
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb2
-rw-r--r--spec/features/projects/graph_spec.rb20
-rw-r--r--spec/features/users/login_spec.rb35
-rw-r--r--spec/features/users/signup_spec.rb9
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb45
-rw-r--r--spec/fixtures/authentication/saml_response.xml42
-rw-r--r--spec/helpers/projects_helper_spec.rb9
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js11
-rw-r--r--spec/javascripts/ide/components/repo_tab_spec.js26
-rw-r--r--spec/javascripts/test_bundle.js1
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js84
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb9
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb8
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb51
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb41
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb12
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb55
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb12
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb100
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb36
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/concerns/sortable_spec.rb18
-rw-r--r--spec/models/merge_request_diff_spec.rb39
-rw-r--r--spec/models/merge_request_spec.rb89
-rw-r--r--spec/models/namespace_spec.rb13
-rw-r--r--spec/requests/api/boards_spec.rb1
-rw-r--r--spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb59
-rw-r--r--spec/services/merge_requests/merge_request_diff_cache_service_spec.rb39
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb12
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb64
-rw-r--r--spec/services/projects/batch_open_issues_count_service_spec.rb54
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb35
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb305
-rw-r--r--spec/services/web_hook_service_spec.rb30
-rw-r--r--spec/support/helpers/login_helpers.rb36
-rw-r--r--spec/support/helpers/stub_object_storage.rb7
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb41
-rw-r--r--spec/workers/delete_user_worker_spec.rb10
-rw-r--r--yarn.lock236
223 files changed, 2764 insertions, 1593 deletions
diff --git a/.gitignore b/.gitignore
index 21dc67384aa..9a42a663fb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,7 @@ eslint-report.html
/app/assets/javascripts/locale/**/app.js
/backups/*
/config/aws.yml
-/config/database.yml
+/config/database*.yml
/config/gitlab.yml
/config/gitlab_ci.yml
/config/initializers/rack_attack.rb
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 30c21b452e0..8703ef6823a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -220,18 +220,6 @@ stages:
paths:
- log/development.log
-# Review docs base
-.review-docs: &review-docs
- <<: *dedicated-runner
- <<: *except-qa
- <<: *single-script-job
- variables:
- <<: *single-script-job-variables
- SCRIPT_NAME: trigger-build-docs
- when: manual
- only:
- - branches
-
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
@@ -273,20 +261,44 @@ package-and-qa:
- //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee
-# Trigger a docs build in gitlab-docs
-# Useful to preview the docs changes live
-review-docs-deploy:
- <<: *review-docs
- stage: build
+# Review docs base
+.review-docs: &review-docs
+ <<: *dedicated-runner
+ <<: *single-script-job
+ variables:
+ <<: *single-script-job-variables
+ SCRIPT_NAME: trigger-build-docs
environment:
name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
- url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
+ url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
+
+# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
+# Useful to preview the docs changes live.
+review-docs-deploy-manual:
+ <<: *review-docs
+ stage: build
+ script:
+ - gem install gitlab --no-ri --no-rdoc
+ - ./$SCRIPT_NAME deploy
+ when: manual
+ only:
+ - branches
+ <<: *except-docs-and-qa
+
+# Always trigger a docs build in gitlab-docs only on docs-only branches.
+# Useful to preview the docs changes live.
+review-docs-deploy:
+ <<: *review-docs
+ stage: post-test
script:
- gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME deploy
+ only:
+ - /(^docs[\/-].*|.*-docs$)/
+ <<: *except-qa
# Cleanup remote environment of gitlab-docs
review-docs-cleanup:
@@ -295,9 +307,10 @@ review-docs-cleanup:
environment:
name: review-docs/$CI_COMMIT_REF_NAME
action: stop
+ when: manual
script:
- gem install gitlab --no-ri --no-rdoc
- - ./SCRIPT_NAME cleanup
+ - ./$SCRIPT_NAME cleanup
##
# Trigger a docker image build in CNG (Cloud Native GitLab) repository
diff --git a/.nvmrc b/.nvmrc
index f7ee06693c1..dba04c1e178 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-9.0.0
+8.11.3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eabacbc2e1d..72725122b8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.0.1 (2018-06-21)
+
+### Security (5 changes)
+
+- Fix XSS vulnerability for table of content generation.
+- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
+- HTML escape branch name in project graphs page.
+- HTML escape the name of the user in ProjectsHelper#link_to_member.
+- Don't show events from internal projects for anonymous users in public feed.
+
+
## 11.0.0 (2018-06-22)
### Security (3 changes)
@@ -242,6 +253,17 @@ entry.
- Workhorse to send raw diff and patch for commits.
+## 10.8.5 (2018-06-21)
+
+### Security (5 changes)
+
+- Fix XSS vulnerability for table of content generation.
+- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
+- HTML escape branch name in project graphs page.
+- HTML escape the name of the user in ProjectsHelper#link_to_member.
+- Don't show events from internal projects for anonymous users in public feed.
+
+
## 10.8.4 (2018-06-06)
- No changes.
@@ -460,6 +482,22 @@ entry.
- Gitaly handles repository forks by default.
+## 10.7.6 (2018-06-21)
+
+### Security (6 changes)
+
+- Fix XSS vulnerability for table of content generation.
+- Update sanitize gem to 4.6.5 to fix HTML injection vulnerability.
+- HTML escape branch name in project graphs page.
+- HTML escape the name of the user in ProjectsHelper#link_to_member.
+- Don't show events from internal projects for anonymous users in public feed.
+- XSS fix to use safe_params instead of params in url_for helpers.
+
+### Other (1 change)
+
+- Replacing gollum libraries for gitlab custom libs. !18343
+
+
## 10.7.5 (2018-05-28)
### Security (3 changes)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f7b12e17c70..fd4e769ecee 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -650,7 +650,7 @@ the feature you contribute through all of these steps.
1. Working and clean code that is commented where needed
1. [Unit, integration, and system tests][testing] that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested
-1. [Documented][doc-styleguide] in the `/doc` directory
+1. [Documented][doc-guidelines] in the `/doc` directory
1. [Changelog entry added][changelog], if necessary
1. Reviewed and any concerns are addressed
1. Merged by a project maintainer
@@ -687,7 +687,7 @@ merge request:
contributors to enhance security
1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
-1. [Documentation styleguide][doc-styleguide]
+1. [Documentation styleguide](https://docs.gitlab.com/ee/development/documentation/styleguide.html)
1. Interface text should be written subjectively instead of objectively. It
should be the GitLab core team addressing a person. It should be written in
present time and never use past tense (has been/was). For example instead
diff --git a/Gemfile b/Gemfile
index 945b5486437..93c6115eeec 100644
--- a/Gemfile
+++ b/Gemfile
@@ -230,7 +230,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'kubeclient', '~> 3.1.0'
# Sanitize user input
-gem 'sanitize', '~> 2.0'
+gem 'sanitize', '~> 4.6.5'
gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input
diff --git a/Gemfile.lock b/Gemfile.lock
index fdc8f54e9c9..8281c1eff9a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -295,13 +295,13 @@ GEM
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-gollum-lib (4.2.7.4)
+ gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1)
- sanitize (~> 2.1)
+ sanitize (~> 4.6.4)
stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4.1)
mime-types (>= 1.15)
@@ -514,6 +514,8 @@ GEM
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
+ nokogumbo (1.5.0)
+ nokogiri
numerizer (0.1.1)
oauth (0.5.4)
oauth2 (1.4.0)
@@ -804,8 +806,10 @@ GEM
et-orbi (~> 1.0)
rugged (0.27.2)
safe_yaml (1.0.4)
- sanitize (2.1.0)
+ sanitize (4.6.5)
+ crass (~> 1.0.2)
nokogiri (>= 1.4.4)
+ nokogumbo (~> 1.4)
sass (3.5.5)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -1151,7 +1155,7 @@ DEPENDENCIES
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.27)
- sanitize (~> 2.0)
+ sanitize (~> 4.6.5)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 679318b9be5..52388f17c7c 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -298,13 +298,13 @@ GEM
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
- gitlab-gollum-lib (4.2.7.4)
+ gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1)
- sanitize (~> 2.1)
+ sanitize (~> 4.6.4)
stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4.1)
mime-types (>= 1.15)
@@ -518,6 +518,8 @@ GEM
nio4r (2.3.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
+ nokogumbo (1.5.0)
+ nokogiri
numerizer (0.1.1)
oauth (0.5.4)
oauth2 (1.4.0)
@@ -813,8 +815,10 @@ GEM
et-orbi (~> 1.0)
rugged (0.27.1)
safe_yaml (1.0.4)
- sanitize (2.1.0)
+ sanitize (4.6.5)
+ crass (~> 1.0.2)
nokogiri (>= 1.4.4)
+ nokogumbo (~> 1.4)
sass (3.5.5)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -1162,7 +1166,7 @@ DEPENDENCIES
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.27)
- sanitize (~> 2.0)
+ sanitize (~> 4.6.5)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index b717c4b0fd4..371be109229 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -6,13 +6,13 @@ import Flash from '../../flash';
import { __ } from '../../locale';
import Sidebar from '../../right_sidebar';
import eventHub from '../../sidebar/event_hub';
-import assigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
-import assignees from '../../sidebar/components/assignees/assignees.vue';
+import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
+import Assignees from '../../sidebar/components/assignees/assignees.vue';
import DueDateSelectors from '../../due_date_select';
-import './sidebar/remove_issue';
+import RemoveBtn from './sidebar/remove_issue.vue';
import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select';
-import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
+import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
import MilestoneSelect from '../../milestone_select';
const Store = gl.issueBoards.BoardsStore;
@@ -22,10 +22,10 @@ window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardSidebar = Vue.extend({
components: {
- assigneeTitle,
- assignees,
- removeBtn: gl.issueBoards.RemoveIssueBtn,
- subscriptions,
+ AssigneeTitle,
+ Assignees,
+ RemoveBtn,
+ Subscriptions,
},
props: {
currentUser: {
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.vue
index 2745ca219ad..e0dac6003f1 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,14 +1,14 @@
-import Vue from 'vue';
+<script>
import Flash from '../../../flash';
import { __ } from '../../../locale';
-import './lists_dropdown';
+import ListsDropdown from './lists_dropdown.vue';
import { pluralize } from '../../../lib/utils/text_utility';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
-gl.issueBoards.ModalFooter = Vue.extend({
+export default {
components: {
- 'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
+ ListsDropdown,
},
mixins: [modalMixin],
data() {
@@ -55,28 +55,32 @@ gl.issueBoards.ModalFooter = Vue.extend({
this.toggleModal(false);
},
},
- template: `
- <footer
- class="form-actions add-issues-footer">
- <div class="float-left">
- <button
- class="btn btn-success"
- type="button"
- :disabled="submitDisabled"
- @click="addIssues">
- {{ submitText }}
- </button>
- <span class="inline add-issues-footer-to-list">
- to list
- </span>
- <lists-dropdown></lists-dropdown>
- </div>
+};
+</script>
+<template>
+ <footer
+ class="form-actions add-issues-footer"
+ >
+ <div class="float-left">
<button
- class="btn btn-default float-right"
+ :disabled="submitDisabled"
+ class="btn btn-success"
type="button"
- @click="toggleModal(false)">
- Cancel
+ @click="addIssues"
+ >
+ {{ submitText }}
</button>
- </footer>
- `,
-});
+ <span class="inline add-issues-footer-to-list">
+ to list
+ </span>
+ <lists-dropdown/>
+ </div>
+ <button
+ class="btn btn-default float-right"
+ type="button"
+ @click="toggleModal(false)"
+ >
+ Cancel
+ </button>
+ </footer>
+</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.js b/app/assets/javascripts/boards/components/modal/header.js
index 5e511bb8935..cc9848058ca 100644
--- a/app/assets/javascripts/boards/components/modal/header.js
+++ b/app/assets/javascripts/boards/components/modal/header.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
import modalFilters from './filters';
-import './tabs';
+import modalTabs from './tabs.vue';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
gl.issueBoards.ModalHeader = Vue.extend({
components: {
- 'modal-tabs': gl.issueBoards.ModalTabs,
+ modalTabs,
modalFilters,
},
mixins: [modalMixin],
diff --git a/app/assets/javascripts/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js
index c10397eaaba..983061f52ae 100644
--- a/app/assets/javascripts/boards/components/modal/index.js
+++ b/app/assets/javascripts/boards/components/modal/index.js
@@ -5,7 +5,7 @@ import queryData from '~/boards/utils/query_data';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import './header';
import './list';
-import './footer';
+import ModalFooter from './footer.vue';
import EmptyState from './empty_state.vue';
import ModalStore from '../../stores/modal_store';
@@ -14,7 +14,7 @@ gl.issueBoards.IssuesModal = Vue.extend({
EmptyState,
'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList,
- 'modal-footer': gl.issueBoards.ModalFooter,
+ ModalFooter,
loadingIcon,
},
props: {
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
deleted file mode 100644
index e644de2d4fc..00000000000
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import Vue from 'vue';
-import ModalStore from '../../stores/modal_store';
-
-gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
- data() {
- return {
- modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
- };
- },
- computed: {
- selected() {
- return this.modal.selectedList || this.state.lists[1];
- },
- },
- destroyed() {
- this.modal.selectedList = null;
- },
- template: `
- <div class="dropdown inline">
- <button
- class="dropdown-menu-toggle"
- type="button"
- data-toggle="dropdown"
- aria-expanded="false">
- <span
- class="dropdown-label-box"
- :style="{ backgroundColor: selected.label.color }">
- </span>
- {{ selected.title }}
- <i class="fa fa-chevron-down"></i>
- </button>
- <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
- <ul>
- <li
- v-for="list in state.lists"
- v-if="list.type == 'label'">
- <a
- href="#"
- role="button"
- :class="{ 'is-active': list.id == selected.id }"
- @click.prevent="modal.selectedList = list">
- <span
- class="dropdown-label-box"
- :style="{ backgroundColor: list.label.color }">
- </span>
- {{ list.title }}
- </a>
- </li>
- </ul>
- </div>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
new file mode 100644
index 00000000000..6a5a39099bd
--- /dev/null
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -0,0 +1,56 @@
+<script>
+import ModalStore from '../../stores/modal_store';
+
+export default {
+ data() {
+ return {
+ modal: ModalStore.store,
+ state: gl.issueBoards.BoardsStore.state,
+ };
+ },
+ computed: {
+ selected() {
+ return this.modal.selectedList || this.state.lists[1];
+ },
+ },
+ destroyed() {
+ this.modal.selectedList = null;
+ },
+};
+</script>
+<template>
+ <div class="dropdown inline">
+ <button
+ class="dropdown-menu-toggle"
+ type="button"
+ data-toggle="dropdown"
+ aria-expanded="false">
+ <span
+ :style="{ backgroundColor: selected.label.color }"
+ class="dropdown-label-box">
+ </span>
+ {{ selected.title }}
+ <i class="fa fa-chevron-down"></i>
+ </button>
+ <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
+ <ul>
+ <li
+ v-for="(list, i) in state.lists"
+ v-if="list.type == 'label'"
+ :key="i">
+ <a
+ :class="{ 'is-active': list.id == selected.id }"
+ href="#"
+ role="button"
+ @click.prevent="modal.selectedList = list">
+ <span
+ :style="{ backgroundColor: list.label.color }"
+ class="dropdown-label-box">
+ </span>
+ {{ list.title }}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js
deleted file mode 100644
index 9d331de8e22..00000000000
--- a/app/assets/javascripts/boards/components/modal/tabs.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import Vue from 'vue';
-import ModalStore from '../../stores/modal_store';
-import modalMixin from '../../mixins/modal_mixins';
-
-gl.issueBoards.ModalTabs = Vue.extend({
- mixins: [modalMixin],
- data() {
- return ModalStore.store;
- },
- computed: {
- selectedCount() {
- return ModalStore.selectedCount();
- },
- },
- destroyed() {
- this.activeTab = 'all';
- },
- template: `
- <div class="top-area prepend-top-10 append-bottom-10">
- <ul class="nav-links issues-state-filters">
- <li :class="{ 'active': activeTab == 'all' }">
- <a
- href="#"
- role="button"
- @click.prevent="changeTab('all')">
- Open issues
- <span class="badge badge-pill">
- {{ issuesCount }}
- </span>
- </a>
- </li>
- <li :class="{ 'active': activeTab == 'selected' }">
- <a
- href="#"
- role="button"
- @click.prevent="changeTab('selected')">
- Selected issues
- <span class="badge badge-pill">
- {{ selectedCount }}
- </span>
- </a>
- </li>
- </ul>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
new file mode 100644
index 00000000000..d926b080094
--- /dev/null
+++ b/app/assets/javascripts/boards/components/modal/tabs.vue
@@ -0,0 +1,49 @@
+<script>
+ import ModalStore from '../../stores/modal_store';
+ import modalMixin from '../../mixins/modal_mixins';
+
+ export default {
+ mixins: [modalMixin],
+ data() {
+ return ModalStore.store;
+ },
+ computed: {
+ selectedCount() {
+ return ModalStore.selectedCount();
+ },
+ },
+ destroyed() {
+ this.activeTab = 'all';
+ },
+ };
+</script>
+<template>
+ <div class="top-area prepend-top-10 append-bottom-10">
+ <ul class="nav-links issues-state-filters">
+ <li :class="{ 'active': activeTab == 'all' }">
+ <a
+ href="#"
+ role="button"
+ @click.prevent="changeTab('all')"
+ >
+ Open issues
+ <span class="badge badge-pill">
+ {{ issuesCount }}
+ </span>
+ </a>
+ </li>
+ <li :class="{ 'active': activeTab == 'selected' }">
+ <a
+ href="#"
+ role="button"
+ @click.prevent="changeTab('selected')"
+ >
+ Selected issues
+ <span class="badge badge-pill">
+ {{ selectedCount }}
+ </span>
+ </a>
+ </li>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
deleted file mode 100644
index 0a0820ec5fd..00000000000
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import Vue from 'vue';
-import Flash from '../../../flash';
-import { __ } from '../../../locale';
-
-const Store = gl.issueBoards.BoardsStore;
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.RemoveIssueBtn = Vue.extend({
- props: {
- issue: {
- type: Object,
- required: true,
- },
- list: {
- type: Object,
- required: true,
- },
- },
- computed: {
- updateUrl() {
- return this.issue.path;
- },
- },
- methods: {
- removeIssue() {
- const issue = this.issue;
- const lists = issue.getLists();
- const listLabelIds = lists.map(list => list.label.id);
-
- let labelIds = issue.labels
- .map(label => label.id)
- .filter(id => !listLabelIds.includes(id));
- if (labelIds.length === 0) {
- labelIds = [''];
- }
-
- const data = {
- issue: {
- label_ids: labelIds,
- },
- };
-
- // Post the remove data
- Vue.http.patch(this.updateUrl, data).catch(() => {
- Flash(__('Failed to remove issue from board, please try again.'));
-
- lists.forEach((list) => {
- list.addIssue(issue);
- });
- });
-
- // Remove from the frontend store
- lists.forEach((list) => {
- list.removeIssue(issue);
- });
-
- Store.detail.issue = {};
- },
- },
- template: `
- <div
- class="block list">
- <button
- class="btn btn-default btn-block"
- type="button"
- @click="removeIssue">
- Remove from board
- </button>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
new file mode 100644
index 00000000000..806e038a95f
--- /dev/null
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -0,0 +1,72 @@
+<script>
+ import Vue from 'vue';
+ import Flash from '../../../flash';
+ import { __ } from '../../../locale';
+
+ const Store = gl.issueBoards.BoardsStore;
+
+ export default {
+ props: {
+ issue: {
+ type: Object,
+ required: true,
+ },
+ list: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ updateUrl() {
+ return this.issue.path;
+ },
+ },
+ methods: {
+ removeIssue() {
+ const issue = this.issue;
+ const lists = issue.getLists();
+ const listLabelIds = lists.map(list => list.label.id);
+
+ let labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
+ if (labelIds.length === 0) {
+ labelIds = [''];
+ }
+
+ const data = {
+ issue: {
+ label_ids: labelIds,
+ },
+ };
+
+ // Post the remove data
+ Vue.http.patch(this.updateUrl, data).catch(() => {
+ Flash(__('Failed to remove issue from board, please try again.'));
+
+ lists.forEach(list => {
+ list.addIssue(issue);
+ });
+ });
+
+ // Remove from the frontend store
+ lists.forEach(list => {
+ list.removeIssue(issue);
+ });
+
+ Store.detail.issue = {};
+ },
+ },
+ };
+</script>
+<template>
+ <div
+ class="block list"
+ >
+ <button
+ class="btn btn-default btn-block"
+ type="button"
+ @click="removeIssue"
+ >
+ Remove from board
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 9de57db48fd..b0f674f2c05 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -7,6 +7,16 @@ function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, '');
}
+export const defaultAutocompleteConfig = {
+ emojis: true,
+ members: true,
+ issues: true,
+ mergeRequests: true,
+ epics: false,
+ milestones: true,
+ labels: true,
+};
+
class GfmAutoComplete {
constructor(dataSources) {
this.dataSources = dataSources || {};
@@ -14,14 +24,7 @@ class GfmAutoComplete {
this.isLoadingData = {};
}
- setup(input, enableMap = {
- emojis: true,
- members: true,
- issues: true,
- milestones: true,
- mergeRequests: true,
- labels: true,
- }) {
+ setup(input, enableMap = defaultAutocompleteConfig) {
// Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input');
this.enableMap = enableMap;
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 9f5eba353d7..f802971a3ca 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,14 +1,14 @@
import $ from 'jquery';
import autosize from 'autosize';
-import GfmAutoComplete from './gfm_auto_complete';
+import GfmAutoComplete, * as GFMConfig from './gfm_auto_complete';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
export default class GLForm {
- constructor(form, enableGFM = false) {
+ constructor(form, enableGFM = {}) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
- this.enableGFM = enableGFM;
+ this.enableGFM = Object.assign({}, GFMConfig.defaultAutocompleteConfig, enableGFM);
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
@@ -34,14 +34,7 @@ export default class GLForm {
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
- this.autoComplete.setup(this.form.find('.js-gfm-input'), {
- emojis: true,
- members: this.enableGFM,
- issues: this.enableGFM,
- milestones: this.enableGFM,
- mergeRequests: this.enableGFM,
- labels: this.enableGFM,
- });
+ this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
dropzoneInput(this.form);
autosize(this.textarea);
}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index 5cda7967130..ee21eeda3cd 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -89,14 +89,14 @@ export default {
<template>
<div class="multi-file-commit-list-item position-relative">
- <button
+ <div
v-tooltip
:title="tooltipTitle"
:class="{
'is-active': isActive
}"
- type="button"
class="multi-file-commit-list-path w-100 border-0 ml-0 mr-0"
+ role="button"
@dblclick="fileAction"
@click="openFileInEditor"
>
@@ -107,7 +107,7 @@ export default {
:css-classes="iconClass"
/>{{ file.name }}
</span>
- </button>
+ </div>
<component
:is="actionComponent"
:path="file.path"
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index dedc2988618..5cd2c9ce188 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -69,7 +69,7 @@ export default {
>
<icon
:size="16"
- name="pipeline"
+ name="rocket"
/>
</button>
</li>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 1ad52c1bd83..03772ae4a4c 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -44,6 +44,8 @@ export default {
methods: {
...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
clickFile(tab) {
+ if (tab.active) return;
+
this.updateDelayViewerUpdated(true);
if (tab.pending) {
diff --git a/app/assets/javascripts/init_notes.js b/app/assets/javascripts/init_notes.js
index 882aedfcc76..3c71258e53b 100644
--- a/app/assets/javascripts/init_notes.js
+++ b/app/assets/javascripts/init_notes.js
@@ -7,10 +7,10 @@ export default () => {
notesIds,
now,
diffView,
- autocomplete,
+ enableGFM,
} = JSON.parse(dataEl.innerHTML);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
- Notes.initialize(notesUrl, notesIds, now, diffView, autocomplete);
+ Notes.initialize(notesUrl, notesIds, now, diffView, enableGFM);
};
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 2f752d2dcd6..da1a52155d8 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -20,6 +20,7 @@ import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_c
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
+import { defaultAutocompleteConfig } from './gfm_auto_complete';
import CommentTypeToggle from './comment_type_toggle';
import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
@@ -45,7 +46,7 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
export default class Notes {
- static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
+ static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM) {
if (!this.instance) {
this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
}
@@ -55,7 +56,7 @@ export default class Notes {
return this.instance;
}
- constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
+ constructor(notes_url, note_ids, last_fetched_at, view, enableGFM = defaultAutocompleteConfig) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
@@ -94,7 +95,7 @@ export default class Notes {
this.cleanBinding();
this.addBinding();
this.setPollingInterval();
- this.setupMainTargetNoteForm();
+ this.setupMainTargetNoteForm(enableGFM);
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
@@ -598,14 +599,14 @@ export default class Notes {
*
* Sets some hidden fields in the form.
*/
- setupMainTargetNoteForm() {
+ setupMainTargetNoteForm(enableGFM) {
var form;
// find the form
form = $('.js-new-note-form');
// Set a global clone of the form for later cloning
this.formClone = form.clone();
// show the form
- this.setupNoteForm(form);
+ this.setupNoteForm(form, enableGFM);
// fix classes
form.removeClass('js-new-note-form');
form.addClass('js-main-target-form');
@@ -633,9 +634,9 @@ export default class Notes {
* setup GFM auto complete
* show the form
*/
- setupNoteForm(form) {
+ setupNoteForm(form, enableGFM = defaultAutocompleteConfig) {
var textarea, key;
- this.glForm = new GLForm(form, this.enableGFM);
+ this.glForm = new GLForm(form, enableGFM);
textarea = form.find('.js-note-text');
key = [
'Note',
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index a62696b39b4..a4e3faa5d75 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -194,7 +194,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea"
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
@click="cancelHandler()">
- Cancel
+ {{ __('Discard draft') }}
</button>
</div>
</form>
diff --git a/app/assets/javascripts/pages/projects/init_form.js b/app/assets/javascripts/pages/projects/init_form.js
index 0b6c5c1d30b..9f20a3e4e46 100644
--- a/app/assets/javascripts/pages/projects/init_form.js
+++ b/app/assets/javascripts/pages/projects/init_form.js
@@ -3,5 +3,5 @@ import GLForm from '~/gl_form';
export default function ($formEl) {
new ZenMode(); // eslint-disable-line no-new
- new GLForm($formEl, true); // eslint-disable-line no-new
+ new GLForm($formEl); // eslint-disable-line no-new
}
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 14fddbc9a05..b2b8e5d2300 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -10,7 +10,7 @@ import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
export default () => {
new ShortcutsNavigation();
- new GLForm($('.issue-form'), true);
+ new GLForm($('.issue-form'));
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 406fc32f9a2..3a3c21f2202 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -12,7 +12,7 @@ import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
export default () => {
new Diff();
new ShortcutsNavigation();
- new GLForm($('.merge-request-form'), true);
+ new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js
index 8d0edf7e06c..b3158f7e939 100644
--- a/app/assets/javascripts/pages/projects/tags/new/index.js
+++ b/app/assets/javascripts/pages/projects/tags/new/index.js
@@ -5,6 +5,6 @@ import GLForm from '../../../../gl_form';
document.addEventListener('DOMContentLoaded', () => {
new ZenMode(); // eslint-disable-line no-new
- new GLForm($('.tag-form'), true); // eslint-disable-line no-new
+ new GLForm($('.tag-form')); // eslint-disable-line no-new
new RefSelectDropdown($('.js-branch-select')); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index 0295653cb29..0a0fe3fc137 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
- new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
+ new GLForm($('.wiki-form')); // eslint-disable-line no-new
const deleteWikiButton = document.getElementById('delete-wiki-button');
diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js
index 72d05da1069..758bbafead3 100644
--- a/app/assets/javascripts/pages/snippets/form.js
+++ b/app/assets/javascripts/pages/snippets/form.js
@@ -3,6 +3,13 @@ import GLForm from '~/gl_form';
import ZenMode from '~/zen_mode';
export default () => {
- new GLForm($('.snippet-form'), false); // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new GLForm($('.snippet-form'), {
+ members: false,
+ issues: false,
+ mergeRequests: false,
+ milestones: false,
+ labels: false,
+ });
new ZenMode(); // eslint-disable-line no-new
};
diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js
index 2f974d6ff9d..060f374310c 100644
--- a/app/assets/javascripts/shared/milestones/form.js
+++ b/app/assets/javascripts/shared/milestones/form.js
@@ -6,5 +6,13 @@ import GLForm from '../../gl_form';
export default (initGFM = true) => {
new ZenMode(); // eslint-disable-line no-new
new DueDateSelectors(); // eslint-disable-line no-new
- new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new GLForm($('.milestone-form'), {
+ emojis: initGFM,
+ members: initGFM,
+ issues: initGFM,
+ mergeRequests: initGFM,
+ milestones: initGFM,
+ labels: initGFM,
+ });
};
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index 96af6d2fcca..78fd7ad441f 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -11,7 +11,6 @@ export default class U2FAuthenticate {
constructor(container, form, u2fParams, fallbackButton, fallbackUI) {
this.u2fUtils = null;
this.container = container;
- this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this);
this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this);
@@ -41,7 +40,6 @@ export default class U2FAuthenticate {
this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge'));
this.templates = {
- notSupported: '#js-authenticate-u2f-not-supported',
setup: '#js-authenticate-u2f-setup',
inProgress: '#js-authenticate-u2f-in-progress',
error: '#js-authenticate-u2f-error',
@@ -55,7 +53,7 @@ export default class U2FAuthenticate {
this.u2fUtils = utils;
this.renderInProgress();
})
- .catch(() => this.renderNotSupported());
+ .catch(() => this.switchToFallbackUI());
}
authenticate() {
@@ -96,10 +94,6 @@ export default class U2FAuthenticate {
this.fallbackButton.classList.add('hidden');
}
- renderNotSupported() {
- return this.renderTemplate('notSupported');
- }
-
switchToFallbackUI() {
this.fallbackButton.classList.add('hidden');
this.container[0].classList.add('hidden');
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 7d26390d9bc..fba67681777 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -62,7 +62,14 @@
/*
GLForm class handles all the toolbar buttons
*/
- return new GLForm($(this.$refs['gl-form']), this.enableAutocomplete);
+ return new GLForm($(this.$refs['gl-form']), {
+ emojis: this.enableAutocomplete,
+ members: this.enableAutocomplete,
+ issues: this.enableAutocomplete,
+ mergeRequests: this.enableAutocomplete,
+ milestones: this.enableAutocomplete,
+ labels: this.enableAutocomplete,
+ });
},
beforeDestroy() {
const glForm = $(this.$refs['gl-form']).data('glForm');
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index ba1f0a975a9..f610a1aea08 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -310,7 +310,7 @@ pre code {
color: $white-light;
h4,
- a,
+ a:not(.btn),
.alert-link {
color: $white-light;
}
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index b40d02f381a..aaa8bed3df0 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -180,10 +180,6 @@
color: $border-and-box-shadow;
}
- .ide-file-list .file.file-active {
- color: $border-and-box-shadow;
- }
-
.ide-sidebar-link {
&.active {
color: $border-and-box-shadow;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2fa71b23314..91a9b956d9d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -527,7 +527,7 @@
.header-user {
.dropdown-menu {
width: auto;
- min-width: 160px;
+ min-width: unset;
margin-top: 4px;
color: $gl-text-color;
left: auto;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 0a56153203c..3c24aaa65e8 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -23,6 +23,7 @@
margin-top: 0;
border-top: 1px solid $white-dark;
padding-bottom: $ide-statusbar-height;
+ color: $gl-text-color;
&.is-collapsed {
.ide-file-list {
@@ -45,12 +46,8 @@
.file {
cursor: pointer;
- &.file-open {
- background: $white-normal;
- }
-
&.file-active {
- font-weight: $gl-font-weight-bold;
+ background: $theme-gray-100;
}
.ide-file-name {
@@ -58,7 +55,9 @@
white-space: nowrap;
text-overflow: ellipsis;
max-width: inherit;
- line-height: 22px;
+ line-height: 16px;
+ display: inline-block;
+ height: 18px;
svg {
vertical-align: middle;
@@ -86,12 +85,14 @@
.ide-new-btn {
display: none;
+
+ .btn {
+ padding: 2px 5px;
+ }
}
&:hover,
&:focus {
- background: $white-normal;
-
.ide-new-btn {
display: block;
}
@@ -281,8 +282,8 @@
}
.margin {
- background-color: $gray-light;
- border-right: 1px solid $white-normal;
+ background-color: $white-light;
+ border-right: 1px solid $theme-gray-100;
.line-insert {
border-right: 1px solid $line-added-dark;
@@ -303,6 +304,15 @@
.multi-file-editor-holder {
height: 100%;
min-height: 0;
+
+ &.is-readonly,
+ .editor.original {
+ .monaco-editor,
+ .monaco-editor-background,
+ .monaco-editor .inputarea.ime-input {
+ background-color: $theme-gray-50;
+ }
+ }
}
.preview-container {
@@ -587,11 +597,17 @@
&:hover,
&:focus {
- background: $white-normal;
+ background: $theme-gray-100;
+ }
+
+ &:active {
+ background: $theme-gray-200;
}
}
.multi-file-commit-list-path {
+ cursor: pointer;
+
&.is-active {
background-color: $white-normal;
}
@@ -611,10 +627,6 @@
.multi-file-commit-list-file-path {
@include str-truncated(calc(100% - 30px));
- &:hover {
- text-decoration: underline;
- }
-
&:active {
text-decoration: none;
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 765c926751a..2d66f336076 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -114,7 +114,7 @@ input[type="checkbox"]:hover {
}
.dropdown-content {
- max-height: 302px;
+ max-height: none;
}
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index cdfe3d6ab1e..9723e400574 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -52,7 +52,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
private
def set_application_setting
- @application_setting = ApplicationSetting.current_without_cache
+ @application_setting = Gitlab::CurrentSettings.current_application_settings
end
def application_setting_params
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 001f6520093..96b7bc65ac9 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -72,10 +72,10 @@ class Admin::GroupsController < Admin::ApplicationController
end
def group_params
- params.require(:group).permit(group_params_ce)
+ params.require(:group).permit(allowed_group_params)
end
- def group_params_ce
+ def allowed_group_params
[
:avatar,
:description,
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index bfeb5a2d097..653f3dfffc4 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -187,10 +187,10 @@ class Admin::UsersController < Admin::ApplicationController
end
def user_params
- params.require(:user).permit(user_params_ce)
+ params.require(:user).permit(allowed_user_params)
end
- def user_params_ce
+ def allowed_user_params
[
:access_level,
:avatar,
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index ba62d2d5142..1547d4b5972 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -119,7 +119,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
set_remember_me(user)
- if user.two_factor_enabled?
+ if user.two_factor_enabled? && !auth_user.bypass_two_factor?
prompt_for_two_factor(user)
else
sign_in_and_redirect(user)
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index dd12d30a085..63f0aea3195 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -160,7 +160,7 @@ class Projects::JobsController < Projects::ApplicationController
def build
@build ||= project.builds.find(params[:id])
- .present(current_user: current_user)
+ .present(current_user: current_user)
end
def build_path(build)
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 242e6491456..aa844e94d89 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -95,6 +95,7 @@ class Projects::WikisController < Projects::ApplicationController
def destroy
@page = @project_wiki.find_page(params[:id])
+
WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to project_wiki_path(@project, :home),
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index efb30ba4715..c2492a137fb 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -63,7 +63,7 @@ class ProjectsController < Projects::ApplicationController
redirect_to(edit_project_path(@project))
end
else
- flash[:alert] = result[:message]
+ flash.now[:alert] = result[:message]
format.html { render 'edit' }
end
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 65d6e019746..74776b2ed1f 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -56,7 +56,7 @@ class UserRecentEventsFinder
visible = target_user
.project_interactions
- .where(visibility_level: [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC])
+ .where(visibility_level: Gitlab::VisibilityLevel.levels_for_user(current_user))
.select(:id)
Gitlab::SQL::Union.new([authorized, visible]).to_sql
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 5459bb63397..e1a0cf1604c 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -143,7 +143,14 @@ module NotesHelper
notesIds: @notes.map(&:id),
now: Time.now.to_i,
diffView: diff_view,
- autocomplete: autocomplete
+ enableGFM: {
+ emojis: true,
+ members: autocomplete,
+ issues: autocomplete,
+ mergeRequests: autocomplete,
+ milestones: autocomplete,
+ labels: autocomplete
+ }
}
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index be3958c40a4..c7a434ea092 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -40,7 +40,8 @@ module ProjectsHelper
name_tag_options[:class] << 'has-tooltip'
end
- content_tag(:span, sanitize(username), name_tag_options)
+ # NOTE: ActionView::Helpers::TagHelper#content_tag HTML escapes username
+ content_tag(:span, username, name_tag_options)
end
def link_to_member(project, author, opts = {}, &block)
@@ -506,6 +507,14 @@ module ProjectsHelper
end
end
+ def sidebar_projects_paths
+ %w[
+ projects#show
+ projects#activity
+ cycle_analytics#show
+ ]
+ end
+
def sidebar_settings_paths
%w[
projects#edit
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 3d58a14882f..bddeb8b0352 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -212,14 +212,6 @@ class ApplicationSetting < ActiveRecord::Base
end
end
- validates_each :disabled_oauth_sign_in_sources do |record, attr, value|
- value&.each do |source|
- unless Devise.omniauth_providers.include?(source.to_sym)
- record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
- end
- end
- end
-
validate :terms_exist, if: :enforce_terms?
before_validation :ensure_uuid!
@@ -330,6 +322,11 @@ class ApplicationSetting < ActiveRecord::Base
::Gitlab::Database.cached_column_exists?(:application_settings, :sidekiq_throttling_enabled)
end
+ def disabled_oauth_sign_in_sources=(sources)
+ sources = (sources || []).map(&:to_s) & Devise.omniauth_providers.map(&:to_s)
+ super(sources)
+ end
+
def domain_whitelist_raw
self.domain_whitelist&.join("\n")
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f430f18ca9a..e5caa3ffa41 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -561,9 +561,9 @@ module Ci
.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
- .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message)
- .append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title)
- .append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description)
+ .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
+ .append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
+ .append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
end
def queued_duration
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index db7254c27e0..cb76ae971d4 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -12,8 +12,8 @@ module Sortable
scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) }
scope :order_updated_asc, -> { reorder(updated_at: :asc) }
- scope :order_name_asc, -> { reorder("lower(name) asc") }
- scope :order_name_desc, -> { reorder("lower(name) desc") }
+ scope :order_name_asc, -> { reorder(Arel::Nodes::Ascending.new(arel_table[:name].lower)) }
+ scope :order_name_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:name].lower)) }
end
module ClassMethods
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 3df1130a6e2..6c96c8ca391 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -129,9 +129,7 @@ class MergeRequest < ActiveRecord::Base
after_transition unchecked: :cannot_be_merged do |merge_request, transition|
begin
- # Merge request can become unmergeable due to many reasons.
- # We only notify if it is due to conflict.
- unless merge_request.project.repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
+ if merge_request.notify_conflict?
NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request)
end
@@ -378,6 +376,10 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def non_latest_diffs
+ merge_request_diffs.where.not(id: merge_request_diff.id)
+ end
+
def diff_size
# Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here.
@@ -619,18 +621,7 @@ class MergeRequest < ActiveRecord::Base
def reload_diff(current_user = nil)
return unless open?
- old_diff_refs = self.diff_refs
- new_diff = create_merge_request_diff
-
- MergeRequests::MergeRequestDiffCacheService.new.execute(self, new_diff)
-
- new_diff_refs = self.diff_refs
-
- update_diff_discussion_positions(
- old_diff_refs: old_diff_refs,
- new_diff_refs: new_diff_refs,
- current_user: current_user
- )
+ MergeRequests::ReloadDiffsService.new(self, current_user).execute
end
def check_if_can_be_merged
@@ -715,6 +706,10 @@ class MergeRequest < ActiveRecord::Base
should_remove_source_branch? || force_remove_source_branch?
end
+ def notify_conflict?
+ (opened? || locked?) && !project.repository.can_be_merged?(diff_head_sha, target_branch)
+ end
+
def related_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 06aa67c600f..3d72c447b4b 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -3,6 +3,7 @@ class MergeRequestDiff < ActiveRecord::Base
include Importable
include ManualInverseAssociation
include IgnorableColumn
+ include EachBatch
# Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100
@@ -17,8 +18,14 @@ class MergeRequestDiff < ActiveRecord::Base
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
state_machine :state, initial: :empty do
+ event :clean do
+ transition any => :without_files
+ end
+
state :collected
state :overflow
+ # Diff files have been deleted by the system
+ state :without_files
# Deprecated states: these are no longer used but these values may still occur
# in the database.
state :timeout
@@ -27,6 +34,7 @@ class MergeRequestDiff < ActiveRecord::Base
state :overflow_diff_lines_limit
end
+ scope :with_files, -> { without_states(:without_files, :empty) }
scope :viewable, -> { without_state(:empty) }
scope :by_commit_sha, ->(sha) do
joins(:merge_request_diff_commits).where(merge_request_diff_commits: { sha: sha }).reorder(nil)
@@ -42,6 +50,10 @@ class MergeRequestDiff < ActiveRecord::Base
find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha)
end
+ def viewable?
+ collected? || without_files? || overflow?
+ end
+
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
@@ -170,6 +182,21 @@ class MergeRequestDiff < ActiveRecord::Base
end
def diffs(diff_options = nil)
+ if without_files? && comparison = diff_refs.compare_in(project)
+ # It should fetch the repository when diffs are cleaned by the system.
+ # We don't keep these for storage overload purposes.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/37639
+ comparison.diffs(diff_options)
+ else
+ diffs_collection(diff_options)
+ end
+ end
+
+ # Should always return the DB persisted diffs collection
+ # (e.g. Gitlab::Diff::FileCollection::MergeRequestDiff.
+ # It's useful when trying to invalidate old caches through
+ # FileCollection::MergeRequestDiff#clear_cache!
+ def diffs_collection(diff_options = nil)
Gitlab::Diff::FileCollection::MergeRequestDiff.new(self, diff_options: diff_options)
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 52fe529c016..7034c633268 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -228,6 +228,10 @@ class Namespace < ActiveRecord::Base
parent.present?
end
+ def root_ancestor
+ ancestors.reorder(nil).find_by(parent_id: nil)
+ end
+
def subgroup?
has_parent?
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 0d777515536..d91d7dcfe9a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2019,6 +2019,10 @@ class Project < ActiveRecord::Base
end
request_cache(:any_lfs_file_locks?) { self.id }
+ def auto_cancel_pending_pipelines?
+ auto_cancel_pending_pipelines == 'enabled'
+ end
+
private
def storage
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index d7d6aaceb27..faa831b1949 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -29,8 +29,8 @@ class ProjectAutoDevops < ActiveRecord::Base
end
if manual?
- variables.append(key: 'STAGING_ENABLED', value: 1)
- variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: 1)
+ variables.append(key: 'STAGING_ENABLED', value: '1')
+ variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1')
end
end
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 33280eda0b9..9a38806baab 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -24,7 +24,7 @@ class ProjectTeam
end
def add_role(user, role, current_user: nil)
- send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
+ public_send(:"add_#{role}", user, current_user: current_user) # rubocop:disable GitlabSecurity/PublicSend
end
def find_member(user_id)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 3089d0162ee..3056c20516a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -21,7 +21,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
- delegate :bundle_to_disk, :create_from_bundle, to: :raw_repository
+ delegate :bundle_to_disk, to: :raw_repository
CreateTreeError = Class.new(StandardError)
diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb
index f2844854112..975e288301c 100644
--- a/app/services/base_count_service.rb
+++ b/app/services/base_count_service.rb
@@ -17,7 +17,7 @@ class BaseCountService
end
def refresh_cache(&block)
- Rails.cache.write(cache_key, block_given? ? yield : uncached_count, raw: raw?)
+ update_cache_for_key(cache_key, &block)
end
def uncached_count
@@ -41,4 +41,8 @@ class BaseCountService
def cache_options
{ raw: raw? }
end
+
+ def update_cache_for_key(key, &block)
+ Rails.cache.write(key, block_given? ? yield : uncached_count, raw: raw?)
+ end
end
diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb
new file mode 100644
index 00000000000..40079b21189
--- /dev/null
+++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb
@@ -0,0 +1,18 @@
+module MergeRequests
+ class DeleteNonLatestDiffsService
+ BATCH_SIZE = 10
+
+ def initialize(merge_request)
+ @merge_request = merge_request
+ end
+
+ def execute
+ diffs = @merge_request.non_latest_diffs.with_files
+
+ diffs.each_batch(of: BATCH_SIZE) do |relation, index|
+ ids = relation.pluck(:id).map { |id| [id] }
+ DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb
deleted file mode 100644
index 10aa9ae609c..00000000000
--- a/app/services/merge_requests/merge_request_diff_cache_service.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module MergeRequests
- class MergeRequestDiffCacheService
- def execute(merge_request, new_diff)
- # Executing the iteration we cache all the highlighted diff information
- merge_request.diffs.diff_files.to_a
-
- # Remove cache for all diffs on this MR. Do not use the association on the
- # model, as that will interfere with other actions happening when
- # reloading the diff.
- MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
- next if merge_request_diff == new_diff
-
- merge_request_diff.diffs.clear_cache!
- end
- end
- end
-end
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index c78e78afcd1..5b160ffba67 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -15,6 +15,7 @@ module MergeRequests
execute_hooks(merge_request, 'merge')
invalidate_cache_counts(merge_request, users: merge_request.assignees)
merge_request.update_project_counter_caches
+ delete_non_latest_diffs(merge_request)
end
private
@@ -31,6 +32,10 @@ module MergeRequests
end
end
+ def delete_non_latest_diffs(merge_request)
+ DeleteNonLatestDiffsService.new(merge_request).execute
+ end
+
def create_merge_event(merge_request, current_user)
EventCreateService.new.merge_mr(merge_request, current_user)
end
diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb
new file mode 100644
index 00000000000..2ec7b403903
--- /dev/null
+++ b/app/services/merge_requests/reload_diffs_service.rb
@@ -0,0 +1,43 @@
+module MergeRequests
+ class ReloadDiffsService
+ def initialize(merge_request, current_user)
+ @merge_request = merge_request
+ @current_user = current_user
+ end
+
+ def execute
+ old_diff_refs = merge_request.diff_refs
+ new_diff = merge_request.create_merge_request_diff
+
+ clear_cache(new_diff)
+ update_diff_discussion_positions(old_diff_refs)
+ end
+
+ private
+
+ attr_reader :merge_request, :current_user
+
+ def update_diff_discussion_positions(old_diff_refs)
+ new_diff_refs = merge_request.diff_refs
+
+ merge_request.update_diff_discussion_positions(old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs,
+ current_user: current_user)
+ end
+
+ def clear_cache(new_diff)
+ # Executing the iteration we cache highlighted diffs for each diff file of
+ # MergeRequestDiff.
+ new_diff.diffs_collection.diff_files.to_a
+
+ # Remove cache for all diffs on this MR. Do not use the association on the
+ # model, as that will interfere with other actions happening when
+ # reloading the diff.
+ MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
+ next if merge_request_diff == new_diff
+
+ merge_request_diff.diffs_collection.clear_cache!
+ end
+ end
+ end
+end
diff --git a/app/services/projects/count_service.rb b/app/services/projects/count_service.rb
index 933829b557b..4c8e000928f 100644
--- a/app/services/projects/count_service.rb
+++ b/app/services/projects/count_service.rb
@@ -22,8 +22,10 @@ module Projects
)
end
- def cache_key
- ['projects', 'count_service', VERSION, @project.id, cache_key_name]
+ def cache_key(key = nil)
+ cache_key = key || cache_key_name
+
+ ['projects', 'count_service', VERSION, @project.id, cache_key]
end
def self.query(project_ids)
diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb
index 0a004677417..78b1477186a 100644
--- a/app/services/projects/open_issues_count_service.rb
+++ b/app/services/projects/open_issues_count_service.rb
@@ -4,6 +4,10 @@ module Projects
class OpenIssuesCountService < Projects::CountService
include Gitlab::Utils::StrongMemoize
+ # Cache keys used to store issues count
+ PUBLIC_COUNT_KEY = 'public_open_issues_count'.freeze
+ TOTAL_COUNT_KEY = 'total_open_issues_count'.freeze
+
def initialize(project, user = nil)
@user = user
@@ -11,7 +15,7 @@ module Projects
end
def cache_key_name
- public_only? ? 'public_open_issues_count' : 'total_open_issues_count'
+ public_only? ? PUBLIC_COUNT_KEY : TOTAL_COUNT_KEY
end
def public_only?
@@ -28,6 +32,32 @@ module Projects
end
end
+ def public_count_cache_key
+ cache_key(PUBLIC_COUNT_KEY)
+ end
+
+ def total_count_cache_key
+ cache_key(TOTAL_COUNT_KEY)
+ end
+
+ def refresh_cache(&block)
+ if block_given?
+ super(&block)
+ else
+ count_grouped_by_confidential = self.class.query(@project, public_only: false).group(:confidential).count
+ public_count = count_grouped_by_confidential[false] || 0
+ total_count = public_count + (count_grouped_by_confidential[true] || 0)
+
+ update_cache_for_key(public_count_cache_key) do
+ public_count
+ end
+
+ update_cache_for_key(total_count_cache_key) do
+ total_count
+ end
+ end
+ end
+
# We only show total issues count for reporters
# which are allowed to view confidential issues
# This will still show a discrepancy on issues number but should be less than before.
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 7ec52b6ce2b..8a86e47f0ea 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -82,7 +82,7 @@ class WebHookService
post_url = hook.url.gsub("#{parsed_url.userinfo}@", '')
basic_auth = {
username: CGI.unescape(parsed_url.user),
- password: CGI.unescape(parsed_url.password)
+ password: CGI.unescape(parsed_url.password.presence || '')
}
make_request(post_url, basic_auth)
end
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 38607ffca1c..bd43504dd37 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -324,3 +324,6 @@
= _('Configure push mirrors.')
.settings-content
= render partial: 'repository_mirrors_form'
+
+= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded
+
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
index 7637471f9ae..ee2d4c8430a 100644
--- a/app/views/admin/labels/_form.html.haml
+++ b/app/views/admin/labels/_form.html.haml
@@ -10,16 +10,16 @@
.col-sm-10
= f.text_field :description, class: "form-control js-quick-submit"
.form-group.row
- = f.label :color, "Background color", class: 'col-form-label col-sm-2'
+ = f.label :color, _("Background color"), class: 'col-form-label col-sm-2'
.col-sm-10
.input-group
.input-group-prepend
.input-group-text.label-color-preview &nbsp;
= f.text_field :color, class: "form-control"
.form-text.text-muted
- Choose any color.
+ = _('Choose any color.')
%br
- Or you can choose one of the suggested colors below
+ = _("Or you can choose one of the suggested colors below")
.suggest-colors
- suggested_colors.each do |color|
@@ -27,5 +27,5 @@
&nbsp;
.form-actions
- = f.submit 'Save', class: 'btn btn-save js-save-button'
- = link_to "Cancel", admin_labels_path, class: 'btn btn-cancel'
+ = f.submit _('Save'), class: 'btn btn-save js-save-button'
+ = link_to _("Cancel"), admin_labels_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml
index 009a47dd517..c3ea2352898 100644
--- a/app/views/admin/labels/_label.html.haml
+++ b/app/views/admin/labels/_label.html.haml
@@ -3,5 +3,5 @@
= render_colored_label(label, tooltip: false)
= markdown_field(label, :description)
.float-right
- = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm'
- = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
+ = link_to _('Edit'), edit_admin_label_path(label), class: 'btn btn-sm'
+ = link_to _('Delete'), admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"}
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 96f0d404ac4..652ed095d00 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -1,7 +1,7 @@
-- add_to_breadcrumbs "Labels", admin_labels_path
-- breadcrumb_title "Edit Label"
-- page_title "Edit", @label.name, "Labels"
+- add_to_breadcrumbs _("Labels"), admin_labels_path
+- breadcrumb_title _("Edit Label")
+- page_title _("Edit"), @label.name, _("Labels")
%h3.page-title
- Edit Label
+ = _('Edit Label')
%hr
= render 'form'
diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml
index add38fb333e..d3e5247447a 100644
--- a/app/views/admin/labels/index.html.haml
+++ b/app/views/admin/labels/index.html.haml
@@ -1,10 +1,10 @@
-- page_title "Labels"
+- page_title _("Labels")
%div
= link_to new_admin_label_path, class: "float-right btn btn-nr btn-new" do
- New label
+ = _('New label')
%h3.page-title
- Labels
+ = _('Labels')
%hr
.labels
@@ -14,5 +14,5 @@
= paginate @labels, theme: 'gitlab'
- else
.card.bg-light
- .nothing-here-block There are no labels yet
+ .nothing-here-block= _('There are no labels yet')
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
index 0135ad0723d..20103fb8a29 100644
--- a/app/views/admin/labels/new.html.haml
+++ b/app/views/admin/labels/new.html.haml
@@ -1,5 +1,5 @@
-- page_title "New Label"
+- page_title _("New Label")
%h3.page-title
- New Label
+ = _('New Label')
%hr
= render 'form'
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 24b6c490a5a..cdfd45fceb1 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -17,6 +17,11 @@
= link_to _("Help"), help_path
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
%li.divider
+ %li
+ = link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do
+ = _("Contribute to GitLab")
+ = icon('external-link')
+ %li.divider
- if current_user_menu?(:sign_out)
%li
= link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link"
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 9f8b3b86474..33416bf76d7 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -8,7 +8,7 @@
.sidebar-context-title
= @project.name
%ul.sidebar-top-level-items
- = nav_link(path: ['projects#show', 'projects#activity', 'cycle_analytics#show'], html_options: { class: 'home' }) do
+ = nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
= link_to project_path(@project), class: 'shortcuts-project' do
.nav-icon-container
= sprite_icon('project')
@@ -29,13 +29,13 @@
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span= _('Activity')
+ = render_if_exists 'projects/sidebar/security_dashboard'
+
- 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')
- = render_if_exists 'projects/sidebar/security_dashboard'
-
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
= link_to project_tree_path(@project), class: 'shortcuts-tree' do
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 6ea358d9f63..c14700794ce 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -4,10 +4,12 @@
.form-group
= f.label :key, class: 'label-light'
- = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the SSH key. Paste the public part, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'."
+ %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key.")
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: 'Typically starts with "ssh-rsa …"'
.form-group
= f.label :title, class: 'label-light'
- = f.text_field :title, class: "form-control", required: true
+ = f.text_field :title, class: "form-control", required: true, placeholder: 'e.g. My MacBook key'
+ %p.form-text.text-muted= _('Name your individual key via a title')
.prepend-top-default
= f.submit 'Add key', class: "btn btn-create"
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 1e206def7ee..55ca8d0ebd4 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -11,10 +11,11 @@
%h5.prepend-top-0
Add an SSH key
%p.profile-settings-content
- Before you can add an SSH key you need to
- = link_to "generate one", help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
- or use an
- = link_to "existing key.", help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
+ - generate_link_url = help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair')
+ - existing_link_url = help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair')
+ - generate_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_link_url }
+ - existing_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: existing_link_url }
+ = _('To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}.').html_safe % { generate_link_start: generate_link_start, existing_link_start: existing_link_start, link_end: '</a>'.html_safe }
= render 'form'
%hr
%h5
diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
index d0402197821..9298d93663d 100644
--- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
@@ -6,7 +6,7 @@
= image_tag 'illustrations/logos/google-cloud-platform_logo.svg'
.col-sm-10
%h4= s_('ClusterIntegration|Redeem up to $500 in free credit for Google Cloud Platform')
- %p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for new GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
+ %p= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
%a.btn.btn-info{ href: 'https://goo.gl/AaJzRW', target: '_blank', rel: 'noopener noreferrer' }
Apply for credit
diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml
index c7ac687e4a6..282566eeadc 100644
--- a/app/views/projects/deployments/_commit.html.haml
+++ b/app/views/projects/deployments/_commit.html.haml
@@ -14,4 +14,4 @@
= author_avatar(deployment.commit, size: 20)
= link_to_markdown commit_title, project_commit_path(@project, deployment.sha), class: "commit-row-message"
- else
- Cant find HEAD commit for this branch
+ = _("Can't find HEAD commit for this branch")
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 520696b01c6..85bc8ec07e3 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -1,14 +1,14 @@
.gl-responsive-table-row.deployment{ role: 'row' }
.table-section.section-10{ role: 'gridcell' }
- .table-mobile-header{ role: 'rowheader' } ID
+ .table-mobile-header{ role: 'rowheader' }= _("ID")
%strong.table-mobile-content ##{deployment.iid}
.table-section.section-30{ role: 'gridcell' }
- .table-mobile-header{ role: 'rowheader' } Commit
+ .table-mobile-header{ role: 'rowheader' }= _("Commit")
= render 'projects/deployments/commit', deployment: deployment
.table-section.section-25.build-column{ role: 'gridcell' }
- .table-mobile-header{ role: 'rowheader' } Job
+ .table-mobile-header{ role: 'rowheader' }= _("Job")
- if deployment.deployable
.table-mobile-content
.flex-truncate-parent
@@ -21,7 +21,7 @@
= user_avatar(user: deployment.user, size: 20)
.table-section.section-15{ role: 'gridcell' }
- .table-mobile-header{ role: 'rowheader' } Created
+ .table-mobile-header{ role: 'rowheader' }= _("Created")
%span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
.table-section.section-20.table-button-footer{ role: 'gridcell' }
diff --git a/app/views/projects/deployments/_rollback.haml b/app/views/projects/deployments/_rollback.haml
index 5941e01c6f1..95f950948ab 100644
--- a/app/views/projects/deployments/_rollback.haml
+++ b/app/views/projects/deployments/_rollback.haml
@@ -1,6 +1,6 @@
- if can?(current_user, :create_deployment, deployment) && deployment.deployable
= link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- if deployment.last?
- Re-deploy
+ = _("Re-deploy")
- else
- Rollback
+ = _("Rollback")
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index 983cb187c2f..3f1974d05f4 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -30,7 +30,7 @@
#{@commits_graph.start_date.strftime('%b %d')}
- end_time = capture do
#{@commits_graph.end_date.strftime('%b %d')}
- = (_("Commit statistics for %{ref} %{start_time} - %{end_time}") % { ref: "<strong>#{@ref}</strong>", start_time: start_time, end_time: end_time }).html_safe
+ = (_("Commit statistics for %{ref} %{start_time} - %{end_time}") % { ref: "<strong>#{h @ref}</strong>", start_time: start_time, end_time: end_time }).html_safe
.col-md-6
.tree-ref-container
diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml
index 19659fe5140..bf3df0abf86 100644
--- a/app/views/projects/merge_requests/diffs/_diffs.html.haml
+++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml
@@ -16,6 +16,6 @@
%span.ref-name= @merge_request.target_branch
.text-center= link_to 'Create commit', project_new_blob_path(@project, @merge_request.source_branch), class: 'btn btn-save'
- else
- - diff_viewable = @merge_request_diff ? @merge_request_diff.collected? || @merge_request_diff.overflow? : true
+ - diff_viewable = @merge_request_diff ? @merge_request_diff.viewable? : true
- if diff_viewable
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 496b94ec953..a88d8f61fb4 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -3,8 +3,8 @@
- @no_breadcrumb_container = true
- @no_container = true
- @content_class = "issue-boards-content"
-- breadcrumb_title "Issue Board"
-- page_title "Boards"
+- breadcrumb_title _("Issue Board")
+- page_title _("Boards")
- content_for :page_specific_javascripts do
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 76843ce7cc0..65de6172d89 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -30,7 +30,7 @@
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
- %button.board-delete.has-tooltip.float-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
+ %button.board-delete.has-tooltip.float-right{ type: "button", title: _("Delete list"), "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
.issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.issue-count-badge-count.float-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
@@ -39,8 +39,8 @@
%button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => 'list.type !== "closed"',
- "aria-label" => "New issue",
- "title" => "New issue",
+ "aria-label" => _("New issue"),
+ "title" => _("New issue"),
data: { placement: "top", container: "body" } }
= icon("plus", class: "js-no-trigger-collapse")
diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index 10217b6cbf0..5630375f428 100644
--- a/app/views/shared/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -1,20 +1,20 @@
.block.due_date
.title
- Due date
+ = _("Due date")
- if can_admin_issue?
= icon("spinner spin", class: "block-loading")
- = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
+ = link_to _("Edit"), "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
.value
.value-content
%span.no-value{ "v-if" => "!issue.dueDate" }
- No due date
+ = _("No due date")
%span.bold{ "v-if" => "issue.dueDate" }
{{ issue.dueDate | due-date }}
- if can_admin_issue?
%span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" }
\-
%a.js-remove-due-date{ href: "#", role: "button" }
- remove due date
+ = _('remove due date')
- if can_admin_issue?
.selectbox
%input{ type: "hidden",
@@ -23,9 +23,9 @@
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" } }
- %span.dropdown-toggle-text Due date
+ %span.dropdown-toggle-text= _("Due date")
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
- = dropdown_title('Due date')
+ = dropdown_title(_('Due date'))
= dropdown_content do
.js-due-date-calendar
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index daee691e358..607e7f471c9 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -1,12 +1,12 @@
.block.labels
.title
- Labels
+ = _("Labels")
- if can_admin_issue?
= icon("spinner spin", class: "block-loading")
- = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
+ = link_to _("Edit"), "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
.value.issuable-show-labels.dont-hide
%span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" }
- None
+ = _("None")
%a{ href: "#",
"v-for" => "label in issue.labels" }
.badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
@@ -28,7 +28,7 @@
namespace_path: @namespace_path,
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
- Label
+ = _("Label")
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index f2bedd5e3c9..b15d60002fc 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -1,12 +1,12 @@
.block.milestone
.title
- Milestone
+ = _("Milestone")
- if can_admin_issue?
= icon("spinner spin", class: "block-loading")
- = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
+ = link_to _("Edit"), "#", class: "js-sidebar-dropdown-toggle edit-link float-right"
.value
%span.no-value{ "v-if" => "!issue.milestone" }
- None
+ = _("None")
%span.bold.has-tooltip{ "v-if" => "issue.milestone" }
{{ issue.milestone.title }}
- if can_admin_issue?
@@ -19,10 +19,10 @@
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
":data-issuable-id" => "issue.iid" }
- Milestone
+ = _("Milestone")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
- = dropdown_title("Assign milestone")
- = dropdown_filter("Search milestones")
+ = dropdown_title(_("Assign milestone"))
+ = dropdown_filter(_("Search milestones"))
= dropdown_content
= dropdown_loading
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index c360f1ffe2a..6b2715b47a7 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -40,5 +40,5 @@
= yield(:note_actions)
- %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
+ %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Discard draft" } }
Discard draft
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index 2d0bb722189..e5c82962f82 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -6,5 +6,4 @@
%fieldset
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
= label_tag ("#{prefix}_scopes_#{scope}"), scope, class: "label-light"
- %span= t(scope, scope: [:doorkeeper, :scopes])
.scope-description= t scope, scope: [:doorkeeper, :scope_desc]
diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml
index 7eb221620ad..1c788b9a737 100644
--- a/app/views/u2f/_authenticate.html.haml
+++ b/app/views/u2f/_authenticate.html.haml
@@ -2,9 +2,6 @@
%a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code
-# haml-lint:disable InlineJavaScript
-%script#js-authenticate-u2f-not-supported{ type: "text/template" }
- %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer).
-
%script#js-authenticate-u2f-in-progress{ type: "text/template" }
%p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 30b6796a7d6..026f756582d 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -118,3 +118,4 @@
- web_hook
- repository_update_remote_mirror
- create_note_diff_file
+- delete_diff_files
diff --git a/app/workers/delete_diff_files_worker.rb b/app/workers/delete_diff_files_worker.rb
new file mode 100644
index 00000000000..bb8fbb9c373
--- /dev/null
+++ b/app/workers/delete_diff_files_worker.rb
@@ -0,0 +1,17 @@
+class DeleteDiffFilesWorker
+ include ApplicationWorker
+
+ def perform(merge_request_diff_id)
+ merge_request_diff = MergeRequestDiff.find(merge_request_diff_id)
+
+ return if merge_request_diff.without_files?
+
+ MergeRequestDiff.transaction do
+ merge_request_diff.clean!
+
+ MergeRequestDiffFile
+ .where(merge_request_diff_id: merge_request_diff.id)
+ .delete_all
+ end
+ end
+end
diff --git a/bin/changelog b/bin/changelog
index 9b60f53ce40..d7b2a1a2de9 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -19,7 +19,24 @@ Options = Struct.new(
)
INVALID_TYPE = -1
+module ChangelogHelpers
+ Abort = Class.new(StandardError)
+ Done = Class.new(StandardError)
+
+ def capture_stdout(cmd)
+ output = IO.popen(cmd, &:read)
+ fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
+ output
+ end
+
+ def fail_with(message)
+ raise Abort, "\e[31merror\e[0m #{message}"
+ end
+end
+
class ChangelogOptionParser
+ extend ChangelogHelpers
+
Type = Struct.new(:name, :description)
TYPES = [
Type.new('added', 'New feature'),
@@ -68,7 +85,7 @@ class ChangelogOptionParser
opts.on('-h', '--help', 'Print help message') do
$stdout.puts opts
- exit
+ raise Done.new
end
end
@@ -108,18 +125,19 @@ class ChangelogOptionParser
def assert_valid_type!(type)
unless type
- $stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
- exit 1
+ raise Abort, "Invalid category index, please select an index between 1 and #{TYPES.length}"
end
end
def git_user_name
- %x{git config user.name}.strip
+ capture_stdout(%w[git config user.name]).strip
end
end
end
class ChangelogEntry
+ include ChangelogHelpers
+
attr_reader :options
def initialize(options)
@@ -159,13 +177,9 @@ class ChangelogEntry
end
def amend_commit
- %x{git add #{file_path}}
- exec("git commit --amend")
- end
+ fail_with "git add failed" unless system(*%W[git add #{file_path}])
- def fail_with(message)
- $stderr.puts "\e[31merror\e[0m #{message}"
- exit 1
+ Kernel.exec(*%w[git commit --amend])
end
def assert_feature_branch!
@@ -203,7 +217,7 @@ class ChangelogEntry
end
def last_commit_subject
- %x{git log --format="%s" -1}.strip
+ capture_stdout(%w[git log --format=%s -1]).strip
end
def file_path
@@ -225,7 +239,7 @@ class ChangelogEntry
end
def branch_name
- @branch_name ||= %x{git symbolic-ref --short HEAD}.strip
+ @branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
end
def remove_trailing_whitespace(yaml_content)
@@ -234,8 +248,15 @@ class ChangelogEntry
end
if $0 == __FILE__
- options = ChangelogOptionParser.parse(ARGV)
- ChangelogEntry.new(options)
+ begin
+ options = ChangelogOptionParser.parse(ARGV)
+ ChangelogEntry.new(options)
+ rescue ChangelogHelpers::Abort => ex
+ $stderr.puts ex.message
+ exit 1
+ rescue ChangelogHelpers::Done
+ exit
+ end
end
# vim: ft=ruby
diff --git a/changelogs/unreleased/40005-u2f-unspported-browsers.yml b/changelogs/unreleased/40005-u2f-unspported-browsers.yml
new file mode 100644
index 00000000000..eb5ff99246e
--- /dev/null
+++ b/changelogs/unreleased/40005-u2f-unspported-browsers.yml
@@ -0,0 +1,5 @@
+---
+title: Improve U2F workflow when using unsupported browsers
+merge_request: 19938
+author: Jan Beckmann
+type: changed
diff --git a/changelogs/unreleased/45933-webide-fade-uneditable-area.yml b/changelogs/unreleased/45933-webide-fade-uneditable-area.yml
new file mode 100644
index 00000000000..dfb186122e7
--- /dev/null
+++ b/changelogs/unreleased/45933-webide-fade-uneditable-area.yml
@@ -0,0 +1,5 @@
+---
+title: Fade uneditable area in Web IDE
+merge_request: 20008
+author:
+type: changed
diff --git a/changelogs/unreleased/46202-webide-file-states.yml b/changelogs/unreleased/46202-webide-file-states.yml
new file mode 100644
index 00000000000..8d697b643be
--- /dev/null
+++ b/changelogs/unreleased/46202-webide-file-states.yml
@@ -0,0 +1,5 @@
+---
+title: Update Web IDE file tree styles
+merge_request: 19969
+author:
+type: changed
diff --git a/changelogs/unreleased/46396-recognise-when-a-user-is-trying-to-validate-a-private-ssh-key-part-1.yml b/changelogs/unreleased/46396-recognise-when-a-user-is-trying-to-validate-a-private-ssh-key-part-1.yml
new file mode 100644
index 00000000000..d8c7d612c3d
--- /dev/null
+++ b/changelogs/unreleased/46396-recognise-when-a-user-is-trying-to-validate-a-private-ssh-key-part-1.yml
@@ -0,0 +1,5 @@
+---
+title: Update new SSH key page to improve copy
+merge_request: 19994
+author:
+type: other
diff --git a/changelogs/unreleased/46571-webhooks-nil-password.yml b/changelogs/unreleased/46571-webhooks-nil-password.yml
new file mode 100644
index 00000000000..34c5f09478f
--- /dev/null
+++ b/changelogs/unreleased/46571-webhooks-nil-password.yml
@@ -0,0 +1,5 @@
+---
+title: Fix webhook error when password is not present
+merge_request: 19945
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/46783-removed-omniauth-provider-causing-invalid-application-setting.yml b/changelogs/unreleased/46783-removed-omniauth-provider-causing-invalid-application-setting.yml
new file mode 100644
index 00000000000..d5ecf5163d4
--- /dev/null
+++ b/changelogs/unreleased/46783-removed-omniauth-provider-causing-invalid-application-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore unknown OAuth sources in ApplicationSetting
+merge_request: 20129
+author:
+type: fixed
diff --git a/changelogs/unreleased/47274-help-users-find-our-contributing-page.yml b/changelogs/unreleased/47274-help-users-find-our-contributing-page.yml
new file mode 100644
index 00000000000..ed13c917a2e
--- /dev/null
+++ b/changelogs/unreleased/47274-help-users-find-our-contributing-page.yml
@@ -0,0 +1,5 @@
+---
+title: Add a link to the contributing page in the user dropdown
+merge_request: 19708
+author:
+type: added
diff --git a/changelogs/unreleased/48269-wiki-page-returns-error-code-502.yml b/changelogs/unreleased/48269-wiki-page-returns-error-code-502.yml
new file mode 100644
index 00000000000..d3830e5b8c6
--- /dev/null
+++ b/changelogs/unreleased/48269-wiki-page-returns-error-code-502.yml
@@ -0,0 +1,5 @@
+---
+title: Remove performance bottleneck preventing large wiki pages from displaying
+merge_request: 20174
+author:
+type: performance
diff --git a/changelogs/unreleased/48339-sorting-by-name-on-explore-projects-page-renders-a-500-error-when-logged-in.yml b/changelogs/unreleased/48339-sorting-by-name-on-explore-projects-page-renders-a-500-error-when-logged-in.yml
new file mode 100644
index 00000000000..933d82b57c5
--- /dev/null
+++ b/changelogs/unreleased/48339-sorting-by-name-on-explore-projects-page-renders-a-500-error-when-logged-in.yml
@@ -0,0 +1,5 @@
+---
+title: Fix sorting by name on explore projects page
+merge_request: 20162
+author:
+type: fixed
diff --git a/changelogs/unreleased/6591-dont-load-omniauth-if-not-enabled.yml b/changelogs/unreleased/6591-dont-load-omniauth-if-not-enabled.yml
new file mode 100644
index 00000000000..dd1c7e6955d
--- /dev/null
+++ b/changelogs/unreleased/6591-dont-load-omniauth-if-not-enabled.yml
@@ -0,0 +1,5 @@
+---
+title: Only load Omniauth if enabled
+merge_request: 20132
+author:
+type: fixed
diff --git a/changelogs/unreleased/6598-notify-only-open-unmergeable-mr.yml b/changelogs/unreleased/6598-notify-only-open-unmergeable-mr.yml
new file mode 100644
index 00000000000..ae92c20fa1a
--- /dev/null
+++ b/changelogs/unreleased/6598-notify-only-open-unmergeable-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Notify conflict for only open merge request
+merge_request: 20125
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-missing-index-for-deployments.yml b/changelogs/unreleased/add-missing-index-for-deployments.yml
new file mode 100644
index 00000000000..7863c0ee039
--- /dev/null
+++ b/changelogs/unreleased/add-missing-index-for-deployments.yml
@@ -0,0 +1,5 @@
+---
+title: Add index on deployable_type/id for deployments
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/enforce-variable-value-to-be-a-string.yml b/changelogs/unreleased/enforce-variable-value-to-be-a-string.yml
new file mode 100644
index 00000000000..e2a932ee5bb
--- /dev/null
+++ b/changelogs/unreleased/enforce-variable-value-to-be-a-string.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incremental rollouts for Auto DevOps
+merge_request: 20061
+author:
+type: fixed
diff --git a/changelogs/unreleased/existing-gcp-accounts.yml b/changelogs/unreleased/existing-gcp-accounts.yml
new file mode 100644
index 00000000000..ce396c70b4a
--- /dev/null
+++ b/changelogs/unreleased/existing-gcp-accounts.yml
@@ -0,0 +1,5 @@
+---
+title: Add back copy for existing gcp accounts within offer banner
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-alert-btn.yml b/changelogs/unreleased/fix-alert-btn.yml
new file mode 100644
index 00000000000..d8bf561f05a
--- /dev/null
+++ b/changelogs/unreleased/fix-alert-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Fix alert button styling so that they don't show up white
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_47729.yml b/changelogs/unreleased/issue_47729.yml
new file mode 100644
index 00000000000..e27972af114
--- /dev/null
+++ b/changelogs/unreleased/issue_47729.yml
@@ -0,0 +1,5 @@
+---
+title: Fix refreshing cache keys for open issues count
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-delete-non-latest-mr-diff-files-upon-merge.yml b/changelogs/unreleased/osw-delete-non-latest-mr-diff-files-upon-merge.yml
new file mode 100644
index 00000000000..3e752125f3a
--- /dev/null
+++ b/changelogs/unreleased/osw-delete-non-latest-mr-diff-files-upon-merge.yml
@@ -0,0 +1,5 @@
+---
+title: Delete non-latest merge request diff files upon merge
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/rails5-fix-mysql-arel-from.yml b/changelogs/unreleased/rails5-fix-mysql-arel-from.yml
new file mode 100644
index 00000000000..9883ff306f1
--- /dev/null
+++ b/changelogs/unreleased/rails5-fix-mysql-arel-from.yml
@@ -0,0 +1,5 @@
+---
+title: Rails5 fix arel from in mysql_median_datetime_sql
+merge_request: 20167
+author: Jasper Maes
+type: fixed
diff --git a/changelogs/unreleased/security-2682-fix-xss-for-markdown-toc.yml b/changelogs/unreleased/security-2682-fix-xss-for-markdown-toc.yml
new file mode 100644
index 00000000000..f595678c3c2
--- /dev/null
+++ b/changelogs/unreleased/security-2682-fix-xss-for-markdown-toc.yml
@@ -0,0 +1,5 @@
+---
+title: Fix XSS vulnerability for table of content generation
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fj-bumping-sanitize-gem.yml b/changelogs/unreleased/security-fj-bumping-sanitize-gem.yml
new file mode 100644
index 00000000000..bec1033425d
--- /dev/null
+++ b/changelogs/unreleased/security-fj-bumping-sanitize-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Update sanitize gem to 4.6.5 to fix HTML injection vulnerability
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-html_escape_branch_name.yml b/changelogs/unreleased/security-html_escape_branch_name.yml
new file mode 100644
index 00000000000..02d1065348f
--- /dev/null
+++ b/changelogs/unreleased/security-html_escape_branch_name.yml
@@ -0,0 +1,5 @@
+---
+title: HTML escape branch name in project graphs page
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-html_escape_usernames.yml b/changelogs/unreleased/security-html_escape_usernames.yml
new file mode 100644
index 00000000000..7e69e4ae266
--- /dev/null
+++ b/changelogs/unreleased/security-html_escape_usernames.yml
@@ -0,0 +1,5 @@
+---
+title: HTML escape the name of the user in ProjectsHelper#link_to_member
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-rd-do-not-show-internal-info-in-public-feed.yml b/changelogs/unreleased/security-rd-do-not-show-internal-info-in-public-feed.yml
new file mode 100644
index 00000000000..ff78c162dff
--- /dev/null
+++ b/changelogs/unreleased/security-rd-do-not-show-internal-info-in-public-feed.yml
@@ -0,0 +1,5 @@
+---
+title: Don't show events from internal projects for anonymous users in public feed
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/update-pipeline-icon-in-web-ide-sidebar.yml b/changelogs/unreleased/update-pipeline-icon-in-web-ide-sidebar.yml
new file mode 100644
index 00000000000..3f1f3c643e2
--- /dev/null
+++ b/changelogs/unreleased/update-pipeline-icon-in-web-ide-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Update pipeline icon in web ide sidebar
+merge_request: 20058
+author: George Tsiolis
+type: changed
diff --git a/config/application.rb b/config/application.rb
index 202e5d5e327..d9483cd806d 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -5,6 +5,12 @@ require 'rails/all'
Bundler.require(:default, Rails.env)
module Gitlab
+ # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
+ def self.rails5?
+ ENV["RAILS5"].in?(%w[1 true])
+ end
+
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
@@ -14,6 +20,11 @@ module Gitlab
require_dependency Rails.root.join('lib/gitlab/current_settings')
require_dependency Rails.root.join('lib/gitlab/middleware/read_only')
+ # This needs to be loaded before DB connection is made
+ # to make sure that all connections have NO_ZERO_DATE
+ # setting disabled
+ require_dependency Rails.root.join('lib/mysql_zero_date')
+
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
@@ -211,10 +222,4 @@ module Gitlab
Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
end
end
-
- # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
- def self.rails5?
- ENV["RAILS5"].in?(%w[1 true])
- end
end
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index fda13d0c4cb..717e30b5b7e 100644
--- a/config/initializers/active_record_data_types.rb
+++ b/config/initializers/active_record_data_types.rb
@@ -65,7 +65,7 @@ elsif Gitlab::Database.mysql?
prepend RegisterDateTimeWithTimeZone
# Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it.
- class MysqlDateTimeWithTimeZone < MysqlDateTime
+ class MysqlDateTimeWithTimeZone < (Gitlab.rails5? ? ActiveRecord::Type::DateTime : MysqlDateTime)
def type
:datetime_with_timezone
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 362b9cc9a88..d051b699102 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -219,5 +219,7 @@ Devise.setup do |config|
end
end
- Gitlab::OmniauthInitializer.new(config).execute(Gitlab.config.omniauth.providers)
+ if Gitlab.config.omniauth.enabled
+ Gitlab::OmniauthInitializer.new(config).execute(Gitlab.config.omniauth.providers)
+ end
end
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index 889111282ef..9f451046462 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -60,17 +60,23 @@ en:
scopes:
api: Access the authenticated user's API
read_user: Read the authenticated user's personal information
+ read_repository: Allows read-access to the repository
+ read_registry: Grants permission to read container registry images
openid: Authenticate using OpenID Connect
- sudo: Perform API actions as any user in the system (if the authenticated user is an admin)
+ sudo: Perform API actions as any user in the system
scope_desc:
api:
- Full access to GitLab as the user, including read/write on all their groups and projects
+ Grants complete read/write access to the API, including all groups and projects.
read_user:
- Read-only access to the user's profile information, like username, public email and full name
+ Grants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.
+ read_repository:
+ Grants read-only access to repositories on private projects using Git-over-HTTP (not using the API).
+ read_registry:
+ Grants read-only access to container registry images on private projects.
openid:
- The ability to authenticate using GitLab, and read-only access to the user's profile information and group memberships
+ Grants permission to authenticate with GitLab using OpenID Connect. Also gives read-only access to the user's profile and group memberships.
sudo:
- Access to the Sudo feature, to perform API actions as any user in the system (only available for admins)
+ Grants permission to perform API actions as any user in the system, when authenticated as an admin user.
flash:
applications:
create:
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index d16060e8f45..3400142db36 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -76,4 +76,5 @@
- [repository_update_remote_mirror, 1]
- [repository_remove_remote, 1]
- [create_note_diff_file, 1]
+ - [delete_diff_files, 1]
diff --git a/db/migrate/20180626125654_add_index_on_deployable_for_deployments.rb b/db/migrate/20180626125654_add_index_on_deployable_for_deployments.rb
new file mode 100644
index 00000000000..a0e3a228f6c
--- /dev/null
+++ b/db/migrate/20180626125654_add_index_on_deployable_for_deployments.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexOnDeployableForDeployments < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deployments, [:deployable_type, :deployable_id]
+ end
+
+ def down
+ remove_concurrent_index :deployments, [:deployable_type, :deployable_id]
+ end
+end
diff --git a/db/migrate/merge_request_diff_file_limits_to_mysql.rb b/db/migrate/merge_request_diff_file_limits_to_mysql.rb
index 3958380e4b9..ca3bc7d6be9 100644
--- a/db/migrate/merge_request_diff_file_limits_to_mysql.rb
+++ b/db/migrate/merge_request_diff_file_limits_to_mysql.rb
@@ -4,7 +4,7 @@ class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration
def up
return unless Gitlab::Database.mysql?
- change_column :merge_request_diff_files, :diff, :text, limit: 2147483647
+ change_column :merge_request_diff_files, :diff, :text, limit: 2147483647, default: nil
end
def down
diff --git a/db/schema.rb b/db/schema.rb
index d05c6afbb9f..0112fc726d4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180608201435) do
+ActiveRecord::Schema.define(version: 20180626125654) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -757,6 +757,7 @@ ActiveRecord::Schema.define(version: 20180608201435) do
end
add_index "deployments", ["created_at"], name: "index_deployments_on_created_at", using: :btree
+ add_index "deployments", ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id", using: :btree
add_index "deployments", ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id", using: :btree
add_index "deployments", ["environment_id", "iid", "project_id"], name: "index_deployments_on_environment_id_and_iid_and_project_id", using: :btree
add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index f0b2054a7f3..a5cd2b642dc 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -134,4 +134,4 @@ We're currently evaluating this feature on dev.gitalb.org or staging.gitlab.com
- TBD
-[ce-44935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18169
+[ce-18169]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18169 \ No newline at end of file
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 48e1685082a..f5cdd310f6f 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -322,50 +322,49 @@ to EE only.
## Previewing the changes live
-To preview your changes to documentation locally, please follow
-this [development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
+NOTE: **Note:**
+To preview your changes to documentation locally, follow this
+[development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
-If you want to preview the doc changes of your merge request live, you can use
-the manual `review-docs-deploy` job in your merge request. You will need at
-least Maintainer permissions to be able to run it and is currently enabled for the
-following projects:
+The live preview is currently enabled for the following projects:
- https://gitlab.com/gitlab-org/gitlab-ce
- https://gitlab.com/gitlab-org/gitlab-ee
+- https://gitlab.com/gitlab-org/gitlab-runner
-NOTE: **Note:**
-You will need to push a branch to those repositories, it doesn't work for forks.
-
-TIP: **Tip:**
If your branch contains only documentation changes, you can use
[special branch names](#branch-naming) to avoid long running pipelines.
-In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
-reveal the `review-docs-deploy` job. Hit the play button for the job to start.
+For [docs-only changes](#branch-naming), the review app is run automatically.
+For all other branches, you can use the manual `review-docs-deploy-manual` job
+in your merge request. You will need at least Maintainer permissions to be able
+to run it. In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
+reveal the `review-docs-deploy-manual` job. Hit the play button for the job to start.
![Manual trigger a docs build](img/manual_build_docs.png)
-This job will:
+NOTE: **Note:**
+You will need to push a branch to those repositories, it doesn't work for forks.
+
+The `review-docs-deploy*` job will:
1. Create a new branch in the [gitlab-docs](https://gitlab.com/gitlab-com/gitlab-docs)
- project named after the scheme: `preview-<branch-slug>`
+ project named after the scheme: `$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG`,
+ where `DOCS_GITLAB_REPO_SUFFIX` is the suffix for each product, e.g, `ce` for
+ CE, etc.
1. Trigger a cross project pipeline and build the docs site with your changes
After a few minutes, the Review App will be deployed and you will be able to
preview the changes. The docs URL can be found in two places:
- In the merge request widget
-- In the output of the `review-docs-deploy` job, which also includes the
+- In the output of the `review-docs-deploy*` job, which also includes the
triggered pipeline so that you can investigate whether something went wrong
In case the Review App URL returns 404, follow these steps to debug:
1. **Did you follow the URL from the merge request widget?** If yes, then check if
- the link is the same as the one in the job output. It can happen that if the
- branch name slug is longer than 35 characters, it is automatically
- truncated. That means that the merge request widget will not show the proper
- URL due to a limitation of how `environment: url` works, but you can find the
- real URL from the output of the `review-docs-deploy` job.
+ the link is the same as the one in the job output.
1. **Did you follow the URL from the job output?** If yes, then it means that
either the site is not yet deployed or something went wrong with the remote
pipeline. Give it a few minutes and it should appear online, otherwise you
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 9a677bf09b2..9d0d7348df9 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -24,6 +24,7 @@ are very appreciative of the work done by translators and proofreaders!
- Paolo Falomo - [GitLab](https://gitlab.com/paolofalomo), [Crowdin](https://crowdin.com/profile/paolo.falomo)
- Japanese
- Yamana Tokiuji - [GitLab](https://gitlab.com/tokiuji), [Crowdin](https://crowdin.com/profile/yamana)
+ - Hiroyuki Sato - [GitLab](https://gitlab.com/hiroponz), [Crowdin](https://crowdin.com/profile/hiroponz)
- Korean
- Chang-Ho Cha - [GitLab](https://gitlab.com/changho-cha), [Crowdin](https://crowdin.com/profile/zzazang)
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
@@ -31,6 +32,7 @@ are very appreciative of the work done by translators and proofreaders!
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz)
- Portuguese, Brazilian
- Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [Crowdin](https://crowdin.com/profile/paulogomes.rep)
+ - André Gama - [GitLab](https://gitlab.com/andregamma), [Crowdin](https://crowdin.com/profile/ToeOficial)
- Russian
- Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007)
- Alexy Lustin - [GitLab](https://gitlab.com/allustin), [Crowdin](https://crowdin.com/profile/lustin)
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index f502866333e..47396666879 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -195,22 +195,22 @@ end
And that's it, we're done!
-## Changing Column Types For Large Tables
+## Changing The Schema For Large Tables
-While `change_column_type_concurrently` can be used for changing the type of a
-column without downtime it doesn't work very well for large tables. Because all
-of the work happens in sequence the migration can take a very long time to
-complete, preventing a deployment from proceeding.
-`change_column_type_concurrently` can also produce a lot of pressure on the
-database due to it rapidly updating many rows in sequence.
+While `change_column_type_concurrently` and `rename_column_concurrently` can be
+used for changing the schema of a table without downtime it doesn't work very
+well for large tables. Because all of the work happens in sequence the migration
+can take a very long time to complete, preventing a deployment from proceeding.
+They can also produce a lot of pressure on the database due to it rapidly
+updating many rows in sequence.
To reduce database pressure you should instead use
-`change_column_type_using_background_migration` when migrating a column in a
-large table (e.g. `issues`). This method works similar to
-`change_column_type_concurrently` but uses background migration to spread the
-work / load over a longer time period, without slowing down deployments.
+`change_column_type_using_background_migration` or `rename_column_concurrently`
+when migrating a column in a large table (e.g. `issues`). These methods work
+similarly to the concurrent counterparts but uses background migration to spread
+the work / load over a longer time period, without slowing down deployments.
-Usage of this method is fairly simple:
+For example, to change the column type using a background migration:
```ruby
class ExampleMigration < ActiveRecord::Migration
@@ -296,6 +296,15 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
end
```
+The same applies to `rename_column_using_background_migration`:
+
+1. Create a migration using the helper, which will schedule background
+ migrations to spread the writes over a longer period of time.
+2. In the next monthly release, create a clean-up migration to steal from the
+ Sidekiq queues, migrate any missing rows, and cleanup the rename. This
+ migration should skip the steps after stealing from the Sidekiq queues if the
+ column has already been renamed.
+
For more information, see [the documentation on cleaning up background
migrations](background_migrations.md#cleaning-up).
diff --git a/doc/install/installation.md b/doc/install/installation.md
index ef415246583..e4011b1a4ab 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -12,9 +12,8 @@ Since installations from source don't have Runit, Sidekiq can't be terminated an
## Select Version to Install
-Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
-In most cases this should be the highest numbered production tag (without rc in it).
-You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-0-stable`).
+You can select the branch in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 3f49432ce93..db06efdae53 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -179,6 +179,81 @@ tell GitLab which groups are external via the `external_groups:` element:
} }
```
+## Bypass two factor authentication
+
+If you want some SAML authentication methods to count as 2FA on a per session basis, you can register them in the
+`upstream_two_factor_authn_contexts` list:
+
+**For Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ upstream_two_factor_authn_contexts:
+ %w(
+ urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN
+ )
+
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ]
+ ```
+
+1. Save the file and [reconfigure][] GitLab for the changes to take effect.
+
+---
+
+**For installations from source:**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ - {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
+ upstream_two_factor_authn_contexts:
+ [
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS',
+ 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN'
+ ]
+
+ },
+ label: 'Company Login' # optional label for SAML login button, defaults to "Saml"
+ }
+ ```
+
+1. Save the file and [restart GitLab][] for the changes ot take effect
+
+
+In addition to the changes in GitLab, make sure that your Idp is returning the
+`AuthnContext`. For example:
+
+```xml
+ <saml:AuthnStatement>
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MediumStrongCertificateProtectedTransport</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+ </saml:AuthnStatement>
+```
+
## Customization
### `auto_sign_in_with_provider`
diff --git a/doc/update/10.8-to-11.0.md b/doc/update/10.8-to-11.0.md
index f9b6044bd2f..22a0c9f950c 100644
--- a/doc/update/10.8-to-11.0.md
+++ b/doc/update/10.8-to-11.0.md
@@ -4,10 +4,9 @@ comments: false
# From 10.8 to 11.0
-Make sure you view this update guide from the tag (version) of GitLab you would
-like to install. In most cases this should be the highest numbered production
-tag (without rc in it). You can select the tag in the version dropdown at the
-top left corner of GitLab (below the menu bar).
+Make sure you view this update guide from the branch (version) of GitLab you would
+like to install (e.g., `11-0-stable`. You can select the branch in the version
+dropdown at the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the
[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 26329f20339..9801a0a14ed 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -38,3 +38,4 @@ semicolon, comma, or a new line.
![Domain Blacklist](img/domain_blacklist.png)
[ce-5259]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5259
+[ce-598]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/598
diff --git a/doc/user/project/img/group_issue_board.png b/doc/user/project/img/group_issue_board.png
new file mode 100644
index 00000000000..be360d18540
--- /dev/null
+++ b/doc/user/project/img/group_issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index 5f6dc9e4e8b..50e051e25a0 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png
index 973d9f7cde4..91098daa1d1 100644
--- a/doc/user/project/img/issue_board_add_list.png
+++ b/doc/user/project/img/issue_board_add_list.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_assignee_lists.png b/doc/user/project/img/issue_board_assignee_lists.png
new file mode 100644
index 00000000000..1ec94d22e33
--- /dev/null
+++ b/doc/user/project/img/issue_board_assignee_lists.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_creation.png b/doc/user/project/img/issue_board_creation.png
new file mode 100644
index 00000000000..9dc4925b0a5
--- /dev/null
+++ b/doc/user/project/img/issue_board_creation.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_edit_button.png b/doc/user/project/img/issue_board_edit_button.png
new file mode 100644
index 00000000000..23883175344
--- /dev/null
+++ b/doc/user/project/img/issue_board_edit_button.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_focus_mode.gif b/doc/user/project/img/issue_board_focus_mode.gif
new file mode 100644
index 00000000000..9565bdb0865
--- /dev/null
+++ b/doc/user/project/img/issue_board_focus_mode.gif
Binary files differ
diff --git a/doc/user/project/img/issue_board_move_issue_card_list.png b/doc/user/project/img/issue_board_move_issue_card_list.png
index 3666dbb87ab..cce252234c1 100644
--- a/doc/user/project/img/issue_board_move_issue_card_list.png
+++ b/doc/user/project/img/issue_board_move_issue_card_list.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_system_notes.png b/doc/user/project/img/issue_board_system_notes.png
index bd0f5f54095..c6ecb498198 100644
--- a/doc/user/project/img/issue_board_system_notes.png
+++ b/doc/user/project/img/issue_board_system_notes.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_view_scope.png b/doc/user/project/img/issue_board_view_scope.png
new file mode 100644
index 00000000000..4e03cecbc2d
--- /dev/null
+++ b/doc/user/project/img/issue_board_view_scope.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png
index 127b9b08cc7..357dff42488 100644
--- a/doc/user/project/img/issue_board_welcome_message.png
+++ b/doc/user/project/img/issue_board_welcome_message.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_add_issues_modal.png b/doc/user/project/img/issue_boards_add_issues_modal.png
index bedaf724a15..625a4304eaf 100644
--- a/doc/user/project/img/issue_boards_add_issues_modal.png
+++ b/doc/user/project/img/issue_boards_add_issues_modal.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png
new file mode 100644
index 00000000000..4b2b8d457f1
--- /dev/null
+++ b/doc/user/project/img/issue_boards_multiple.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_remove_issue.png b/doc/user/project/img/issue_boards_remove_issue.png
index 8b3beca97cf..9a2fad2cc7f 100644
--- a/doc/user/project/img/issue_boards_remove_issue.png
+++ b/doc/user/project/img/issue_boards_remove_issue.png
Binary files differ
diff --git a/doc/user/project/import/bitbucket.md b/doc/user/project/import/bitbucket.md
index b22c7db0047..e3d625cc621 100644
--- a/doc/user/project/import/bitbucket.md
+++ b/doc/user/project/import/bitbucket.md
@@ -9,6 +9,10 @@ The [Bitbucket integration][bb-import] must be first enabled in order to be
able to import your projects from Bitbucket. Ask your GitLab administrator
to enable this if not already.
+>**Note:**
+The BitBucket importer currently only works with BitBucket's cloud offering
+(bitbucket.org) and does not work with BitBucket Server (aka Stash).
+
- At its current state, the Bitbucket importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 9ca1e6226c5..10647e33f4c 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -1,16 +1,10 @@
-# Issue Board
+# Issue Boards
->**Note:**
-[Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board).
+> [Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board).
The GitLab Issue Board is a software project management tool used to plan,
organize, and visualize a workflow for a feature or product release.
-It can be seen like a light version of a [Kanban] or a [Scrum] board.
-
-Other interesting links:
-
-- [GitLab Issue Board landing page on about.gitlab.com][landing]
-- [YouTube video introduction to Issue Boards][youtube]
+It can be used as a [Kanban] or a [Scrum] board.
![GitLab Issue Board](img/issue_board.png)
@@ -18,7 +12,7 @@ Other interesting links:
The Issue Board builds on GitLab's existing
[issue tracking functionality](issues/index.md#issue-tracker) and
-leverages the power of [labels] by utilizing them as lists of the scrum board.
+leverages the power of [labels](labels.md) by utilizing them as lists of the scrum board.
With the Issue Board you can have a different view of your issues while
maintaining the same filtering and sorting abilities you see across the
@@ -33,15 +27,23 @@ You create issues, host code, perform reviews, build, test,
and deploy from one single platform. Issue Boards help you to visualize
and manage the entire process _in_ GitLab.
-With [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards), available
-only in [GitLab Ultimate](https://about.gitlab.com/products/),
+With [Multiple Issue Boards](#multiple-issue-boards), available
+only in [GitLab Enterprise Edition](#features-per-tier),
you go even further, as you can not only keep yourself and your project
organized from a broader perspective with one Issue Board per project,
but also allow your team members to organize their own workflow by creating
multiple Issue Boards within the same project.
+For a visual overview, see our [Issue Board feature page](https://about.gitlab.com/features/issueboard/)
+on about.gitlab.com or our [video introduction to Issue Boards](https://www.youtube.com/watch?v=UWsJ8tkHAa8).
+
## Use cases
+There are many ways to use GitLab Issue Boards tailored to your own preferred workflow.
+Here are some common use cases for Issue Boards.
+
+### Use cases for a single Issue Board
+
GitLab Workflow allows you to discuss proposals in issues, categorize them
with labels, and from there organize and prioritize them with Issue Boards.
@@ -65,33 +67,66 @@ beginning of the development lifecycle until deployed to production
![issue card moving](img/issue_board_move_issue_card_list.png)
-> **Notes:**
->
->- For a broader use case, please check the blog post
+### Use cases for Multiple Issue Boards
+
+With [Multiple Issue Boards](#multiple-issue-boards), available only in
+[GitLab Enterprise Edition](https://about.gitlab.com/products/),
+each team can have their own board to organize their workflow individually.
+
+#### Scrum team
+
+With multiple Issue Boards, each team has one board. Now you can move issues through each
+part of the process. For instance: **To Do**, **Doing**, and **Done**.
+
+#### Organization of topics
+
+Create lists to order things by topic and quickly change them between topics or groups,
+such as between **UX**, **Frontend**, and **Backend**. The changes will be reflected across boards,
+as changing lists will update the label accordingly.
+
+#### Advanced team handover
+
+For example, suppose we have a UX team with an Issue Board that contains:
+
+- **To Do**
+- **Doing**
+- **Frontend**
+
+When done with something, they move the card to **Frontend**. The Frontend team's board looks like:
+
+- **Frontend**
+- **Doing**
+- **Done**
+
+Cards finished by the UX team will automatically appear in the **Frontend** column when they're ready for them.
+
+NOTE: **Note:**
+For a broader use case, please see the blog post
[GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
->
->- For a real use case, please check why
+For a real use case example, you can read why
[Codepen decided to adopt Issue Boards](https://about.gitlab.com/2017/01/27/codepen-welcome-to-gitlab/#project-management-everything-in-one-place)
-to improve their workflow with [multiple boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+to improve their workflow with multiple boards.
-## Issue Board terminology
+#### Quick assignments
-Below is a table of the definitions used for GitLab's Issue Board.
+Create lists for each of your team members and quickly drag-and-drop issues onto each team member.
-| What we call it | What it means |
-| -------------- | ------------- |
-| **Issue Board** | It represents a different view for your issues. It can have multiple lists with each list consisting of issues represented by cards. |
-| **List** | Each label that exists in the issue tracker can have its own dedicated list. Every list is named after the label it is based on and is represented by a column which contains all the issues associated with that label. You can think of a list like the results you get when you filter the issues by a label in your issue tracker. |
-| **Card** | Every card represents an issue and it is shown under the list for which it has a label. The information you can see on a card consists of the issue number, the issue title, the assignee and the labels associated with it. You can drag cards around from one list to another. You can re-order cards within a list. |
+## Permissions
-There are two types of lists, the ones you create based on your labels, and
-two defaults:
+[Developers and up](../permissions.md) can use all the functionality of the
+Issue Board, that is, create or delete lists and drag issues from one list to another.
-- Label list: a list based on a label. It shows all opened issues with that label.
-- **Backlog** (default): shows all open issues that does not belong to one of lists. Always appears on the very left.
-- **Closed** (default): shows all closed issues. Always appears on the very right.
+## Issue Board terminology
+
+- **Issue Board** - Each board represents a unique view for your issues. It can have multiple lists with each list consisting of issues represented by cards.
+- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Backlog' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee.
+ - **Label list**: a list based on a label. It shows all opened issues with that label.
+ - **Assignee list**: a list which includes all issues assigned to a user.
+ - **Backlog** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
+ - **Closed** (default): shows all closed issues. Always appears as the rightmost list.
+- **Card** - A box in the list that represents an individual issue. The information you can see on a card consists of the issue number, the issue title, the assignee, and the labels associated with the issue. You can drag cards from one list to another to change their label or assignee from that of the source list to that of the destination list.
-In short, here's a list of actions you can take in an Issue Board:
+## Actions you can take on an Issue Board
- [Create a new list](#creating-a-new-list).
- [Delete an existing list](#deleting-a-list).
@@ -129,7 +164,7 @@ right corner of the Issue Board.
![Issue Board welcome message](img/issue_board_add_list.png)
-Simply choose the label to create the list from. The new list will be inserted
+Simply choose the label or user to create the list from. The new list will be inserted
at the end of the lists, before **Done**. Moving and reordering lists is as
easy as dragging them around.
@@ -174,17 +209,19 @@ to the system so that anybody who visits the same board later will see the reord
with some exceptions.
The first time a given issue appears in any board (i.e. the first time a user
-loads a board containing that issue), it will be ordered with
-respect to other issues in that list according to [Priority order][label-priority].
+loads a board containing that issue), it will be ordered with
+respect to other issues in that list according to [Priority order](labels.md#label-priority).
+
At that point, that issue will be assigned a relative order value by the system
representing its relative order with respect to the other issues in the list. Any time
you drag-and-drop reorder that issue, its relative order value will change accordingly.
+
Also, any time that issue appears in any board when it is loaded by a user,
the updated relative order value will be used for the ordering. (It's only the first
time an issue appears that it takes from the Priority order mentioned above.) This means that
if issue `A` is drag-and-drop reordered to be above issue `B` by any user in
a given board inside your GitLab instance, any time those two issues are subsequently
-loaded in any board in the same instance (could be a different project board or a different group board, for example),
+loaded in any board in the same instance (could be a different project board or a different group board, for example),
that ordering will be maintained.
## Filtering issues
@@ -205,8 +242,8 @@ something between lists by changing a label.
A typical workflow of using the Issue Board would be:
-1. You have [created][create-labels] and [prioritized][label-priority] labels
- so that you can easily categorize your issues.
+1. You have [created](labels.md#creating-labels) and [prioritized](labels.md#label-priority)
+ labels so that you can easily categorize your issues.
1. You have a bunch of issues (ideally labeled).
1. You visit the Issue Board and start [creating lists](#creating-a-new-list) to
create a workflow.
@@ -230,21 +267,98 @@ to another list the label changes and a system not is recorded.
![Issue Board system notes](img/issue_board_system_notes.png)
-## Permissions
+## Multiple Issue Boards **[STARTER]**
-[Developers and up](../permissions.md) can use all the functionality of the
-Issue Board, that is create/delete lists and drag issues around.
+> Introduced in [GitLab Enterprise Edition 8.13](https://about.gitlab.com/2016/10/22/gitlab-8-13-released/#multiple-issue-boards-ee).
-## Group Issue Board
+Multiple Issue Boards, as the name suggests, allow for more than one Issue Board
+for a given project or group. This is great for large projects with more than one team
+or in situations where a repository is used to host the code of multiple
+products.
-> Introduced in [GitLab 10.6](https://about.gitlab.com/2018/03/22/gitlab-10-6-released/#single-group-issue-board-in-core-and-free)
+Clicking on the current board name in the upper left corner will reveal a
+menu from where you can create another Issue Board and rename or delete the
+existing one.
-Group issue board is analogous to project-level issue board and it is accessible at the group
-navigation level. A group-level issue board allows you to view all issues from all projects in that group or descendant subgroups. Similarly, you can only filter by group labels for these
+NOTE: **Note:**
+The Multiple Issue Boards feature is available for
+**projects in GitLab Starter Edition** and for **groups in GitLab Premium Edition**.
+
+![Multiple Issue Boards](img/issue_boards_multiple.png)
+
+## Configurable Issue Boards **[STARTER]**
+
+> Introduced in [GitLab Starter Edition 10.2](https://about.gitlab.com/2017/11/22/gitlab-10-2-released/#issue-boards-configuration).
+
+An Issue Board can be associated with GitLab [Milestone](milestones/index.md#milestones),
+[Labels](labels.md), Assignee and Weight
+which will automatically filter the Board issues according to these fields.
+This allows you to create unique boards according to your team's need.
+
+![Create scoped board](img/issue_board_creation.png)
+
+You can define the scope of your board when creating it or by clicking on the "Edit board" button. Once a milestone, assignee or weight is assigned to an Issue Board, you will no longer be able to filter
+through these in the search bar. In order to do that, you need to remove the desired scope (e.g. milestone, assignee or weight) from the Issue Board.
+
+![Edit board configuration](img/issue_board_edit_button.png)
+
+If you don't have editing permission in a board, you're still able to see the configuration by clicking on "View scope".
+
+![Viewing board configuration](img/issue_board_view_scope.png)
+
+## Focus mode **[STARTER]**
+
+> Introduced in [GitLab Starter 9.1](https://about.gitlab.com/2017/04/22/gitlab-9-1-released/#issue-boards-focus-mode-ees-eep).
+
+Click the button at the top right to toggle focus mode on and off. In focus mode, the navigation UI is hidden, allowing you to focus on issues in the board.
+
+![Board focus mode](img/issue_board_focus_mode.gif)
+
+## Group Issue Boards **[PREMIUM]**
+
+> Introduced in [GitLab Premium 10.0](https://about.gitlab.com/2017/09/22/gitlab-10-0-released/#group-issue-boards).
+
+Accessible at the group navigation level, a group issue board offers the same features as a project-level board,
+but it can display issues from all projects in that
+group and its descendant subgroups. Similarly, you can only filter by group labels for these
boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only
group-level objects are available.
-One group issue board per group was made available in GitLab 10.6 Core after multiple group issue boards were originally introduced in [GitLab 10.0 Premium](https://about.gitlab.com/2017/09/22/gitlab-10-0-released/#group-issue-boards).
+NOTE: **Note:**
+Multiple group issue boards were originally introduced in [GitLab 10.0 Premium](https://about.gitlab.com/2017/09/22/gitlab-10-0-released/#group-issue-boards) and
+one group issue board per group was made available in GitLab 10.6 Core.
+
+![Group issue board](img/group_issue_board.png)
+
+## Assignee lists **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5784) in GitLab 11.0 Premium.
+
+Like a regular list that shows all issues that have the list label, you can add
+an assignee list that shows all issues assigned to the given user.
+You can have a board with both label lists and assignee lists. To add an
+assignee list:
+
+1. Click **Add list**.
+1. Select the **Assignee list** tab.
+1. Search and click on the user you want to add as an assignee.
+
+Now that the assignee list is added, you can assign or unassign issues to that user
+by [dragging issues](#dragging-issues-between-lists) to and/or from an assignee list.
+To remove an assignee list, just as with a label list, click the trash icon.
+
+![Assignee lists](img/issue_board_assignee_lists.png)
+
+## Dragging issues between lists
+
+When dragging issues between lists, different behavior occurs depending on the source list and the target list.
+
+| | To Backlog | To Closed | To label `B` list | To assignee `Bob` list |
+| --- | --- | --- | --- | --- |
+| From Backlog | - | Issue closed | `B` added | `Bob` assigned |
+| From Closed | Issue reopened | - | Issue reopened<br/>`B` added | Issue reopened<br/>`Bob` assigned |
+| From label `A` list | `A` removed | Issue closed | `A` removed<br/>`B` added | `Bob` assigned |
+| From assignee `Alice` list | `Alice` unassigned | Issue closed | `B` added | `Alice` unassigned<br/>`Bob` assigned |
## Features per tier
@@ -261,11 +375,8 @@ Different issue board features are available in different [GitLab tiers](https:/
A few things to remember:
-- The label that corresponds to a list is hidden for issues under that list.
- Moving an issue between lists removes the label from the list it came from
and adds the label from the list it goes to.
-- When moving a card to **Done**, the label of the list it came from is removed
- and the issue gets closed.
- An issue can exist in multiple lists if it has more than one label.
- Lists are populated with issues automatically if the issues are labeled.
- Clicking on the issue title inside a card will take you to that issue.
@@ -276,10 +387,5 @@ A few things to remember:
20 will appear.
[ce-5554]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5554
-[labels]: ./labels.md
[scrum]: https://en.wikipedia.org/wiki/Scrum_(software_development)
[kanban]: https://en.wikipedia.org/wiki/Kanban_(development)
-[create-labels]: ./labels.md#create-new-labels
-[label-priority]: ./labels.md#prioritize-labels
-[landing]: https://about.gitlab.com/solutions/issueboard
-[youtube]: https://www.youtube.com/watch?v=UWsJ8tkHAa8
diff --git a/doc/user/project/issues/deleting_issues.md b/doc/user/project/issues/deleting_issues.md
index d7442104c53..536a0de8974 100644
--- a/doc/user/project/issues/deleting_issues.md
+++ b/doc/user/project/issues/deleting_issues.md
@@ -8,4 +8,6 @@ You can delete an issue by editing it and clicking on the delete button.
![delete issue - button](img/delete_issue.png)
->**Note:** Only [project owners](../../permissions.md) can delete issues. \ No newline at end of file
+>**Note:** Only [project owners](../../permissions.md) can delete issues.
+
+[ce-2982]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2982 \ No newline at end of file
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index edb0c6bdc30..5dc62a30128 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -111,7 +111,7 @@ by yourself (except when an issue is due). You will only receive automatic
notifications when somebody else comments or adds changes to the ones that
you've created or mentions you.
-If a merge request becomes unmergeable, its author will be notified about the cause.
+If an open merge request becomes unmergeable due to conflict, its author will be notified about the cause.
If a user has also set the merge request to automatically merge once pipeline succeeds,
then that user will also be notified.
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 762bf616268..760cd87d4cc 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -31,7 +31,7 @@ A Todo appears in your Todos dashboard when:
- you are `@mentioned` in a comment on a commit,
- a job in the CI pipeline running for your merge request failed, but this
job is not allowed to fail.
-- a merge request becomes unmergeable, and you are either:
+- an open merge request becomes unmergeable due to conflict, and you are either:
- the author, or
- have set it to automatically merge once pipeline succeeds.
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 50a5e340191..af762db517c 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -48,7 +48,7 @@ module Backup
end
def backup_project(project)
- gitaly_migrate(:repository_backup) do |is_enabled|
+ gitaly_migrate(:repository_backup, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
backup_project_gitaly(project)
else
@@ -80,7 +80,7 @@ module Backup
end
def delete_all_repositories(name, repository_storage)
- gitaly_migrate(:delete_all_repositories) do |is_enabled|
+ gitaly_migrate(:delete_all_repositories, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
Gitlab::GitalyClient::StorageService.new(name).delete_all_repositories
else
@@ -148,7 +148,7 @@ module Backup
end
def backup_custom_hooks(project)
- gitaly_migrate(:backup_custom_hooks) do |is_enabled|
+ gitaly_migrate(:backup_custom_hooks, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_backup_custom_hooks(project)
else
@@ -159,7 +159,7 @@ module Backup
def restore_custom_hooks(project)
in_path(path_to_tars(project)) do |dir|
- gitaly_migrate(:restore_custom_hooks) do |is_enabled|
+ gitaly_migrate(:restore_custom_hooks, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
if is_enabled
gitaly_restore_custom_hooks(project, dir)
else
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 4bc82ecb4d6..bb9f488cd87 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -56,10 +56,12 @@ module Banzai
# Pattern to match allowed image extensions
ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze
+ # Do not perform linking inside these tags.
+ IGNORED_ANCESTOR_TAGS = %w(pre code tt).to_set
+
def call
doc.search(".//text()").each do |node|
- # Do not perform linking inside <code> blocks
- next unless node.ancestors('code').empty?
+ next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS)
# A Gollum ToC tag is `[[_TOC_]]`, but due to MarkdownFilter running
# before this one, it will be converted into `[[<em>TOC</em>]]`, so it
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 6786b9d07b6..afc2ca4e362 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -25,10 +25,11 @@ module Banzai
# Only push these customizations once
return if customized?(whitelist[:transformers])
- # Allow table alignment; we whitelist specific style properties in a
+ # Allow table alignment; we whitelist specific text-align values in a
# transformer below
whitelist[:attributes]['th'] = %w(style)
whitelist[:attributes]['td'] = %w(style)
+ whitelist[:css] = { properties: ['text-align'] }
# Allow span elements
whitelist[:elements].push('span')
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index 97244159985..b32660a8341 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -92,7 +92,7 @@ module Banzai
def text
return '' unless node
- @text ||= node.text
+ @text ||= EscapeUtils.escape_html(node.text)
end
private
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 6c5d0788a0a..e7283b2f9e8 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -74,6 +74,10 @@ module Gitlab
gl_user
end
+ def bypass_two_factor?
+ false
+ end
+
protected
def should_save?
diff --git a/lib/gitlab/auth/saml/auth_hash.rb b/lib/gitlab/auth/saml/auth_hash.rb
index c345a7e3f6c..3bc5e2864df 100644
--- a/lib/gitlab/auth/saml/auth_hash.rb
+++ b/lib/gitlab/auth/saml/auth_hash.rb
@@ -6,6 +6,17 @@ module Gitlab
Array.wrap(get_raw(Gitlab::Auth::Saml::Config.groups))
end
+ def authn_context
+ response_object = auth_hash.extra[:response_object]
+ return nil if response_object.blank?
+
+ document = response_object.decrypted_document
+ document ||= response_object.document
+ return nil if document.blank?
+
+ extract_authn_context(document)
+ end
+
private
def get_raw(key)
@@ -13,6 +24,10 @@ module Gitlab
# otherwise just the first value is returned
auth_hash.extra[:raw_info].all[key]
end
+
+ def extract_authn_context(document)
+ REXML::XPath.first(document, "//saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef/text()").to_s
+ end
end
end
end
diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb
index 5fa9581f837..625dab7c6f4 100644
--- a/lib/gitlab/auth/saml/config.rb
+++ b/lib/gitlab/auth/saml/config.rb
@@ -7,6 +7,10 @@ module Gitlab
Gitlab::Auth::OAuth::Provider.config_for('saml')
end
+ def upstream_two_factor_authn_contexts
+ options.args[:upstream_two_factor_authn_contexts]
+ end
+
def groups
options[:groups_attribute]
end
diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb
index b8c84c37cd5..6c3b75f3eb0 100644
--- a/lib/gitlab/auth/saml/user.rb
+++ b/lib/gitlab/auth/saml/user.rb
@@ -34,6 +34,10 @@ module Gitlab
gl_user.changed? || gl_user.identities.any?(&:changed?)
end
+ def bypass_two_factor?
+ saml_config.upstream_two_factor_authn_contexts&.include?(auth_hash.authn_context)
+ end
+
protected
def saml_config
diff --git a/lib/gitlab/background_migration/cleanup_concurrent_rename.rb b/lib/gitlab/background_migration/cleanup_concurrent_rename.rb
new file mode 100644
index 00000000000..d3f366f3480
--- /dev/null
+++ b/lib/gitlab/background_migration/cleanup_concurrent_rename.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Background migration for cleaning up a concurrent column rename.
+ class CleanupConcurrentRename < CleanupConcurrentSchemaChange
+ RESCHEDULE_DELAY = 10.minutes
+
+ def cleanup_concurrent_schema_change(table, old_column, new_column)
+ cleanup_concurrent_column_rename(table, old_column, new_column)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb b/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb
new file mode 100644
index 00000000000..54f77f184d5
--- /dev/null
+++ b/lib/gitlab/background_migration/cleanup_concurrent_schema_change.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Base class for cleaning up concurrent schema changes.
+ class CleanupConcurrentSchemaChange
+ include Database::MigrationHelpers
+
+ # table - The name of the table the migration is performed for.
+ # old_column - The name of the old (to drop) column.
+ # new_column - The name of the new column.
+ def perform(table, old_column, new_column)
+ return unless column_exists?(table, new_column)
+
+ rows_to_migrate = define_model_for(table)
+ .where(new_column => nil)
+ .where
+ .not(old_column => nil)
+
+ if rows_to_migrate.any?
+ BackgroundMigrationWorker.perform_in(
+ RESCHEDULE_DELAY,
+ self.class.name,
+ [table, old_column, new_column]
+ )
+ else
+ cleanup_concurrent_schema_change(table, old_column, new_column)
+ end
+ end
+
+ # These methods are necessary so we can re-use the migration helpers in
+ # this class.
+ def connection
+ ActiveRecord::Base.connection
+ end
+
+ def method_missing(name, *args, &block)
+ connection.__send__(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend
+ end
+
+ def respond_to_missing?(*args)
+ connection.respond_to?(*args) || super
+ end
+
+ def define_model_for(table)
+ Class.new(ActiveRecord::Base) do
+ self.table_name = table
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb
index de622f657b2..48411095dbb 100644
--- a/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb
+++ b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb
@@ -2,52 +2,12 @@
module Gitlab
module BackgroundMigration
- # Background migration for cleaning up a concurrent column rename.
- class CleanupConcurrentTypeChange
- include Database::MigrationHelpers
-
+ # Background migration for cleaning up a concurrent column type changeb.
+ class CleanupConcurrentTypeChange < CleanupConcurrentSchemaChange
RESCHEDULE_DELAY = 10.minutes
- # table - The name of the table the migration is performed for.
- # old_column - The name of the old (to drop) column.
- # new_column - The name of the new column.
- def perform(table, old_column, new_column)
- return unless column_exists?(:issues, new_column)
-
- rows_to_migrate = define_model_for(table)
- .where(new_column => nil)
- .where
- .not(old_column => nil)
-
- if rows_to_migrate.any?
- BackgroundMigrationWorker.perform_in(
- RESCHEDULE_DELAY,
- 'CleanupConcurrentTypeChange',
- [table, old_column, new_column]
- )
- else
- cleanup_concurrent_column_type_change(table, old_column)
- end
- end
-
- # These methods are necessary so we can re-use the migration helpers in
- # this class.
- def connection
- ActiveRecord::Base.connection
- end
-
- def method_missing(name, *args, &block)
- connection.__send__(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend
- end
-
- def respond_to_missing?(*args)
- connection.respond_to?(*args) || super
- end
-
- def define_model_for(table)
- Class.new(ActiveRecord::Base) do
- self.table_name = table
- end
+ def cleanup_concurrent_schema_change(table, old_column, new_column)
+ cleanup_concurrent_column_type_change(table, old_column)
end
end
end
diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb
index d00e5b07f95..222aa06b800 100644
--- a/lib/gitlab/ci/variables/collection/item.rb
+++ b/lib/gitlab/ci/variables/collection/item.rb
@@ -4,6 +4,9 @@ module Gitlab
class Collection
class Item
def initialize(key:, value:, public: true, file: false)
+ raise ArgumentError, "`value` must be of type String, while it was: #{value.class}" unless
+ value.is_a?(String) || value.nil?
+
@variable = {
key: key, value: value, public: public, file: file
}
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 3cac007a42c..f64e3d53138 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -33,7 +33,13 @@ module Gitlab
end
def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
- query = arel_table
+ arel_from = if Gitlab.rails5?
+ arel_table.from
+ else
+ arel_table
+ end
+
+ query = arel_from
.from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name))
.project(average([arel_table[column_sym]], 'median'))
.where(
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index c21bae5e16b..4fe5b4cc835 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -596,6 +596,97 @@ module Gitlab
end
end
+ # Renames a column using a background migration.
+ #
+ # Because this method uses a background migration it's more suitable for
+ # large tables. For small tables it's better to use
+ # `rename_column_concurrently` since it can complete its work in a much
+ # shorter amount of time and doesn't rely on Sidekiq.
+ #
+ # Example usage:
+ #
+ # rename_column_using_background_migration(
+ # :users,
+ # :feed_token,
+ # :rss_token
+ # )
+ #
+ # table - The name of the database table containing the column.
+ #
+ # old - The old column name.
+ #
+ # new - The new column name.
+ #
+ # type - The type of the new column. If no type is given the old column's
+ # type is used.
+ #
+ # batch_size - The number of rows to schedule in a single background
+ # migration.
+ #
+ # interval - The time interval between every background migration.
+ def rename_column_using_background_migration(
+ table,
+ old_column,
+ new_column,
+ type: nil,
+ batch_size: 10_000,
+ interval: 10.minutes
+ )
+
+ check_trigger_permissions!(table)
+
+ old_col = column_for(table, old_column)
+ new_type = type || old_col.type
+ max_index = 0
+
+ add_column(table, new_column, new_type,
+ limit: old_col.limit,
+ precision: old_col.precision,
+ scale: old_col.scale)
+
+ # We set the default value _after_ adding the column so we don't end up
+ # updating any existing data with the default value. This isn't
+ # necessary since we copy over old values further down.
+ change_column_default(table, new_column, old_col.default) if old_col.default
+
+ install_rename_triggers(table, old_column, new_column)
+
+ model = Class.new(ActiveRecord::Base) do
+ self.table_name = table
+
+ include ::EachBatch
+ end
+
+ # Schedule the jobs that will copy the data from the old column to the
+ # new one. Rows with NULL values in our source column are skipped since
+ # the target column is already NULL at this point.
+ model.where.not(old_column => nil).each_batch(of: batch_size) do |batch, index|
+ start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
+ max_index = index
+
+ BackgroundMigrationWorker.perform_in(
+ index * interval,
+ 'CopyColumn',
+ [table, old_column, new_column, start_id, end_id]
+ )
+ end
+
+ # Schedule the renaming of the column to happen (initially) 1 hour after
+ # the last batch finished.
+ BackgroundMigrationWorker.perform_in(
+ (max_index * interval) + 1.hour,
+ 'CleanupConcurrentRename',
+ [table, old_column, new_column]
+ )
+
+ if perform_background_migration_inline?
+ # To ensure the schema is up to date immediately we perform the
+ # migration inline in dev / test environments.
+ Gitlab::BackgroundMigration.steal('CopyColumn')
+ Gitlab::BackgroundMigration.steal('CleanupConcurrentRename')
+ end
+ end
+
def perform_background_migration_inline?
Rails.env.test? || Rails.env.development?
end
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 156d077a69c..604bb11e712 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -21,13 +21,31 @@ module Gitlab
attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id, :loaded_size, :binary
class << self
- def find(repository, sha, path)
- Gitlab::GitalyClient.migrate(:project_raw_show) do |is_enabled|
- if is_enabled
- find_by_gitaly(repository, sha, path)
- else
- find_by_rugged(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
- end
+ def find(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
+ return unless path
+
+ path = path.sub(%r{\A/*}, '')
+ path = '/' if path.empty?
+ name = File.basename(path)
+
+ # Gitaly will think that setting the limit to 0 means unlimited, while
+ # the client might only need the metadata and thus set the limit to 0.
+ # In this method we'll then set the limit to 1, but clear the byte of data
+ # that we got back so for the outside world it looks like the limit was
+ # actually 0.
+ req_limit = limit == 0 ? 1 : limit
+
+ entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, req_limit)
+ return unless entry
+
+ entry.data = "" if limit == 0
+
+ case entry.type
+ when :COMMIT
+ new(id: entry.oid, name: name, size: 0, data: '', path: path, commit_id: sha)
+ when :BLOB
+ new(id: entry.oid, name: name, size: entry.size, data: entry.data.dup, mode: entry.mode.to_s(8),
+ path: path, commit_id: sha, binary: binary?(entry.data))
end
end
@@ -56,7 +74,7 @@ module Gitlab
repository.gitaly_blob_client.get_blobs(blob_references, blob_size_limit).to_a
else
blob_references.map do |sha, path|
- find_by_rugged(repository, sha, path, limit: blob_size_limit)
+ find(repository, sha, path, limit: blob_size_limit)
end
end
end
@@ -136,85 +154,6 @@ module Gitlab
)
end
- def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
- return unless path
-
- path = path.sub(%r{\A/*}, '')
- path = '/' if path.empty?
- name = File.basename(path)
-
- # Gitaly will think that setting the limit to 0 means unlimited, while
- # the client might only need the metadata and thus set the limit to 0.
- # In this method we'll then set the limit to 1, but clear the byte of data
- # that we got back so for the outside world it looks like the limit was
- # actually 0.
- req_limit = limit == 0 ? 1 : limit
-
- entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, req_limit)
- return unless entry
-
- entry.data = "" if limit == 0
-
- case entry.type
- when :COMMIT
- new(
- id: entry.oid,
- name: name,
- size: 0,
- data: '',
- path: path,
- commit_id: sha
- )
- when :BLOB
- new(
- id: entry.oid,
- name: name,
- size: entry.size,
- data: entry.data.dup,
- mode: entry.mode.to_s(8),
- path: path,
- commit_id: sha,
- binary: binary?(entry.data)
- )
- end
- end
-
- def find_by_rugged(repository, sha, path, limit:)
- return unless path
-
- # Strip any leading / characters from the path
- path = path.sub(%r{\A/*}, '')
-
- rugged_commit = repository.lookup(sha)
- root_tree = rugged_commit.tree
-
- blob_entry = find_entry_by_path(repository, root_tree.oid, *path.split('/'))
-
- return nil unless blob_entry
-
- if blob_entry[:type] == :commit
- submodule_blob(blob_entry, path, sha)
- else
- blob = repository.lookup(blob_entry[:oid])
-
- if blob
- new(
- id: blob.oid,
- name: blob_entry[:name],
- size: blob.size,
- # Rugged::Blob#content is expensive; don't call it if we don't have to.
- data: limit.zero? ? '' : blob.content(limit),
- mode: blob_entry[:filemode].to_s(8),
- path: path,
- commit_id: sha,
- binary: blob.binary?
- )
- end
- end
- rescue Rugged::ReferenceError
- nil
- end
-
def rugged_raw(repository, sha, limit:)
blob = repository.lookup(sha)
diff --git a/lib/gitlab/git/remote_mirror.rb b/lib/gitlab/git/remote_mirror.rb
index ebe46722890..e4743b4db0a 100644
--- a/lib/gitlab/git/remote_mirror.rb
+++ b/lib/gitlab/git/remote_mirror.rb
@@ -7,81 +7,8 @@ module Gitlab
end
def update(only_branches_matching: [])
- @repository.gitaly_migrate(:remote_update_remote_mirror) do |is_enabled|
- if is_enabled
- gitaly_update(only_branches_matching)
- else
- rugged_update(only_branches_matching)
- end
- end
- end
-
- private
-
- def gitaly_update(only_branches_matching)
- @repository.gitaly_remote_client.update_remote_mirror(@ref_name, only_branches_matching)
- end
-
- def rugged_update(only_branches_matching)
- local_branches = refs_obj(@repository.local_branches, only_refs_matching: only_branches_matching)
- remote_branches = refs_obj(@repository.remote_branches(@ref_name), only_refs_matching: only_branches_matching)
-
- updated_branches = changed_refs(local_branches, remote_branches)
- push_branches(updated_branches.keys) if updated_branches.present?
-
- delete_refs(local_branches, remote_branches)
-
- local_tags = refs_obj(@repository.tags)
- remote_tags = refs_obj(@repository.remote_tags(@ref_name))
-
- updated_tags = changed_refs(local_tags, remote_tags)
- @repository.push_remote_branches(@ref_name, updated_tags.keys) if updated_tags.present?
-
- delete_refs(local_tags, remote_tags)
- end
-
- def refs_obj(refs, only_refs_matching: [])
- refs.each_with_object({}) do |ref, refs|
- next if only_refs_matching.present? && !only_refs_matching.include?(ref.name)
-
- refs[ref.name] = ref
- end
- end
-
- def changed_refs(local_refs, remote_refs)
- local_refs.select do |ref_name, ref|
- remote_ref = remote_refs[ref_name]
-
- remote_ref.nil? || ref.dereferenced_target != remote_ref.dereferenced_target
- end
- end
-
- def push_branches(branches)
- default_branch, branches = branches.partition do |branch|
- @repository.root_ref == branch
- end
-
- # Push the default branch first so it works fine when remote mirror is empty.
- branches.unshift(*default_branch)
-
- @repository.push_remote_branches(@ref_name, branches)
- end
-
- def delete_refs(local_refs, remote_refs)
- refs = refs_to_delete(local_refs, remote_refs)
-
- @repository.delete_remote_branches(@ref_name, refs.keys) if refs.present?
- end
-
- def refs_to_delete(local_refs, remote_refs)
- default_branch_id = @repository.commit.id
-
- remote_refs.select do |remote_ref_name, remote_ref|
- next false if local_refs[remote_ref_name] # skip if branch or tag exist in local repo
-
- remote_ref_id = remote_ref.dereferenced_target.try(:id)
-
- remote_ref_id && @repository.rugged_is_ancestor?(remote_ref_id, default_branch_id)
+ @repository.wrapped_gitaly_errors do
+ @repository.gitaly_remote_client.update_remote_mirror(@ref_name, only_branches_matching)
end
end
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 0904e1c2973..b3016c1a637 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -549,24 +549,9 @@ module Gitlab
end
end
- # Gitaly note: JV: check gitlab-ee before removing this method.
- def rugged_is_ancestor?(ancestor_id, descendant_id)
- return false if ancestor_id.nil? || descendant_id.nil?
-
- rugged_merge_base(ancestor_id, descendant_id) == ancestor_id
- rescue Rugged::OdbError
- false
- end
-
# Returns true is +from+ is direct ancestor to +to+, otherwise false
def ancestor?(from, to)
- Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
- if is_enabled
- gitaly_commit_client.ancestor?(from, to)
- else
- rugged_is_ancestor?(from, to)
- end
- end
+ gitaly_commit_client.ancestor?(from, to)
end
def merged_branch_names(branch_names = [])
@@ -978,29 +963,8 @@ module Gitlab
end
def languages(ref = nil)
- gitaly_migrate(:commit_languages, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled|
- if is_enabled
- gitaly_commit_client.languages(ref)
- else
- ref ||= rugged.head.target_id
- languages = Linguist::Repository.new(rugged, ref).languages
- total = languages.map(&:last).sum
-
- languages = languages.map do |language|
- name, share = language
- color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}"
- {
- value: (share.to_f * 100 / total).round(2),
- label: name,
- color: color,
- highlight: color
- }
- end
-
- languages.sort do |x, y|
- y[:value] <=> x[:value]
- end
- end
+ wrapped_gitaly_errors do
+ gitaly_commit_client.languages(ref)
end
end
@@ -1158,16 +1122,7 @@ module Gitlab
end
def create_from_bundle(bundle_path)
- gitaly_migrate(:create_repo_from_bundle) do |is_enabled|
- if is_enabled
- gitaly_repository_client.create_from_bundle(bundle_path)
- else
- run_git!(%W(clone --bare -- #{bundle_path} #{path}), chdir: nil)
- self.class.create_hooks(path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
- end
- end
-
- true
+ gitaly_repository_client.create_from_bundle(bundle_path)
end
def create_from_snapshot(url, auth)
@@ -1268,16 +1223,10 @@ module Gitlab
return unless full_path.present?
# This guard avoids Gitaly log/error spam
- unless exists?
- raise NoRepository, 'repository does not exist'
- end
+ raise NoRepository, 'repository does not exist' unless exists?
- gitaly_migrate(:write_config) do |is_enabled|
- if is_enabled
- gitaly_repository_client.write_config(full_path: full_path)
- else
- rugged_write_config(full_path: full_path)
- end
+ wrapped_gitaly_errors do
+ gitaly_repository_client.write_config(full_path: full_path)
end
end
diff --git a/lib/mysql_zero_date.rb b/lib/mysql_zero_date.rb
new file mode 100644
index 00000000000..64634f789da
--- /dev/null
+++ b/lib/mysql_zero_date.rb
@@ -0,0 +1,18 @@
+# Disable NO_ZERO_DATE mode for mysql in rails 5.
+# We use zero date as a default value
+# (config/initializers/active_record_mysql_timestamp.rb), in
+# Rails 5 using zero date fails by default (https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/75450216)
+# and NO_ZERO_DATE has to be explicitly disabled. Disabling strict mode
+# is not sufficient.
+
+require 'active_record/connection_adapters/abstract_mysql_adapter'
+
+module MysqlZeroDate
+ def configure_connection
+ super
+
+ @connection.query "SET @@SESSION.sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_DATE', '');" # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+end
+
+ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate) if Gitlab.rails5?
diff --git a/package.json b/package.json
index 06b07c37d2b..c42bbbb0351 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
+ "postinstall": "node ./scripts/frontend/postinstall.js",
"prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
@@ -17,7 +18,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
- "@gitlab-org/gitlab-svgs": "^1.23.0",
+ "@gitlab-org/gitlab-svgs": "^1.24.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-core": "^6.26.3",
diff --git a/qa/qa/specs/features/repository/protected_branches_spec.rb b/qa/qa/specs/features/repository/protected_branches_spec.rb
index ec3802e7a76..c5b8c271d7d 100644
--- a/qa/qa/specs/features/repository/protected_branches_spec.rb
+++ b/qa/qa/specs/features/repository/protected_branches_spec.rb
@@ -13,11 +13,15 @@ module QA
Page::Main::Login.act { sign_in_using_credentials }
end
- after do
+ after do |example|
# We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()'
+
+ # In order to help diagnose a false failure
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/48241
+ log_push_output if example.exception
end
context 'when developers and maintainers are allowed to push to a protected branch' do
@@ -27,9 +31,9 @@ module QA
expect(protected_branch.name).to have_content(branch_name)
expect(protected_branch.push_allowance).to have_content('Developers + Maintainers')
- push = push_new_file(branch_name)
+ @push = push_new_file(branch_name)
- expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/)
+ expect(@push.output).to match(/remote: To create a merge request for protected-branch, visit/)
end
end
@@ -37,11 +41,11 @@ module QA
scenario 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
- push = push_new_file(branch_name)
+ @push = push_new_file(branch_name)
- expect(push.output)
+ expect(@push.output)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
- expect(push.output)
+ expect(@push.output)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
@@ -65,5 +69,13 @@ module QA
resource.new_branch = false
end
end
+
+ def log_push_output
+ if defined?(@push)
+ filename = File.join('tmp', "push-output-#{project.name}")
+ puts "Exception detected. Push output will be saved to #{filename}"
+ IO.binwrite(filename, @push.output)
+ end
+ end
end
end
diff --git a/scripts/frontend/postinstall.js b/scripts/frontend/postinstall.js
new file mode 100644
index 00000000000..682039a41b3
--- /dev/null
+++ b/scripts/frontend/postinstall.js
@@ -0,0 +1,22 @@
+const chalk = require('chalk');
+
+// check that fsevents is available if we're on macOS
+if (process.platform === 'darwin') {
+ try {
+ require.resolve('fsevents');
+ } catch (e) {
+ console.error(`${chalk.red('error')} Dependency postinstall check failed.`);
+ console.error(
+ chalk.red(`
+ The fsevents driver is not installed properly.
+ If you are running a new version of Node, please
+ ensure that it is supported by the fsevents library.
+
+ You can try installing again with \`${chalk.cyan('yarn install --force')}\`
+ `)
+ );
+ process.exit(1);
+ }
+}
+
+console.log(`${chalk.green('success')} Dependency postinstall check passed.`);
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
index c9aaba91aa0..2a0e7f4d76e 100755
--- a/scripts/trigger-build-docs
+++ b/scripts/trigger-build-docs
@@ -27,7 +27,7 @@ def docs_branch
# Prefix the remote branch with the slug of the project in order
# to avoid name conflicts in the rare case the branch name already
# exists in the docs repo and truncate to max length.
- "#{slug}-#{ENV["CI_COMMIT_REF_SLUG"]}"[0...max]
+ "#{slug}-#{ENV["CI_ENVIRONMENT_SLUG"]}"[0...max]
end
#
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index fc1bf67d7b9..f278043028f 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -56,11 +56,11 @@ describe 'bin/changelog' do
it 'parses -h' do
expect do
expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
- end.to raise_error(SystemExit)
+ end.to raise_error(ChangelogHelpers::Done)
end
it 'assigns title' do
- options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
+ options = described_class.parse(%W[foo -m 1 bar\n baz\r\n --amend])
expect(options.title).to eq 'foo bar baz'
end
@@ -82,9 +82,10 @@ describe 'bin/changelog' do
it 'shows error message and exits the program' do
allow($stdin).to receive(:getc).and_return(type)
expect do
- expect do
- expect { described_class.read_type }.to raise_error(SystemExit)
- end.to output("Invalid category index, please select an index between 1 and 8\n").to_stderr
+ expect { described_class.read_type }.to raise_error(
+ ChangelogHelpers::Abort,
+ 'Invalid category index, please select an index between 1 and 8'
+ )
end.to output.to_stdout
end
end
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index 5f0e8c5eca9..b23f183fec8 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -1,127 +1,162 @@
require 'spec_helper'
-describe OmniauthCallbacksController do
+describe OmniauthCallbacksController, type: :controller do
include LoginHelpers
- let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
-
- before do
- mock_auth_hash(provider.to_s, extern_uid, user.email)
- stub_omniauth_provider(provider, context: request)
- end
-
- context 'when the user is on the last sign in attempt' do
- let(:extern_uid) { 'my-uid' }
+ describe 'omniauth' do
+ let(:user) { create(:omniauth_user, extern_uid: extern_uid, provider: provider) }
before do
- user.update(failed_attempts: User.maximum_attempts.pred)
- subject.response = ActionDispatch::Response.new
+ mock_auth_hash(provider.to_s, extern_uid, user.email)
+ stub_omniauth_provider(provider, context: request)
end
- context 'when using a form based provider' do
- let(:provider) { :ldap }
-
- it 'locks the user when sign in fails' do
- allow(subject).to receive(:params).and_return(ActionController::Parameters.new(username: user.username))
- request.env['omniauth.error.strategy'] = OmniAuth::Strategies::LDAP.new(nil)
-
- subject.send(:failure)
+ context 'when the user is on the last sign in attempt' do
+ let(:extern_uid) { 'my-uid' }
- expect(user.reload).to be_access_locked
+ before do
+ user.update(failed_attempts: User.maximum_attempts.pred)
+ subject.response = ActionDispatch::Response.new
end
- end
- context 'when using a button based provider' do
- let(:provider) { :github }
+ context 'when using a form based provider' do
+ let(:provider) { :ldap }
- it 'does not lock the user when sign in fails' do
- request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil)
+ it 'locks the user when sign in fails' do
+ allow(subject).to receive(:params).and_return(ActionController::Parameters.new(username: user.username))
+ request.env['omniauth.error.strategy'] = OmniAuth::Strategies::LDAP.new(nil)
- subject.send(:failure)
+ subject.send(:failure)
- expect(user.reload).not_to be_access_locked
+ expect(user.reload).to be_access_locked
+ end
end
- end
- end
- context 'strategies' do
- context 'github' do
- let(:extern_uid) { 'my-uid' }
- let(:provider) { :github }
+ context 'when using a button based provider' do
+ let(:provider) { :github }
- it 'allows sign in' do
- post provider
+ it 'does not lock the user when sign in fails' do
+ request.env['omniauth.error.strategy'] = OmniAuth::Strategies::GitHub.new(nil)
- expect(request.env['warden']).to be_authenticated
- end
-
- shared_context 'sign_up' do
- let(:user) { double(email: 'new@example.com') }
+ subject.send(:failure)
- before do
- stub_omniauth_setting(block_auto_created_users: false)
+ expect(user.reload).not_to be_access_locked
end
end
+ end
- context 'sign up' do
- include_context 'sign_up'
+ context 'strategies' do
+ context 'github' do
+ let(:extern_uid) { 'my-uid' }
+ let(:provider) { :github }
- it 'is allowed' do
+ it 'allows sign in' do
post provider
expect(request.env['warden']).to be_authenticated
end
- end
-
- context 'when OAuth is disabled' do
- before do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- settings = Gitlab::CurrentSettings.current_application_settings
- settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
- end
- it 'prevents login via POST' do
- post provider
+ shared_context 'sign_up' do
+ let(:user) { double(email: 'new@example.com') }
- expect(request.env['warden']).not_to be_authenticated
+ before do
+ stub_omniauth_setting(block_auto_created_users: false)
+ end
end
- it 'shows warning when attempting login' do
- post provider
-
- expect(response).to redirect_to new_user_session_path
- expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
- end
+ context 'sign up' do
+ include_context 'sign_up'
- it 'allows linking the disabled provider' do
- user.identities.destroy_all
- sign_in(user)
+ it 'is allowed' do
+ post provider
- expect { post provider }.to change { user.reload.identities.count }.by(1)
+ expect(request.env['warden']).to be_authenticated
+ end
end
- context 'sign up' do
- include_context 'sign_up'
+ context 'when OAuth is disabled' do
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ settings = Gitlab::CurrentSettings.current_application_settings
+ settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
+ end
- it 'is prevented' do
+ it 'prevents login via POST' do
post provider
expect(request.env['warden']).not_to be_authenticated
end
+
+ it 'shows warning when attempting login' do
+ post provider
+
+ expect(response).to redirect_to new_user_session_path
+ expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
+ end
+
+ it 'allows linking the disabled provider' do
+ user.identities.destroy_all
+ sign_in(user)
+
+ expect { post provider }.to change { user.reload.identities.count }.by(1)
+ end
+
+ context 'sign up' do
+ include_context 'sign_up'
+
+ it 'is prevented' do
+ post provider
+
+ expect(request.env['warden']).not_to be_authenticated
+ end
+ end
+ end
+ end
+
+ context 'auth0' do
+ let(:extern_uid) { '' }
+ let(:provider) { :auth0 }
+
+ it 'does not allow sign in without extern_uid' do
+ post 'auth0'
+
+ expect(request.env['warden']).not_to be_authenticated
+ expect(response.status).to eq(302)
+ expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
end
end
end
+ end
+
+ describe '#saml' do
+ let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml') }
+ let(:mock_saml_response) { File.read('spec/fixtures/authentication/saml_response.xml') }
+ let(:saml_config) { mock_saml_config_with_upstream_two_factor_authn_contexts }
+
+ before do
+ stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
+ providers: [saml_config] })
+ mock_auth_hash('saml', 'my-uid', user.email, mock_saml_response)
+ request.env["devise.mapping"] = Devise.mappings[:user]
+ request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
+ post :saml, params: { SAMLResponse: mock_saml_response }
+ end
- context 'auth0' do
- let(:extern_uid) { '' }
- let(:provider) { :auth0 }
+ context 'when worth two factors' do
+ let(:mock_saml_response) do
+ File.read('spec/fixtures/authentication/saml_response.xml')
+ .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN')
+ end
- it 'does not allow sign in without extern_uid' do
- post 'auth0'
+ it 'expects user to be signed_in' do
+ expect(request.env['warden']).to be_authenticated
+ end
+ end
+ context 'when not worth two factors' do
+ it 'expects user to provide second factor' do
+ expect(response).to render_template('devise/sessions/two_factor')
expect(request.env['warden']).not_to be_authenticated
- expect(response.status).to eq(302)
- expect(controller).to set_flash[:alert].to('Wrong extern UID provided. Make sure Auth0 is configured correctly.')
end
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 90e698925b6..27f04be3fdf 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -329,7 +329,7 @@ describe ProjectsController do
expect { update_project path: 'renamed_path' }
.not_to change { project.reload.path }
- expect(controller).to set_flash[:alert].to(/container registry tags/)
+ expect(controller).to set_flash.now[:alert].to(/container registry tags/)
expect(response).to have_gitlab_http_status(200)
end
end
diff --git a/spec/dependencies/omniauth_saml_spec.rb b/spec/dependencies/omniauth_saml_spec.rb
new file mode 100644
index 00000000000..ccc604dc230
--- /dev/null
+++ b/spec/dependencies/omniauth_saml_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+require 'omniauth/strategies/saml'
+
+describe 'processing of SAMLResponse in dependencies' do
+ let(:mock_saml_response) { File.read('spec/fixtures/authentication/saml_response.xml') }
+ let(:saml_strategy) { OmniAuth::Strategies::SAML.new({}) }
+ let(:session_mock) { {} }
+ let(:settings) { OpenStruct.new({ soft: false, idp_cert_fingerprint: 'something' }) }
+ let(:auth_hash) { Gitlab::Auth::Saml::AuthHash.new(saml_strategy) }
+
+ subject { auth_hash.authn_context }
+
+ before do
+ allow(saml_strategy).to receive(:session).and_return(session_mock)
+ allow_any_instance_of(OneLogin::RubySaml::Response).to receive(:is_valid?).and_return(true)
+ saml_strategy.send(:handle_response, mock_saml_response, {}, settings ) { }
+ end
+
+ it 'can extract AuthnContextClassRef from SAMLResponse param' do
+ is_expected.to eq 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
+ end
+end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index e7aca94db66..f3ab4ff771a 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -124,6 +124,29 @@ feature 'Admin updates settings' do
expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).not_to include('google_oauth2')
end
+ scenario 'Oauth providers do not raise validation errors when saving unrelated changes' do
+ expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to be_empty
+
+ page.within('.as-signin') do
+ uncheck 'Google'
+ click_button 'Save changes'
+ end
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+
+ # Remove google_oauth2 from the Omniauth strategies
+ allow(Devise).to receive(:omniauth_providers).and_return([])
+
+ # Save an unrelated setting
+ page.within('.as-ci-cd') do
+ click_button 'Save changes'
+ end
+
+ expect(page).to have_content "Application settings saved successfully"
+ expect(Gitlab::CurrentSettings.disabled_oauth_sign_in_sources).to include('google_oauth2')
+ end
+
scenario 'Change Help page' do
page.within('.as-help-page') do
fill_in 'Help page text', with: 'Example text'
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 6397df086a7..53866c32c69 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -62,7 +62,7 @@ describe "User adds a comment on a commit", :js do
click_diff_line(sample_commit.line_code)
expect(page).to have_css(".js-temp-notes-holder form.new-note")
- .and have_css(".js-close-discussion-note-form", text: "Cancel")
+ .and have_css(".js-close-discussion-note-form", text: "Discard draft")
# The `Cancel` button closes the current form. The page should not have any open forms after that.
find(".js-close-discussion-note-form").click
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index 57172610aed..335174b7729 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe 'Project Graph', :js do
let(:user) { create :user }
let(:project) { create(:project, :repository, namespace: user.namespace) }
+ let(:branch_name) { 'master' }
before do
project.add_master(user)
@@ -12,7 +13,7 @@ describe 'Project Graph', :js do
shared_examples 'page should have commits graphs' do
it 'renders commits' do
- expect(page).to have_content('Commit statistics for master')
+ expect(page).to have_content("Commit statistics for #{branch_name}")
expect(page).to have_content('Commits per day of month')
end
end
@@ -57,6 +58,23 @@ describe 'Project Graph', :js do
it_behaves_like 'page should have languages graphs'
end
+ context 'chart graph with HTML escaped branch name' do
+ let(:branch_name) { '<h1>evil</h1>' }
+
+ before do
+ project.repository.create_branch(branch_name, 'master')
+
+ visit charts_project_graph_path(project, branch_name)
+ end
+
+ it_behaves_like 'page should have commits graphs'
+
+ it 'HTML escapes branch name' do
+ expect(page.body).to include("Commit statistics for <strong>#{ERB::Util.html_escape(branch_name)}</strong>")
+ expect(page.body).not_to include(branch_name)
+ end
+ end
+
context 'when CI enabled' do
before do
project.enable_ci
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 1f8d31a5c88..24a2c89f50b 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -177,14 +177,35 @@ feature 'Login' do
end
context 'logging in via OAuth' do
- it 'shows 2FA prompt after OAuth login' do
- stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
- user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
- gitlab_sign_in_via('saml', user, 'my-uid')
+ let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')}
+ let(:mock_saml_response) do
+ File.read('spec/fixtures/authentication/saml_response.xml')
+ end
- expect(page).to have_content('Two-Factor Authentication')
- enter_code(user.current_otp)
- expect(current_path).to eq root_path
+ before do
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
+ providers: [mock_saml_config_with_upstream_two_factor_authn_contexts])
+ gitlab_sign_in_via('saml', user, 'my-uid', mock_saml_response)
+ end
+
+ context 'when authn_context is worth two factors' do
+ let(:mock_saml_response) do
+ File.read('spec/fixtures/authentication/saml_response.xml')
+ .gsub('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
+ end
+
+ it 'signs user in without prompting for second factor' do
+ expect(page).not_to have_content('Two-Factor Authentication')
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'when authn_context is not worth two factors' do
+ it 'shows 2FA prompt after OAuth login' do
+ expect(page).to have_content('Two-Factor Authentication')
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
end
end
end
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index b51ca5d130b..bfe11ddf673 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -40,6 +40,15 @@ describe 'Signup' do
expect(find('.username')).to have_css '.gl-field-error-outline'
end
+
+ it 'shows an error message on submit if the username contains special characters' do
+ fill_in 'new_user_username', with: 'new$user!username'
+ wait_for_requests
+
+ click_button "Register"
+
+ expect(page).to have_content("Please create a username with only alphanumeric characters.")
+ end
end
context 'with no errors' do
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index 3ca0f7c3c89..da043f94021 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -1,31 +1,50 @@
require 'spec_helper'
describe UserRecentEventsFinder do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:project_owner) { project.creator }
- let!(:event) { create(:event, project: project, author: project_owner) }
+ let(:current_user) { create(:user) }
+ let(:project_owner) { create(:user) }
+ let(:private_project) { create(:project, :private, creator: project_owner) }
+ let(:internal_project) { create(:project, :internal, creator: project_owner) }
+ let(:public_project) { create(:project, :public, creator: project_owner) }
+ let!(:private_event) { create(:event, project: private_project, author: project_owner) }
+ let!(:internal_event) { create(:event, project: internal_project, author: project_owner) }
+ let!(:public_event) { create(:event, project: public_project, author: project_owner) }
- subject(:finder) { described_class.new(user, project_owner) }
+ subject(:finder) { described_class.new(current_user, project_owner) }
describe '#execute' do
- it 'does not include the event when a user does not have access to the project' do
- expect(finder.execute).to be_empty
+ context 'current user does not have access to projects' do
+ it 'returns public and internal events' do
+ records = finder.execute
+
+ expect(records).to include(public_event, internal_event)
+ expect(records).not_to include(private_event)
+ end
end
- context 'when the user has access to a project' do
+ context 'when current user has access to the projects' do
before do
- project.add_developer(user)
+ private_project.add_developer(current_user)
+ internal_project.add_developer(current_user)
+ public_project.add_developer(current_user)
end
- it 'includes the event' do
- expect(finder.execute).to include(event)
+ it 'returns all the events' do
+ expect(finder.execute).to include(private_event, internal_event, public_event)
end
- it 'does not include the event if the user cannot read cross project' do
- expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+ it 'does not include the events if the user cannot read cross project' do
+ expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
expect(finder.execute).to be_empty
end
end
+
+ context 'when current user is anonymous' do
+ let(:current_user) { nil }
+
+ it 'returns public events only' do
+ expect(finder.execute).to eq([public_event])
+ end
+ end
end
end
diff --git a/spec/fixtures/authentication/saml_response.xml b/spec/fixtures/authentication/saml_response.xml
new file mode 100644
index 00000000000..ac7b662be22
--- /dev/null
+++ b/spec/fixtures/authentication/saml_response.xml
@@ -0,0 +1,42 @@
+<?xml version='1.0'?>
+<samlp:Response xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion' ID='pfxb9b71715-2202-9a51-8ae5-689d5b9dd25a' Version='2.0' IssueInstant='2014-07-17T01:01:48Z' Destination='http://sp.example.com/demo1/index.php?acs' InResponseTo='ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685'>
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><ds:Signature xmlns:ds='http://www.w3.org/2000/09/xmldsig#'>
+ <ds:SignedInfo><ds:CanonicalizationMethod Algorithm='http://www.w3.org/2001/10/xml-exc-c14n#'/>
+ <ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>
+ <ds:Reference URI='#pfxb9b71715-2202-9a51-8ae5-689d5b9dd25a'><ds:Transforms><ds:Transform Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature'/><ds:Transform Algorithm='http://www.w3.org/2001/10/xml-exc-c14n#'/></ds:Transforms><ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/><ds:DigestValue>z0Y25hsUHVJJnYhgB5LzPVjqbgM=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>NSdsZopzNX4kJETipLNbU+7dG4GPTj5e40iSBaUeUMc1UUSX4UCe9Qx6R9ADEkEQgNekgYaCFOuY90kLNh9Ky0Czq8gd4w7ykQJEVJ7VF7LakmG8dPedHAKyAMAuZ8y3mNGye31vtR9frYaznCVoxB3eAi9rbVOXkQtdOTRMHec=</ds:SignatureValue>
+ <ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIICajCCAdOgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBSMQswCQYDVQQGEwJ1czETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UECgwMT25lbG9naW4gSW5jMRcwFQYDVQQDDA5zcC5leGFtcGxlLmNvbTAeFw0xNDA3MTcxNDEyNTZaFw0xNTA3MTcxNDEyNTZaMFIxCzAJBgNVBAYTAnVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxPbmVsb2dpbiBJbmMxFzAVBgNVBAMMDnNwLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZx+ON4IUoIWxgukTb1tOiX3bMYzYQiwWPUNMp+Fq82xoNogso2bykZG0yiJm5o8zv/sd6pGouayMgkx/2FSOdc36T0jGbCHuRSbtia0PEzNIRtmViMrt3AeoWBidRXmZsxCNLwgIV6dn2WpuE5Az0bHgpZnQxTKFek0BMKU/d8wIDAQABo1AwTjAdBgNVHQ4EFgQUGHxYqZYyX7cTxKVODVgZwSTdCnwwHwYDVR0jBBgwFoAUGHxYqZYyX7cTxKVODVgZwSTdCnwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQByFOl+hMFICbd3DJfnp2Rgd/dqttsZG/tyhILWvErbio/DEe98mXpowhTkC04ENprOyXi7ZbUqiicF89uAGyt1oqgTUCD1VsLahqIcmrzgumNyTwLGWo17WDAa1/usDhetWAMhgzF/Cnf5ek0nK00m0YZGyc4LzgD0CROMASTWNg==</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>
+ <samlp:Status>
+ <samlp:StatusCode Value='urn:oasis:names:tc:SAML:2.0:status:Success'/>
+ </samlp:Status>
+ <saml:Assertion xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xs='http://www.w3.org/2001/XMLSchema' ID='_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75' Version='2.0' IssueInstant='2014-07-17T01:01:48Z'>
+ <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
+ <saml:Subject>
+ <saml:NameID SPNameQualifier='http://sp.example.com/demo1/metadata.php' Format='urn:oasis:names:tc:SAML:2.0:nameid-format:transient'>_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
+ <saml:SubjectConfirmation Method='urn:oasis:names:tc:SAML:2.0:cm:bearer'>
+ <saml:SubjectConfirmationData NotOnOrAfter='2024-01-18T06:21:48Z' Recipient='http://sp.example.com/demo1/index.php?acs' InResponseTo='ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685'/>
+ </saml:SubjectConfirmation>
+ </saml:Subject>
+ <saml:Conditions NotBefore='2014-07-17T01:01:18Z' NotOnOrAfter='2024-01-18T06:21:48Z'>
+ <saml:AudienceRestriction>
+ <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
+ </saml:AudienceRestriction>
+ </saml:Conditions>
+ <saml:AuthnStatement AuthnInstant='2014-07-17T01:01:48Z' SessionNotOnOrAfter='2024-07-17T09:01:48Z' SessionIndex='_be9967abd904ddcae3c0eb4189adbe3f71e327cf93'>
+ <saml:AuthnContext>
+ <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
+ </saml:AuthnContext>
+ </saml:AuthnStatement>
+ <saml:AttributeStatement>
+ <saml:Attribute Name='uid' NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:basic'>
+ <saml:AttributeValue xsi:type='xs:string'>test</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name='mail' NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:basic'>
+ <saml:AttributeValue xsi:type='xs:string'>test@example.com</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name='eduPersonAffiliation' NameFormat='urn:oasis:names:tc:SAML:2.0:attrname-format:basic'>
+ <saml:AttributeValue xsi:type='xs:string'>users</saml:AttributeValue>
+ <saml:AttributeValue xsi:type='xs:string'>examplerole1</saml:AttributeValue>
+ </saml:Attribute>
+ </saml:AttributeStatement>
+ </saml:Assertion>
+</samlp:Response>
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 5cf9e9e8f12..80147b13739 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -248,7 +248,7 @@ describe ProjectsHelper do
describe '#link_to_member' do
let(:group) { build_stubbed(:group) }
let(:project) { build_stubbed(:project, group: group) }
- let(:user) { build_stubbed(:user) }
+ let(:user) { build_stubbed(:user, name: '<h1>Administrator</h1>') }
describe 'using the default options' do
it 'returns an HTML link to the user' do
@@ -256,6 +256,13 @@ describe ProjectsHelper do
expect(link).to match(%r{/#{user.username}})
end
+
+ it 'HTML escapes the name of the user' do
+ link = helper.link_to_member(project, user)
+
+ expect(link).to include(ERB::Util.html_escape(user.name))
+ expect(link).not_to include(user.name)
+ end
end
end
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
index fbc3926d332..68158cf52e4 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
@@ -17,6 +17,17 @@ describe('Filtered Search Token Keys', () => {
});
});
+ describe('getKeys', () => {
+ it('should return keys', () => {
+ const getKeys = FilteredSearchTokenKeys.getKeys();
+ const keys = FilteredSearchTokenKeys.get().map(i => i.key);
+
+ keys.forEach((key, i) => {
+ expect(key).toEqual(getKeys[i]);
+ });
+ });
+ });
+
describe('getConditions', () => {
let conditions;
diff --git a/spec/javascripts/ide/components/repo_tab_spec.js b/spec/javascripts/ide/components/repo_tab_spec.js
index 8cabc6e8935..fc0695a4263 100644
--- a/spec/javascripts/ide/components/repo_tab_spec.js
+++ b/spec/javascripts/ide/components/repo_tab_spec.js
@@ -38,6 +38,26 @@ describe('RepoTab', () => {
expect(name.textContent.trim()).toEqual(vm.tab.name);
});
+ it('does not call openPendingTab when tab is active', done => {
+ vm = createComponent({
+ tab: {
+ ...file(),
+ pending: true,
+ active: true,
+ },
+ });
+
+ spyOn(vm, 'openPendingTab');
+
+ vm.$el.click();
+
+ vm.$nextTick(() => {
+ expect(vm.openPendingTab).not.toHaveBeenCalled();
+
+ done();
+ });
+ });
+
it('fires clickFile when the link is clicked', () => {
vm = createComponent({
tab: file(),
@@ -112,9 +132,9 @@ describe('RepoTab', () => {
});
it('renders a tooltip', () => {
- expect(
- vm.$el.querySelector('span:nth-child(2)').dataset.originalTitle,
- ).toContain('Locked by testuser');
+ expect(vm.$el.querySelector('span:nth-child(2)').dataset.originalTitle).toContain(
+ 'Locked by testuser',
+ );
});
});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index aeb936b0e3c..0eff98bcc9d 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -3,7 +3,6 @@
import $ from 'jquery';
import 'vendor/jasmine-jquery';
import '~/commons';
-
import Vue from 'vue';
import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index d84b13b07c4..57e0caa692c 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -6,7 +6,7 @@ import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', function () {
preloadFixtures('u2f/authenticate.html.raw');
- beforeEach((done) => {
+ beforeEach(() => {
loadFixtures('u2f/authenticate.html.raw');
this.u2fDevice = new MockU2FDevice();
this.container = $('#js-authenticate-u2f');
@@ -19,46 +19,70 @@ describe('U2FAuthenticate', function () {
document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'),
);
+ });
- // bypass automatic form submission within renderAuthenticated
- spyOn(this.component, 'renderAuthenticated').and.returnValue(true);
+ describe('with u2f unavailable', () => {
+ beforeEach(() => {
+ spyOn(this.component, 'switchToFallbackUI');
+ this.oldu2f = window.u2f;
+ window.u2f = null;
+ });
- this.component.start().then(done).catch(done.fail);
- });
+ afterEach(() => {
+ window.u2f = this.oldu2f;
+ });
- it('allows authenticating via a U2F device', () => {
- const inProgressMessage = this.container.find('p');
- expect(inProgressMessage.text()).toContain('Trying to communicate with your device');
- this.u2fDevice.respondToAuthenticateRequest({
- deviceData: 'this is data from the device',
+ it('falls back to normal 2fa', (done) => {
+ this.component.start().then(() => {
+ expect(this.component.switchToFallbackUI).toHaveBeenCalled();
+ done();
+ }).catch(done.fail);
});
- expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
});
- describe('errors', () => {
- it('displays an error message', () => {
- const setupButton = this.container.find('#js-login-u2f-device');
- setupButton.trigger('click');
- this.u2fDevice.respondToAuthenticateRequest({
- errorCode: 'error!',
- });
- const errorMessage = this.container.find('p');
- return expect(errorMessage.text()).toContain('There was a problem communicating with your device');
+ describe('with u2f available', () => {
+ beforeEach((done) => {
+ // bypass automatic form submission within renderAuthenticated
+ spyOn(this.component, 'renderAuthenticated').and.returnValue(true);
+ this.u2fDevice = new MockU2FDevice();
+
+ this.component.start().then(done).catch(done.fail);
});
- return it('allows retrying authentication after an error', () => {
- let setupButton = this.container.find('#js-login-u2f-device');
- setupButton.trigger('click');
- this.u2fDevice.respondToAuthenticateRequest({
- errorCode: 'error!',
- });
- const retryButton = this.container.find('#js-u2f-try-again');
- retryButton.trigger('click');
- setupButton = this.container.find('#js-login-u2f-device');
- setupButton.trigger('click');
+
+ it('allows authenticating via a U2F device', () => {
+ const inProgressMessage = this.container.find('p');
+ expect(inProgressMessage.text()).toContain('Trying to communicate with your device');
this.u2fDevice.respondToAuthenticateRequest({
deviceData: 'this is data from the device',
});
expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
});
+
+ describe('errors', () => {
+ it('displays an error message', () => {
+ const setupButton = this.container.find('#js-login-u2f-device');
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ errorCode: 'error!',
+ });
+ const errorMessage = this.container.find('p');
+ return expect(errorMessage.text()).toContain('There was a problem communicating with your device');
+ });
+ return it('allows retrying authentication after an error', () => {
+ let setupButton = this.container.find('#js-login-u2f-device');
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ errorCode: 'error!',
+ });
+ const retryButton = this.container.find('#js-u2f-try-again');
+ retryButton.trigger('click');
+ setupButton = this.container.find('#js-login-u2f-device');
+ setupButton.trigger('click');
+ this.u2fDevice.respondToAuthenticateRequest({
+ deviceData: 'this is data from the device',
+ });
+ expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
+ });
+ });
});
});
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 17a620ef603..d930c608b18 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -93,6 +93,16 @@ describe Banzai::Filter::SanitizationFilter do
expect(doc.at_css('td')['style']).to eq 'text-align: center'
end
+ it 'disallows `text-align` property in `style` attribute on other elements' do
+ html = <<~HTML
+ <div style="text-align: center">Text</div>
+ HTML
+
+ doc = filter(html)
+
+ expect(doc.at_css('div')['style']).to be_nil
+ end
+
it 'allows `span` elements' do
exp = act = %q{<span>Hello</span>}
expect(filter(act).to_html).to eq exp
@@ -224,7 +234,7 @@ describe Banzai::Filter::SanitizationFilter do
'protocol-based JS injection: spaces and entities' => {
input: '<a href=" &#14; javascript:alert(\'XSS\');">foo</a>',
- output: '<a href="">foo</a>'
+ output: '<a href>foo</a>'
},
'protocol whitespace' => {
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 0cfef4ff5bf..7213cd58ea7 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -139,5 +139,14 @@ describe Banzai::Filter::TableOfContentsFilter do
expect(items[5].ancestors).to include(items[4])
end
end
+
+ context 'header text contains escaped content' do
+ let(:content) { '&lt;img src="x" onerror="alert(42)"&gt;' }
+ let(:results) { result(header(1, content)) }
+
+ it 'outputs escaped content' do
+ expect(doc.inner_html).to include(content)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 64f3d09a25b..3a8667e434d 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -779,4 +779,12 @@ describe Gitlab::Auth::OAuth::User do
end
end
end
+
+ describe '#bypass_two_factor?' do
+ subject { oauth_user.bypass_two_factor? }
+
+ it 'returns always false' do
+ is_expected.to be_falsey
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index bb950e6bbf8..76f49e778fb 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -37,4 +37,55 @@ describe Gitlab::Auth::Saml::AuthHash do
end
end
end
+
+ describe '#authn_context' do
+ let(:auth_hash_data) do
+ {
+ provider: 'saml',
+ uid: 'some_uid',
+ info:
+ {
+ name: 'mockuser',
+ email: 'mock@email.ch',
+ image: 'mock_user_thumbnail_url'
+ },
+ credentials:
+ {
+ token: 'mock_token',
+ secret: 'mock_secret'
+ },
+ extra:
+ {
+ raw_info:
+ {
+ info:
+ {
+ name: 'mockuser',
+ email: 'mock@email.ch',
+ image: 'mock_user_thumbnail_url'
+ }
+ }
+ }
+ }
+ end
+
+ subject(:saml_auth_hash) { described_class.new(OmniAuth::AuthHash.new(auth_hash_data)) }
+
+ context 'with response_object' do
+ before do
+ auth_hash_data[:extra][:response_object] = { document:
+ saml_xml(File.read('spec/fixtures/authentication/saml_response.xml')) }
+ end
+
+ it 'can extract authn_context' do
+ expect(saml_auth_hash.authn_context).to eq 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password'
+ end
+ end
+
+ context 'without response_object' do
+ it 'returns an empty string' do
+ expect(saml_auth_hash.authn_context).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index 62514ca0688..c523f5e177f 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -400,4 +400,45 @@ describe Gitlab::Auth::Saml::User do
end
end
end
+
+ describe '#bypass_two_factor?' do
+ let(:saml_config) { mock_saml_config_with_upstream_two_factor_authn_contexts }
+
+ subject { saml_user.bypass_two_factor? }
+
+ context 'with authn_contexts_worth_two_factors configured' do
+ before do
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
+ end
+
+ it 'returns true when authn_context is worth two factors' do
+ allow(saml_user.auth_hash).to receive(:authn_context).and_return('urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
+ is_expected.to be_truthy
+ end
+
+ it 'returns false when authn_context is not worth two factors' do
+ allow(saml_user.auth_hash).to receive(:authn_context).and_return('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
+ is_expected.to be_falsey
+ end
+
+ it 'returns false when authn_context is blank' do
+ is_expected.to be_falsey
+ end
+ end
+
+ context 'without auth_contexts_worth_two_factors_configured' do
+ before do
+ stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [mock_saml_config])
+ end
+
+ it 'returns false when authn_context is present' do
+ allow(saml_user.auth_hash).to receive(:authn_context).and_return('urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS')
+ is_expected.to be_falsey
+ end
+
+ it 'returns false when authn_context is blank' do
+ is_expected.to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index e79f0a7f257..adb3ff4321f 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -1,19 +1,69 @@
require 'spec_helper'
describe Gitlab::Ci::Variables::Collection::Item do
+ let(:variable_key) { 'VAR' }
+ let(:variable_value) { 'something' }
+ let(:expected_value) { variable_value }
+
let(:variable) do
- { key: 'VAR', value: 'something', public: true }
+ { key: variable_key, value: variable_value, public: true }
end
describe '.new' do
- it 'raises error if unknown key i specified' do
- expect { described_class.new(key: 'VAR', value: 'abc', files: true) }
- .to raise_error ArgumentError, 'unknown keyword: files'
+ context 'when unknown keyword is specified' do
+ it 'raises error' do
+ expect { described_class.new(key: variable_key, value: 'abc', files: true) }
+ .to raise_error ArgumentError, 'unknown keyword: files'
+ end
+ end
+
+ context 'when required keywords are not specified' do
+ it 'raises error' do
+ expect { described_class.new(key: variable_key) }
+ .to raise_error ArgumentError, 'missing keyword: value'
+ end
end
- it 'raises error when required keywords are not specified' do
- expect { described_class.new(key: 'VAR') }
- .to raise_error ArgumentError, 'missing keyword: value'
+ shared_examples 'creates variable' do
+ subject { described_class.new(key: variable_key, value: variable_value) }
+
+ it 'saves given value' do
+ expect(subject[:key]).to eq variable_key
+ expect(subject[:value]).to eq expected_value
+ end
+ end
+
+ shared_examples 'raises error for invalid type' do
+ it do
+ expect { described_class.new(key: variable_key, value: variable_value) }
+ .to raise_error ArgumentError, /`value` must be of type String, while it was:/
+ end
+ end
+
+ it_behaves_like 'creates variable'
+
+ context "when it's nil" do
+ let(:variable_value) { nil }
+ let(:expected_value) { nil }
+
+ it_behaves_like 'creates variable'
+ end
+
+ context "when it's an empty string" do
+ let(:variable_value) { '' }
+ let(:expected_value) { '' }
+
+ it_behaves_like 'creates variable'
+ end
+
+ context 'when provided value is not a string' do
+ [1, false, [], {}, Object.new].each do |val|
+ context "when it's #{val}" do
+ let(:variable_value) { val }
+
+ it_behaves_like 'raises error for invalid type'
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index cb2f7718c9c..5c91816a586 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -29,7 +29,7 @@ describe Gitlab::Ci::Variables::Collection do
end
it 'appends an internal resource' do
- collection = described_class.new([{ key: 'TEST', value: 1 }])
+ collection = described_class.new([{ key: 'TEST', value: '1' }])
subject.append(collection.first)
@@ -74,15 +74,15 @@ describe Gitlab::Ci::Variables::Collection do
describe '#+' do
it 'makes it possible to combine with an array' do
- collection = described_class.new([{ key: 'TEST', value: 1 }])
+ collection = described_class.new([{ key: 'TEST', value: '1' }])
variables = [{ key: 'TEST', value: 'something' }]
expect((collection + variables).count).to eq 2
end
it 'makes it possible to combine with another collection' do
- collection = described_class.new([{ key: 'TEST', value: 1 }])
- other = described_class.new([{ key: 'TEST', value: 2 }])
+ collection = described_class.new([{ key: 'TEST', value: '1' }])
+ other = described_class.new([{ key: 'TEST', value: '2' }])
expect((collection + other).count).to eq 2
end
@@ -90,10 +90,10 @@ describe Gitlab::Ci::Variables::Collection do
describe '#to_runner_variables' do
it 'creates an array of hashes in a runner-compatible format' do
- collection = described_class.new([{ key: 'TEST', value: 1 }])
+ collection = described_class.new([{ key: 'TEST', value: '1' }])
expect(collection.to_runner_variables)
- .to eq [{ key: 'TEST', value: 1, public: true }]
+ .to eq [{ key: 'TEST', value: '1', public: true }]
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 280f799f2ab..eb7148ff108 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1178,6 +1178,61 @@ describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#rename_column_using_background_migration' do
+ let!(:issue) { create(:issue, :closed, closed_at: Time.zone.now) }
+
+ it 'renames a column using a background migration' do
+ expect(model)
+ .to receive(:add_column)
+ .with(
+ 'issues',
+ :closed_at_timestamp,
+ :datetime_with_timezone,
+ limit: anything,
+ precision: anything,
+ scale: anything
+ )
+
+ expect(model)
+ .to receive(:install_rename_triggers)
+ .with('issues', :closed_at, :closed_at_timestamp)
+
+ expect(BackgroundMigrationWorker)
+ .to receive(:perform_in)
+ .ordered
+ .with(
+ 10.minutes,
+ 'CopyColumn',
+ ['issues', :closed_at, :closed_at_timestamp, issue.id, issue.id]
+ )
+
+ expect(BackgroundMigrationWorker)
+ .to receive(:perform_in)
+ .ordered
+ .with(
+ 1.hour + 10.minutes,
+ 'CleanupConcurrentRename',
+ ['issues', :closed_at, :closed_at_timestamp]
+ )
+
+ expect(Gitlab::BackgroundMigration)
+ .to receive(:steal)
+ .ordered
+ .with('CopyColumn')
+
+ expect(Gitlab::BackgroundMigration)
+ .to receive(:steal)
+ .ordered
+ .with('CleanupConcurrentRename')
+
+ model.rename_column_using_background_migration(
+ 'issues',
+ :closed_at,
+ :closed_at_timestamp
+ )
+ end
+ end
+
describe '#perform_background_migration_inline?' do
it 'returns true in a test environment' do
allow(Rails.env)
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 6015086f002..b6061df349d 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
- shared_examples 'finding blobs' do
+ describe '.find' do
context 'nil path' do
let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, nil) }
@@ -125,16 +125,6 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
- describe '.find' do
- context 'when project_raw_show Gitaly feature is enabled' do
- it_behaves_like 'finding blobs'
- end
-
- context 'when project_raw_show Gitaly feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'finding blobs'
- end
- end
-
shared_examples 'finding blobs by ID' do
let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
let(:bad_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::BigCommit::ID) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 45f0006dc85..b78fe4ba310 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1871,49 +1871,39 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository_rugged.config["gitlab.fullpath"] = repository_path
end
- shared_examples 'writing repo config' do
- context 'is given a path' do
- it 'writes it to disk' do
- repository.write_config(full_path: "not-the/real-path.git")
+ context 'is given a path' do
+ it 'writes it to disk' do
+ repository.write_config(full_path: "not-the/real-path.git")
- config = File.read(File.join(repository_path, "config"))
+ config = File.read(File.join(repository_path, "config"))
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = not-the/real-path.git")
- end
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = not-the/real-path.git")
end
+ end
- context 'it is given an empty path' do
- it 'does not write it to disk' do
- repository.write_config(full_path: "")
+ context 'it is given an empty path' do
+ it 'does not write it to disk' do
+ repository.write_config(full_path: "")
- config = File.read(File.join(repository_path, "config"))
+ config = File.read(File.join(repository_path, "config"))
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = #{repository_path}")
- end
+ expect(config).to include("[gitlab]")
+ expect(config).to include("fullpath = #{repository_path}")
end
+ end
- context 'repository does not exist' do
- it 'raises NoRepository and does not call Gitaly WriteConfig' do
- repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '')
+ context 'repository does not exist' do
+ it 'raises NoRepository and does not call Gitaly WriteConfig' do
+ repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '')
- expect(repository.gitaly_repository_client).not_to receive(:write_config)
+ expect(repository.gitaly_repository_client).not_to receive(:write_config)
- expect do
- repository.write_config(full_path: 'foo/bar.git')
- end.to raise_error(Gitlab::Git::Repository::NoRepository)
- end
+ expect do
+ repository.write_config(full_path: 'foo/bar.git')
+ end.to raise_error(Gitlab::Git::Repository::NoRepository)
end
end
-
- context "when gitaly_write_config is enabled" do
- it_behaves_like "writing repo config"
- end
-
- context "when gitaly_write_config is disabled", :disable_gitaly do
- it_behaves_like "writing repo config"
- end
end
describe '#merge' do
@@ -2160,43 +2150,33 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe '#create_from_bundle' do
- shared_examples 'creating repo from bundle' do
- let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
- let(:project) { create(:project) }
- let(:imported_repo) { project.repository.raw }
-
- before do
- expect(repository.bundle_to_disk(bundle_path)).to be true
- end
-
- after do
- FileUtils.rm_rf(bundle_path)
- end
+ let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:project) { create(:project) }
+ let(:imported_repo) { project.repository.raw }
- it 'creates a repo from a bundle file' do
- expect(imported_repo).not_to exist
+ before do
+ expect(repository.bundle_to_disk(bundle_path)).to be_truthy
+ end
- result = imported_repo.create_from_bundle(bundle_path)
+ after do
+ FileUtils.rm_rf(bundle_path)
+ end
- expect(result).to be true
- expect(imported_repo).to exist
- expect { imported_repo.fsck }.not_to raise_exception
- end
+ it 'creates a repo from a bundle file' do
+ expect(imported_repo).not_to exist
- it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(bundle_path)
- hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
+ result = imported_repo.create_from_bundle(bundle_path)
- expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
- end
+ expect(result).to be_truthy
+ expect(imported_repo).to exist
+ expect { imported_repo.fsck }.not_to raise_exception
end
- context 'when Gitaly create_repo_from_bundle feature is enabled' do
- it_behaves_like 'creating repo from bundle'
- end
+ it 'creates a symlink to the global hooks dir' do
+ imported_repo.create_from_bundle(bundle_path)
+ hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
- context 'when Gitaly create_repo_from_bundle feature is disabled', :disable_gitaly do
- it_behaves_like 'creating repo from bundle'
+ expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
end
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index 013b8895f67..7ffa84f906d 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::ImportExport::RepoRestorer do
end
it 'restores the repo successfully' do
- expect(restorer.restore).to be true
+ expect(restorer.restore).to be_truthy
end
it 'has the webhooks' do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 3e6656e0f12..02f74e2ea54 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -25,15 +25,6 @@ describe ApplicationSetting do
it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
- describe 'disabled_oauth_sign_in_sources validations' do
- before do
- allow(Devise).to receive(:omniauth_providers).and_return([:github])
- end
-
- it { is_expected.to allow_value(['github']).for(:disabled_oauth_sign_in_sources) }
- it { is_expected.not_to allow_value(['test']).for(:disabled_oauth_sign_in_sources) }
- end
-
describe 'default_artifacts_expire_in' do
it 'sets an error if it cannot parse' do
setting.update(default_artifacts_expire_in: 'a')
@@ -314,6 +305,33 @@ describe ApplicationSetting do
end
end
+ describe '#disabled_oauth_sign_in_sources=' do
+ before do
+ allow(Devise).to receive(:omniauth_providers).and_return([:github])
+ end
+
+ it 'removes unknown sources (as strings) from the array' do
+ subject.disabled_oauth_sign_in_sources = %w[github test]
+
+ expect(subject).to be_valid
+ expect(subject.disabled_oauth_sign_in_sources).to eq ['github']
+ end
+
+ it 'removes unknown sources (as symbols) from the array' do
+ subject.disabled_oauth_sign_in_sources = %i[github test]
+
+ expect(subject).to be_valid
+ expect(subject.disabled_oauth_sign_in_sources).to eq ['github']
+ end
+
+ it 'ignores nil' do
+ subject.disabled_oauth_sign_in_sources = nil
+
+ expect(subject).to be_valid
+ expect(subject.disabled_oauth_sign_in_sources).to be_empty
+ end
+ end
+
context 'restricted signup domains' do
it 'sets single domain' do
setting.domain_whitelist_raw = 'example.com'
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 51b9b518117..6758adc59eb 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1871,7 +1871,11 @@ describe Ci::Build do
end
context 'when yaml_variables are undefined' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: project.default_branch)
+ end
before do
build.yaml_variables = nil
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index b821a84d5e0..39c16ae60af 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -40,15 +40,25 @@ describe Sortable do
describe 'ordering by name' do
it 'ascending' do
- expect(relation).to receive(:reorder).with("lower(name) asc")
+ expect(relation).to receive(:reorder).once.and_call_original
- relation.order_by('name_asc')
+ table = Regexp.escape(ActiveRecord::Base.connection.quote_table_name(:namespaces))
+ column = Regexp.escape(ActiveRecord::Base.connection.quote_column_name(:name))
+
+ sql = relation.order_by('name_asc').to_sql
+
+ expect(sql).to match /.+ORDER BY LOWER\(#{table}.#{column}\) ASC\z/
end
it 'descending' do
- expect(relation).to receive(:reorder).with("lower(name) desc")
+ expect(relation).to receive(:reorder).once.and_call_original
+
+ table = Regexp.escape(ActiveRecord::Base.connection.quote_table_name(:namespaces))
+ column = Regexp.escape(ActiveRecord::Base.connection.quote_column_name(:name))
+
+ sql = relation.order_by('name_desc').to_sql
- relation.order_by('name_desc')
+ expect(sql).to match /.+ORDER BY LOWER\(#{table}.#{column}\) DESC\z/
end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index b4249d72fc8..48c01fc4d4e 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -47,6 +47,45 @@ describe MergeRequestDiff do
end
describe '#diffs' do
+ let(:merge_request) { create(:merge_request, :with_diffs) }
+ let!(:diff) { merge_request.merge_request_diff.reload }
+
+ context 'when it was not cleaned by the system' do
+ it 'returns persisted diffs' do
+ expect(diff).to receive(:load_diffs)
+
+ diff.diffs
+ end
+ end
+
+ context 'when diff was cleaned by the system' do
+ before do
+ diff.clean!
+ end
+
+ it 'returns diffs from repository if can compare with current diff refs' do
+ expect(diff).not_to receive(:load_diffs)
+
+ expect(Compare)
+ .to receive(:new)
+ .with(instance_of(Gitlab::Git::Compare), merge_request.target_project,
+ base_sha: diff.base_commit_sha, straight: false)
+ .and_call_original
+
+ diff.diffs
+ end
+
+ it 'returns persisted diffs if cannot compare with diff refs' do
+ expect(diff).to receive(:load_diffs)
+
+ diff.update!(head_commit_sha: 'invalid-sha')
+
+ diff.diffs
+ end
+ end
+ end
+
+ describe '#raw_diffs' do
context 'when the :ignore_whitespace_change option is set' do
it 'creates a new compare object instead of loading from the DB' do
expect(diff_with_commits).not_to receive(:load_diffs)
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 7ae70c3afb4..ec72fefd137 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1630,28 +1630,17 @@ describe MergeRequest do
end
describe "#reload_diff" do
- let(:discussion) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject).to_discussion }
- let(:commit) { subject.project.commit(sample_commit.id) }
-
- it "does not change existing merge request diff" do
- expect(subject.merge_request_diff).not_to receive(:save_git_content)
- subject.reload_diff
- end
-
- it "creates new merge request diff" do
- expect { subject.reload_diff }.to change { subject.merge_request_diffs.count }.by(1)
- end
-
- it "executes diff cache service" do
- expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject, an_instance_of(MergeRequestDiff))
+ it 'calls MergeRequests::ReloadDiffsService#execute with correct params' do
+ user = create(:user)
+ service = instance_double(MergeRequests::ReloadDiffsService, execute: nil)
- subject.reload_diff
- end
+ expect(MergeRequests::ReloadDiffsService)
+ .to receive(:new).with(subject, user)
+ .and_return(service)
- it "calls update_diff_discussion_positions" do
- expect(subject).to receive(:update_diff_discussion_positions)
+ subject.reload_diff(user)
- subject.reload_diff
+ expect(service).to have_received(:execute)
end
context 'when using the after_update hook to update' do
@@ -2145,8 +2134,7 @@ describe MergeRequest do
describe 'transition to cannot_be_merged' do
let(:notification_service) { double(:notification_service) }
let(:todo_service) { double(:todo_service) }
-
- subject { create(:merge_request, merge_status: :unchecked) }
+ subject { create(:merge_request, state, merge_status: :unchecked) }
before do
allow(NotificationService).to receive(:new).and_return(notification_service)
@@ -2155,33 +2143,52 @@ describe MergeRequest do
allow(subject.project.repository).to receive(:can_be_merged?).and_return(false)
end
- it 'notifies conflict, but does not notify again if rechecking still results in cannot_be_merged' do
- expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once
- expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once
+ [:opened, :locked].each do |state|
+ context state do
+ let(:state) { state }
- subject.mark_as_unmergeable
- subject.mark_as_unchecked
- subject.mark_as_unmergeable
- end
+ it 'notifies conflict, but does not notify again if rechecking still results in cannot_be_merged' do
+ expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once
+ expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once
- it 'notifies conflict, whenever newly unmergeable' do
- expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice
- expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice
+ subject.mark_as_unmergeable
+ subject.mark_as_unchecked
+ subject.mark_as_unmergeable
+ end
+
+ it 'notifies conflict, whenever newly unmergeable' do
+ expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice
+ expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice
+
+ subject.mark_as_unmergeable
+ subject.mark_as_unchecked
+ subject.mark_as_mergeable
+ subject.mark_as_unchecked
+ subject.mark_as_unmergeable
+ end
+
+ it 'does not notify whenever merge request is newly unmergeable due to other reasons' do
+ allow(subject.project.repository).to receive(:can_be_merged?).and_return(true)
- subject.mark_as_unmergeable
- subject.mark_as_unchecked
- subject.mark_as_mergeable
- subject.mark_as_unchecked
- subject.mark_as_unmergeable
+ expect(notification_service).not_to receive(:merge_request_unmergeable)
+ expect(todo_service).not_to receive(:merge_request_became_unmergeable)
+
+ subject.mark_as_unmergeable
+ end
+ end
end
- it 'does not notify whenever merge request is newly unmergeable due to other reasons' do
- allow(subject.project.repository).to receive(:can_be_merged?).and_return(true)
+ [:closed, :merged].each do |state|
+ let(:state) { state }
- expect(notification_service).not_to receive(:merge_request_unmergeable)
- expect(todo_service).not_to receive(:merge_request_became_unmergeable)
+ context state do
+ it 'does not notify' do
+ expect(notification_service).not_to receive(:merge_request_unmergeable)
+ expect(todo_service).not_to receive(:merge_request_became_unmergeable)
- subject.mark_as_unmergeable
+ subject.mark_as_unmergeable
+ end
+ end
end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 18b01c3e6b7..70f1a1c8b38 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -655,6 +655,19 @@ describe Namespace do
end
end
+ describe '#root_ancestor' do
+ it 'returns the top most ancestor', :nested_groups do
+ root_group = create(:group)
+ nested_group = create(:group, parent: root_group)
+ deep_nested_group = create(:group, parent: nested_group)
+ very_deep_nested_group = create(:group, parent: deep_nested_group)
+
+ expect(nested_group.root_ancestor).to eq(root_group)
+ expect(deep_nested_group.root_ancestor).to eq(root_group)
+ expect(very_deep_nested_group.root_ancestor).to eq(root_group)
+ end
+ end
+
describe '#remove_exports' do
let(:legacy_project) { create(:project, :with_export, :legacy_storage, namespace: namespace) }
let(:hashed_project) { create(:project, :with_export, namespace: namespace) }
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index 92b614b087e..7710f19ce4e 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
describe API::Boards do
set(:user) { create(:user) }
- set(:user2) { create(:user) }
set(:non_member) { create(:user) }
set(:guest) { create(:user) }
set(:admin) { create(:user, :admin) }
diff --git a/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
new file mode 100644
index 00000000000..1c632847940
--- /dev/null
+++ b/spec/services/merge_requests/delete_non_latest_diffs_service_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe MergeRequests::DeleteNonLatestDiffsService, :clean_gitlab_redis_shared_state do
+ let(:merge_request) { create(:merge_request) }
+
+ let!(:subject) { described_class.new(merge_request) }
+
+ describe '#execute' do
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ 3.times { merge_request.create_merge_request_diff }
+ end
+
+ it 'schedules non-latest merge request diffs removal' do
+ diffs = merge_request.merge_request_diffs
+
+ expect(diffs.count).to eq(4)
+
+ Timecop.freeze do
+ expect(DeleteDiffFilesWorker)
+ .to receive(:bulk_perform_in)
+ .with(5.minutes, [[diffs.first.id], [diffs.second.id]])
+ expect(DeleteDiffFilesWorker)
+ .to receive(:bulk_perform_in)
+ .with(10.minutes, [[diffs.third.id]])
+
+ subject.execute
+ end
+ end
+
+ it 'schedules no removal if it is already cleaned' do
+ merge_request.merge_request_diffs.each(&:clean!)
+
+ expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)
+
+ subject.execute
+ end
+
+ it 'schedules no removal if it is empty' do
+ merge_request.merge_request_diffs.each { |diff| diff.update!(state: :empty) }
+
+ expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)
+
+ subject.execute
+ end
+
+ it 'schedules no removal if there is no non-latest diffs' do
+ merge_request
+ .merge_request_diffs
+ .where.not(id: merge_request.latest_merge_request_diff_id)
+ .destroy_all
+
+ expect(DeleteDiffFilesWorker).not_to receive(:bulk_perform_in)
+
+ subject.execute
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
deleted file mode 100644
index 57b6165cfb0..00000000000
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'spec_helper'
-
-describe MergeRequests::MergeRequestDiffCacheService, :use_clean_rails_memory_store_caching do
- let(:subject) { described_class.new }
- let(:merge_request) { create(:merge_request) }
-
- describe '#execute' do
- before do
- allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
- allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
- end
-
- it 'retrieves the diff files to cache the highlighted result' do
- new_diff = merge_request.merge_request_diff
- cache_key = new_diff.diffs.cache_key
-
- expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
- expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
-
- subject.execute(merge_request, new_diff)
- end
-
- it 'clears the cache for older diffs on the merge request' do
- old_diff = merge_request.merge_request_diff
- old_cache_key = old_diff.diffs.cache_key
-
- subject.execute(merge_request, old_diff)
-
- new_diff = merge_request.create_merge_request_diff
- new_cache_key = new_diff.diffs.cache_key
-
- expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
- expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
- expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
-
- subject.execute(merge_request, new_diff)
- end
- end
-end
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
index 70957431942..790ecce8ded 100644
--- a/spec/services/merge_requests/post_merge_service_spec.rb
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -35,5 +35,17 @@ describe MergeRequests::PostMergeService do
described_class.new(project, user, {}).execute(merge_request)
end
+
+ it 'deletes non-latest diffs' do
+ diff_removal_service = instance_double(MergeRequests::DeleteNonLatestDiffsService, execute: nil)
+
+ expect(MergeRequests::DeleteNonLatestDiffsService)
+ .to receive(:new).with(merge_request)
+ .and_return(diff_removal_service)
+
+ described_class.new(project, user, {}).execute(merge_request)
+
+ expect(diff_removal_service).to have_received(:execute)
+ end
end
end
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
new file mode 100644
index 00000000000..a0a27d247fc
--- /dev/null
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_caching do
+ let(:current_user) { create(:user) }
+ let(:merge_request) { create(:merge_request) }
+ let(:subject) { described_class.new(merge_request, current_user) }
+
+ describe '#execute' do
+ it 'creates new merge request diff' do
+ expect { subject.execute }.to change { merge_request.merge_request_diffs.count }.by(1)
+ end
+
+ it 'calls update_diff_discussion_positions with correct params' do
+ old_diff_refs = merge_request.diff_refs
+ new_diff = merge_request.create_merge_request_diff
+ new_diff_refs = merge_request.diff_refs
+
+ expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff)
+ expect(merge_request).to receive(:update_diff_discussion_positions)
+ .with(old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs,
+ current_user: current_user)
+
+ subject.execute
+ end
+
+ it 'does not change existing merge request diff' do
+ expect(merge_request.merge_request_diff).not_to receive(:save_git_content)
+
+ subject.execute
+ end
+
+ context 'cache clearing' do
+ before do
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
+ allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
+ end
+
+ it 'retrieves the diff files to cache the highlighted result' do
+ new_diff = merge_request.create_merge_request_diff
+ cache_key = new_diff.diffs_collection.cache_key
+
+ expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff)
+ expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
+
+ subject.execute
+ end
+
+ it 'clears the cache for older diffs on the merge request' do
+ old_diff = merge_request.merge_request_diff
+ old_cache_key = old_diff.diffs_collection.cache_key
+ new_diff = merge_request.create_merge_request_diff
+ new_cache_key = new_diff.diffs_collection.cache_key
+
+ expect(merge_request).to receive(:create_merge_request_diff).and_return(new_diff)
+ expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
+ expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
+ subject.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/batch_open_issues_count_service_spec.rb b/spec/services/projects/batch_open_issues_count_service_spec.rb
new file mode 100644
index 00000000000..599aaf62080
--- /dev/null
+++ b/spec/services/projects/batch_open_issues_count_service_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Projects::BatchOpenIssuesCountService do
+ let!(:project_1) { create(:project) }
+ let!(:project_2) { create(:project) }
+
+ let(:subject) { described_class.new([project_1, project_2]) }
+
+ context '#refresh_cache', :use_clean_rails_memory_store_caching do
+ before do
+ create(:issue, project: project_1)
+ create(:issue, project: project_1, confidential: true)
+
+ create(:issue, project: project_2)
+ create(:issue, project: project_2, confidential: true)
+ end
+
+ context 'when cache is clean' do
+ it 'refreshes cache keys correctly' do
+ subject.refresh_cache
+
+ # It does not update total issues cache
+ expect(Rails.cache.read(get_cache_key(subject, project_1))).to eq(nil)
+ expect(Rails.cache.read(get_cache_key(subject, project_2))).to eq(nil)
+
+ expect(Rails.cache.read(get_cache_key(subject, project_1, true))).to eq(1)
+ expect(Rails.cache.read(get_cache_key(subject, project_1, true))).to eq(1)
+ end
+ end
+
+ context 'when issues count is already cached' do
+ before do
+ create(:issue, project: project_2)
+ subject.refresh_cache
+ end
+
+ it 'does update cache again' do
+ expect(Rails.cache).not_to receive(:write)
+
+ subject.refresh_cache
+ end
+ end
+ end
+
+ def get_cache_key(subject, project, public_key = false)
+ service = subject.count_service.new(project)
+
+ if public_key
+ service.cache_key(service.class::PUBLIC_COUNT_KEY)
+ else
+ service.cache_key(service.class::TOTAL_COUNT_KEY)
+ end
+ end
+end
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index 06b470849b3..562c14a8df8 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -50,5 +50,40 @@ describe Projects::OpenIssuesCountService do
end
end
end
+
+ context '#refresh_cache', :use_clean_rails_memory_store_caching do
+ let(:subject) { described_class.new(project) }
+
+ before do
+ create(:issue, :opened, project: project)
+ create(:issue, :opened, project: project)
+ create(:issue, :opened, confidential: true, project: project)
+ end
+
+ context 'when cache is empty' do
+ it 'refreshes cache keys correctly' do
+ subject.refresh_cache
+
+ expect(Rails.cache.read(subject.cache_key(described_class::PUBLIC_COUNT_KEY))).to eq(2)
+ expect(Rails.cache.read(subject.cache_key(described_class::TOTAL_COUNT_KEY))).to eq(3)
+ end
+ end
+
+ context 'when cache is outdated' do
+ before do
+ subject.refresh_cache
+ end
+
+ it 'refreshes cache keys correctly' do
+ create(:issue, :opened, project: project)
+ create(:issue, :opened, confidential: true, project: project)
+
+ subject.refresh_cache
+
+ expect(Rails.cache.read(subject.cache_key(described_class::PUBLIC_COUNT_KEY))).to eq(3)
+ expect(Rails.cache.read(subject.cache_key(described_class::TOTAL_COUNT_KEY))).to eq(5)
+ end
+ end
+ end
end
end
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index 723cb374c37..5c2e79ff9af 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe Projects::UpdateRemoteMirrorService do
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
+ let(:owner) { project.owner }
let(:remote_project) { create(:forked_project_with_submodules) }
let(:repository) { project.repository }
let(:raw_repository) { repository.raw }
@@ -9,13 +10,11 @@ describe Projects::UpdateRemoteMirrorService do
subject { described_class.new(project, project.creator) }
- describe "#execute", :skip_gitaly_mock do
+ describe "#execute" do
before do
- create_branch(repository, 'existing-branch')
- allow(raw_repository).to receive(:remote_tags) do
- generate_tags(repository, 'v1.0.0', 'v1.1.0')
- end
- allow(raw_repository).to receive(:push_remote_branches).and_return(true)
+ repository.add_branch(owner, 'existing-branch', 'master')
+
+ allow(remote_mirror).to receive(:update_repository).and_return(true)
end
it "fetches the remote repository" do
@@ -34,307 +33,57 @@ describe Projects::UpdateRemoteMirrorService do
expect(result[:status]).to eq(:success)
end
- describe 'Syncing branches' do
+ context 'when syncing all branches' do
it "push all the branches the first time" do
allow(repository).to receive(:fetch_remote)
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, local_branch_names)
-
- subject.execute(remote_mirror)
- end
-
- it "does not push anything is remote is up to date" do
- allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) }
-
- expect(raw_repository).not_to receive(:push_remote_branches)
-
- subject.execute(remote_mirror)
- end
-
- it "sync new branches" do
- # call local_branch_names early so it is not called after the new branch has been created
- current_branches = local_branch_names
- allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, current_branches) }
- create_branch(repository, 'my-new-branch')
-
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['my-new-branch'])
-
- subject.execute(remote_mirror)
- end
-
- it "sync updated branches" do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- update_branch(repository, 'existing-branch')
- end
-
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
+ expect(remote_mirror).to receive(:update_repository).with({})
subject.execute(remote_mirror)
end
-
- context 'when push only protected branches option is set' do
- let(:unprotected_branch_name) { 'existing-branch' }
- let(:protected_branch_name) do
- project.repository.branch_names.find { |n| n != unprotected_branch_name }
- end
- let!(:protected_branch) do
- create(:protected_branch, project: project, name: protected_branch_name)
- end
-
- before do
- project.reload
- remote_mirror.only_protected_branches = true
- end
-
- it "sync updated protected branches" do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- update_branch(repository, protected_branch_name)
- end
-
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
-
- subject.execute(remote_mirror)
- end
-
- it 'does not sync unprotected branches' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- update_branch(repository, unprotected_branch_name)
- end
-
- expect(raw_repository).not_to receive(:push_remote_branches).with(remote_mirror.remote_name, [unprotected_branch_name])
-
- subject.execute(remote_mirror)
- end
- end
-
- context 'when branch exists in local and remote repo' do
- context 'when it has diverged' do
- it 'syncs branches' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- update_remote_branch(repository, remote_mirror.remote_name, 'markdown')
- end
-
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['markdown'])
-
- subject.execute(remote_mirror)
- end
- end
- end
-
- describe 'for delete' do
- context 'when branch exists in local and remote repo' do
- it 'deletes the branch from remote repo' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- delete_branch(repository, 'existing-branch')
- end
-
- expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
-
- subject.execute(remote_mirror)
- end
- end
-
- context 'when push only protected branches option is set' do
- before do
- remote_mirror.only_protected_branches = true
- end
-
- context 'when branch exists in local and remote repo' do
- let!(:protected_branch_name) { local_branch_names.first }
-
- before do
- create(:protected_branch, project: project, name: protected_branch_name)
- project.reload
- end
-
- it 'deletes the protected branch from remote repo' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- delete_branch(repository, protected_branch_name)
- end
-
- expect(raw_repository).not_to receive(:delete_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
-
- subject.execute(remote_mirror)
- end
-
- it 'does not delete the unprotected branch from remote repo' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
- delete_branch(repository, 'existing-branch')
- end
-
- expect(raw_repository).not_to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['existing-branch'])
-
- subject.execute(remote_mirror)
- end
- end
-
- context 'when branch only exists on remote repo' do
- let!(:protected_branch_name) { 'remote-branch' }
-
- before do
- create(:protected_branch, project: project, name: protected_branch_name)
- end
-
- context 'when it has diverged' do
- it 'does not delete the remote branch' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
-
- rev = repository.find_branch('markdown').dereferenced_target
- create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', rev.id)
- end
-
- expect(raw_repository).not_to receive(:delete_remote_branches)
-
- subject.execute(remote_mirror)
- end
- end
-
- context 'when it has not diverged' do
- it 'deletes the remote branch' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
-
- masterrev = repository.find_branch('master').dereferenced_target
- create_remote_branch(repository, remote_mirror.remote_name, protected_branch_name, masterrev.id)
- end
-
- expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, [protected_branch_name])
-
- subject.execute(remote_mirror)
- end
- end
- end
- end
-
- context 'when branch only exists on remote repo' do
- context 'when it has diverged' do
- it 'does not delete the remote branch' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
-
- rev = repository.find_branch('markdown').dereferenced_target
- create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', rev.id)
- end
-
- expect(raw_repository).not_to receive(:delete_remote_branches)
-
- subject.execute(remote_mirror)
- end
- end
-
- context 'when it has not diverged' do
- it 'deletes the remote branch' do
- allow(repository).to receive(:fetch_remote) do
- sync_remote(repository, remote_mirror.remote_name, local_branch_names)
-
- masterrev = repository.find_branch('master').dereferenced_target
- create_remote_branch(repository, remote_mirror.remote_name, 'remote-branch', masterrev.id)
- end
-
- expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['remote-branch'])
-
- subject.execute(remote_mirror)
- end
- end
- end
- end
end
- describe 'Syncing tags' do
- before do
- allow(repository).to receive(:fetch_remote) { sync_remote(repository, remote_mirror.remote_name, local_branch_names) }
+ context 'when only syncing protected branches' do
+ let(:unprotected_branch_name) { 'existing-branch' }
+ let(:protected_branch_name) do
+ project.repository.branch_names.find { |n| n != unprotected_branch_name }
end
-
- context 'when there are not tags to push' do
- it 'does not try to push tags' do
- allow(repository).to receive(:remote_tags) { {} }
- allow(repository).to receive(:tags) { [] }
-
- expect(repository).not_to receive(:push_tags)
-
- subject.execute(remote_mirror)
- end
+ let!(:protected_branch) do
+ create(:protected_branch, project: project, name: protected_branch_name)
end
- context 'when there are some tags to push' do
- it 'pushes tags to remote' do
- allow(raw_repository).to receive(:remote_tags) { {} }
-
- expect(raw_repository).to receive(:push_remote_branches).with(remote_mirror.remote_name, ['v1.0.0', 'v1.1.0'])
-
- subject.execute(remote_mirror)
- end
+ before do
+ project.reload
+ remote_mirror.only_protected_branches = true
end
- context 'when there are some tags to delete' do
- it 'deletes tags from remote' do
- remote_tags = generate_tags(repository, 'v1.0.0', 'v1.1.0')
- allow(raw_repository).to receive(:remote_tags) { remote_tags }
-
- repository.rm_tag(create(:user), 'v1.0.0')
-
- expect(raw_repository).to receive(:delete_remote_branches).with(remote_mirror.remote_name, ['v1.0.0'])
+ it "sync updated protected branches" do
+ allow(repository).to receive(:fetch_remote)
+ expect(remote_mirror).to receive(:update_repository).with(only_branches_matching: [protected_branch_name])
- subject.execute(remote_mirror)
- end
+ subject.execute(remote_mirror)
end
end
end
- def create_branch(repository, branch_name)
- rugged = repository.rugged
- masterrev = repository.find_branch('master').dereferenced_target
- parentrev = repository.commit(masterrev).parent_id
-
- rugged.references.create("refs/heads/#{branch_name}", parentrev)
-
- repository.expire_branches_cache
- end
-
- def create_remote_branch(repository, remote_name, branch_name, source_id)
- rugged = repository.rugged
-
- rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", source_id)
- end
-
def sync_remote(repository, remote_name, local_branch_names)
- rugged = repository.rugged
-
local_branch_names.each do |branch|
- target = repository.find_branch(branch).try(:dereferenced_target)
- rugged.references.create("refs/remotes/#{remote_name}/#{branch}", target.id) if target
+ commit = repository.commit(branch)
+ repository.write_ref("refs/remotes/#{remote_name}/#{branch}", commit.id) if commit
end
end
def update_remote_branch(repository, remote_name, branch)
- rugged = repository.rugged
- masterrev = repository.find_branch('master').dereferenced_target.id
+ masterrev = repository.commit('master').id
- rugged.references.create("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true)
+ repository.write_ref("refs/remotes/#{remote_name}/#{branch}", masterrev, force: true)
repository.expire_branches_cache
end
def update_branch(repository, branch)
- rugged = repository.rugged
- masterrev = repository.find_branch('master').dereferenced_target.id
-
- # Updated existing branch
- rugged.references.create("refs/heads/#{branch}", masterrev, force: true)
- repository.expire_branches_cache
- end
-
- def delete_branch(repository, branch)
- rugged = repository.rugged
+ masterrev = repository.commit('master').id
- rugged.references.delete("refs/heads/#{branch}")
+ repository.write_ref("refs/heads/#{branch}", masterrev, force: true)
repository.expire_branches_cache
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 7995f2c9ae7..622e56e1da5 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -60,6 +60,36 @@ describe WebHookService do
).once
end
+ context 'when auth credentials are present' do
+ let(:url) {'https://example.org'}
+ let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
+
+ it 'uses the credentials' do
+ WebMock.stub_request(:post, url)
+
+ service_instance.execute
+
+ expect(WebMock).to have_requested(:post, url).with(
+ headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v')
+ ).once
+ end
+ end
+
+ context 'when auth credentials are partial present' do
+ let(:url) {'https://example.org'}
+ let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
+
+ it 'uses the credentials anyways' do
+ WebMock.stub_request(:post, url)
+
+ service_instance.execute
+
+ expect(WebMock).to have_requested(:post, url).with(
+ headers: headers.merge('Authorization' => 'Basic ZGVtbzo=')
+ ).once
+ end
+ end
+
it 'catches exceptions' do
WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 329f18cd288..87cfb6c04dc 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -46,8 +46,8 @@ module LoginHelpers
@current_user = user
end
- def gitlab_sign_in_via(provider, user, uid)
- mock_auth_hash(provider, uid, user.email)
+ def gitlab_sign_in_via(provider, user, uid, saml_response = nil)
+ mock_auth_hash(provider, uid, user.email, saml_response)
visit new_user_session_path
click_link provider
end
@@ -87,7 +87,7 @@ module LoginHelpers
click_link "oauth-login-#{provider}"
end
- def mock_auth_hash(provider, uid, email)
+ def mock_auth_hash(provider, uid, email, saml_response = nil)
# The mock_auth configuration allows you to set per-provider (or default)
# authentication hashes to return during integration testing.
OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({
@@ -109,12 +109,21 @@ module LoginHelpers
email: email,
image: 'mock_user_thumbnail_url'
}
+ },
+ response_object: {
+ document: saml_xml(saml_response)
}
}
})
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
end
+ def saml_xml(raw_saml_response)
+ return '' if raw_saml_response.blank?
+
+ XMLSecurity::SignedDocument.new(raw_saml_response, [])
+ end
+
def mock_saml_config
OpenStruct.new(name: 'saml', label: 'saml', args: {
assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
@@ -125,6 +134,14 @@ module LoginHelpers
})
end
+ def mock_saml_config_with_upstream_two_factor_authn_contexts
+ config = mock_saml_config
+ config.args[:upstream_two_factor_authn_contexts] = %w(urn:oasis:names:tc:SAML:2.0:ac:classes:CertificateProtectedTransport
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorOTPSMS
+ urn:oasis:names:tc:SAML:2.0:ac:classes:SecondFactorIGTOKEN)
+ config
+ end
+
def stub_omniauth_provider(provider, context: Rails.application)
env = env_from_context(context)
@@ -140,13 +157,16 @@ module LoginHelpers
env['omniauth.error.strategy'] = strategy
end
- def stub_omniauth_saml_config(messages)
- set_devise_mapping(context: Rails.application)
- Rails.application.routes.disable_clear_and_finalize = true
- Rails.application.routes.draw do
+ def stub_omniauth_saml_config(messages, context: Rails.application)
+ set_devise_mapping(context: context)
+ routes = Rails.application.routes
+ routes.disable_clear_and_finalize = true
+ routes.formatter.clear
+ routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
end
- allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: mock_saml_config)
+ saml_config = messages.key?(:providers) ? messages[:providers].first : mock_saml_config
+ allow(Gitlab::Auth::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
stub_omniauth_setting(messages)
stub_saml_authorize_path_helpers
end
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index bceaf8277ee..471b0a74a19 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -15,9 +15,14 @@ module StubObjectStorage
return unless enabled
+ stub_object_storage(connection_params: uploader.object_store_credentials,
+ remote_directory: remote_directory)
+ end
+
+ def stub_object_storage(connection_params:, remote_directory:)
Fog.mock!
- ::Fog::Storage.new(uploader.object_store_credentials).tap do |connection|
+ ::Fog::Storage.new(connection_params).tap do |connection|
begin
connection.directories.create(key: remote_directory)
rescue Excon::Error::Conflict
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
new file mode 100644
index 00000000000..e0edd313922
--- /dev/null
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe DeleteDiffFilesWorker do
+ describe '#perform' do
+ let(:merge_request) { create(:merge_request) }
+ let(:merge_request_diff) { merge_request.merge_request_diff }
+
+ it 'deletes all merge request diff files' do
+ expect { described_class.new.perform(merge_request_diff.id) }
+ .to change { merge_request_diff.merge_request_diff_files.count }
+ .from(20).to(0)
+ end
+
+ it 'updates state to without_files' do
+ expect { described_class.new.perform(merge_request_diff.id) }
+ .to change { merge_request_diff.reload.state }
+ .from('collected').to('without_files')
+ end
+
+ it 'does nothing if diff was already marked as "without_files"' do
+ merge_request_diff.clean!
+
+ expect_any_instance_of(MergeRequestDiff).not_to receive(:clean!)
+
+ described_class.new.perform(merge_request_diff.id)
+ end
+
+ it 'rollsback if something goes wrong' do
+ expect(MergeRequestDiffFile).to receive_message_chain(:where, :delete_all)
+ .and_raise
+
+ expect { described_class.new.perform(merge_request_diff.id) }
+ .to raise_error
+
+ merge_request_diff.reload
+
+ expect(merge_request_diff.state).to eq('collected')
+ expect(merge_request_diff.merge_request_diff_files.count).to eq(20)
+ end
+ end
+end
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 36594515005..06d9e125105 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -5,15 +5,17 @@ describe DeleteUserWorker do
let!(:current_user) { create(:user) }
it "calls the DeleteUserWorker with the params it was given" do
- expect_any_instance_of(Users::DestroyService).to receive(:execute)
- .with(user, {})
+ expect_next_instance_of(Users::DestroyService) do |service|
+ expect(service).to receive(:execute).with(user, {})
+ end
described_class.new.perform(current_user.id, user.id)
end
it "uses symbolized keys" do
- expect_any_instance_of(Users::DestroyService).to receive(:execute)
- .with(user, test: "test")
+ expect_next_instance_of(Users::DestroyService) do |service|
+ expect(service).to receive(:execute).with(user, test: "test")
+ end
described_class.new.perform(current_user.id, user.id, "test" => "test")
end
diff --git a/yarn.lock b/yarn.lock
index cefd7c9a62e..ef7fa659d6e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -78,9 +78,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.23.0":
- version "1.23.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.23.0.tgz#42047aeedcc06bc12d417ed1efadad1749af9670"
+"@gitlab-org/gitlab-svgs@^1.24.0":
+ version "1.24.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.24.0.tgz#3b2b58c5a1d58ce784f486d648bd87cbbb06cedc"
"@sindresorhus/is@^0.7.0":
version "0.7.0"
@@ -297,13 +297,6 @@ ajv-keywords@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be"
-ajv@^4.9.1:
- version "4.11.8"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
- dependencies:
- co "^4.6.0"
- json-stable-stringify "^1.0.1"
-
ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
version "5.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
@@ -1300,12 +1293,6 @@ blob@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
-block-stream@*:
- version "0.0.9"
- resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
- dependencies:
- inherits "~2.0.0"
-
bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.4.6, bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@@ -2365,7 +2352,7 @@ de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
-debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6:
+debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9, debug@~2.6.4, debug@~2.6.6:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
@@ -3423,6 +3410,12 @@ fs-access@^1.0.0:
dependencies:
null-check "^1.0.0"
+fs-minipass@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
+ dependencies:
+ minipass "^2.2.1"
+
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@@ -3437,28 +3430,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
fsevents@^1.0.0:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8"
- dependencies:
- nan "^2.3.0"
- node-pre-gyp "^0.6.39"
-
-fstream-ignore@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426"
dependencies:
- fstream "^1.0.0"
- inherits "2"
- minimatch "^3.0.0"
-
-fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
- dependencies:
- graceful-fs "^4.1.2"
- inherits "~2.0.0"
- mkdirp ">=0.5 0"
- rimraf "2"
+ nan "^2.9.2"
+ node-pre-gyp "^0.10.0"
ftp@~0.3.10:
version "0.3.10"
@@ -3690,10 +3666,6 @@ handlebars@^4.0.1, handlebars@^4.0.3:
optionalDependencies:
uglify-js "^2.6"
-har-schema@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
-
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -3707,13 +3679,6 @@ har-validator@~2.0.6:
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
-har-validator@~4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
- dependencies:
- ajv "^4.9.1"
- har-schema "^1.0.5"
-
har-validator@~5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
@@ -3816,7 +3781,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
-hawk@3.1.3, hawk@~3.1.3:
+hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
dependencies:
@@ -3988,6 +3953,12 @@ iconv-lite@0.4.19, iconv-lite@^0.4.17:
version "0.4.19"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+iconv-lite@^0.4.4:
+ version "0.4.23"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -4010,6 +3981,12 @@ ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+ignore-walk@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
+ dependencies:
+ minimatch "^3.0.4"
+
ignore@^3.3.3, ignore@^3.3.7:
version "3.3.8"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b"
@@ -4069,7 +4046,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
@@ -4657,12 +4634,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-json-stable-stringify@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
- dependencies:
- jsonify "~0.0.0"
-
json-stringify-safe@5.0.x, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -4675,10 +4646,6 @@ json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-jsonify@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
-
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
@@ -5238,7 +5205,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
-"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@@ -5256,6 +5223,19 @@ minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
+minipass@^2.2.1, minipass@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
+ dependencies:
+ safe-buffer "^5.1.2"
+ yallist "^3.0.0"
+
+minizlib@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
+ dependencies:
+ minipass "^2.2.1"
+
mississippi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f"
@@ -5278,7 +5258,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@@ -5334,9 +5314,9 @@ mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-nan@^2.3.0:
- version "2.8.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
+nan@^2.9.2:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
nanomatch@^1.2.9:
version "1.2.9"
@@ -5359,6 +5339,14 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+needle@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d"
+ dependencies:
+ debug "^2.1.2"
+ iconv-lite "^0.4.4"
+ sax "^1.2.4"
+
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -5407,21 +5395,20 @@ node-forge@0.6.33:
util "^0.10.3"
vm-browserify "0.0.4"
-node-pre-gyp@^0.6.39:
- version "0.6.39"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
+node-pre-gyp@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46"
dependencies:
detect-libc "^1.0.2"
- hawk "3.1.3"
mkdirp "^0.5.1"
+ needle "^2.2.0"
nopt "^4.0.1"
+ npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.1.7"
- request "2.81.0"
rimraf "^2.6.1"
semver "^5.3.0"
- tar "^2.2.1"
- tar-pack "^3.4.0"
+ tar "^4"
node-uuid@~1.4.7:
version "1.4.8"
@@ -5546,6 +5533,17 @@ normalize-url@^1.4.0:
query-string "^4.1.0"
sort-keys "^1.0.0"
+npm-bundled@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308"
+
+npm-packlist@^1.1.6:
+ version "1.1.10"
+ resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a"
+ dependencies:
+ ignore-walk "^3.0.1"
+ npm-bundled "^1.0.1"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -5630,7 +5628,7 @@ on-headers@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
-once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
+once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -5905,10 +5903,6 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
-performance-now@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
-
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6370,10 +6364,6 @@ qs@~6.2.0:
version "6.2.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe"
-qs@~6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
-
query-string@^4.1.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd"
@@ -6491,7 +6481,7 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
-"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3:
+"readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3:
version "2.3.4"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071"
dependencies:
@@ -6697,33 +6687,6 @@ request@2.75.x:
tough-cookie "~2.3.0"
tunnel-agent "~0.4.1"
-request@2.81.0:
- version "2.81.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.1.1"
- har-validator "~4.2.1"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- oauth-sign "~0.8.1"
- performance-now "^0.2.0"
- qs "~6.4.0"
- safe-buffer "^5.0.1"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "^0.6.0"
- uuid "^3.0.0"
-
request@^2.0.0, request@^2.74.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
@@ -6830,7 +6793,7 @@ right-align@^0.1.1:
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
dependencies:
@@ -6875,12 +6838,20 @@ safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+safe-buffer@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
dependencies:
ret "~0.1.10"
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+
sanitize-html@^1.16.1:
version "1.16.3"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.16.3.tgz#96c1b44a36ff7312e1c22a14b05274370ac8bd56"
@@ -6893,6 +6864,10 @@ sanitize-html@^1.16.1:
srcset "^1.0.0"
xtend "^4.0.0"
+sax@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+
sax@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
@@ -7549,26 +7524,17 @@ tapable@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
-tar-pack@^3.4.0:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
- dependencies:
- debug "^2.2.0"
- fstream "^1.0.10"
- fstream-ignore "^1.0.5"
- once "^1.3.3"
- readable-stream "^2.1.4"
- rimraf "^2.5.1"
- tar "^2.2.1"
- uid-number "^0.0.6"
-
-tar@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+tar@^4:
+ version "4.4.4"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd"
dependencies:
- block-stream "*"
- fstream "^1.0.2"
- inherits "2"
+ chownr "^1.0.1"
+ fs-minipass "^1.2.5"
+ minipass "^2.3.3"
+ minizlib "^1.1.0"
+ mkdirp "^0.5.0"
+ safe-buffer "^5.1.2"
+ yallist "^3.0.2"
term-size@^1.2.0:
version "1.2.0"
@@ -7793,10 +7759,6 @@ uglifyjs-webpack-plugin@^1.2.4:
webpack-sources "^1.1.0"
worker-farm "^1.5.2"
-uid-number@^0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
-
ultron@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
@@ -7975,7 +7937,7 @@ utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
-uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
+uuid@^3.0.1, uuid@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
@@ -8386,6 +8348,10 @@ yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+yallist@^3.0.0, yallist@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
+
yargs-parser@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"