summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--.rubocop.yml19
-rw-r--r--CHANGELOG.md36
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock7
-rw-r--r--Gemfile.rails5.lock7
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue3
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue63
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue6
-rw-r--r--app/assets/javascripts/ide/constants.js8
-rw-r--r--app/assets/javascripts/ide/stores/actions.js4
-rw-r--r--app/assets/javascripts/ide/stores/index.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/actions.js15
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/actions.js30
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/getters.js4
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/index.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/mutation_types.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/mutations.js19
-rw-r--r--app/assets/javascripts/ide/stores/modules/pane/state.js5
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js5
-rw-r--r--app/assets/javascripts/ide/stores/state.js1
-rw-r--r--app/assets/javascripts/issue_show/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue6
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework/common.scss10
-rw-r--r--app/assets/stylesheets/framework/modal.scss2
-rw-r--r--app/controllers/application_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/finders/events_finder.rb1
-rw-r--r--app/finders/joined_groups_finder.rb19
-rw-r--r--app/finders/user_recent_events_finder.rb1
-rw-r--r--app/helpers/avatars_helper.rb21
-rw-r--r--app/mailers/emails/merge_requests.rb12
-rw-r--r--app/models/blob_viewer/package_json.rb3
-rw-r--r--app/models/ci/pipeline_variable.rb4
-rw-r--r--app/models/event.rb14
-rw-r--r--app/models/group.rb30
-rw-r--r--app/models/hooks/web_hook.rb44
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/repository.rb16
-rw-r--r--app/models/user.rb10
-rw-r--r--app/serializers/diff_line_entity.rb2
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb6
-rw-r--r--app/services/clusters/applications/install_service.rb8
-rw-r--r--app/services/users/build_service.rb6
-rw-r--r--app/views/help/index.html.haml3
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/profiles/show.html.haml7
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/empty.html.haml2
-rw-r--r--app/views/projects/forks/_fork_button.html.haml4
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml3
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml5
-rw-r--r--changelogs/unreleased/23197-add-custom-header-for-error-responses.yml6
-rw-r--r--changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml6
-rw-r--r--changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml5
-rw-r--r--changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml6
-rw-r--r--changelogs/unreleased/51021-more-attr-encrypted.yml5
-rw-r--r--changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml5
-rw-r--r--changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml5
-rw-r--r--changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml5
-rw-r--r--changelogs/unreleased/add-gl-link-to-download-viewer.yml5
-rw-r--r--changelogs/unreleased/add-gl-link-to-user-avatar-link.yml5
-rw-r--r--changelogs/unreleased/bvl-remove-sha-from-help.yml5
-rw-r--r--changelogs/unreleased/feature-set-public-email-through-api.yml5
-rw-r--r--changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml5
-rw-r--r--changelogs/unreleased/fix-events-finder-incomplete.yml5
-rw-r--r--changelogs/unreleased/frozen-string-app-enforce.yml5
-rw-r--r--changelogs/unreleased/frozen-string-docs.yml5
-rw-r--r--changelogs/unreleased/ide-fetch-templates-pages.yml5
-rw-r--r--changelogs/unreleased/improve-empty-project-placeholder.yml5
-rw-r--r--changelogs/unreleased/lib-api-frozen-string-enable.yml5
-rw-r--r--changelogs/unreleased/mk-asymmetric-exists-cache.yml6
-rw-r--r--changelogs/unreleased/pipeline-event-variables.yml5
-rw-r--r--changelogs/unreleased/rename-local-variable.yml5
-rw-r--r--changelogs/unreleased/security-2697-code-highlight-timeout.yml5
-rw-r--r--changelogs/unreleased/security-acet-issue-details.yml5
-rw-r--r--changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml5
-rw-r--r--changelogs/unreleased/security-package-json-xss.yml5
-rw-r--r--changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-52009.yml5
-rw-r--r--config/initializers/postgresql_opclasses_support.rb4
-rw-r--r--db/migrate/20170222111732_create_gpg_keys.rb6
-rw-r--r--db/migrate/20170613154149_create_gpg_signatures.rb6
-rw-r--r--db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb2
-rw-r--r--db/migrate/20170927161718_create_gpg_key_subkeys.rb6
-rw-r--r--db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb15
-rw-r--r--db/migrate/20180916011959_add_index_pipelines_project_id_source.rb20
-rw-r--r--db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb33
-rw-r--r--db/post_migrate/20180916014356_populate_external_pipeline_source.rb33
-rw-r--r--db/schema.rb5
-rw-r--r--doc/api/members.md2
-rw-r--r--doc/api/repository_files.md6
-rw-r--r--doc/api/users.md40
-rw-r--r--doc/ci/interactive_web_terminal/index.md2
-rw-r--r--doc/development/contributing/issue_workflow.md4
-rw-r--r--doc/development/i18n/index.md2
-rw-r--r--doc/development/i18n/merging_translations.md60
-rw-r--r--doc/development/performance.md6
-rw-r--r--doc/update/README.md3
-rw-r--r--doc/user/group/subgroups/index.md32
-rw-r--r--doc/user/project/img/issue_board_milestone_lists.pngbin0 -> 58740 bytes
-rw-r--r--doc/user/project/img/issue_board_summed_weights.pngbin0 -> 26691 bytes
-rwxr-xr-xdoc/user/project/img/issue_boards_core.pngbin0 -> 61230 bytes
-rwxr-xr-xdoc/user/project/img/issue_boards_premium.pngbin0 -> 72434 bytes
-rw-r--r--doc/user/project/integrations/webhooks.md8
-rw-r--r--doc/user/project/issue_board.md289
-rw-r--r--doc/user/project/pages/img/icons/click.pngbin0 -> 10148 bytes
-rw-r--r--doc/user/project/pages/img/icons/cogs.pngbin0 -> 9670 bytes
-rw-r--r--doc/user/project/pages/img/icons/fork.pngbin0 -> 9597 bytes
-rw-r--r--doc/user/project/pages/img/icons/free.pngbin0 -> 8689 bytes
-rw-r--r--doc/user/project/pages/img/icons/lock.pngbin0 -> 7892 bytes
-rw-r--r--doc/user/project/pages/img/icons/monitor.pngbin0 -> 5039 bytes
-rw-r--r--doc/user/project/pages/img/icons/terminal.pngbin0 -> 4972 bytes
-rw-r--r--doc/user/project/pages/img/ssgs_pages.pngbin0 -> 12010 bytes
-rw-r--r--doc/user/project/pages/index.md188
-rw-r--r--lib/api/access_requests.rb2
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/api_guard.rb2
-rw-r--r--lib/api/applications.rb2
-rw-r--r--lib/api/avatar.rb2
-rw-r--r--lib/api/award_emoji.rb2
-rw-r--r--lib/api/badges.rb2
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/boards_responses.rb2
-rw-r--r--lib/api/branches.rb2
-rw-r--r--lib/api/broadcast_messages.rb2
-rw-r--r--lib/api/circuit_breakers.rb2
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/custom_attributes_endpoints.rb2
-rw-r--r--lib/api/deploy_keys.rb2
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/events.rb24
-rw-r--r--lib/api/features.rb2
-rw-r--r--lib/api/files.rb2
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/group_milestones.rb2
-rw-r--r--lib/api/group_variables.rb2
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb5
-rw-r--r--lib/api/helpers/badges_helpers.rb2
-rw-r--r--lib/api/helpers/common_helpers.rb2
-rw-r--r--lib/api/helpers/custom_attributes.rb2
-rw-r--r--lib/api/helpers/custom_validators.rb2
-rw-r--r--lib/api/helpers/headers_helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/members_helpers.rb2
-rw-r--r--lib/api/helpers/notes_helpers.rb2
-rw-r--r--lib/api/helpers/pagination.rb2
-rw-r--r--lib/api/helpers/project_snapshots_helpers.rb2
-rw-r--r--lib/api/helpers/projects_helpers.rb2
-rw-r--r--lib/api/helpers/related_resources_helpers.rb2
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/issues.rb2
-rw-r--r--lib/api/job_artifacts.rb2
-rw-r--r--lib/api/jobs.rb2
-rw-r--r--lib/api/keys.rb2
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/lint.rb2
-rw-r--r--lib/api/markdown.rb2
-rw-r--r--lib/api/members.rb2
-rw-r--r--lib/api/merge_request_diffs.rb2
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/namespaces.rb2
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/notification_settings.rb2
-rw-r--r--lib/api/pages_domains.rb2
-rw-r--r--lib/api/pagination_params.rb2
-rw-r--r--lib/api/pipeline_schedules.rb2
-rw-r--r--lib/api/pipelines.rb2
-rw-r--r--lib/api/project_export.rb2
-rw-r--r--lib/api/project_hooks.rb2
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/project_milestones.rb2
-rw-r--r--lib/api/project_snapshots.rb2
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/projects_relation_builder.rb2
-rw-r--r--lib/api/protected_branches.rb2
-rw-r--r--lib/api/protected_tags.rb2
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/runner.rb2
-rw-r--r--lib/api/runners.rb2
-rw-r--r--lib/api/scope.rb2
-rw-r--r--lib/api/search.rb2
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--lib/api/snippets.rb2
-rw-r--r--lib/api/subscriptions.rb2
-rw-r--r--lib/api/system_hooks.rb2
-rw-r--r--lib/api/tags.rb2
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/api/time_tracking_endpoints.rb2
-rw-r--r--lib/api/todos.rb2
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb4
-rw-r--r--lib/api/variables.rb2
-rw-r--r--lib/api/version.rb2
-rw-r--r--lib/api/wikis.rb2
-rw-r--r--lib/backup/artifacts.rb2
-rw-r--r--lib/backup/builds.rb2
-rw-r--r--lib/backup/database.rb2
-rw-r--r--lib/backup/files.rb2
-rw-r--r--lib/backup/helper.rb2
-rw-r--r--lib/backup/lfs.rb2
-rw-r--r--lib/backup/manager.rb2
-rw-r--r--lib/backup/pages.rb2
-rw-r--r--lib/backup/registry.rb2
-rw-r--r--lib/backup/repository.rb2
-rw-r--r--lib/backup/uploads.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb6
-rw-r--r--lib/gitlab/background_migration/encrypt_columns.rb80
-rw-r--r--lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb28
-rw-r--r--lib/gitlab/background_migration/populate_external_pipeline_source.rb50
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml17
-rw-r--r--lib/gitlab/data_builder/pipeline.rb3
-rw-r--r--lib/gitlab/database/migration_helpers.rb4
-rw-r--r--lib/gitlab/diff/highlight.rb2
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb18
-rw-r--r--lib/gitlab/highlight.rb14
-rw-r--r--lib/gitlab/import_export/import_export.yml6
-rw-r--r--lib/gitlab/repository_cache.rb16
-rw-r--r--lib/gitlab/repository_cache_adapter.rb181
-rw-r--r--lib/gitlab/tree_summary.rb28
-rw-r--r--rubocop/cop/group_public_or_visible_to_user.rb22
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/review_apps/automated_cleanup.rb4
-rw-r--r--spec/controllers/application_controller_spec.rb76
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb5
-rw-r--r--spec/features/issues/issue_detail_spec.rb17
-rw-r--r--spec/features/projects/fork_spec.rb12
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json5
-rw-r--r--spec/helpers/avatars_helper_spec.rb24
-rw-r--r--spec/javascripts/ide/components/ide_status_bar_spec.js5
-rw-r--r--spec/javascripts/ide/components/panes/right_spec.js24
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js4
-rw-r--r--spec/javascripts/ide/helpers.js2
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/actions_spec.js46
-rw-r--r--spec/javascripts/ide/stores/modules/pane/actions_spec.js87
-rw-r--r--spec/javascripts/ide/stores/modules/pane/getters_spec.js61
-rw-r--r--spec/javascripts/ide/stores/modules/pane/mutations_spec.js42
-rw-r--r--spec/javascripts/ide/stores/modules/pipelines/actions_spec.js10
-rw-r--r--spec/javascripts/issue_show/index_spec.js19
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js22
-rw-r--r--spec/lib/backup/manager_spec.rb4
-rw-r--r--spec/lib/backup/repository_spec.rb4
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb69
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb62
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb47
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb28
-rw-r--r--spec/lib/gitlab/highlight_spec.rb17
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb138
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb85
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb21
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb9
-rw-r--r--spec/models/event_spec.rb124
-rw-r--r--spec/models/group_spec.rb42
-rw-r--r--spec/models/hooks/web_hook_spec.rb6
-rw-r--r--spec/models/repository_spec.rb101
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/redacted_events_spec.rb68
-rw-r--r--spec/rubocop/cop/group_public_or_visible_to_user_spec.rb28
-rw-r--r--spec/serializers/diff_line_entity_spec.rb45
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb2
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb2
-rw-r--r--spec/services/users/build_service_spec.rb43
-rw-r--r--spec/views/help/index.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb1
-rw-r--r--vendor/licenses.csv1
291 files changed, 3102 insertions, 522 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 90bc9f74ef3..9c5b40dce21 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -95,7 +95,7 @@ stages:
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
-# https://docs.gitlab.com/ce/development/writing_documentation.html#testing
+# https://docs.gitlab.com/ce/development/documentation/#testing
.except-docs: &except-docs
except:
- /(^docs[\/-].*|.*-docs$)/
@@ -729,6 +729,9 @@ karma:
dependencies:
- compile-assets
- setup-test-env
+ variables:
+ # we override the max_old_space_size to prevent OOM errors
+ NODE_OPTIONS: --max_old_space_size=3584
script:
- export BABEL_ENV=coverage CHROME_LOG_FILE=chrome_debug.log
- date
diff --git a/.rubocop.yml b/.rubocop.yml
index ce7be208186..b7aec5b8b14 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -35,6 +35,25 @@ Style/MutableConstant:
Style/SafeNavigation:
Enabled: false
+# Frozen String Literal
+Style/FrozenStringLiteralComment:
+ Enabled: true
+ Exclude:
+ - 'config.ru'
+ - 'Dangerfile'
+ - 'Gemfile'
+ - 'Rakefile'
+ - 'app/views/**/*'
+ - 'config/**/*'
+ - 'danger/**/*'
+ - 'db/**/*'
+ - 'ee/**/*'
+ - 'lib/**/*'
+ - 'qa/**/*'
+ - 'rubocop/**/*'
+ - 'scripts/**/*'
+ - 'spec/**/*'
+
Naming/FileName:
ExpectMatchingDefinition: true
Exclude:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e514a42108c..c9ab8599d99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,18 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.3.1 (2018-09-26)
+
+### Security (6 changes)
+
+- Redact confidential events in the API.
+- Set timeout for syntax highlighting.
+- Sanitize JSON data properly to fix XSS on Issue details page.
+- Fix stored XSS in merge requests from imported repository.
+- Fix xss vulnerability sourced from package.json.
+- Block loopback addresses in UrlBlocker.
+
+
## 11.3.0 (2018-09-22)
### Security (5 changes, 1 of them is from the community)
@@ -249,6 +261,18 @@ entry.
- Creates Vue component for artifacts block on job page.
+## 11.2.4 (2018-09-26)
+
+### Security (6 changes)
+
+- Redact confidential events in the API.
+- Set timeout for syntax highlighting.
+- Sanitize JSON data properly to fix XSS on Issue details page.
+- Fix stored XSS in merge requests from imported repository.
+- Fix xss vulnerability sourced from package.json.
+- Block loopback addresses in UrlBlocker.
+
+
## 11.2.3 (2018-08-28)
### Fixed (1 change)
@@ -516,6 +540,18 @@ entry.
- Moves help_popover component to a common location.
+## 11.1.7 (2018-09-26)
+
+### Security (6 changes)
+
+- Redact confidential events in the API.
+- Set timeout for syntax highlighting.
+- Sanitize JSON data properly to fix XSS on Issue details page.
+- Fix stored XSS in merge requests from imported repository.
+- Fix xss vulnerability sourced from package.json.
+- Block loopback addresses in UrlBlocker.
+
+
## 11.1.6 (2018-08-28)
### Fixed (1 change)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index ee1e4d2aee5..3ba7bd5ba83 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.122.0
+0.123.0
diff --git a/Gemfile b/Gemfile
index 35e83a530f0..ecf5e6392c0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -112,9 +112,6 @@ gem 'hamlit', '~> 2.8.8'
gem 'carrierwave', '= 1.2.3'
gem 'mini_magick'
-# Drag and Drop UI
-gem 'dropzonejs-rails', '~> 0.7.1'
-
# for backups
gem 'fog-aws', '~> 2.0.1'
gem 'fog-core', '~> 1.44'
@@ -423,7 +420,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.117.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
diff --git a/Gemfile.lock b/Gemfile.lock
index 877a07ea007..69a1c899cc5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -175,8 +175,6 @@ GEM
doorkeeper-openid_connect (1.5.0)
doorkeeper (~> 4.3)
json-jwt (~> 1.6)
- dropzonejs-rails (0.7.2)
- rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.6)
email_spec (2.2.0)
@@ -276,7 +274,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.117.0)
+ gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
@@ -1006,7 +1004,6 @@ DEPENDENCIES
diffy (~> 3.1.0)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5)
- dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
@@ -1031,7 +1028,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.117.0)
+ gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 17fab719008..30605e460e6 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -178,8 +178,6 @@ GEM
doorkeeper-openid_connect (1.5.0)
doorkeeper (~> 4.3)
json-jwt (~> 1.6)
- dropzonejs-rails (0.7.2)
- rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.6)
email_spec (2.2.0)
@@ -279,7 +277,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (0.117.0)
+ gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
@@ -1015,7 +1013,6 @@ DEPENDENCIES
diffy (~> 3.1.0)
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.5)
- dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
@@ -1040,7 +1037,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 0.117.0)
+ gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index d3666cf3980..8b5536200e1 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -149,7 +149,8 @@
<a
:href="issue.path"
:title="issue.title"
- class="js-no-trigger">{{ issue.title }}</a>
+ class="js-no-trigger"
+ @mousemove.stop>{{ issue.title }}</a>
<span
v-if="issueId"
class="board-card-number append-right-5"
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 715dc1bfb42..a04d09ef374 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -50,7 +50,9 @@ export default {
this.stopPipelinePolling();
},
methods: {
- ...mapActions(['setRightPane']),
+ ...mapActions('rightPane', {
+ openRightPane: 'open',
+ }),
...mapActions('pipelines', ['fetchLatestPipeline', 'stopPipelinePolling']),
startTimer() {
this.intervalId = setInterval(() => {
@@ -88,7 +90,7 @@ export default {
<button
type="button"
class="p-0 border-0 h-50"
- @click="setRightPane($options.rightSidebarViews.pipelines)"
+ @click="openRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 75a9a9e9b8f..bd07f372177 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -1,5 +1,6 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
+import _ from 'underscore';
import { __ } from '~/locale';
import tooltip from '../../../vue_shared/directives/tooltip';
import Icon from '../../../vue_shared/components/icon.vue';
@@ -30,14 +31,10 @@ export default {
},
},
computed: {
- ...mapState(['rightPane', 'currentMergeRequestId', 'clientsidePreviewEnabled']),
+ ...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']),
+ ...mapState('rightPane', ['isOpen', 'currentView']),
...mapGetters(['packageJson']),
- pipelinesActive() {
- return (
- this.rightPane === rightSidebarViews.pipelines ||
- this.rightPane === rightSidebarViews.jobsDetail
- );
- },
+ ...mapGetters('rightPane', ['isActiveView', 'isAliveView']),
showLivePreview() {
return this.packageJson && this.clientsidePreviewEnabled;
},
@@ -46,22 +43,26 @@ export default {
{
show: this.currentMergeRequestId,
title: __('Merge Request'),
- isActive: this.rightPane === rightSidebarViews.mergeRequestInfo,
- view: rightSidebarViews.mergeRequestInfo,
+ views: [
+ rightSidebarViews.mergeRequestInfo,
+ ],
icon: 'text-description',
},
{
show: true,
title: __('Pipelines'),
- isActive: this.pipelinesActive,
- view: rightSidebarViews.pipelines,
+ views: [
+ rightSidebarViews.pipelines,
+ rightSidebarViews.jobsDetail,
+ ],
icon: 'rocket',
},
{
show: this.showLivePreview,
title: __('Live preview'),
- isActive: this.rightPane === rightSidebarViews.clientSidePreview,
- view: rightSidebarViews.clientSidePreview,
+ views: [
+ rightSidebarViews.clientSidePreview,
+ ],
icon: 'live-preview',
},
];
@@ -71,13 +72,26 @@ export default {
.concat(this.extensionTabs)
.filter(tab => tab.show);
},
+ tabViews() {
+ return _.flatten(this.tabs.map(tab => tab.views));
+ },
+ aliveTabViews() {
+ return this.tabViews.filter(view => this.isAliveView(view.name));
+ },
},
methods: {
- ...mapActions(['setRightPane']),
- clickTab(e, view) {
+ ...mapActions('rightPane', ['toggleOpen', 'open']),
+ clickTab(e, tab) {
e.target.blur();
- this.setRightPane(view);
+ if (this.isActiveTab(tab)) {
+ this.toggleOpen();
+ } else {
+ this.open(tab.views[0]);
+ }
+ },
+ isActiveTab(tab) {
+ return tab.views.some(view => this.isActiveView(view.name));
},
},
};
@@ -88,15 +102,22 @@ export default {
class="multi-file-commit-panel ide-right-sidebar"
>
<resizable-panel
- v-if="rightPane"
+ v-show="isOpen"
:collapsible="false"
:initial-width="350"
:min-size="350"
- :class="`ide-right-sidebar-${rightPane}`"
+ :class="`ide-right-sidebar-${currentView}`"
side="right"
class="multi-file-commit-panel-inner"
>
- <component :is="rightPane" />
+ <div
+ v-for="tabView in aliveTabViews"
+ v-show="isActiveView(tabView.name)"
+ :key="tabView.name"
+ class="h-100"
+ >
+ <component :is="tabView.name" />
+ </div>
</resizable-panel>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
@@ -109,13 +130,13 @@ export default {
:title="tab.title"
:aria-label="tab.title"
:class="{
- active: tab.isActive
+ active: isActiveTab(tab) && isOpen
}"
data-container="body"
data-placement="left"
class="ide-sidebar-link is-right"
type="button"
- @click="clickTab($event, tab.view)"
+ @click="clickTab($event, tab)"
>
<icon
:size="16"
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index d3a73e84cc7..b2599128213 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -22,12 +22,14 @@ export default {
},
},
computed: {
+ ...mapState('rightPane', {
+ rightPaneIsOpen: 'isOpen',
+ }),
...mapState([
'rightPanelCollapsed',
'viewer',
'panelResizing',
'currentActivityView',
- 'rightPane',
]),
...mapGetters([
'currentMergeRequest',
@@ -99,7 +101,7 @@ export default {
this.editor.updateDimensions();
}
},
- rightPane() {
+ rightPaneIsOpen() {
this.editor.updateDimensions();
},
},
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 8caa5b86a9b..3b201f006aa 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -29,10 +29,10 @@ export const diffModes = {
};
export const rightSidebarViews = {
- pipelines: 'pipelines-list',
- jobsDetail: 'jobs-detail',
- mergeRequestInfo: 'merge-request-info',
- clientSidePreview: 'clientside',
+ pipelines: { name: 'pipelines-list', keepAlive: true },
+ jobsDetail: { name: 'jobs-detail', keepAlive: false },
+ mergeRequestInfo: { name: 'merge-request-info', keepAlive: true },
+ clientSidePreview: { name: 'clientside', keepAlive: false },
};
export const stageKeys = {
diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js
index b8b64aead30..e10a132ab4b 100644
--- a/app/assets/javascripts/ide/stores/actions.js
+++ b/app/assets/javascripts/ide/stores/actions.js
@@ -184,10 +184,6 @@ export const burstUnusedSeal = ({ state, commit }) => {
}
};
-export const setRightPane = ({ commit }, view) => {
- commit(types.SET_RIGHT_PANE, view);
-};
-
export const setLinks = ({ commit }, links) => commit(types.SET_LINKS, links);
export const setErrorMessage = ({ commit }, errorMessage) =>
diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js
index 877d88bb060..f1f544b52b2 100644
--- a/app/assets/javascripts/ide/stores/index.js
+++ b/app/assets/javascripts/ide/stores/index.js
@@ -9,6 +9,7 @@ import pipelines from './modules/pipelines';
import mergeRequests from './modules/merge_requests';
import branches from './modules/branches';
import fileTemplates from './modules/file_templates';
+import paneModule from './modules/pane';
Vue.use(Vuex);
@@ -24,6 +25,7 @@ export const createStore = () =>
mergeRequests,
branches,
fileTemplates: fileTemplates(),
+ rightPane: paneModule(),
},
});
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
index dd53213ed18..cc9f6c8638c 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js
@@ -1,5 +1,6 @@
import Api from '~/api';
import { __ } from '~/locale';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
import eventHub from '../../../eventhub';
@@ -22,13 +23,21 @@ export const receiveTemplateTypesError = ({ commit, dispatch }) => {
export const receiveTemplateTypesSuccess = ({ commit }, templates) =>
commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates);
-export const fetchTemplateTypes = ({ dispatch, state }) => {
+export const fetchTemplateTypes = ({ dispatch, state }, page = 1) => {
if (!Object.keys(state.selectedTemplateType).length) return Promise.reject();
dispatch('requestTemplateTypes');
- return Api.templates(state.selectedTemplateType.key)
- .then(({ data }) => dispatch('receiveTemplateTypesSuccess', data))
+ return Api.templates(state.selectedTemplateType.key, { page })
+ .then(({ data, headers }) => {
+ const nextPage = parseInt(normalizeHeaders(headers)['X-NEXT-PAGE'], 10);
+
+ dispatch('receiveTemplateTypesSuccess', data);
+
+ if (nextPage) {
+ dispatch('fetchTemplateTypes', nextPage);
+ }
+ })
.catch(() => dispatch('receiveTemplateTypesError'));
};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
index 674782a28ca..d519c033769 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -9,7 +9,7 @@ export default {
},
[types.RECEIVE_TEMPLATE_TYPES_SUCCESS](state, templates) {
state.isLoading = false;
- state.templates = templates;
+ state.templates = state.templates.concat(templates);
},
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
state.selectedTemplateType = type;
diff --git a/app/assets/javascripts/ide/stores/modules/pane/actions.js b/app/assets/javascripts/ide/stores/modules/pane/actions.js
new file mode 100644
index 00000000000..7f5d167a14f
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/actions.js
@@ -0,0 +1,30 @@
+import * as types from './mutation_types';
+
+export const toggleOpen = ({ dispatch, state }, view) => {
+ if (state.isOpen) {
+ dispatch('close');
+ } else {
+ dispatch('open', view);
+ }
+};
+
+export const open = ({ commit }, view) => {
+ commit(types.SET_OPEN, true);
+
+ if (view) {
+ const { name, keepAlive } = view;
+
+ commit(types.SET_CURRENT_VIEW, name);
+
+ if (keepAlive) {
+ commit(types.KEEP_ALIVE_VIEW, name);
+ }
+ }
+};
+
+export const close = ({ commit }) => {
+ commit(types.SET_OPEN, false);
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/pane/getters.js b/app/assets/javascripts/ide/stores/modules/pane/getters.js
new file mode 100644
index 00000000000..c346cf13689
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/getters.js
@@ -0,0 +1,4 @@
+export const isActiveView = state => view => state.currentView === view;
+
+export const isAliveView = (state, getters) => view =>
+ state.keepAliveViews[view] || (state.isOpen && getters.isActiveView(view));
diff --git a/app/assets/javascripts/ide/stores/modules/pane/index.js b/app/assets/javascripts/ide/stores/modules/pane/index.js
new file mode 100644
index 00000000000..5f61cb732c8
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/index.js
@@ -0,0 +1,12 @@
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+import state from './state';
+
+export default () => ({
+ namespaced: true,
+ state: state(),
+ actions,
+ getters,
+ mutations,
+});
diff --git a/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js b/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js
new file mode 100644
index 00000000000..abdebc4d913
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_OPEN = 'SET_OPEN';
+export const SET_CURRENT_VIEW = 'SET_CURRENT_VIEW';
+export const KEEP_ALIVE_VIEW = 'KEEP_ALIVE_VIEW';
diff --git a/app/assets/javascripts/ide/stores/modules/pane/mutations.js b/app/assets/javascripts/ide/stores/modules/pane/mutations.js
new file mode 100644
index 00000000000..c16484b4402
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/mutations.js
@@ -0,0 +1,19 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_OPEN](state, isOpen) {
+ Object.assign(state, {
+ isOpen,
+ });
+ },
+ [types.SET_CURRENT_VIEW](state, currentView) {
+ Object.assign(state, {
+ currentView,
+ });
+ },
+ [types.KEEP_ALIVE_VIEW](state, viewName) {
+ Object.assign(state.keepAliveViews, {
+ [viewName]: true,
+ });
+ },
+};
diff --git a/app/assets/javascripts/ide/stores/modules/pane/state.js b/app/assets/javascripts/ide/stores/modules/pane/state.js
new file mode 100644
index 00000000000..353065b5735
--- /dev/null
+++ b/app/assets/javascripts/ide/stores/modules/pane/state.js
@@ -0,0 +1,5 @@
+export default () => ({
+ isOpen: false,
+ currentView: null,
+ keepAliveViews: {},
+});
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
index 3e67b222e66..8fa86995ef0 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
@@ -113,7 +113,7 @@ export const toggleStageCollapsed = ({ commit }, stageId) =>
export const setDetailJob = ({ commit, dispatch }, job) => {
commit(types.SET_DETAIL_JOB, job);
- dispatch('setRightPane', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, {
+ dispatch('rightPane/open', job ? rightSidebarViews.jobsDetail : rightSidebarViews.pipelines, {
root: true,
});
};
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index 5a7991d2fa7..a5f8098dc17 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -68,8 +68,6 @@ export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
export const BURST_UNUSED_SEAL = 'BURST_UNUSED_SEAL';
-export const SET_RIGHT_PANE = 'SET_RIGHT_PANE';
-
export const CLEAR_PROJECTS = 'CLEAR_PROJECTS';
export const RESET_OPEN_FILES = 'RESET_OPEN_FILES';
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 2c8535bda59..78cdfda74f0 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -166,11 +166,6 @@ export default {
unusedSeal: false,
});
},
- [types.SET_RIGHT_PANE](state, view) {
- Object.assign(state, {
- rightPane: state.rightPane === view ? null : view,
- });
- },
[types.SET_LINKS](state, links) {
Object.assign(state, { links });
},
diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js
index 46b52fa00fc..d400b9831a9 100644
--- a/app/assets/javascripts/ide/stores/state.js
+++ b/app/assets/javascripts/ide/stores/state.js
@@ -23,7 +23,6 @@ export default () => ({
currentActivityView: activityBarViews.edit,
unusedSeal: true,
fileFindVisible: false,
- rightPane: null,
links: {},
errorMessage: null,
entryModal: {
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 75dfdedcf1b..d08e8ba0c4b 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,10 +1,11 @@
import Vue from 'vue';
+import sanitize from 'sanitize-html';
import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor';
-document.addEventListener('DOMContentLoaded', () => {
+export default function initIssueableApp() {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
- const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
+ const props = JSON.parse(sanitize(initialDataEl.textContent).replace(/&quot;/g, '"'));
return new Vue({
el: document.getElementById('js-issuable-app'),
@@ -17,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
-});
+}
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index 74b3a515e84..ef65196872c 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -3,9 +3,10 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
-import '~/issue_show/index';
+import initIssueableApp from '~/issue_show';
export default function () {
+ initIssueableApp();
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
index f1ef50d0e3d..a07d63a495d 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue
@@ -1,9 +1,11 @@
<script>
+import { Link } from '@gitlab-org/gitlab-ui';
import Icon from '../../icon.vue';
import { numberToHumanSize } from '../../../../lib/utils/number_utils';
export default {
components: {
+ 'gl-link': Link,
Icon,
},
props: {
@@ -37,7 +39,7 @@ export default {
({{ fileSizeReadable }})
</template>
</p>
- <a
+ <gl-link
:href="path"
class="btn btn-default"
rel="nofollow"
@@ -49,7 +51,7 @@ export default {
css-classes="float-left append-right-8"
/>
{{ __('Download') }}
- </a>
+ </gl-link>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index 08e102e57c3..ee3157bcb1b 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -18,12 +18,14 @@
*/
+import { Link } from '@gitlab-org/gitlab-ui';
import userAvatarImage from './user_avatar_image.vue';
import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarLink',
components: {
+ 'gl-link': Link,
userAvatarImage,
},
directives: {
@@ -83,7 +85,7 @@ export default {
</script>
<template>
- <a
+ <gl-link
:href="linkHref"
class="user-avatar-link">
<user-avatar-image
@@ -99,5 +101,5 @@ export default {
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
>{{ username }}</span>
- </a>
+ </gl-link>
</template>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index f2950308019..ffe65ce780e 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -5,7 +5,6 @@
*= require jquery.atwho
*= require select2
*= require_self
- *= require dropzone/basic
*= require cropper.css
*/
@@ -18,6 +17,7 @@
*/
@import "../../../node_modules/pikaday/scss/pikaday";
+@import "../../../node_modules/dropzone/dist/basic.css";
/*
* GitLab UI framework
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 28dda65091d..3c9505a21d6 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -324,6 +324,16 @@ img.emoji {
word-wrap: break-word;
}
+.checkbox-icon-inline-wrapper {
+ .checkbox {
+ display: inline;
+
+ label {
+ display: inline;
+ }
+ }
+}
+
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-2 { margin-top: 2px; }
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 7d53a631cdf..f10eaedcc04 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -59,7 +59,7 @@
}
@include media-breakpoint-up(sm) {
- .btn:first-of-type {
+ .btn:nth-child(1) {
margin-left: auto;
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b87034d10b6..d7dbc712743 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -109,6 +109,15 @@ class ApplicationController < ActionController::Base
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end
+ def render(*args)
+ super.tap do
+ # Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
+ if response.content_type == 'text/html' && (400..599).cover?(response.status)
+ response.headers['X-GitLab-Custom-Error'] = '1'
+ end
+ end
+ end
+
protected
def append_info_to_payload(payload)
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 86583adc6a4..5639402a1e9 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -106,6 +106,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@commits = set_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit
+ # FIXME: We have to assign a presenter to another instance variable
+ # due to class_name checks being made with issuable classes
+ @mr_presenter = @merge_request.present(current_user: current_user)
+
@labels = LabelsFinder.new(current_user, project_id: @project.id).execute
set_pipeline_variables
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index dfb69de650b..d691744d72a 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -333,6 +333,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@target_project = @merge_request.target_project
@target_branches = @merge_request.target_project.repository.branch_names
@noteable = @merge_request
+
+ # FIXME: We have to assign a presenter to another instance variable
+ # due to class_name checks being made with issuable classes
+ @mr_presenter = @merge_request.present(current_user: current_user)
end
def finder_type
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 7352c5e9bec..a9417369ca2 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -16,6 +16,7 @@ class ProjectsController < Projects::ApplicationController
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
+ before_action :present_project, only: [:edit]
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
@@ -433,4 +434,8 @@ class ProjectsController < Projects::ApplicationController
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
end
+
+ def present_project
+ @project = @project.present(current_user: current_user)
+ end
end
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index fd7aeca0d8b..2e82bda8730 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -12,6 +12,7 @@ class EventsFinder
# Arguments:
# source - which user or project to looks for events on
# current_user - only return events for projects visible to this user
+ # WARNING: does not consider project feature visibility!
# params:
# action: string
# target_type: string
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
index 18cc6891ca4..4d8128dd824 100644
--- a/app/finders/joined_groups_finder.rb
+++ b/app/finders/joined_groups_finder.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class JoinedGroupsFinder < UnionFinder
+class JoinedGroupsFinder
def initialize(user)
@user = user
end
@@ -8,19 +8,8 @@ class JoinedGroupsFinder < UnionFinder
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
def execute(current_user = nil)
- segments = all_groups(current_user)
-
- find_union(segments, Group).order_id_desc
- end
-
- private
-
- def all_groups(current_user)
- groups = []
-
- groups << @user.authorized_groups.visible_to_user(current_user) if current_user
- groups << @user.authorized_groups.public_to_user(current_user)
-
- groups
+ @user.authorized_groups
+ .public_or_visible_to_user(current_user)
+ .order_id_desc
end
end
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index a4daf5b5841..eeca5026da1 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -3,6 +3,7 @@
# Get user activity feed for projects common for a user and a logged in user
#
# - current_user: The user viewing the events
+# WARNING: does not consider project feature visibility!
# - user: The user for which to load the events
# - params:
# - offset: The page of events to return
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 321811a3ca3..7fc4c1a023f 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module AvatarsHelper
- def project_icon(project_id, options = {})
- source_icon(Project, project_id, options)
+ def project_icon(project, options = {})
+ source_icon(project, options)
end
- def group_icon(group_id, options = {})
- source_icon(Group, group_id, options)
+ def group_icon(group, options = {})
+ source_icon(group, options)
end
# Takes both user and email and returns the avatar_icon by
@@ -110,16 +110,11 @@ module AvatarsHelper
private
- def source_icon(klass, source_id, options = {})
- source =
- if source_id.respond_to?(:avatar_url)
- source_id
- else
- klass.find_by_full_path(source_id)
- end
+ def source_icon(source, options = {})
+ avatar_url = source.try(:avatar_url)
- if source.avatar_url
- image_tag source.avatar_url, options
+ if avatar_url
+ image_tag avatar_url, options
else
source_identicon(source, options)
end
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 67af0a4eb98..be085496731 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -3,13 +3,14 @@
module Emails
module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
- setup_merge_request_mail(merge_request_id, recipient_id)
+ setup_merge_request_mail(merge_request_id, recipient_id, present: true)
mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason))
end
def new_mention_in_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
- setup_merge_request_mail(merge_request_id, recipient_id)
+ setup_merge_request_mail(merge_request_id, recipient_id, present: true)
+
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
@@ -75,11 +76,16 @@ module Emails
private
- def setup_merge_request_mail(merge_request_id, recipient_id)
+ def setup_merge_request_mail(merge_request_id, recipient_id, present: false)
@merge_request = MergeRequest.find(merge_request_id)
@project = @merge_request.project
@target_url = project_merge_request_url(@project, @merge_request)
+ if present
+ recipient = User.find(recipient_id)
+ @mr_presenter = @merge_request.present(current_user: recipient)
+ end
+
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end
diff --git a/app/models/blob_viewer/package_json.rb b/app/models/blob_viewer/package_json.rb
index d12dd93ce2e..7cae60a74d6 100644
--- a/app/models/blob_viewer/package_json.rb
+++ b/app/models/blob_viewer/package_json.rb
@@ -33,7 +33,8 @@ module BlobViewer
end
def homepage
- json_data['homepage']
+ url = json_data['homepage']
+ url if Gitlab::UrlSanitizer.valid?(url)
end
def npm_url
diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb
index 017ec0b145a..08514d6af4e 100644
--- a/app/models/ci/pipeline_variable.rb
+++ b/app/models/ci/pipeline_variable.rb
@@ -10,5 +10,9 @@ module Ci
alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :pipeline_id }
+
+ def hook_attrs
+ { key: key, value: value }
+ end
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index 596155a9525..2e690f8c013 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -148,6 +148,8 @@ class Event < ActiveRecord::Base
end
end
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil)
if push? || commit_note?
Ability.allowed?(user, :download_code, project)
@@ -159,12 +161,18 @@ class Event < ActiveRecord::Base
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
+ elsif personal_snippet_note?
+ Ability.allowed?(user, :read_personal_snippet, note_target)
+ elsif project_snippet_note?
+ Ability.allowed?(user, :read_project_snippet, note_target)
elsif milestone?
- Ability.allowed?(user, :read_project, project)
+ Ability.allowed?(user, :read_milestone, project)
else
false # No other event types are visible
end
end
+ # rubocop:enable Metrics/PerceivedComplexity
+ # rubocop:enable Metrics/CyclomaticComplexity
def project_name
if project
@@ -306,6 +314,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_snippet?
end
+ def personal_snippet_note?
+ note? && target && target.for_personal_snippet?
+ end
+
def note_target
target.noteable
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 62af20d2142..612c546ca57 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -82,8 +82,17 @@ class Group < Namespace
User.reference_pattern
end
- def visible_to_user(user)
- where(id: user.authorized_groups.select(:id).reorder(nil))
+ # WARNING: This method should never be used on its own
+ # please do make sure the number of rows you are filtering is small
+ # enough for this query
+ def public_or_visible_to_user(user)
+ return public_to_user unless user
+
+ public_for_user = public_to_user_arel(user)
+ visible_for_user = visible_to_user_arel(user)
+ public_or_visible = public_for_user.or(visible_for_user)
+
+ where(public_or_visible)
end
def select_for_project_authorization
@@ -95,6 +104,23 @@ class Group < Namespace
super
end
end
+
+ private
+
+ def public_to_user_arel(user)
+ self.arel_table[:visibility_level]
+ .in(Gitlab::VisibilityLevel.levels_for_user(user))
+ end
+
+ def visible_to_user_arel(user)
+ groups_table = self.arel_table
+ authorized_groups = user.authorized_groups.as('authorized')
+
+ groups_table.project(1)
+ .from(authorized_groups)
+ .where(authorized_groups[:id].eq(groups_table[:id]))
+ .exists
+ end
end
# Overrides notification_settings has_many association
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 771a61b090f..68ba4b213b2 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -3,6 +3,16 @@
class WebHook < ActiveRecord::Base
include Sortable
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_truncated
+
+ attr_encrypted :url,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_truncated
+
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?),
@@ -27,4 +37,38 @@ class WebHook < ActiveRecord::Base
def allow_local_requests?
false
end
+
+ # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields.
+ # Ensure that the encrypted version always takes precedence if present.
+ alias_method :attr_encrypted_token, :token
+ def token
+ attr_encrypted_token.presence || read_attribute(:token)
+ end
+
+ # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields.
+ # Pending a background migration to encrypt all fields, we should just clear
+ # the unencrypted value whenever the new value is set.
+ alias_method :'attr_encrypted_token=', :'token='
+ def token=(value)
+ self.attr_encrypted_token = value
+
+ write_attribute(:token, nil)
+ end
+
+ # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields.
+ # Ensure that the encrypted version always takes precedence if present.
+ alias_method :attr_encrypted_url, :url
+ def url
+ attr_encrypted_url.presence || read_attribute(:url)
+ end
+
+ # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields.
+ # Pending a background migration to encrypt all fields, we should just clear
+ # the unencrypted value whenever the new value is set.
+ alias_method :'attr_encrypted_url=', :'url='
+ def url=(value)
+ self.attr_encrypted_url = value
+
+ write_attribute(:url, nil)
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index dd5d494997d..0481a4a3d28 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base
include Issuable
include Noteable
include Referable
+ include Presentable
include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 12fbf7d5d1d..a3a3ce179fc 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -510,7 +510,7 @@ class Repository
raw_repository.exists?
end
- cache_method :exists?
+ cache_method_asymmetrically :exists?
# We don't need to cache the output of this method because both exists? and
# has_visible_content? are already memoized and cached. There's no guarantee
@@ -612,7 +612,7 @@ class Repository
Licensee::License.new(license_key)
end
- cache_method :license, memoize_only: true
+ memoize_method :license
def gitignore
file_on_head(:gitignore)
@@ -668,6 +668,14 @@ class Repository
end
end
+ def list_last_commits_for_tree(sha, path, offset: 0, limit: 25)
+ commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
+
+ commits.each do |path, commit|
+ commits[path] = ::Commit.new(commit, @project)
+ end
+ end
+
def last_commit_for_path(sha, path)
commit = raw_repository.last_commit_for_path(sha, path)
::Commit.new(commit, @project) if commit
@@ -1029,6 +1037,10 @@ class Repository
@cache ||= Gitlab::RepositoryCache.new(self)
end
+ def request_store_cache
+ @request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
+ end
+
def tags_sorted_by_committed_date
tags.sort_by do |tag|
# Annotated tags can point to any object (e.g. a blob), but generally
diff --git a/app/models/user.rb b/app/models/user.rb
index eeac87e2e52..cd3b1c95b7e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -674,10 +674,12 @@ class User < ActiveRecord::Base
# Returns the groups a user has access to, either through a membership or a project authorization
def authorized_groups
- Group.from_union([
- groups,
- authorized_projects.joins(:namespace).select('namespaces.*')
- ])
+ Group.unscoped do
+ Group.from_union([
+ groups,
+ authorized_projects.joins(:namespace).select('namespaces.*')
+ ])
+ end
end
# Returns the groups a user is a member of, either directly or through a parent group
diff --git a/app/serializers/diff_line_entity.rb b/app/serializers/diff_line_entity.rb
index 2119a1017d3..942714b7787 100644
--- a/app/serializers/diff_line_entity.rb
+++ b/app/serializers/diff_line_entity.rb
@@ -9,6 +9,6 @@ class DiffLineEntity < Grape::Entity
expose :meta_positions, as: :meta_data
expose :rich_text do |line|
- line.rich_text || CGI.escapeHTML(line.text)
+ ERB::Util.html_escape(line.rich_text || line.text)
end
end
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index 35f5cff0e0c..5017fa093f3 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -14,8 +14,8 @@ module Clusters
else
check_timeout
end
- rescue Kubeclient::HttpError => ke
- app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored?
+ rescue Kubeclient::HttpError
+ app.make_errored!("Kubernetes error") unless app.errored?
end
private
@@ -27,7 +27,7 @@ module Clusters
end
def on_failed
- app.make_errored!(installation_errors || 'Installation silently failed')
+ app.make_errored!('Installation failed')
ensure
remove_installation_pod
end
diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb
index 7e3c0e77a83..dd8d2ed5eb6 100644
--- a/app/services/clusters/applications/install_service.rb
+++ b/app/services/clusters/applications/install_service.rb
@@ -12,10 +12,10 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
- rescue Kubeclient::HttpError => ke
- app.make_errored!("Kubernetes error: #{ke.message}")
- rescue StandardError => e
- app.make_errored!("Can't start installation process. #{e.message}")
+ rescue Kubeclient::HttpError
+ app.make_errored!("Kubernetes error.")
+ rescue StandardError
+ app.make_errored!("Can't start installation process.")
end
end
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 9417c63c43a..de6ff92d1da 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -55,7 +55,6 @@ module Users
:force_random_password,
:hide_no_password,
:hide_no_ssh_key,
- :key_id,
:linkedin,
:name,
:password,
@@ -69,7 +68,10 @@ module Users
:twitter,
:username,
:website_url,
- :private_profile
+ :private_profile,
+ :organization,
+ :location,
+ :public_email
]
end
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 7a66bac09cb..198c2d35b29 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -7,8 +7,7 @@
GitLab
Community Edition
- if user_signed_in?
- %span= Gitlab::VERSION
- %small= link_to Gitlab.revision, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab.revision)
+ %span= link_to Gitlab::VERSION, Gitlab::COM_URL + namespace_project_tag_path('gitlab-org', 'gitlab-ce', "v#{Gitlab::VERSION}")
= version_status_badge
%hr
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index dd6a84e503d..5acd45b74a7 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -9,7 +9,7 @@
%p
Assignee: #{@merge_request.assignee_name}
-= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request
+= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
- if @merge_request.description
%div
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index d5b8f8d764f..754f4bca1cd 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -5,6 +5,6 @@ New Merge Request <%= @merge_request.to_reference %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %>
-<%= render_if_exists 'notify/merge_request_approvers', merge_request: @merge_request %>
+<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
<%= @merge_request.description %>
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 51f5ecf2166..ea215e3e718 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -109,10 +109,11 @@
= f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr
%h5= ("Private profile")
- - private_profile_label = capture do
- = s_("Profiles|Don't display activity-related personal information on your profiles")
+ .checkbox-icon-inline-wrapper
+ - private_profile_label = capture do
+ = s_("Profiles|Don't display activity-related personal information on your profiles")
+ = f.check_box :private_profile, label: private_profile_label
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
- = f.check_box :private_profile, label: private_profile_label
%h5= s_("Profiles|Private contributions")
= f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
.help-block
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index bfd165d8ba5..07fc9e1c682 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -47,7 +47,7 @@
.form-group
- if @project.avatar?
.avatar-container.s160.append-bottom-15
- = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160', width: 160, height: 160)
+ = project_icon(@project, alt: '', class: 'avatar project-avatar s160', width: 160, height: 160)
- if @project.avatar_in_git
%p.light
= _("Project avatar in repository: %{link}").html_safe % { link: @project.avatar_in_git }
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index d104608b2fe..75f35360e5e 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -9,7 +9,7 @@
.project-empty-note-panel
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
.prepend-top-20
- %h4
+ %h4.append-bottom-20
= _('The repository for this project is empty')
- if @project.can_current_user_push_code?
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 12cf40bb65f..a69146513d8 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,7 +5,7 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_icon(namespace, class: "avatar s100 identicon")
+ = group_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
@@ -18,7 +18,7 @@
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = project_icon(namespace, class: "avatar s100 identicon")
+ = group_icon(namespace, class: "avatar s100 identicon")
- else
.avatar-container.s100
= image_tag(avatar, class: "avatar s100")
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 5a59f956cb5..13b967beba1 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,4 +1,4 @@
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request],
html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' },
data: { markdown_version: @merge_request.cached_markdown_version } do |f|
- = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request, presenter: @mr_presenter
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index d5c4134dee2..464f8fa65e9 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -11,7 +11,7 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f|
- = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits
+ = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
= f.hidden_field :target_project_id
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 5b28a43a361..b33c758b464 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,6 +1,7 @@
- form = local_assigns.fetch(:f)
- commits = local_assigns[:commits]
- project = @target_project || @project
+- presenter = local_assigns.fetch(:presenter, nil)
= form_errors(issuable)
@@ -29,7 +30,7 @@
= render 'shared/issuable/form/metadata', issuable: issuable, form: form
-= render_if_exists 'shared/issuable/approvals', issuable: issuable, form: form
+= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index af29c0fe59e..2682d92fc56 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -22,7 +22,7 @@
%strong Blocked
- if user.two_factor_enabled?
- %label.label.label-info
+ %label.badge.badge-info
2FA
- if source.instance_of?(Group) && source != @group
diff --git a/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml
new file mode 100644
index 00000000000..27c70318b92
--- /dev/null
+++ b/changelogs/unreleased/-48445-Issue-card-in-board-view-is-too-sensitive-to-drag-event.yml
@@ -0,0 +1,5 @@
+---
+title: Fix link handling for issue cards to avoid too sensitive drag events.
+merge_request: 21910
+author: Johann Hubert Sonntagbauer
+type: fixed
diff --git a/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml
new file mode 100644
index 00000000000..a5ffc197a0c
--- /dev/null
+++ b/changelogs/unreleased/23197-add-custom-header-for-error-responses.yml
@@ -0,0 +1,6 @@
+---
+title: Set a header for custom error pages to prevent them from being intercepted
+ by gitlab-workhorse
+merge_request: 21870
+author: David Piegza
+type: fixed
diff --git a/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml
new file mode 100644
index 00000000000..04662a7cfe2
--- /dev/null
+++ b/changelogs/unreleased/37433-solve-n-1-in-refs-controller-logs-tree.yml
@@ -0,0 +1,6 @@
+---
+title: Adds support for Gitaly ListLastCommitsForTree RPC in order to make bulk-fetch
+ of commits more performant
+merge_request: 21921
+author:
+type: performance
diff --git a/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml
new file mode 100644
index 00000000000..64776abdc07
--- /dev/null
+++ b/changelogs/unreleased/45453-fix-delete-protected-branch-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes modal button alignment
+merge_request: 22024
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml b/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml
new file mode 100644
index 00000000000..042731fb9be
--- /dev/null
+++ b/changelogs/unreleased/48399-skip-auto-devops-jobs-based-on-license.yml
@@ -0,0 +1,6 @@
+---
+title: Skip creating auto devops jobs for sast, container_scanning, dast, dependency_scanning
+ when not licensed
+merge_request: 21959
+author:
+type: performance
diff --git a/changelogs/unreleased/51021-more-attr-encrypted.yml b/changelogs/unreleased/51021-more-attr-encrypted.yml
new file mode 100644
index 00000000000..0e18c59f1bb
--- /dev/null
+++ b/changelogs/unreleased/51021-more-attr-encrypted.yml
@@ -0,0 +1,5 @@
+---
+title: Encrypt webhook tokens and URLs in the database
+merge_request: 21645
+author:
+type: security
diff --git a/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml b/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml
new file mode 100644
index 00000000000..da02a91041b
--- /dev/null
+++ b/changelogs/unreleased/51404-update-groups-and-projects-api-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Docs for Project/Groups members API with inherited members
+merge_request: 21984
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml
new file mode 100644
index 00000000000..d4e4503508d
--- /dev/null
+++ b/changelogs/unreleased/51476-private-profile-help-url-should-not-toggle-checkbox.yml
@@ -0,0 +1,5 @@
+---
+title: Prevents private profile help link from toggling checkbox
+merge_request: 21757
+author:
+type: other
diff --git a/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml b/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml
new file mode 100644
index 00000000000..6720430e5fc
--- /dev/null
+++ b/changelogs/unreleased/51651-fill-pipeline-source-for-external-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Retroactively fill pipeline source for external pipelines.
+merge_request: 21814
+author:
+type: other
diff --git a/changelogs/unreleased/add-gl-link-to-download-viewer.yml b/changelogs/unreleased/add-gl-link-to-download-viewer.yml
new file mode 100644
index 00000000000..ce3d916f045
--- /dev/null
+++ b/changelogs/unreleased/add-gl-link-to-download-viewer.yml
@@ -0,0 +1,5 @@
+---
+title: Add link component to DownloadViewer component
+merge_request: 21987
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml
new file mode 100644
index 00000000000..ef87ef541dd
--- /dev/null
+++ b/changelogs/unreleased/add-gl-link-to-user-avatar-link.yml
@@ -0,0 +1,5 @@
+---
+title: Add link component to UserAvatarLink component
+merge_request: 21986
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/bvl-remove-sha-from-help.yml b/changelogs/unreleased/bvl-remove-sha-from-help.yml
new file mode 100644
index 00000000000..37f797f98f4
--- /dev/null
+++ b/changelogs/unreleased/bvl-remove-sha-from-help.yml
@@ -0,0 +1,5 @@
+---
+title: Link to the tag for a version on the help page instead of to the commit
+merge_request: 22015
+author:
+type: changed
diff --git a/changelogs/unreleased/feature-set-public-email-through-api.yml b/changelogs/unreleased/feature-set-public-email-through-api.yml
new file mode 100644
index 00000000000..22fae71e9d8
--- /dev/null
+++ b/changelogs/unreleased/feature-set-public-email-through-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for setting the public email through the api
+merge_request: 21938
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml
new file mode 100644
index 00000000000..4d85e1b9af2
--- /dev/null
+++ b/changelogs/unreleased/fix-add-organization-and-location-to-allowed-parameters.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting user's organization and location attributes through the API by adding them to the list of allowed parameters
+merge_request: 21938
+author: Alexis Reigel
+type: fixed
diff --git a/changelogs/unreleased/fix-events-finder-incomplete.yml b/changelogs/unreleased/fix-events-finder-incomplete.yml
new file mode 100644
index 00000000000..f3a4e421d33
--- /dev/null
+++ b/changelogs/unreleased/fix-events-finder-incomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Redact confidential events in the API
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/frozen-string-app-enforce.yml b/changelogs/unreleased/frozen-string-app-enforce.yml
new file mode 100644
index 00000000000..44686557c45
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-app-enforce.yml
@@ -0,0 +1,5 @@
+---
+title: Check frozen string in style builds
+merge_request:
+author: gfyoung
+type: other
diff --git a/changelogs/unreleased/frozen-string-docs.yml b/changelogs/unreleased/frozen-string-docs.yml
new file mode 100644
index 00000000000..2eb8a446ee8
--- /dev/null
+++ b/changelogs/unreleased/frozen-string-docs.yml
@@ -0,0 +1,5 @@
+---
+title: Update docs regarding frozen string
+merge_request:
+author: gfyoung
+type: other
diff --git a/changelogs/unreleased/ide-fetch-templates-pages.yml b/changelogs/unreleased/ide-fetch-templates-pages.yml
new file mode 100644
index 00000000000..d4703e530f2
--- /dev/null
+++ b/changelogs/unreleased/ide-fetch-templates-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed file templates not fully being fetched in Web IDE
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/improve-empty-project-placeholder.yml b/changelogs/unreleased/improve-empty-project-placeholder.yml
new file mode 100644
index 00000000000..11fe21e7710
--- /dev/null
+++ b/changelogs/unreleased/improve-empty-project-placeholder.yml
@@ -0,0 +1,5 @@
+---
+title: Improve empty project placeholder for non-members and members without write access
+merge_request: 21977
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/lib-api-frozen-string-enable.yml b/changelogs/unreleased/lib-api-frozen-string-enable.yml
new file mode 100644
index 00000000000..eb59f0bc2d1
--- /dev/null
+++ b/changelogs/unreleased/lib-api-frozen-string-enable.yml
@@ -0,0 +1,5 @@
+---
+title: Enable frozen string in lib/api and lib/backup
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/mk-asymmetric-exists-cache.yml b/changelogs/unreleased/mk-asymmetric-exists-cache.yml
new file mode 100644
index 00000000000..b6eec7d1fc6
--- /dev/null
+++ b/changelogs/unreleased/mk-asymmetric-exists-cache.yml
@@ -0,0 +1,6 @@
+---
+title: 'Resolve "Geo: Does not mark repositories as missing on primary due to stale
+ cache"'
+merge_request: 21789
+author:
+type: fixed
diff --git a/changelogs/unreleased/pipeline-event-variables.yml b/changelogs/unreleased/pipeline-event-variables.yml
new file mode 100644
index 00000000000..90fd964efd5
--- /dev/null
+++ b/changelogs/unreleased/pipeline-event-variables.yml
@@ -0,0 +1,5 @@
+---
+title: pipeline webhook event now contain pipeline variables
+merge_request: 18171
+author: Pierre Tardy
+type: added
diff --git a/changelogs/unreleased/rename-local-variable.yml b/changelogs/unreleased/rename-local-variable.yml
new file mode 100644
index 00000000000..70281dfef08
--- /dev/null
+++ b/changelogs/unreleased/rename-local-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Rename block scope local variable in table pagination spec
+merge_request: 21969
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/security-2697-code-highlight-timeout.yml b/changelogs/unreleased/security-2697-code-highlight-timeout.yml
new file mode 100644
index 00000000000..66ad9ff822b
--- /dev/null
+++ b/changelogs/unreleased/security-2697-code-highlight-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Set timeout for syntax highlighting
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-acet-issue-details.yml b/changelogs/unreleased/security-acet-issue-details.yml
new file mode 100644
index 00000000000..64147a9d6e8
--- /dev/null
+++ b/changelogs/unreleased/security-acet-issue-details.yml
@@ -0,0 +1,5 @@
+---
+title: Sanitize JSON data properly to fix XSS on Issue details page
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml
new file mode 100644
index 00000000000..7520aa624c7
--- /dev/null
+++ b/changelogs/unreleased/security-fj-stored-xss-in-repository-imports.yml
@@ -0,0 +1,5 @@
+---
+title: Fix stored XSS in merge requests from imported repository
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-package-json-xss.yml b/changelogs/unreleased/security-package-json-xss.yml
new file mode 100644
index 00000000000..6ab4854e44f
--- /dev/null
+++ b/changelogs/unreleased/security-package-json-xss.yml
@@ -0,0 +1,5 @@
+---
+title: Fix xss vulnerability sourced from package.json
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml b/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml
new file mode 100644
index 00000000000..f18e6207b87
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-forks-with-no-gravatar.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Error 500 when forking projects with Gravatar disabled
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-52009.yml b/changelogs/unreleased/sh-fix-issue-52009.yml
new file mode 100644
index 00000000000..fc22a58a66a
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-52009.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent Error 500s with invalid relative links
+merge_request: 22001
+author:
+type: fixed
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
index 12a0770a455..07b06629dea 100644
--- a/config/initializers/postgresql_opclasses_support.rb
+++ b/config/initializers/postgresql_opclasses_support.rb
@@ -208,5 +208,9 @@ module ActiveRecord
index_parts << "comment: #{index.comment.inspect}" if Gitlab.rails5? && index.comment
index_parts
end
+
+ def format_options(options)
+ options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
+ end
end
end
diff --git a/db/migrate/20170222111732_create_gpg_keys.rb b/db/migrate/20170222111732_create_gpg_keys.rb
index 541228e8735..0d6d454bbf3 100644
--- a/db/migrate/20170222111732_create_gpg_keys.rb
+++ b/db/migrate/20170222111732_create_gpg_keys.rb
@@ -1,4 +1,6 @@
class CreateGpgKeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
DOWNTIME = false
def change
@@ -12,8 +14,8 @@ class CreateGpgKeys < ActiveRecord::Migration
t.text :key
- t.index :primary_keyid, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
- t.index :fingerprint, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
+ t.index :primary_keyid, unique: true, length: mysql_compatible_index_length
+ t.index :fingerprint, unique: true, length: mysql_compatible_index_length
end
end
end
diff --git a/db/migrate/20170613154149_create_gpg_signatures.rb b/db/migrate/20170613154149_create_gpg_signatures.rb
index f6b5e7ebb7b..abef13a7a0b 100644
--- a/db/migrate/20170613154149_create_gpg_signatures.rb
+++ b/db/migrate/20170613154149_create_gpg_signatures.rb
@@ -1,4 +1,6 @@
class CreateGpgSignatures < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
DOWNTIME = false
def change
@@ -16,8 +18,8 @@ class CreateGpgSignatures < ActiveRecord::Migration
t.text :gpg_key_user_name
t.text :gpg_key_user_email
- t.index :commit_sha, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
- t.index :gpg_key_primary_keyid, length: Gitlab::Database.mysql? ? 20 : nil
+ t.index :commit_sha, unique: true, length: mysql_compatible_index_length
+ t.index :gpg_key_primary_keyid, length: mysql_compatible_index_length
end
end
end
diff --git a/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb b/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb
index 1b360b231a8..2140ff7b05e 100644
--- a/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb
+++ b/db/migrate/20170827123848_add_index_on_merge_request_diff_commit_sha.rb
@@ -8,7 +8,7 @@ class AddIndexOnMergeRequestDiffCommitSha < ActiveRecord::Migration
disable_ddl_transaction!
def up
- add_concurrent_index :merge_request_diff_commits, :sha, length: Gitlab::Database.mysql? ? 20 : nil
+ add_concurrent_index :merge_request_diff_commits, :sha, length: mysql_compatible_index_length
end
def down
diff --git a/db/migrate/20170927161718_create_gpg_key_subkeys.rb b/db/migrate/20170927161718_create_gpg_key_subkeys.rb
index c03c40416a8..d9dc2404cac 100644
--- a/db/migrate/20170927161718_create_gpg_key_subkeys.rb
+++ b/db/migrate/20170927161718_create_gpg_key_subkeys.rb
@@ -1,4 +1,6 @@
class CreateGpgKeySubkeys < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
DOWNTIME = false
def up
@@ -8,8 +10,8 @@ class CreateGpgKeySubkeys < ActiveRecord::Migration
t.binary :keyid
t.binary :fingerprint
- t.index :keyid, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
- t.index :fingerprint, unique: true, length: Gitlab::Database.mysql? ? 20 : nil
+ t.index :keyid, unique: true, length: mysql_compatible_index_length
+ t.index :fingerprint, unique: true, length: mysql_compatible_index_length
end
add_reference :gpg_signatures, :gpg_key_subkey, index: true, foreign_key: { on_delete: :nullify }
diff --git a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
new file mode 100644
index 00000000000..72f5c8d653b
--- /dev/null
+++ b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddAttrEncryptedColumnsToWebHook < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :web_hooks, :encrypted_token, :string
+ add_column :web_hooks, :encrypted_token_iv, :string
+
+ add_column :web_hooks, :encrypted_url, :string
+ add_column :web_hooks, :encrypted_url_iv, :string
+ end
+end
diff --git a/db/migrate/20180916011959_add_index_pipelines_project_id_source.rb b/db/migrate/20180916011959_add_index_pipelines_project_id_source.rb
new file mode 100644
index 00000000000..b9bebf30cf0
--- /dev/null
+++ b/db/migrate/20180916011959_add_index_pipelines_project_id_source.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexPipelinesProjectIdSource < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, [:project_id, :source]
+ end
+
+ def down
+ remove_concurrent_index :ci_pipelines, [:project_id, :source]
+ end
+end
diff --git a/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb b/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb
new file mode 100644
index 00000000000..05ec4864a9e
--- /dev/null
+++ b/db/post_migrate/20180914162043_encrypt_web_hooks_columns.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class EncryptWebHooksColumns < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ BATCH_SIZE = 10000
+ RANGE_SIZE = 100
+ MIGRATION = 'EncryptColumns'
+ COLUMNS = [:token, :url]
+
+ WebHook = ::Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook
+
+ disable_ddl_transaction!
+
+ def up
+ WebHook.each_batch(of: BATCH_SIZE) do |relation, index|
+ delay = index * 2.minutes
+
+ relation.each_batch(of: RANGE_SIZE) do |relation|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+ args = [WebHook, COLUMNS, *range]
+
+ BackgroundMigrationWorker.perform_in(delay, MIGRATION, args)
+ end
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/post_migrate/20180916014356_populate_external_pipeline_source.rb b/db/post_migrate/20180916014356_populate_external_pipeline_source.rb
new file mode 100644
index 00000000000..5577d05cf40
--- /dev/null
+++ b/db/post_migrate/20180916014356_populate_external_pipeline_source.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class PopulateExternalPipelineSource < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+ MIGRATION = 'PopulateExternalPipelineSource'.freeze
+ BATCH_SIZE = 500
+
+ disable_ddl_transaction!
+
+ class Pipeline < ActiveRecord::Base
+ include EachBatch
+ self.table_name = 'ci_pipelines'
+ end
+
+ def up
+ Pipeline.where(source: nil).tap do |relation|
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ MIGRATION,
+ 5.minutes,
+ batch_size: BATCH_SIZE)
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f92d8005dfb..13c6d65255e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -475,6 +475,7 @@ ActiveRecord::Schema.define(version: 20180917172041) do
add_index "ci_pipelines", ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
add_index "ci_pipelines", ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
add_index "ci_pipelines", ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
+ add_index "ci_pipelines", ["project_id", "source"], name: "index_ci_pipelines_on_project_id_and_source", using: :btree
add_index "ci_pipelines", ["project_id", "status", "config_source"], name: "index_ci_pipelines_on_project_id_and_status_and_config_source", using: :btree
add_index "ci_pipelines", ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree
add_index "ci_pipelines", ["status"], name: "index_ci_pipelines_on_status", using: :btree
@@ -2271,6 +2272,10 @@ ActiveRecord::Schema.define(version: 20180917172041) do
t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events"
t.text "push_events_branch_filter"
+ t.string "encrypted_token"
+ t.string "encrypted_token_iv"
+ t.string "encrypted_url"
+ t.string "encrypted_url_iv"
end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
diff --git a/doc/api/members.md b/doc/api/members.md
index 7b228b92594..bb4fae35f52 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -15,6 +15,7 @@ The access levels are defined in the `Gitlab::Access` module. Currently, these l
## List all members of a group or project
Gets a list of group or project members viewable by the authenticated user.
+Returns only direct members and not inherited members through ancestors groups.
```
GET /groups/:id/members
@@ -61,6 +62,7 @@ Example response:
## List all members of a group or project including inherited members
Gets a list of group or project members viewable by the authenticated user, including inherited members through ancestor groups.
+Returns multiple times the same user (with different member attributes) when the user is a member of the project/group and of one or more ancestor group.
```
GET /groups/:id/members/all
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 49fb9bc141d..0623a6b02ae 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -90,6 +90,8 @@ Like [Get file from repository](repository_files.md#get-file-from-repository) yo
## Create new file in repository
+This allows you to create a single file. For creating multiple files with a single request see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions).
+
```
POST /projects/:id/repository/files/:file_path
```
@@ -120,6 +122,8 @@ Parameters:
## Update existing file in repository
+This allows you to update a single file. For updating multiple files with a single request see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions).
+
```
PUT /projects/:id/repository/files/:file_path
```
@@ -160,6 +164,8 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify
## Delete existing file in repository
+This allows you to delete a single file. For deleting multiple files with a singleh request see the [commits API](commits.html#create-a-commit-with-multiple-files-and-actions).
+
```
DELETE /projects/:id/repository/files/:file_path
```
diff --git a/doc/api/users.md b/doc/api/users.md
index 762ea53edee..3b41e0f7ec6 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -288,6 +288,7 @@ Parameters:
- `provider` (optional) - External provider name
- `bio` (optional) - User's biography
- `location` (optional) - User's location
+- `public_email` (optional) - The public email of the user
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `skip_confirmation` (optional) - Skip confirmation - true or false (default)
@@ -305,26 +306,27 @@ PUT /users/:id
Parameters:
-- `email` - Email
-- `username` - Username
-- `name` - Name
-- `password` - Password
-- `skype` - Skype ID
-- `linkedin` - LinkedIn
-- `twitter` - Twitter account
-- `website_url` - Website URL
-- `organization` - Organization name
-- `projects_limit` - Limit projects each user can create
-- `extern_uid` - External UID
-- `provider` - External provider name
-- `bio` - User's biography
-- `location` (optional) - User's location
-- `admin` (optional) - User is admin - true or false (default)
-- `can_create_group` (optional) - User can create groups - true or false
+- `email` - Email
+- `username` - Username
+- `name` - Name
+- `password` - Password
+- `skype` - Skype ID
+- `linkedin` - LinkedIn
+- `twitter` - Twitter account
+- `website_url` - Website URL
+- `organization` - Organization name
+- `projects_limit` - Limit projects each user can create
+- `extern_uid` - External UID
+- `provider` - External provider name
+- `bio` - User's biography
+- `location` (optional) - User's location
+- `public_email` (optional) - The public email of the user
+- `admin` (optional) - User is admin - true or false (default)
+- `can_create_group` (optional) - User can create groups - true or false
- `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default)
-- `external` (optional) - Flags the user as external - true or false(default)
-- `avatar` (optional) - Image file for user's avatar
-- `private_profile` (optional) - User's profile is private - true or false
+- `external` (optional) - Flags the user as external - true or false(default)
+- `avatar` (optional) - Image file for user's avatar
+- `private_profile` (optional) - User's profile is private - true or false
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 7990917f809..df83f30fbb7 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -17,7 +17,7 @@ Two things need to be configured for the interactive web terminal to work:
- The Runner needs to have [`[session_server]` configured
properly][session-server]
-- Web terminals need to be
+- If you are using a reverse proxy with your GitLab instance, web terminals need to be
[enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support)
## Debugging a running job
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 7ba8e3dce95..fed29d37b26 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -177,7 +177,7 @@ If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
as soon as possible. The product manager will then pull in appropriate GitLab team
members to further discuss scope, design, and technical considerations. This will
-ensure that that your contribution is aligned with the GitLab product and minimize
+ensure that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master.
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
@@ -226,7 +226,7 @@ code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
-Please submit Feature Proposals using the ['Feature Proposal' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Feature%20Proposal.md) provided on the issue tracker.
+Please submit Feature Proposals using the ['Feature Proposal' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Feature%20proposal.md) provided on the issue tracker.
For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md
index 7290a175501..c44690a4c5d 100644
--- a/doc/development/i18n/index.md
+++ b/doc/development/i18n/index.md
@@ -50,3 +50,5 @@ able to proofread and instructions on becoming a proofreader yourself.
## Release
Translations are typically included in the next major or minor release.
+
+See [Merging translations from Crowdin](merging_translations.md)
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
new file mode 100644
index 00000000000..d172aa6da21
--- /dev/null
+++ b/doc/development/i18n/merging_translations.md
@@ -0,0 +1,60 @@
+# Merging translations from Crowdin
+
+Crowdin automatically syncs the `gitlab.pot` file presenting newly
+added translations to the community of translators.
+
+At the same time, it creates a merge request to merge all newly added
+& approved translations. Find the [merge reqeust created by
+`gitlab-crowdin-bot`](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&author_username=gitlab-crowdin-bot)
+to see new and merged merge requests. They are created in EE and need
+to be ported to CE manually.
+
+## Validation
+
+By default Crowdin commits translations with `[skip ci]` in the commit
+message. This is done to avoid a bunch of pipelines being run. Before
+merging translations, make sure to trigger a pipeline to validate
+translations, we have static analysis validating things Crowdin
+doesn't do. Create a [new pipeline](https://gitlab.com/gitlab-org/gitlab-ee/pipelines/new) for the
+`master-i18n` branch.
+
+If there are validation errors, the easiest solution is to disapprove
+the offending string in Crowdin, leaving a comment with what is
+required to fix the offense. There is an
+[issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/49208)
+suggesting to automate this process. Disapproving will exclude the
+invalid translation, the merge request will be updated within a few
+minutes.
+
+It might be handy to pause the integration on the Crowdin side for a
+little while so translations don't keep coming. This can be done by
+clicking `Pause sync` on the [Crowdin integration settings
+page](https://translate.gitlab.com/project/gitlab-ee/settings#integration).
+
+When all failures are resolved, the translations need to be double
+checked once more [as discussed in this
+issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/37850).
+
+## Merging translations
+
+When all translations are found good and pipelines pass the
+translations can be merged into the master branch. After that is done,
+create a new merge request cherry-picking the translations from EE to
+CE. When merging the translations, make sure to check the `Remove
+source branch` checkbox, so Crowdin recreates the `master-i18n` from
+master after the new translation was merged.
+
+We are discussing automating this entire process
+[here](https://gitlab.com/gitlab-org/gitlab-ce/issues/39309).
+
+## Recreate the merge request
+
+Crowdin creates a new merge request as soon as the old one is closed
+or merged. But it won't recreate the `master-i18n` branch every
+time. To force Crowdin to recreate the branch, close any [open merge
+request](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&author_username=gitlab-crowdin-bot)
+and delete the
+[`master-18n`](https://gitlab.com/gitlab-org/gitlab-ee/branches/all?utf8=%E2%9C%93&search=master-i18n).
+
+This might be needed when the merge request contains failures that
+have been fixed on master.
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 05caffb150a..c7b10dfd5ce 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -364,8 +364,7 @@ Depending on the size of the String and how frequently it would be allocated
there's no guarantee it will.
Strings will be frozen by default in Ruby 3.0. To prepare our code base for
-this eventuality, it's a good practice to add the following header to all
-Ruby files:
+this eventuality, we will be adding the following header to all Ruby files:
```ruby
# frozen_string_literal: true
@@ -379,6 +378,9 @@ test = +"hello"
test += " world"
```
+When adding new Ruby files, please check that you can add the above header,
+as omitting it may lead to style check failures.
+
## Anti-Patterns
This is a collection of [anti-patterns][anti-pattern] that should be avoided
diff --git a/doc/update/README.md b/doc/update/README.md
index 2c1fbc15719..7d3c4c310a4 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -41,7 +41,8 @@ However, for this to work there are the following requirements:
1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to
9.3.
2. You have to use [post-deployment
- migrations](../development/post_deployment_migrations.md).
+ migrations](../development/post_deployment_migrations.md) (included in
+ zero downtime update steps below)
3. You are using PostgreSQL. If you are using MySQL please look at the release
post to see if downtime is required.
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index b55946a788f..8db36c4a0e8 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -1,9 +1,8 @@
# Subgroups
-> **Notes:**
-> - [Introduced][ce-2772] in GitLab 9.0.
-> - Not available when using MySQL as external database (support removed in
-> GitLab 9.3 [due to performance reasons][issue]).
+NOTE: **Note:**
+[Introduced][ce-2772] in GitLab 9.0. Not available when using MySQL as external
+database (support removed in GitLab 9.3 [due to performance reasons][issue]).
With subgroups (aka nested groups or hierarchical groups) you can have
up to 20 levels of nested groups, which among other things can help you to:
@@ -79,14 +78,14 @@ structure.
## Creating a subgroup
-> **Notes:**
-> - You need to be an Owner of a group in order to be able to create
-> a subgroup. For more information check the [permissions table][permissions].
-> - For a list of words that are not allowed to be used as group names see the
-> [reserved names][reserved].
-> - Users can always create subgroups if they are explicitly added as an Owner to
-> a parent group even if group creation is disabled by an administrator in their
-> settings.
+NOTE: **Note:**
+You need to be an Owner of a group in order to be able to create a subgroup. For
+more information check the [permissions table][permissions].
+For a list of words that are not allowed to be used as group names see the
+[reserved names][reserved].
+Users can always create subgroups if they are explicitly added as an Owner to
+a parent group even if group creation is disabled by an administrator in their
+settings.
To create a subgroup:
@@ -136,12 +135,15 @@ From the image above, we can deduct the following things:
### Overriding the ancestor group membership
->**Note:**
+NOTE: **Note:**
You need to be an Owner of a group in order to be able to add members to it.
+NOTE: **Note:**
+A user's permissions in a subgroup cannot be lower than in any of its ancestor groups.
+Therefore, you cannot reduce a user's permissions in a subgroup with respect to its ancestor groups.
+
To override a user's membership of an ancestor group (the first group they were
-added to), simply add the user in the new subgroup again, but with different
-permissions.
+added to), add the user to the new subgroup again with a higher set of permissions.
For example, if User0 was first added to group `group-1/group-1-1` with Developer
permissions, then they will inherit those permissions in every other subgroup
diff --git a/doc/user/project/img/issue_board_milestone_lists.png b/doc/user/project/img/issue_board_milestone_lists.png
new file mode 100644
index 00000000000..91926f58f87
--- /dev/null
+++ b/doc/user/project/img/issue_board_milestone_lists.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_summed_weights.png b/doc/user/project/img/issue_board_summed_weights.png
new file mode 100644
index 00000000000..2288d767d8c
--- /dev/null
+++ b/doc/user/project/img/issue_board_summed_weights.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_core.png b/doc/user/project/img/issue_boards_core.png
new file mode 100755
index 00000000000..9e819160861
--- /dev/null
+++ b/doc/user/project/img/issue_boards_core.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_premium.png b/doc/user/project/img/issue_boards_premium.png
new file mode 100755
index 00000000000..bd9164b2961
--- /dev/null
+++ b/doc/user/project/img/issue_boards_premium.png
Binary files differ
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 5a38f5d8aed..7d12cd8f7c2 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -943,7 +943,13 @@ X-Gitlab-Event: Pipeline Hook
],
"created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
- "duration": 63
+ "duration": 63,
+ "variables": [
+ {
+ "key": "NESTOR_PROD_ENVIRONMENT",
+ "value": "us-west-1"
+ }
+ ]
},
"user":{
"name": "Administrator",
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 7c6d547d626..464fa5987c1 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -2,13 +2,44 @@
> [Introduced][ce-5554] in [GitLab 8.11](https://about.gitlab.com/2016/08/22/gitlab-8-11-released/#issue-board).
+## Overview
+
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 used as a [Kanban] or a [Scrum] board.
-![GitLab Issue Board](img/issue_board.png)
+It provides perfect pairing between issue tracking and project management,
+keeping everything in the same place, so that you don't need to jump
+between different platforms to organize your workflow.
-## Overview
+With GitLab Issue Boards, you organize your issues in lists that correspond to
+their assigned labels, visualizing issues designed as cards throughout that lists.
+
+You define your process and GitLab organizes it for you. You add your labels
+then create the corresponding list to pull in your existing issues. When
+you're ready, you can drag and drop your issue cards from one step to the next.
+
+![GitLab Issue Board - Core](img/issue_boards_core.png)
+
+**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+Watch a [video presentation](https://youtu.be/UWsJ8tkHAa8) of
+Issue Boards** (version introduced in GitLab 8.11 - August 2016).
+
+### Advanced features of Issue Boards
+
+With [GitLab Starter](https://about.gitlab.com/pricing/), you can create
+[multiple issue boards](#multiple-issue-boards) for a given project. **[STARTER]**
+
+With [GitLab Premium](https://about.gitlab.com/pricing/), you can also create multiple
+issue boards for your groups, and add lists for [assignees](#assignee-lists) and
+[milestones](#milestone-lists). **[PREMIUM]**
+
+Check all the [advanced features of Issue Boards](#gitlab-enterprise-features-for-issue-boards)
+below.
+
+![GitLab Issue Boards - Premium](img/issue_boards_premium.png)
+
+## How it works
The Issue Board builds on GitLab's existing
[issue tracking functionality](issues/index.md#issue-tracker) and
@@ -28,15 +59,12 @@ and deploy from one single platform. Issue Boards help you to visualize
and manage the entire process _in_ GitLab.
With [Multiple Issue Boards](#use-cases-for-multiple-issue-boards), available
-only in [GitLab Enterprise Edition](#features-per-tier),
+only in [different tiers of GitLab Enterprise Edition](#gitlab-enterprise-features-for-issue-boards),
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.
@@ -111,11 +139,6 @@ to improve their workflow with multiple boards.
Create lists for each of your team members and quickly drag-and-drop issues onto each team member.
-## Permissions
-
-[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.
-
## 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.
@@ -126,6 +149,139 @@ Issue Board, that is, create or delete lists and drag issues from one list to an
- **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.
+## Permissions
+
+[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.
+
+## GitLab Enterprise features for Issue Boards
+
+GitLab Issue Boards are available on GitLab Core and GitLab.com Free, but some
+advanced functionalities are only present in higher tiers: GitLab.com Bronze,
+Silver, or Gold, or GitLab self-managed Starter, Premium, and Ultimate, as described
+on the following sections.
+
+For a collection of [features per tier](#summary-of-features-per-tier), check the summary below.
+
+### Multiple Issue Boards **[STARTER]**
+
+> Introduced in [GitLab Enterprise Edition 8.13](https://about.gitlab.com/2016/10/22/gitlab-8-13-released/#multiple-issue-boards-ee).
+
+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.
+
+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.
+
+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 a 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)
+
+### Sum of Issue Weights **[STARTER]**
+
+The top of each list indicates the sum of issue weights for the issues that
+belong to that list. This is useful when using boards for capacity allocation,
+especially in combination with [assignee lists](#assignee-lists).
+
+![Issue Board summed weights](img/issue_board_summed_weights.png)
+
+### 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.
+
+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)
+
+### Milestone lists **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6469) in GitLab 11.2 Premium.
+
+As of 11.2, you're also able to create lists of a milestone. As the name states,
+these are lists that filter issues by the assigned milestone, giving you more
+freedom and visibility on the Issue Board. To do so:
+
+1. Click **Add list**.
+1. Select the **Milestone** tab.
+1. Search and click on the milestone.
+
+Similar to the assignee lists, you're now able to [drag issues](#dragging-issues-between-lists)
+to and/or from a milestone list to manipulate the milestone of the dragged issues.
+As on another list types, click on the trash icon to remove it.
+
+![Milestone lists](img/issue_board_milestone_lists.png)
+
+### Summary of features per tier
+
+Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
+
+| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Issue Boards | Assignee Lists |
+|----------|--------------------------------|------------------------------|---------------------------|----------------|
+| Core / Free | 1 | 1 | No | No |
+| Starter / Bronze | Multiple | 1 | Yes | No |
+| Premium / Silver | Multiple | Multiple | Yes | Yes |
+| Ultimate / Gold | Multiple | Multiple | Yes | Yes |
+
## Actions you can take on an Issue Board
- [Create a new list](#creating-a-new-list).
@@ -142,7 +298,7 @@ Issue Board, that is, create or delete lists and drag issues from one list to an
If you are not able to perform one or more of the things above, make sure you
have the right [permissions](#permissions).
-## First time using the Issue Board
+### First time using the Issue Board
The first time you navigate to your Issue Board, you will be presented with
a default list (**Done**) and a welcoming message that gives
@@ -157,7 +313,7 @@ which means the system has no way of populating them automatically. That's of
course if the predefined labels don't already exist. If any of them does exist,
the list will be created and filled with the issues that have that label.
-## Creating a new list
+### Creating a new list
Create a new list by clicking on the **Add list** button at the upper
right corner of the Issue Board.
@@ -172,7 +328,7 @@ To create a list for a label that doesn't yet exist, simply create the label by
choosing **Create new label**. The label will be created on-the-fly and it will
be immediately added to the dropdown. You can now choose it to create a list.
-## Deleting a list
+### Deleting a list
To delete a list from the Issue Board use the small trash icon that is present
in the list's heading. A confirmation dialog will appear for you to confirm.
@@ -180,7 +336,7 @@ in the list's heading. A confirmation dialog will appear for you to confirm.
Deleting a list doesn't have any effect in issues and labels, it's just the
list view that is removed. You can always add it back later if you need.
-## Adding issues to a list
+### Adding issues to a list
You can add issues to a list by clicking the **Add issues** button that is
present in the upper right corner of the Issue Board. This will open up a modal
@@ -192,7 +348,7 @@ the list by filtering by author, assignee, milestone and label.
![Bulk adding issues to lists](img/issue_boards_add_issues_modal.png)
-## Removing an issue from a list
+### Removing an issue from a list
Removing an issue from a list can be done by clicking on the issue card and then
clicking the **Remove from board** button in the sidebar. Under the hood, the
@@ -201,7 +357,7 @@ board itself.
![Remove issue from list](img/issue_boards_remove_issue.png)
-## Issue ordering in a list
+### Issue ordering in a list
When visiting a board, issues appear ordered in any list. You are able to change
that order simply by dragging and dropping the issues. The changed order will be saved
@@ -224,7 +380,7 @@ a given board inside your GitLab instance, any time those two issues are subsequ
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
+### Filtering issues
You should be able to use the filters on top of your Issue Board to show only
the results you want. This is similar to the filtering used in the issue tracker
@@ -232,7 +388,7 @@ since the metadata from the issues and labels are re-used in the Issue Board.
You can filter by author, assignee, milestone and label.
-## Creating workflows
+### Creating workflows
By reordering your lists, you can create workflows. As lists in Issue Boards are
based on labels, it works out of the box with your existing issues. So if you've
@@ -267,89 +423,7 @@ to another list the label changes and a system not is recorded.
![Issue Board system notes](img/issue_board_system_notes.png)
-## Multiple Issue Boards **[STARTER]**
-
-> Introduced in [GitLab Enterprise Edition 8.13](https://about.gitlab.com/2016/10/22/gitlab-8-13-released/#multiple-issue-boards-ee).
-
-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.
-
-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.
-
-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.
-
-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
+### Dragging issues between lists
When dragging issues between lists, different behavior occurs depending on the source list and the target list.
@@ -360,17 +434,6 @@ When dragging issues between lists, different behavior occurs depending on the s
| 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
-
-Different issue board features are available in different [GitLab tiers](https://about.gitlab.com/pricing/), as shown in the following table:
-
-| Tier | Number of Project Issue Boards | Number of Group Issue Boards | Configurable Issue Boards | Assignee Lists |
-|----------|--------------------------------|------------------------------|---------------------------|----------------|
-| Core | 1 | 1 | No | No |
-| Starter | Multiple | 1 | Yes | No |
-| Premium | Multiple | Multiple | Yes | Yes |
-| Ultimate | Multiple | Multiple | Yes | Yes |
-
## Tips
A few things to remember:
diff --git a/doc/user/project/pages/img/icons/click.png b/doc/user/project/pages/img/icons/click.png
new file mode 100644
index 00000000000..daaf760ec08
--- /dev/null
+++ b/doc/user/project/pages/img/icons/click.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/cogs.png b/doc/user/project/pages/img/icons/cogs.png
new file mode 100644
index 00000000000..a12da1b5e8c
--- /dev/null
+++ b/doc/user/project/pages/img/icons/cogs.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/fork.png b/doc/user/project/pages/img/icons/fork.png
new file mode 100644
index 00000000000..e2c9577e7ce
--- /dev/null
+++ b/doc/user/project/pages/img/icons/fork.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/free.png b/doc/user/project/pages/img/icons/free.png
new file mode 100644
index 00000000000..3b8f8f6863e
--- /dev/null
+++ b/doc/user/project/pages/img/icons/free.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/lock.png b/doc/user/project/pages/img/icons/lock.png
new file mode 100644
index 00000000000..1c1f0b4457b
--- /dev/null
+++ b/doc/user/project/pages/img/icons/lock.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/monitor.png b/doc/user/project/pages/img/icons/monitor.png
new file mode 100644
index 00000000000..7b99d430eef
--- /dev/null
+++ b/doc/user/project/pages/img/icons/monitor.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/terminal.png b/doc/user/project/pages/img/icons/terminal.png
new file mode 100644
index 00000000000..ab5ae11310c
--- /dev/null
+++ b/doc/user/project/pages/img/icons/terminal.png
Binary files differ
diff --git a/doc/user/project/pages/img/ssgs_pages.png b/doc/user/project/pages/img/ssgs_pages.png
new file mode 100644
index 00000000000..608881c8e31
--- /dev/null
+++ b/doc/user/project/pages/img/ssgs_pages.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 4f0774dba5c..60144fa1971 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -4,71 +4,180 @@ description: 'Learn how to use GitLab Pages to deploy a static website at no add
# GitLab Pages
-With GitLab Pages it's easy to publish your project website. GitLab Pages is a hosting service for static websites, at no additional cost.
-
-## Getting Started
-
-[Create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch)
-to get you started quickly, or,
-alternatively, start from an existing project as follows:
-
-1. [Fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project) an [example project](https://gitlab.com/pages):
+**GitLab Pages is a feature that allows you to publish static websites
+directly from a repository in GitLab.**
+
+You can use it either for personal or business websites, such as
+portfolios, documentation, manifestos, and business presentations,
+and attribute any license to your content.
+
+<table class="borderless-table center fixed-table">
+ <tr>
+ <td style="width: 22%"><img src="img/icons/cogs.png" alt="SSGs" class="image-noshadow half-width"></td>
+ <td style="width: 4%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 22%"><img src="img/icons/monitor.png" alt="Websites" class="image-noshadow half-width"></td>
+ <td style="width: 4%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 22%"><img src="img/icons/free.png" alt="Pages is free" class="image-noshadow half-width"></td>
+ <td style="width: 4%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 22%"><img src="img/icons/lock.png" alt="Secure your website" class="image-noshadow half-width"></td>
+ </tr>
+ <tr>
+ <td><em>Use any static website generator or plain HTML</em></td>
+ <td></td>
+ <td><em>Create websites for your projects, groups, or user account</em></td>
+ <td></td>
+ <td><em>Host on GitLab.com for free, or on your own GitLab instance</em></td>
+ <td></td>
+ <td><em>Connect your custom domain(s) and TLS certificates</em></td>
+ </tr>
+</table>
+
+Pages is available for free for all GitLab.com users as well as for self-managed
+instances (GitLab Core, Starter, Premium, and Ultimate).
+
+## Overview
+
+<div class="row">
+<div class="col-md-9">
+<p style="margin-top: 18px;">
+To publish a website with Pages, you can use any Static Site Generator (SSG),
+such as Jekyll, Hugo, Middleman, Harp, Hexo, and Brunch, just to name a few. You can also
+publish any website written directly in plain HTML, CSS, and JavaScript.</p>
+<p>Pages does <strong>not</strong> support dynamic server-side processing, for instance, as <code>.php</code> and <code>.asp</code> requires. See this article to learn more about
+<a href="https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/">static websites vs dynamic websites</a>.</p>
+</div>
+<div class="col-md-3"><img src="img/ssgs_pages.png" alt="Examples of SSGs supported by Pages" class="image-noshadow middle display-block"></div>
+</div>
+
+### Availability
+
+If you're using GitLab.com, your website will be publicly available to the internet.
+If you're using self-managed instances (Core, Starter, Premium, or Ultimate),
+your websites will be published on your own server, according to the
+[Pages admin settings](../../../administration/pages/index.md) chosen by your sysdamin,
+who can opt for making them public or internal to your server.
+
+### How it works
+
+To use GitLab Pages, first you need to create a project in GitLab to upload your website's
+files to. These projects can be either public, internal, or private, at your own choice.
+GitLab will always deploy your website from a very specific folder called `public` in your
+repository. Note that when you create a new project in GitLab, a [repository](../repository/index.md)
+becomes available automatically.
+
+To deploy your site, GitLab will use its built-in tool called [GitLab CI/CD](../../../ci/README.md),
+that will build your site and publish it to the GitLab Pages server. The sequence of
+scripts that GitLab CI/CD runs to accomplish this task is created from a file named
+`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will.
+
+You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain),
+`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
+need admin access to your domain's registrar (or control panel) to set it up with Pages.
+
+Optionally, when adding your own domain, you can add an SSL/TLS certificate to secure your
+site under the HTTPS protocol.
+
+## Getting started
+
+To get started with GitLab Pages, you can either [create a project from scratch](getting_started_part_two.md#create-a-project-from-scratch)
+or quickly start from copying an existing example project, as follows:
+
+1. Choose an [example project](https://gitlab.com/pages) to [fork](../../../gitlab-basics/fork-project.md#how-to-fork-a-project):
by forking a project, you create a copy of the codebase you're forking from to start from a template instead of starting from scratch.
-2. Change a file to trigger a GitLab CI/CD pipeline: GitLab CI/CD will build and deploy your site to GitLab Pages.
-3. Visit your project's **Settings > Pages** to see your **website link**, and click on it. Bam! Your website is live! :)
-
- _Further steps (optional):_
-
-4. Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from)
+1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** and click
+**Run pipeline** so that GitLab CI/CD will build and deploy your site to the server.
+1. Once the pipeline has finished successfully, find the link to visit your website from your
+ project's **Settings > Pages**.
+
+<table class="borderless-table center fixed-table middle width-80">
+ <tr>
+ <td style="width: 30%"><img src="img/icons/fork.png" alt="Fork" class="image-noshadow half-width"></td>
+ <td style="width: 10%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 30%"><img src="img/icons/terminal.png" alt="Deploy" class="image-noshadow half-width"></td>
+ <td style="width: 10%">
+ <strong>
+ <i class="fa fa-angle-double-right" aria-hidden="true"></i>
+ </strong>
+ </td>
+ <td style="width: 30%"><img src="img/icons/click.png" alt="Visit" class="image-noshadow half-width"></td>
+ </tr>
+ <tr>
+ <td><em>Fork an example project</em></td>
+ <td></td>
+ <td><em>Deploy your website</em></td>
+ <td></td>
+ <td><em>Visit your website's URL</em></td>
+ </tr>
+</table>
+
+Your website is then visible on your domain, and you can modify your files
+as you wish. For every modification pushed to your repository, GitLab CI/CD will run
+a new pipeline to publish your changes to the server.
+
+You can also take some optional further steps:
+
+- Remove the [fork relationship](getting_started_part_two.md#fork-a-project-to-get-started-from)
(_You don't need the relationship unless you intent to contribute back to the example project you forked from_).
-5. Make it a [user/group website](getting_started_part_one.md#user-and-group-websites)
+- Make it a [user/group website](getting_started_part_one.md#user-and-group-websites)
-**Watch a video with the steps above: https://www.youtube.com/watch?v=TWqh9MtT4Bg**
+**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=TWqh9MtT4Bg) with all the steps above!**
_Advanced options:_
- [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages)
- Apply [SSL/TLS certification](getting_started_part_three.md#ssl-tls-certificates) to your custom domain
-## How Does It Work?
-
-With GitLab Pages you can create [static websites](getting_started_part_one.md#what-you-need-to-know-before-getting-started)
-for your GitLab projects, groups, or user accounts.
-
-It supports plain static content, such as HTML, and **all** [static site generators (SSGs)](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/), such as Jekyll, Middleman, Hexo, Hugo, and Pelican.
-
-Connect as many custom domains as you like and bring your own TLS certificate
-to secure them.
-
-Your files live in a project [repository](../repository/index.md) on GitLab.
-[GitLab CI](../../../ci/README.md) picks up those files and makes them available at, typically,
-`https://<username>.gitlab.io/<projectname>`. Please read through the docs on
-[GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain) for more info.
-
## Explore GitLab Pages
-Read the following tutorials to know more about:
+To learn more about GitLab Pages, read the following tutorials:
- [Static websites and GitLab Pages domains](getting_started_part_one.md): Understand what is a static website, and how GitLab Pages default domains work
- [Projects for GitLab Pages and URL structure](getting_started_part_two.md): Forking projects and creating new ones from scratch, understanding URLs structure and baseurls
-- [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md): How to add custom domains and subdomains to your website, configure DNS records, and SSL/TLS certificates
+- [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md): How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates
- [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md): Understand how to create your own `.gitlab-ci.yml` for your site
- [Technical aspects, custom 404 pages, limitations](introduction.md)
-- [Hosting on GitLab.com with GitLab Pages](https://about.gitlab.com/2016/04/07/gitlab-pages-setup/) (outdated)
-_Blog posts series about Static Site Generators (SSGs):_
+### GitLab Pages with Static Site Generators (SSGs)
+
+To understand more about SSGs, their advantages, and how to get the most from them
+with Pages, read through this series:
- [SSGs part 1: Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- [SSGs part 2: Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/)
- [SSGs part 3: Build any SSG site with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-_Blog posts for securing GitLab Pages custom domains with SSL/TLS certificates:_
+### GitLab Pages with SSL/TLS certificates
+
+If you're using GitLab Pages default domain (`.gitlab.io`), your website will be
+automatically secure and available under HTTPS. If you're using your own domain, you can
+optionally secure it with with SSL/TLS certificates. You can read the following
+tutorials to learn how to use these third-party certificates with GitLab Pages:
- [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
-- [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (outdated)
+- [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (mind that although this article is out-of-date, it can still be useful to guide you through the basic steps)
## Advanced use
+There are quite some great examples of GitLab Pages websites built for some
+specific reasons. These examples can teach you some advanced techniques
+to use and adapt to your own needs:
+
- [Posting to your GitLab Pages blog from iOS](https://about.gitlab.com/2016/08/19/posting-to-your-gitlab-pages-blog-from-ios/)
- [GitLab CI: Run jobs sequentially, in parallel, or build a custom pipeline](https://about.gitlab.com/2016/07/29/the-basics-of-gitlab-ci/)
- [GitLab CI: Deployment & environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
@@ -80,10 +189,9 @@ _Blog posts for securing GitLab Pages custom domains with SSL/TLS certificates:_
Enable and configure GitLab Pages on your own instance (GitLab Community Edition and Enterprise Editions) with
the [admin guide](../../../administration/pages/index.md).
-**Watch the video: https://www.youtube.com/watch?v=dD8c7WNcc6s**
+**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a [video tutorial](https://www.youtube.com/watch?v=dD8c7WNcc6s) for getting started with GitLab Pages admin!**
## More information about GitLab Pages
-- For an overview, visit the [feature webpage](https://about.gitlab.com/features/pages/)
- Announcement (2016-12-24): ["We're bringing GitLab Pages to CE"](https://about.gitlab.com/2016/12/24/were-bringing-gitlab-pages-to-community-edition/)
- Announcement (2017-03-06): ["We are changing the IP of GitLab Pages on GitLab.com"](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/)
diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb
index 18063fb20a2..cecff6d3b81 100644
--- a/lib/api/access_requests.rb
+++ b/lib/api/access_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class AccessRequests < Grape::API
include PaginationParams
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e89d9337853..06c8b48b8cc 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class API < Grape::API
include APIGuard
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 8ee7987cfff..61357b3f1d6 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Guard API with OAuth 2.0 Access Token
require 'rack/oauth2'
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index b122cdefe4e..f29cd7fc003 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# External applications API
class Applications < Grape::API
diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb
index 70219bc8ea0..0f14d003065 100644
--- a/lib/api/avatar.rb
+++ b/lib/api/avatar.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Avatar < Grape::API
resource :avatar do
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index e334af22183..c2abf9155f3 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class AwardEmoji < Grape::API
include PaginationParams
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index 8ceffe9c5ef..ab670988f47 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Badges < Grape::API
include PaginationParams
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index 0f89414148b..c80e1c57864 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Boards < Grape::API
include BoardsResponses
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index 3322b37c6ff..86d9b24802f 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module BoardsResponses
extend ActiveSupport::Concern
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 5d106ed93a0..2735d410c8e 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'mime/types'
module API
diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb
index d7138b2f2fe..19148758fc5 100644
--- a/lib/api/broadcast_messages.rb
+++ b/lib/api/broadcast_messages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class BroadcastMessages < Grape::API
include PaginationParams
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
index c13154dc0ec..6eddc5e5b61 100644
--- a/lib/api/circuit_breakers.rb
+++ b/lib/api/circuit_breakers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class CircuitBreakers < Grape::API
before { authenticated_as_admin! }
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 8e6f706afd4..99553d993ca 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'mime/types'
module API
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 5aeffc8fb99..f0db1318146 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'mime/types'
module API
diff --git a/lib/api/custom_attributes_endpoints.rb b/lib/api/custom_attributes_endpoints.rb
index b5864665cc3..2149e04451e 100644
--- a/lib/api/custom_attributes_endpoints.rb
+++ b/lib/api/custom_attributes_endpoints.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module CustomAttributesEndpoints
extend ActiveSupport::Concern
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 501e9f64db0..ce35720d408 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class DeployKeys < Grape::API
include PaginationParams
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index b7892599295..6747e2e5005 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Deployments RESTful API endpoints
class Deployments < Grape::API
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 88668992215..39c6d28391d 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Discussions < Grape::API
include PaginationParams
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 12c4340c1ba..a78a93cbfd9 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Entities
class WikiPageBasic < Grape::Entity
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index fa828f43001..c64217a6977 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Environments RESTfull API endpoints
class Environments < Grape::API
diff --git a/lib/api/events.rb b/lib/api/events.rb
index dfe0e81af26..6e0b508be19 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Events < Grape::API
include PaginationParams
@@ -16,12 +18,27 @@ module API
desc: 'Return events sorted in ascending and descending order'
end
+ RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze
+
+ def redact_events(events)
+ events.map do |event|
+ if event.visible_to_user?(current_user)
+ event
+ else
+ RedactedEvent
+ end
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
- def present_events(events)
+ def present_events(events, redact: true)
events = events.reorder(created_at: params[:sort])
.with_associations
- present paginate(events), with: Entities::Event
+ events = paginate(events)
+ events = redact_events(events) if redact
+
+ present events, with: Entities::Event
end
# rubocop: enable CodeReuse/ActiveRecord
end
@@ -44,7 +61,8 @@ module API
events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target)
- present_events(events)
+ # Since we're viewing our own events, redaction is unnecessary
+ present_events(events, redact: false)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 79be8c1903e..6f2422af13a 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Features < Grape::API
before { authenticated_as_admin! }
diff --git a/lib/api/files.rb b/lib/api/files.rb
index ac02488d30c..bcd2cd48a45 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Files < Grape::API
FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index 3832cdc10a8..dc30e868e2e 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class GroupBoards < Grape::API
include BoardsResponses
diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb
index 4b4352c2b27..b36436dbf43 100644
--- a/lib/api/group_milestones.rb
+++ b/lib/api/group_milestones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class GroupMilestones < Grape::API
include MilestoneResponses
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index b6610dd04b3..ae7241e9a30 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class GroupVariables < Grape::API
include PaginationParams
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 018ca72c32a..64b998ab455 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Groups < Grape::API
include PaginationParams
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 85e3e06e4fd..a7ba8066233 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
include Gitlab::Utils
@@ -381,9 +383,10 @@ module API
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
trace = exception.backtrace
- message = "\n#{exception.class} (#{exception.message}):\n"
+ message = ["\n#{exception.class} (#{exception.message}):\n"]
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ")
+ message = message.join
API.logger.add Logger::FATAL, message
diff --git a/lib/api/helpers/badges_helpers.rb b/lib/api/helpers/badges_helpers.rb
index 1f8afbf3c90..46ce5b4e7b5 100644
--- a/lib/api/helpers/badges_helpers.rb
+++ b/lib/api/helpers/badges_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module BadgesHelpers
diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb
index 9993caa5249..7551ca50a7f 100644
--- a/lib/api/helpers/common_helpers.rb
+++ b/lib/api/helpers/common_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module CommonHelpers
diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb
index 3bbe827967e..88208226c40 100644
--- a/lib/api/helpers/custom_attributes.rb
+++ b/lib/api/helpers/custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module CustomAttributes
diff --git a/lib/api/helpers/custom_validators.rb b/lib/api/helpers/custom_validators.rb
index dd4f6c41131..23b1cd1ad45 100644
--- a/lib/api/helpers/custom_validators.rb
+++ b/lib/api/helpers/custom_validators.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module CustomValidators
diff --git a/lib/api/helpers/headers_helpers.rb b/lib/api/helpers/headers_helpers.rb
index c9c44e3c218..7553af9d156 100644
--- a/lib/api/helpers/headers_helpers.rb
+++ b/lib/api/helpers/headers_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module HeadersHelpers
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 83151be82ad..4eaaca96b49 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module InternalHelpers
diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb
index 518aaa62aef..73d58ee7f37 100644
--- a/lib/api/helpers/members_helpers.rb
+++ b/lib/api/helpers/members_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable GitlabSecurity/PublicSend
module API
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 7b1f5c2584b..216b2c45741 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module NotesHelpers
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 50bcd4e0437..d311cbb5f7e 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module Pagination
diff --git a/lib/api/helpers/project_snapshots_helpers.rb b/lib/api/helpers/project_snapshots_helpers.rb
index 94798a8cb51..1b5dc281e38 100644
--- a/lib/api/helpers/project_snapshots_helpers.rb
+++ b/lib/api/helpers/project_snapshots_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module ProjectSnapshotsHelpers
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 98672f2f765..e6a72b949f9 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module ProjectsHelpers
diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb
index bc7333ca4b3..793ae11b41d 100644
--- a/lib/api/helpers/related_resources_helpers.rb
+++ b/lib/api/helpers/related_resources_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module RelatedResourcesHelpers
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 61eb88d3331..45d0343bc89 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module Helpers
module Runner
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 71b87f60bf6..6a264c4cc6d 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Internal access API
class Internal < Grape::API
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index bcb03a0b540..25d78053c88 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Issues < Grape::API
include PaginationParams
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index ab4203c4e25..2229cbcd9d4 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class JobArtifacts < Grape::API
before { authenticate_non_get! }
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 27ffd42834c..63fab6b0abb 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Jobs < Grape::API
include PaginationParams
diff --git a/lib/api/keys.rb b/lib/api/keys.rb
index fd93f797f72..d5280a0035d 100644
--- a/lib/api/keys.rb
+++ b/lib/api/keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Keys API
class Keys < Grape::API
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index 98c9818db39..28555454307 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Labels < Grape::API
include PaginationParams
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index d202eaa4c49..0342a4b6654 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Lint < Grape::API
namespace :ci do
diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb
index 5d55224c1a7..50d8a1ac596 100644
--- a/lib/api/markdown.rb
+++ b/lib/api/markdown.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Markdown < Grape::API
params do
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 4d8e23dee91..a8f67be3463 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Members < Grape::API
include PaginationParams
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 95ef8f42954..e4fb890960a 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index cad38271cbb..764905ca00f 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class MergeRequests < Grape::API
include PaginationParams
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index a8eb137e46a..a0ca39b69d4 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module MilestoneResponses
extend ActiveSupport::Concern
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 32b77aedba8..76639fbb031 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Namespaces < Grape::API
include PaginationParams
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index dc9373bb3c2..9f323b87baf 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Notes < Grape::API
include PaginationParams
diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb
index bf0d6b9e434..4d9a4629268 100644
--- a/lib/api/notification_settings.rb
+++ b/lib/api/notification_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# notification_settings API
class NotificationSettings < Grape::API
diff --git a/lib/api/pages_domains.rb b/lib/api/pages_domains.rb
index 8730c91b426..c9ad47e0f0d 100644
--- a/lib/api/pages_domains.rb
+++ b/lib/api/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class PagesDomains < Grape::API
include PaginationParams
diff --git a/lib/api/pagination_params.rb b/lib/api/pagination_params.rb
index f566eb3ed2b..ae03595eb25 100644
--- a/lib/api/pagination_params.rb
+++ b/lib/api/pagination_params.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Concern for declare pagination params.
#
diff --git a/lib/api/pipeline_schedules.rb b/lib/api/pipeline_schedules.rb
index 5bd1ce8c5e1..ed0a38b9d70 100644
--- a/lib/api/pipeline_schedules.rb
+++ b/lib/api/pipeline_schedules.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class PipelineSchedules < Grape::API
include PaginationParams
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 5cce96d5ae7..1cfb982c04b 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Pipelines < Grape::API
include PaginationParams
diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb
index 8562ae6d737..e34ed0bdb44 100644
--- a/lib/api/project_export.rb
+++ b/lib/api/project_export.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectExport < Grape::API
before do
diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index 0fb454bc22e..4af4c6ac593 100644
--- a/lib/api/project_hooks.rb
+++ b/lib/api/project_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectHooks < Grape::API
include PaginationParams
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index bc5152e539f..cbfa0c5bc1c 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectImport < Grape::API
include PaginationParams
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index 72cf32d7717..c7137ba5217 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectMilestones < Grape::API
include PaginationParams
diff --git a/lib/api/project_snapshots.rb b/lib/api/project_snapshots.rb
index 71005acc587..175fbb2ce92 100644
--- a/lib/api/project_snapshots.rb
+++ b/lib/api/project_snapshots.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectSnapshots < Grape::API
helpers ::API::Helpers::ProjectSnapshotsHelpers
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 1ef176b1320..f3a1b73b153 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProjectSnippets < Grape::API
include PaginationParams
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index ee426f39523..00bad49ebdc 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_dependency 'declarative_policy'
module API
diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb
index 9fd79c491c2..8edcfea7c93 100644
--- a/lib/api/projects_relation_builder.rb
+++ b/lib/api/projects_relation_builder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module ProjectsRelationBuilder
extend ActiveSupport::Concern
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 804f6fa9b73..47752f40e58 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProtectedBranches < Grape::API
include PaginationParams
diff --git a/lib/api/protected_tags.rb b/lib/api/protected_tags.rb
index e406344e42d..ed1c5f0cc05 100644
--- a/lib/api/protected_tags.rb
+++ b/lib/api/protected_tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class ProtectedTags < Grape::API
include PaginationParams
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 79736107bbb..5125f302fbb 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'mime/types'
module API
diff --git a/lib/api/runner.rb b/lib/api/runner.rb
index b2d46cef23c..d8768a54986 100644
--- a/lib/api/runner.rb
+++ b/lib/api/runner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Runner < Grape::API
helpers ::API::Helpers::Runner
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 9bcdfc8cb15..60868821810 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Runners < Grape::API
include PaginationParams
diff --git a/lib/api/scope.rb b/lib/api/scope.rb
index d5165b2e482..707775e5d15 100644
--- a/lib/api/scope.rb
+++ b/lib/api/scope.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Encapsulate a scope used for authorization, such as `api`, or `read_user`
module API
class Scope
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 37fbabe419c..12d97dcfe7f 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Search < Grape::API
include PaginationParams
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 8d71bd9dff1..edbd134822c 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Settings < Grape::API
before { authenticated_as_admin! }
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index 11f2b40269a..daa9598a204 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'sidekiq/api'
module API
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 6352a9c8742..f1786c15f4f 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
# Snippets API
class Snippets < Grape::API
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index b3e1e23031a..077e9373ac4 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Subscriptions < Grape::API
before { authenticate! }
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 07552aa18e8..51fae0e54aa 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class SystemHooks < Grape::API
include PaginationParams
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 5e0afc6a7e4..f739eacf9ba 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Tags < Grape::API
include PaginationParams
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 7bf0e0f5934..8ff3b2ac33c 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Templates < Grape::API
include PaginationParams
diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb
index 2bb451dea89..93fe06bec27 100644
--- a/lib/api/time_tracking_endpoints.rb
+++ b/lib/api/time_tracking_endpoints.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
module TimeTrackingEndpoints
extend ActiveSupport::Concern
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index c6dbcf84e3a..ed2cf2cc31b 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Todos < Grape::API
include PaginationParams
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 2339505b05b..f784c857883 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Triggers < Grape::API
include PaginationParams
diff --git a/lib/api/users.rb b/lib/api/users.rb
index ac09ca7f7b7..11a7f4ef64d 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Users < Grape::API
include PaginationParams
@@ -42,12 +44,12 @@ module API
optional :provider, type: String, desc: 'The external provider'
optional :bio, type: String, desc: 'The biography of the user'
optional :location, type: String, desc: 'The location of the user'
+ optional :public_email, type: String, desc: 'The public email of the user'
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user'
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
- optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
all_or_none_of :extern_uid, :provider
end
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 50e6fa6bcdf..c844ba321ed 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Variables < Grape::API
include PaginationParams
diff --git a/lib/api/version.rb b/lib/api/version.rb
index 3b10bfa6a7d..74cd857f447 100644
--- a/lib/api/version.rb
+++ b/lib/api/version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Version < Grape::API
before { authenticate! }
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index e86ebc573f2..6e1d4eb335f 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module API
class Wikis < Grape::API
helpers do
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index 45a935ab352..33658ae225f 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb
index adf85ca4719..5e795a449de 100644
--- a/lib/backup/builds.rb
+++ b/lib/backup/builds.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 086ca5986bd..e6bf3d1856f 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'yaml'
module Backup
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index e287aa1e392..0032ae8f84b 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'open3'
require_relative 'helper'
diff --git a/lib/backup/helper.rb b/lib/backup/helper.rb
index 54b9ce10b4d..22f00aef569 100644
--- a/lib/backup/helper.rb
+++ b/lib/backup/helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Backup
module Helper
def access_denied_error(path)
diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb
index 185ff8ae6bd..0dfe56e214f 100644
--- a/lib/backup/lfs.rb
+++ b/lib/backup/lfs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index a3641505196..5d4a7efc456 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Backup
class Manager
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze
diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb
index 542e35a7c7c..a4be728df08 100644
--- a/lib/backup/pages.rb
+++ b/lib/backup/pages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb
index 35821805797..d16ed2facf1 100644
--- a/lib/backup/registry.rb
+++ b/lib/backup/registry.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 906ed498026..c8a5377bfa0 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'yaml'
module Backup
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 49b117a7ee3..9577df2634a 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'backup/files'
module Backup
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 8e838d04bad..7acbc933adc 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -60,7 +60,11 @@ module Banzai
path_parts.unshift(relative_url_root, project.full_path)
end
- path = Addressable::URI.escape(File.join(*path_parts))
+ begin
+ path = Addressable::URI.escape(File.join(*path_parts))
+ rescue Addressable::URI::InvalidURIError
+ return
+ end
html_attr.value =
if context[:only_path]
diff --git a/lib/gitlab/background_migration/encrypt_columns.rb b/lib/gitlab/background_migration/encrypt_columns.rb
new file mode 100644
index 00000000000..0d333e47e7b
--- /dev/null
+++ b/lib/gitlab/background_migration/encrypt_columns.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # EncryptColumn migrates data from an unencrypted column - `foo`, say - to
+ # an encrypted column - `encrypted_foo`, say.
+ #
+ # For this background migration to work, the table that is migrated _has_ to
+ # have an `id` column as the primary key. Additionally, the encrypted column
+ # should be managed by attr_encrypted, and map to an attribute with the same
+ # name as the unencrypted column (i.e., the unencrypted column should be
+ # shadowed).
+ #
+ # To avoid depending on a particular version of the model in app/, add a
+ # model to `lib/gitlab/background_migration/models/encrypt_columns` and use
+ # it in the migration that enqueues the jobs, so code can be shared.
+ class EncryptColumns
+ def perform(model, attributes, from, to)
+ model = model.constantize if model.is_a?(String)
+ attributes = expand_attributes(model, Array(attributes).map(&:to_sym))
+
+ model.transaction do
+ # Use SELECT ... FOR UPDATE to prevent the value being changed while
+ # we are encrypting it
+ relation = model.where(id: from..to).lock
+
+ relation.each do |instance|
+ encrypt!(instance, attributes)
+ end
+ end
+ end
+
+ private
+
+ # Build a hash of { attribute => encrypted column name }
+ def expand_attributes(klass, attributes)
+ expanded = attributes.flat_map do |attribute|
+ attr_config = klass.encrypted_attributes[attribute]
+ crypt_column_name = attr_config&.fetch(:attribute)
+
+ raise "Couldn't determine encrypted column for #{klass}##{attribute}" if
+ crypt_column_name.nil?
+
+ [attribute, crypt_column_name]
+ end
+
+ Hash[*expanded]
+ end
+
+ # Generate ciphertext for each column and update the database
+ def encrypt!(instance, attributes)
+ to_clear = attributes
+ .map { |plain, crypt| apply_attribute!(instance, plain, crypt) }
+ .compact
+ .flat_map { |plain| [plain, nil] }
+
+ to_clear = Hash[*to_clear]
+
+ if instance.changed?
+ instance.save!
+ instance.update_columns(to_clear)
+ end
+ end
+
+ def apply_attribute!(instance, plain_column, crypt_column)
+ plaintext = instance[plain_column]
+ ciphertext = instance[crypt_column]
+
+ # No need to do anything if the plaintext is nil, or an encrypted
+ # value already exists
+ return nil unless plaintext.present? && !ciphertext.present?
+
+ # attr_encrypted will calculate and set the expected value for us
+ instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend
+
+ plain_column
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb b/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb
new file mode 100644
index 00000000000..bb76eb8ed48
--- /dev/null
+++ b/lib/gitlab/background_migration/models/encrypt_columns/web_hook.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ module Models
+ module EncryptColumns
+ # This model is shared between synchronous and background migrations to
+ # encrypt the `token` and `url` columns
+ class WebHook < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'web_hooks'
+ self.inheritance_column = :_type_disabled
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_truncated
+
+ attr_encrypted :url,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_truncated
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/populate_external_pipeline_source.rb b/lib/gitlab/background_migration/populate_external_pipeline_source.rb
new file mode 100644
index 00000000000..036fe641757
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_external_pipeline_source.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class PopulateExternalPipelineSource
+ module Migratable
+ class Pipeline < ActiveRecord::Base
+ self.table_name = 'ci_pipelines'
+
+ def self.sources
+ {
+ unknown: nil,
+ push: 1,
+ web: 2,
+ trigger: 3,
+ schedule: 4,
+ api: 5,
+ external: 6
+ }
+ end
+ end
+
+ class CommitStatus < ActiveRecord::Base
+ self.table_name = 'ci_builds'
+ self.inheritance_column = :_type_disabled
+
+ scope :has_pipeline, -> { where('ci_builds.commit_id=ci_pipelines.id') }
+ scope :of_type, -> (type) { where('type=?', type) }
+ end
+ end
+
+ def perform(start_id, stop_id)
+ external_pipelines(start_id, stop_id)
+ .update_all(source: Migratable::Pipeline.sources[:external])
+ end
+
+ private
+
+ def external_pipelines(start_id, stop_id)
+ Migratable::Pipeline.where(id: (start_id..stop_id))
+ .where(
+ 'EXISTS (?) AND NOT EXISTS (?)',
+ Migratable::CommitStatus.of_type('GenericCommitStatus').has_pipeline.select(1),
+ Migratable::CommitStatus.of_type('Ci::Build').has_pipeline.select(1)
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index d8fcdfac266..aebaf2d6982 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -162,7 +162,10 @@ sast:
artifacts:
paths: [gl-sast-report.json]
only:
- - branches
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bsast\b/
except:
variables:
- $SAST_DISABLED
@@ -179,7 +182,10 @@ dependency_scanning:
artifacts:
paths: [gl-dependency-scanning-report.json]
only:
- - branches
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bdependency_scanning\b/
except:
variables:
- $DEPENDENCY_SCANNING_DISABLED
@@ -196,7 +202,10 @@ container_scanning:
artifacts:
paths: [gl-container-scanning-report.json]
only:
- - branches
+ refs:
+ - branches
+ variables:
+ - $GITLAB_FEATURES =~ /\bsast_container\b/
except:
variables:
- $CONTAINER_SCANNING_DISABLED
@@ -215,6 +224,8 @@ dast:
refs:
- branches
kubernetes: active
+ variables:
+ - $GITLAB_FEATURES =~ /\bdast\b/
except:
refs:
- master
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index eb246d393a1..f382992cb0a 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -26,7 +26,8 @@ module Gitlab
stages: pipeline.stages_names,
created_at: pipeline.created_at,
finished_at: pipeline.finished_at,
- duration: pipeline.duration
+ duration: pipeline.duration,
+ variables: pipeline.variables.map(&:hook_attrs)
}
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 7f012312819..30541ee3553 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -1073,6 +1073,10 @@ into similar problems in the future (e.g. when new tables are created).
connection.select_value(index_sql).to_i > 0
end
+
+ def mysql_compatible_index_length
+ Gitlab::Database.mysql? ? 20 : nil
+ end
end
end
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 1f012043e56..a605ddb5c33 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -24,7 +24,7 @@ module Gitlab
# ignore highlighting for "match" lines
next diff_line if diff_line.meta?
- rich_line = highlight_line(diff_line) || diff_line.text
+ rich_line = highlight_line(diff_line) || ERB::Util.html_escape(diff_line.text)
if line_inline_diffs = inline_diffs[i]
begin
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 3d5a63bdbac..45d42c7078f 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -953,6 +953,12 @@ module Gitlab
end
end
+ def list_last_commits_for_tree(sha, path, offset: 0, limit: 25)
+ wrapped_gitaly_errors do
+ gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
+ end
+ end
+
def last_commit_for_path(sha, path)
wrapped_gitaly_errors do
gitaly_commit_client.last_commit_for_path(sha, path)
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 07e5e204b68..085b2a127a5 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -148,6 +148,24 @@ module Gitlab
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
end
+ def list_last_commits_for_tree(revision, path, offset: 0, limit: 25)
+ request = Gitaly::ListLastCommitsForTreeRequest.new(
+ repository: @gitaly_repo,
+ revision: encode_binary(revision),
+ path: encode_binary(path.to_s),
+ offset: offset,
+ limit: limit
+ )
+
+ response = GitalyClient.call(@repository.storage, :commit_service, :list_last_commits_for_tree, request, timeout: GitalyClient.medium_timeout)
+
+ response.each_with_object({}) do |gitaly_response, hsh|
+ gitaly_response.commits.each do |commit_for_tree|
+ hsh[commit_for_tree.path] = Gitlab::Git::Commit.new(@repository, commit_for_tree.commit)
+ end
+ end
+ end
+
def last_commit_for_path(revision, path)
request = Gitaly::LastCommitForPathRequest.new(
repository: @gitaly_repo,
diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb
index 5408a1a6838..0b6cc893db1 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,5 +1,8 @@
module Gitlab
class Highlight
+ TIMEOUT_BACKGROUND = 30.seconds
+ TIMEOUT_FOREGROUND = 3.seconds
+
def self.highlight(blob_name, blob_content, repository: nil, plain: false)
new(blob_name, blob_content, repository: repository)
.highlight(blob_content, continue: false, plain: plain)
@@ -51,11 +54,20 @@ module Gitlab
end
def highlight_rich(text, continue: true)
- @formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe
+ tag = lexer.tag
+ tokens = lexer.lex(text, continue: continue)
+ Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe }
+ rescue Timeout::Error => e
+ Gitlab::Sentry.track_exception(e)
+ highlight_plain(text)
rescue
highlight_plain(text)
end
+ def timeout_time
+ Sidekiq.server? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND
+ end
+
def link_dependencies(text, highlighted_text)
Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index a19b3c88627..2bed470514b 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -147,6 +147,12 @@ excluded_attributes:
- :reference
- :reference_html
- :epic_id
+ hooks:
+ - :token
+ - :encrypted_token
+ - :encrypted_token_iv
+ - :encrypted_url
+ - :encrypted_url_iv
methods:
labels:
diff --git a/lib/gitlab/repository_cache.rb b/lib/gitlab/repository_cache.rb
index b1bf3ca4143..a03ce07b6a1 100644
--- a/lib/gitlab/repository_cache.rb
+++ b/lib/gitlab/repository_cache.rb
@@ -29,5 +29,21 @@ module Gitlab
def read(key)
backend.read(cache_key(key))
end
+
+ def write(key, value)
+ backend.write(cache_key(key), value)
+ end
+
+ def fetch_without_caching_false(key, &block)
+ value = read(key)
+ return value if value
+
+ value = yield
+
+ # Don't cache false values
+ write(key, value) if value
+
+ value
+ end
end
end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index 2ec871f0754..d95024fccf7 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -1,23 +1,80 @@
module Gitlab
module RepositoryCacheAdapter
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
class_methods do
- # Wraps around the given method and caches its output in Redis and an instance
- # variable.
+ # Caches and strongly memoizes the method.
#
# This only works for methods that do not take any arguments.
- def cache_method(name, fallback: nil, memoize_only: false)
- original = :"_uncached_#{name}"
+ #
+ # name - The name of the method to be cached.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil.
+ def cache_method(name, fallback: nil)
+ uncached_name = alias_uncached_method(name)
+
+ define_method(name) do
+ cache_method_output(name, fallback: fallback) do
+ __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
- alias_method(original, name)
+ # Caches truthy values from the method. All values are strongly memoized,
+ # and cached in RequestStore.
+ #
+ # Currently only used to cache `exists?` since stale false values are
+ # particularly troublesome. This can occur, for example, when an NFS mount
+ # is temporarily down.
+ #
+ # This only works for methods that do not take any arguments.
+ #
+ # name - The name of the method to be cached.
+ def cache_method_asymmetrically(name)
+ uncached_name = alias_uncached_method(name)
define_method(name) do
- cache_method_output(name, fallback: fallback, memoize_only: memoize_only) do
- __send__(original) # rubocop:disable GitlabSecurity/PublicSend
+ cache_method_output_asymmetrically(name) do
+ __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
+
+ # Strongly memoizes the method.
+ #
+ # This only works for methods that do not take any arguments.
+ #
+ # name - The name of the method to be memoized.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil. The fallback value
+ # is not memoized.
+ def memoize_method(name, fallback: nil)
+ uncached_name = alias_uncached_method(name)
+
+ define_method(name) do
+ memoize_method_output(name, fallback: fallback) do
+ __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+
+ # Prepends "_uncached_" to the target method name
+ #
+ # Returns the uncached method name
+ def alias_uncached_method(name)
+ uncached_name = :"_uncached_#{name}"
+
+ alias_method(uncached_name, name)
+
+ uncached_name
+ end
+ end
+
+ # RequestStore-backed RepositoryCache to be used. Should be overridden by
+ # the including class
+ def request_store_cache
+ raise NotImplementedError
end
# RepositoryCache to be used. Should be overridden by the including class
@@ -30,65 +87,93 @@ module Gitlab
raise NotImplementedError
end
- # Caches the supplied block both in a cache and in an instance variable.
+ # Caches and strongly memoizes the supplied block.
#
- # The cache key and instance variable are named the same way as the value of
- # the `key` argument.
+ # name - The name of the method to be cached.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil.
+ def cache_method_output(name, fallback: nil, &block)
+ memoize_method_output(name, fallback: fallback) do
+ cache.fetch(name, &block)
+ end
+ end
+
+ # Caches truthy values from the supplied block. All values are strongly
+ # memoized, and cached in RequestStore.
#
- # This method will return `nil` if the corresponding instance variable is also
- # set to `nil`. This ensures we don't keep yielding the block when it returns
- # `nil`.
+ # Currently only used to cache `exists?` since stale false values are
+ # particularly troublesome. This can occur, for example, when an NFS mount
+ # is temporarily down.
#
- # key - The name of the key to cache the data in.
- # fallback - A value to fall back to in the event of a Git error.
- def cache_method_output(key, fallback: nil, memoize_only: false, &block)
- ivar = cache_instance_variable_name(key)
-
- if instance_variable_defined?(ivar)
- instance_variable_get(ivar)
- else
- # If the repository doesn't exist and a fallback was specified we return
- # that value inmediately. This saves us Rugged/gRPC invocations.
- return fallback unless fallback.nil? || cache.repository.exists?
-
- begin
- value =
- if memoize_only
- yield
- else
- cache.fetch(key, &block)
- end
-
- instance_variable_set(ivar, value)
- rescue Gitlab::Git::Repository::NoRepository
- # Even if the above `#exists?` check passes these errors might still
- # occur (for example because of a non-existing HEAD). We want to
- # gracefully handle this and not cache anything
- fallback
+ # name - The name of the method to be cached.
+ def cache_method_output_asymmetrically(name, &block)
+ memoize_method_output(name) do
+ request_store_cache.fetch(name) do
+ cache.fetch_without_caching_false(name, &block)
end
end
end
+ # Strongly memoizes the supplied block.
+ #
+ # name - The name of the method to be memoized.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil. The fallback value is
+ # not memoized.
+ def memoize_method_output(name, fallback: nil, &block)
+ no_repository_fallback(name, fallback: fallback) do
+ strong_memoize(memoizable_name(name), &block)
+ end
+ end
+
+ # Returns the fallback value if the repository does not exist
+ def no_repository_fallback(name, fallback: nil, &block)
+ # Avoid unnecessary gRPC invocations
+ return fallback if fallback && fallback_early?(name)
+
+ yield
+ rescue Gitlab::Git::Repository::NoRepository
+ # Even if the `#exists?` check in `fallback_early?` passes, these errors
+ # might still occur (for example because of a non-existing HEAD). We
+ # want to gracefully handle this and not memoize anything.
+ fallback
+ end
+
# Expires the caches of a specific set of methods
def expire_method_caches(methods)
- methods.each do |key|
- unless cached_methods.include?(key.to_sym)
- Rails.logger.error "Requested to expire non-existent method '#{key}' for Repository"
+ methods.each do |name|
+ unless cached_methods.include?(name.to_sym)
+ Rails.logger.error "Requested to expire non-existent method '#{name}' for Repository"
next
end
- cache.expire(key)
+ cache.expire(name)
- ivar = cache_instance_variable_name(key)
-
- remove_instance_variable(ivar) if instance_variable_defined?(ivar)
+ clear_memoization(memoizable_name(name))
end
+
+ expire_request_store_method_caches(methods)
end
private
- def cache_instance_variable_name(key)
- :"@#{key.to_s.tr('?!', '')}"
+ def memoizable_name(name)
+ "#{name.to_s.tr('?!', '')}"
+ end
+
+ def expire_request_store_method_caches(methods)
+ methods.each do |name|
+ request_store_cache.expire(name)
+ end
+ end
+
+ # All cached repository methods depend on the existence of a Git repository,
+ # so if the repository doesn't exist, we already know not to call it.
+ def fallback_early?(method_name)
+ # Avoid infinite loop
+ return false if method_name == :exists?
+
+ !exists?
end
end
end
diff --git a/lib/gitlab/tree_summary.rb b/lib/gitlab/tree_summary.rb
index b05d408b1c0..c2955cd374c 100644
--- a/lib/gitlab/tree_summary.rb
+++ b/lib/gitlab/tree_summary.rb
@@ -73,25 +73,29 @@ module Gitlab
end
def fill_last_commits!(entries)
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37433
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- entries.each do |entry|
- raw_commit = repository.last_commit_for_path(commit.id, entry_path(entry))
+ # Ensure the path is in "path/" format
+ ensured_path =
+ if path
+ File.join(*[path, ""])
+ end
+
+ commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit)
- if raw_commit
- commit = resolve_commit(raw_commit)
+ entries.each do |entry|
+ path_key = entry_path(entry)
+ commit = cache_commit(commits_hsh[path_key])
- entry[:commit] = commit
- entry[:commit_path] = commit_path(commit)
- end
+ if commit
+ entry[:commit] = commit
+ entry[:commit_path] = commit_path(commit)
end
end
end
- def resolve_commit(raw_commit)
- return nil unless raw_commit.present?
+ def cache_commit(commit)
+ return nil unless commit.present?
- resolved_commits[raw_commit.id] ||= ::Commit.new(raw_commit, project)
+ resolved_commits[commit.id] ||= commit
end
def commit_path(commit)
diff --git a/rubocop/cop/group_public_or_visible_to_user.rb b/rubocop/cop/group_public_or_visible_to_user.rb
new file mode 100644
index 00000000000..beda0b7f8ba
--- /dev/null
+++ b/rubocop/cop/group_public_or_visible_to_user.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+#
+module RuboCop
+ module Cop
+ # Cop that blacklists the usage of Group.public_or_visible_to_user
+ class GroupPublicOrVisibleToUser < RuboCop::Cop::Cop
+ MSG = '`Group.public_or_visible_to_user` should be used with extreme care. ' \
+ 'Please ensure that you are not using it on its own and that the amount ' \
+ 'of rows being filtered is reasonable.'
+
+ def_node_matcher :public_or_visible_to_user?, <<~PATTERN
+ (send (const nil? :Group) :public_or_visible_to_user ...)
+ PATTERN
+
+ def on_send(node)
+ return unless public_or_visible_to_user?(node)
+
+ add_offense(node, location: :expression)
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index ff929c7b6ce..76d6037706e 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -38,3 +38,4 @@ require_relative 'cop/code_reuse/service_class'
require_relative 'cop/code_reuse/presenter'
require_relative 'cop/code_reuse/serializer'
require_relative 'cop/code_reuse/active_record'
+require_relative 'cop/group_public_or_visible_to_user'
diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb
index ea53f89c844..a5f0ec372d8 100755
--- a/scripts/review_apps/automated_cleanup.rb
+++ b/scripts/review_apps/automated_cleanup.rb
@@ -97,13 +97,13 @@ end
automated_cleanup = AutomatedCleanup.new
timed('Review apps cleanup') do
- automated_cleanup.perform_gitlab_environment_cleanup!(days_for_stop: 5, days_for_delete: 6)
+ automated_cleanup.perform_gitlab_environment_cleanup!(days_for_stop: 2, days_for_delete: 3)
end
puts
timed('Helm releases cleanup') do
- automated_cleanup.perform_helm_releases_cleanup!(days: 7)
+ automated_cleanup.perform_helm_releases_cleanup!(days: 3)
end
exit(0)
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 7202cee04ea..2b28cfd16cc 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -728,4 +728,80 @@ describe ApplicationController do
end
end
end
+
+ context 'X-GitLab-Custom-Error header' do
+ before do
+ sign_in user
+ end
+
+ context 'given a 422 error page' do
+ controller do
+ def index
+ render 'errors/omniauth_error', layout: 'errors', status: 422
+ end
+ end
+
+ it 'sets a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
+ end
+ end
+
+ context 'given a 500 error page' do
+ controller do
+ def index
+ render 'errors/omniauth_error', layout: 'errors', status: 500
+ end
+ end
+
+ it 'sets a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
+ end
+ end
+
+ context 'given a 200 success page' do
+ controller do
+ def index
+ render 'errors/omniauth_error', layout: 'errors', status: 200
+ end
+ end
+
+ it 'does not set a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
+ end
+
+ context 'given a json response' do
+ controller do
+ def index
+ render json: {}, status: :unprocessable_entity
+ end
+ end
+
+ it 'does not set a custom header' do
+ get :index, format: :json
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
+ end
+
+ context 'given a json response for an html request' do
+ controller do
+ def index
+ render json: {}, status: :unprocessable_entity
+ end
+ end
+
+ it 'does not set a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 0d49033c691..5c7415a318d 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -90,6 +90,11 @@ describe Projects::PipelinesController do
context 'when performing gitaly calls', :request_store do
it 'limits the Gitaly requests' do
+ # Isolate from test preparation (Repository#exists? is also cached in RequestStore)
+ RequestStore.end!
+ RequestStore.clear!
+ RequestStore.begin!
+
expect { get_pipelines_index_json }
.to change { Gitlab::GitalyClient.get_request_count }.by(2)
end
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index 088ab114df3..76bc93e9766 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -18,6 +18,23 @@ describe 'Issue Detail', :js do
end
end
+ context 'when issue description has xss snippet' do
+ before do
+ issue.update!(description: '![xss" onload=alert(1);//](a)')
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_requests
+ end
+
+ it 'should encode the description to prevent xss issues' do
+ page.within('.issuable-details .detail-page-description') do
+ expect(page).to have_selector('img', count: 1)
+ expect(find('img')['onerror']).to be_nil
+ expect(find('img')['src']).to end_with('/a')
+ end
+ end
+ end
+
context 'when edited by a user who is later deleted' do
before do
sign_in(user)
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index cd5fef8238e..7c71b4c52e0 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -53,6 +53,18 @@ describe 'Project fork' do
expect(current_path).to have_content(/#{user.namespace.name}/i)
end
+ it 'shows avatars when Gravatar is disabled' do
+ stub_application_setting(gravatar_enabled: false)
+
+ visit project_path(project)
+
+ click_link 'Fork'
+
+ page.within('.fork-thumbnail-container') do
+ expect(page).to have_css('div.identicon')
+ end
+ end
+
it 'shows the forked project on the list' do
visit project_path(project)
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
index 70d59c6e621..07e674216fa 100644
--- a/spec/fixtures/api/schemas/job/job_details.json
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -4,6 +4,9 @@
],
"description": "An extension of job.json with more detailed information",
"required": [
+ "artifact",
+ "runner",
+ "runners",
"has_trace"
],
"properties": {
@@ -12,7 +15,7 @@
"trigger": { "$ref": "trigger.json" },
"deployment_status": { "$ref": "deployment_status.json" },
"runner": { "$ref": "runner.json" },
- "runners": { "type": "runners.json" },
+ "runners": { "$ref": "runners.json" },
"has_trace": { "type": "boolean" }
}
}
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 55ee87163f9..aa0442ab847 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -32,18 +32,6 @@ describe AvatarsHelper do
end
end
- context 'when providing a project path' do
- it_behaves_like 'resource with a default avatar', 'project' do
- let(:resource) { create(:project, name: 'foo') }
- let(:helper_args) { [resource.full_path] }
- end
-
- it_behaves_like 'resource with a custom avatar', 'project' do
- let(:resource) { create(:project, :public, avatar: File.open(uploaded_image_temp_path)) }
- let(:helper_args) { [resource.full_path] }
- end
- end
-
context 'when providing a group' do
it_behaves_like 'resource with a default avatar', 'group' do
let(:resource) { create(:group, name: 'foo') }
@@ -55,18 +43,6 @@ describe AvatarsHelper do
let(:helper_args) { [resource] }
end
end
-
- context 'when providing a group path' do
- it_behaves_like 'resource with a default avatar', 'group' do
- let(:resource) { create(:group, name: 'foo') }
- let(:helper_args) { [resource.full_path] }
- end
-
- it_behaves_like 'resource with a custom avatar', 'group' do
- let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
- let(:helper_args) { [resource.full_path] }
- end
- end
end
describe '#avatar_icon_for' do
diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js
index 0e93c5193a1..47d6492a7a6 100644
--- a/spec/javascripts/ide/components/ide_status_bar_spec.js
+++ b/spec/javascripts/ide/components/ide_status_bar_spec.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import store from '~/ide/stores';
import ideStatusBar from '~/ide/components/ide_status_bar.vue';
+import { rightSidebarViews } from '~/ide/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
import { projectData } from '../mock_data';
@@ -64,7 +65,7 @@ describe('ideStatusBar', () => {
describe('pipeline status', () => {
it('opens right sidebar on clicking icon', done => {
- spyOn(vm, 'setRightPane');
+ spyOn(vm, 'openRightPane');
Vue.set(vm.$store.state.pipelines, 'latestPipeline', {
details: {
status: {
@@ -80,7 +81,7 @@ describe('ideStatusBar', () => {
.then(() => {
vm.$el.querySelector('.ide-status-pipeline button').click();
- expect(vm.setRightPane).toHaveBeenCalledWith('pipelines-list');
+ expect(vm.openRightPane).toHaveBeenCalledWith(rightSidebarViews.pipelines);
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/ide/components/panes/right_spec.js b/spec/javascripts/ide/components/panes/right_spec.js
index c75975d2af6..4899f850cf4 100644
--- a/spec/javascripts/ide/components/panes/right_spec.js
+++ b/spec/javascripts/ide/components/panes/right_spec.js
@@ -25,7 +25,8 @@ describe('IDE right pane', () => {
describe('active', () => {
it('renders merge request button as active', done => {
- vm.$store.state.rightPane = rightSidebarViews.mergeRequestInfo;
+ vm.$store.state.rightPane.isOpen = true;
+ vm.$store.state.rightPane.currentView = rightSidebarViews.mergeRequestInfo.name;
vm.$store.state.currentMergeRequestId = '123';
vm.$store.state.currentProjectId = 'gitlab-ce';
vm.$store.state.currentMergeRequestId = 1;
@@ -41,20 +42,21 @@ describe('IDE right pane', () => {
},
};
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null);
- expect(
- vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'),
- ).toBe('Merge Request');
-
- done();
- });
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null);
+ expect(
+ vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'),
+ ).toBe('Merge Request');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('click', () => {
beforeEach(() => {
- spyOn(vm, 'setRightPane');
+ spyOn(vm, 'open');
});
it('sets view to merge request', done => {
@@ -63,7 +65,7 @@ describe('IDE right pane', () => {
vm.$nextTick(() => {
vm.$el.querySelector('.ide-sidebar-link').click();
- expect(vm.setRightPane).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo);
+ expect(vm.open).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo);
done();
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 0e2e246defd..991fb750876 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -319,8 +319,8 @@ describe('RepoEditor', () => {
});
});
- it('calls updateDimensions when rightPane is updated', done => {
- vm.$store.state.rightPane = 'testing';
+ it('calls updateDimensions when rightPane is opened', done => {
+ vm.$store.state.rightPane.isOpen = true;
vm.$nextTick(() => {
expect(vm.editor.updateDimensions).toHaveBeenCalled();
diff --git a/spec/javascripts/ide/helpers.js b/spec/javascripts/ide/helpers.js
index 3ce9c9fcda1..7e107747346 100644
--- a/spec/javascripts/ide/helpers.js
+++ b/spec/javascripts/ide/helpers.js
@@ -6,6 +6,7 @@ import mergeRequestsState from '~/ide/stores/modules/merge_requests/state';
import pipelinesState from '~/ide/stores/modules/pipelines/state';
import branchesState from '~/ide/stores/modules/branches/state';
import fileTemplatesState from '~/ide/stores/modules/file_templates/state';
+import paneState from '~/ide/stores/modules/pane/state';
export const resetStore = store => {
const newState = {
@@ -15,6 +16,7 @@ export const resetStore = store => {
pipelines: pipelinesState(),
branches: branchesState(),
fileTemplates: fileTemplatesState(),
+ rightPane: paneState(),
};
store.replaceState(newState);
};
diff --git a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
index c29dd9f0d06..734233100ab 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js
@@ -69,11 +69,17 @@ describe('IDE file templates actions', () => {
describe('fetchTemplateTypes', () => {
describe('success', () => {
+ let nextPage;
+
beforeEach(() => {
- mock.onGet(/api\/(.*)\/templates\/licenses/).replyOnce(200, [
- {
- name: 'MIT',
- },
+ mock.onGet(/api\/(.*)\/templates\/licenses/).replyOnce(() => [
+ 200,
+ [
+ {
+ name: 'MIT',
+ },
+ ],
+ { 'X-NEXT-PAGE': nextPage },
]);
});
@@ -116,6 +122,38 @@ describe('IDE file templates actions', () => {
done,
);
});
+
+ it('dispatches actions for next page', done => {
+ nextPage = '2';
+ state.selectedTemplateType = {
+ key: 'licenses',
+ };
+
+ testAction(
+ actions.fetchTemplateTypes,
+ null,
+ state,
+ [],
+ [
+ {
+ type: 'requestTemplateTypes',
+ },
+ {
+ type: 'receiveTemplateTypesSuccess',
+ payload: [
+ {
+ name: 'MIT',
+ },
+ ],
+ },
+ {
+ type: 'fetchTemplateTypes',
+ payload: 2,
+ },
+ ],
+ done,
+ );
+ });
});
describe('error', () => {
diff --git a/spec/javascripts/ide/stores/modules/pane/actions_spec.js b/spec/javascripts/ide/stores/modules/pane/actions_spec.js
new file mode 100644
index 00000000000..f150ded6df5
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pane/actions_spec.js
@@ -0,0 +1,87 @@
+import * as actions from '~/ide/stores/modules/pane/actions';
+import * as types from '~/ide/stores/modules/pane/mutation_types';
+import testAction from 'spec/helpers/vuex_action_helper';
+
+describe('IDE pane module actions', () => {
+ const TEST_VIEW = { name: 'test' };
+ const TEST_VIEW_KEEP_ALIVE = { name: 'test-keep-alive', keepAlive: true };
+
+ describe('toggleOpen', () => {
+ it('dispatches open if closed', done => {
+ testAction(
+ actions.toggleOpen,
+ TEST_VIEW,
+ { isOpen: false },
+ [],
+ [{ type: 'open', payload: TEST_VIEW }],
+ done,
+ );
+ });
+
+ it('dispatches close if opened', done => {
+ testAction(
+ actions.toggleOpen,
+ TEST_VIEW,
+ { isOpen: true },
+ [],
+ [{ type: 'close' }],
+ done,
+ );
+ });
+ });
+
+ describe('open', () => {
+ it('commits SET_OPEN', done => {
+ testAction(
+ actions.open,
+ null,
+ {},
+ [{ type: types.SET_OPEN, payload: true }],
+ [],
+ done,
+ );
+ });
+
+ it('commits SET_CURRENT_VIEW if view is given', done => {
+ testAction(
+ actions.open,
+ TEST_VIEW,
+ {},
+ [
+ { type: types.SET_OPEN, payload: true },
+ { type: types.SET_CURRENT_VIEW, payload: TEST_VIEW.name },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('commits KEEP_ALIVE_VIEW if keepAlive is true', done => {
+ testAction(
+ actions.open,
+ TEST_VIEW_KEEP_ALIVE,
+ {},
+ [
+ { type: types.SET_OPEN, payload: true },
+ { type: types.SET_CURRENT_VIEW, payload: TEST_VIEW_KEEP_ALIVE.name },
+ { type: types.KEEP_ALIVE_VIEW, payload: TEST_VIEW_KEEP_ALIVE.name },
+ ],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('close', () => {
+ it('commits SET_OPEN', done => {
+ testAction(
+ actions.close,
+ null,
+ {},
+ [{ type: types.SET_OPEN, payload: false }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/pane/getters_spec.js b/spec/javascripts/ide/stores/modules/pane/getters_spec.js
new file mode 100644
index 00000000000..2060863b5d6
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pane/getters_spec.js
@@ -0,0 +1,61 @@
+import * as getters from '~/ide/stores/modules/pane/getters';
+import state from '~/ide/stores/modules/pane/state';
+
+describe('IDE pane module getters', () => {
+ const TEST_VIEW = 'test-view';
+ const TEST_KEEP_ALIVE_VIEWS = {
+ [TEST_VIEW]: true,
+ };
+
+ describe('isActiveView', () => {
+ it('returns true if given view matches currentView', () => {
+ const result = getters.isActiveView({ currentView: 'A' })('A');
+
+ expect(result).toBe(true);
+ });
+
+ it('returns false if given view does not match currentView', () => {
+ const result = getters.isActiveView({ currentView: 'A' })('B');
+
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('isAliveView', () => {
+ it('returns true if given view is in keepAliveViews', () => {
+ const result = getters.isAliveView(
+ { keepAliveViews: TEST_KEEP_ALIVE_VIEWS },
+ {},
+ )(TEST_VIEW);
+
+ expect(result).toBe(true);
+ });
+
+ it('returns true if given view is active view and open', () => {
+ const result = getters.isAliveView(
+ { ...state(), isOpen: true },
+ { isActiveView: () => true },
+ )(TEST_VIEW);
+
+ expect(result).toBe(true);
+ });
+
+ it('returns false if given view is active view and closed', () => {
+ const result = getters.isAliveView(
+ state(),
+ { isActiveView: () => true },
+ )(TEST_VIEW);
+
+ expect(result).toBe(false);
+ });
+
+ it('returns false if given view is not activeView', () => {
+ const result = getters.isAliveView(
+ { ...state(), isOpen: true },
+ { isActiveView: () => false },
+ )(TEST_VIEW);
+
+ expect(result).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js b/spec/javascripts/ide/stores/modules/pane/mutations_spec.js
new file mode 100644
index 00000000000..b5fcd35912e
--- /dev/null
+++ b/spec/javascripts/ide/stores/modules/pane/mutations_spec.js
@@ -0,0 +1,42 @@
+import state from '~/ide/stores/modules/pane/state';
+import mutations from '~/ide/stores/modules/pane/mutations';
+import * as types from '~/ide/stores/modules/pane/mutation_types';
+
+describe('IDE pane module mutations', () => {
+ const TEST_VIEW = 'test-view';
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('SET_OPEN', () => {
+ it('sets isOpen', () => {
+ mockedState.isOpen = false;
+
+ mutations[types.SET_OPEN](mockedState, true);
+
+ expect(mockedState.isOpen).toBe(true);
+ });
+ });
+
+ describe('SET_CURRENT_VIEW', () => {
+ it('sets currentView', () => {
+ mockedState.currentView = null;
+
+ mutations[types.SET_CURRENT_VIEW](mockedState, TEST_VIEW);
+
+ expect(mockedState.currentView).toEqual(TEST_VIEW);
+ });
+ });
+
+ describe('KEEP_ALIVE_VIEW', () => {
+ it('adds entry to keepAliveViews', () => {
+ mutations[types.KEEP_ALIVE_VIEW](mockedState, TEST_VIEW);
+
+ expect(mockedState.keepAliveViews).toEqual({
+ [TEST_VIEW]: true,
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
index 91edb388791..d85354c3681 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js
@@ -315,29 +315,29 @@ describe('IDE pipelines actions', () => {
'job',
mockedState,
[{ type: types.SET_DETAIL_JOB, payload: 'job' }],
- [{ type: 'setRightPane', payload: 'jobs-detail' }],
+ [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }],
done,
);
});
- it('dispatches setRightPane as pipeline when job is null', done => {
+ it('dispatches rightPane/open as pipeline when job is null', done => {
testAction(
setDetailJob,
null,
mockedState,
[{ type: types.SET_DETAIL_JOB, payload: null }],
- [{ type: 'setRightPane', payload: rightSidebarViews.pipelines }],
+ [{ type: 'rightPane/open', payload: rightSidebarViews.pipelines }],
done,
);
});
- it('dispatches setRightPane as job', done => {
+ it('dispatches rightPane/open as job', done => {
testAction(
setDetailJob,
'job',
mockedState,
[{ type: types.SET_DETAIL_JOB, payload: 'job' }],
- [{ type: 'setRightPane', payload: rightSidebarViews.jobsDetail }],
+ [{ type: 'rightPane/open', payload: rightSidebarViews.jobsDetail }],
done,
);
});
diff --git a/spec/javascripts/issue_show/index_spec.js b/spec/javascripts/issue_show/index_spec.js
new file mode 100644
index 00000000000..fa0b426c06c
--- /dev/null
+++ b/spec/javascripts/issue_show/index_spec.js
@@ -0,0 +1,19 @@
+import initIssueableApp from '~/issue_show';
+
+describe('Issue show index', () => {
+ describe('initIssueableApp', () => {
+ it('should initialize app with no potential XSS attack', () => {
+ const d = document.createElement('div');
+ d.id = 'js-issuable-app-initial-data';
+ d.innerHTML = JSON.stringify({
+ initialDescriptionHtml: '&lt;img src=x onerror=alert(1)&gt;',
+ });
+ document.body.appendChild(d);
+
+ const alertSpy = spyOn(window, 'alert');
+ initIssueableApp();
+
+ expect(alertSpy).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index c36b607a34e..d193667210b 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
@@ -5,13 +5,13 @@ describe('Pagination component', () => {
let component;
let PaginationComponent;
let spy;
- let mountComponet;
+ let mountComponent;
beforeEach(() => {
spy = jasmine.createSpy('spy');
PaginationComponent = Vue.extend(paginationComp);
- mountComponet = function (props) {
+ mountComponent = function (props) {
return new PaginationComponent({
propsData: props,
}).$mount();
@@ -20,7 +20,7 @@ describe('Pagination component', () => {
describe('render', () => {
it('should not render anything', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 1,
page: 1,
@@ -37,7 +37,7 @@ describe('Pagination component', () => {
describe('prev button', () => {
it('should be disabled and non clickable', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 2,
page: 1,
@@ -59,7 +59,7 @@ describe('Pagination component', () => {
});
it('should be enabled and clickable', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
@@ -78,7 +78,7 @@ describe('Pagination component', () => {
describe('first button', () => {
it('should call the change callback with the first page', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
@@ -102,7 +102,7 @@ describe('Pagination component', () => {
describe('last button', () => {
it('should call the change callback with the last page', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
@@ -126,7 +126,7 @@ describe('Pagination component', () => {
describe('next button', () => {
it('should be disabled and non clickable', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 5,
page: 5,
@@ -148,7 +148,7 @@ describe('Pagination component', () => {
});
it('should be enabled and clickable', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
@@ -168,7 +168,7 @@ describe('Pagination component', () => {
describe('numbered buttons', () => {
it('should render 5 pages', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
@@ -185,7 +185,7 @@ describe('Pagination component', () => {
});
it('should render the spread operator', () => {
- component = mountComponet({
+ component = mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index ca319679e80..9633caac788 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -11,10 +11,6 @@ describe Backup::Manager do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
- allow_any_instance_of(String).to receive(:color) do |string, _color|
- string
- end
-
@old_progress = $progress # rubocop:disable Style/GlobalVars
$progress = progress # rubocop:disable Style/GlobalVars
end
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index c5a854b5660..fdeea814bb2 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -11,10 +11,6 @@ describe Backup::Repository do
allow(FileUtils).to receive(:mkdir_p).and_return(true)
allow(FileUtils).to receive(:mv).and_return(true)
- allow_any_instance_of(String).to receive(:color) do |string, _color|
- string
- end
-
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index ba8dc68ceda..ed1ebe9ebf6 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -83,6 +83,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error
end
+ it 'does not raise an exception with a space in the path' do
+ act = link("/uploads/d18213acd3732630991986120e167e3d/Landscape_8.jpg \nBut here's some more unexpected text :smile:)")
+ expect { filter(act) }.not_to raise_error
+ end
+
it 'ignores ref if commit is passed' do
doc = filter(link('non/existent.file'), commit: project.commit('empty-branch') )
expect(doc.at_css('a')['href'])
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
new file mode 100644
index 00000000000..2a869446753
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
+ let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
+ let(:web_hooks) { table(:web_hooks) }
+
+ let(:plaintext_attrs) do
+ {
+ 'encrypted_token' => nil,
+ 'encrypted_url' => nil,
+ 'token' => 'secret',
+ 'url' => 'http://example.com?access_token=secret'
+ }
+ end
+
+ let(:encrypted_attrs) do
+ {
+ 'encrypted_token' => be_present,
+ 'encrypted_url' => be_present,
+ 'token' => nil,
+ 'url' => nil
+ }
+ end
+
+ describe '#perform' do
+ it 'encrypts columns for the specified range' do
+ hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id)
+
+ # Encrypt all but the first and last rows
+ subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id)
+
+ hooks = web_hooks.where(id: hooks.map(&:id)).order(:id)
+
+ aggregate_failures do
+ expect(hooks[0]).to have_attributes(plaintext_attrs)
+ expect(hooks[1]).to have_attributes(encrypted_attrs)
+ expect(hooks[2]).to have_attributes(encrypted_attrs)
+ expect(hooks[3]).to have_attributes(encrypted_attrs)
+ expect(hooks[4]).to have_attributes(plaintext_attrs)
+ end
+ end
+
+ it 'acquires an exclusive lock for the update' do
+ relation = double('relation', each: nil)
+
+ expect(model).to receive(:where) { relation }
+ expect(relation).to receive(:lock) { relation }
+
+ subject.perform(model, [:token, :url], 1, 1)
+ end
+
+ it 'skips already-encrypted columns' do
+ values = {
+ 'encrypted_token' => 'known encrypted token',
+ 'encrypted_url' => 'known encrypted url',
+ 'token' => 'token',
+ 'url' => 'url'
+ }
+
+ hook = web_hooks.create(values)
+
+ subject.perform(model, [:token, :url], hook.id, hook.id)
+
+ hook.reload
+
+ expect(hook).to have_attributes(values)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
new file mode 100644
index 00000000000..c7b272cd6ca
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do
+ let(:migration) { described_class.new }
+
+ let!(:internal_pipeline) { create(:ci_pipeline, source: :web) }
+ let(:pipelines) { [internal_pipeline, unknown_pipeline].map(&:id) }
+
+ let!(:unknown_pipeline) do
+ build(:ci_pipeline, source: :unknown)
+ .tap { |pipeline| pipeline.save(validate: false) }
+ end
+
+ subject { migration.perform(pipelines.min, pipelines.max) }
+
+ shared_examples 'no changes' do
+ it 'does not change the pipeline source' do
+ expect { subject }.not_to change { unknown_pipeline.reload.source }
+ end
+ end
+
+ context 'when unknown pipeline is external' do
+ before do
+ create(:generic_commit_status, pipeline: unknown_pipeline)
+ end
+
+ it 'populates the pipeline source' do
+ subject
+
+ expect(unknown_pipeline.reload.source).to eq('external')
+ end
+
+ it 'can be repeated without effect' do
+ subject
+
+ expect { subject }.not_to change { unknown_pipeline.reload.source }
+ end
+ end
+
+ context 'when unknown pipeline has just a build' do
+ before do
+ create(:ci_build, pipeline: unknown_pipeline)
+ end
+
+ it_behaves_like 'no changes'
+ end
+
+ context 'when unknown pipeline has no statuses' do
+ it_behaves_like 'no changes'
+ end
+
+ context 'when unknown pipeline has a build and a status' do
+ before do
+ create(:generic_commit_status, pipeline: unknown_pipeline)
+ create(:ci_build, pipeline: unknown_pipeline)
+ end
+
+ it_behaves_like 'no changes'
+ end
+end
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 9ca960502c8..98f1696badb 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -6,10 +6,10 @@ describe Gitlab::DataBuilder::Pipeline do
let(:pipeline) do
create(:ci_pipeline,
- project: project,
- status: 'success',
- sha: project.commit.sha,
- ref: project.default_branch)
+ project: project,
+ status: 'success',
+ sha: project.commit.sha,
+ ref: project.default_branch)
end
let!(:build) { create(:ci_build, pipeline: pipeline) }
@@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do
let(:build_data) { data[:builds].first }
let(:project_data) { data[:project] }
- it { expect(attributes).to be_a(Hash) }
- it { expect(attributes[:ref]).to eq(pipeline.ref) }
- it { expect(attributes[:sha]).to eq(pipeline.sha) }
- it { expect(attributes[:tag]).to eq(pipeline.tag) }
- it { expect(attributes[:id]).to eq(pipeline.id) }
- it { expect(attributes[:status]).to eq(pipeline.status) }
- it { expect(attributes[:detailed_status]).to eq('passed') }
+ it 'has correct attributes' do
+ expect(attributes).to be_a(Hash)
+ expect(attributes[:ref]).to eq(pipeline.ref)
+ expect(attributes[:sha]).to eq(pipeline.sha)
+ expect(attributes[:tag]).to eq(pipeline.tag)
+ expect(attributes[:id]).to eq(pipeline.id)
+ expect(attributes[:status]).to eq(pipeline.status)
+ expect(attributes[:detailed_status]).to eq('passed')
+ expect(build_data).to be_a(Hash)
+ expect(build_data[:id]).to eq(build.id)
+ expect(build_data[:status]).to eq(build.status)
+ expect(project_data).to eq(project.hook_attrs(backward: false))
+ end
- it { expect(build_data).to be_a(Hash) }
- it { expect(build_data[:id]).to eq(build.id) }
- it { expect(build_data[:status]).to eq(build.status) }
+ context 'pipeline without variables' do
+ it 'has empty variables hash' do
+ expect(attributes[:variables]).to be_a(Array)
+ expect(attributes[:variables]).to be_empty()
+ end
+ end
- it { expect(project_data).to eq(project.hook_attrs(backward: false)) }
+ context 'pipeline with variables' do
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:data) { described_class.build(pipeline) }
+ let(:attributes) { data[:object_attributes] }
+ let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1') }
+
+ it { expect(attributes[:variables]).to be_a(Array) }
+ it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
+ end
end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 3c8cf9c56cc..5d0a603d11d 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -8,6 +8,20 @@ describe Gitlab::Diff::Highlight do
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
+ shared_examples 'without inline diffs' do
+ let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' }
+
+ before do
+ allow(Gitlab::Diff::InlineDiff).to receive(:for_lines).and_return([])
+ allow_any_instance_of(Gitlab::Diff::Line).to receive(:text).and_return(code)
+ end
+
+ it 'returns html escaped diff text' do
+ expect(subject[1].rich_text).to eq html_escape(code)
+ expect(subject[1].rich_text).to be_html_safe
+ end
+ end
+
describe '#highlight' do
context "with a diff file" do
let(:subject) { described_class.new(diff_file, repository: project.repository).highlight }
@@ -38,6 +52,16 @@ describe Gitlab::Diff::Highlight do
expect(subject[5].rich_text).to eq(code)
end
+
+ context 'when no diff_refs' do
+ before do
+ allow(diff_file).to receive(:diff_refs).and_return(nil)
+ end
+
+ context 'when no inline diffs' do
+ it_behaves_like 'without inline diffs'
+ end
+ end
end
context "with diff lines" do
@@ -93,6 +117,10 @@ describe Gitlab::Diff::Highlight do
expect { subject }. to raise_exception(RangeError)
end
end
+
+ context 'when no inline diffs' do
+ it_behaves_like 'without inline diffs'
+ end
end
end
end
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index 29e61d15726..88f7099ff3c 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -56,5 +56,22 @@ describe Gitlab::Highlight do
described_class.highlight('file.name', 'Contents')
end
+
+ context 'timeout' do
+ subject { described_class.new('file.name', 'Contents') }
+
+ it 'utilizes timeout for web' do
+ expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_FOREGROUND).and_call_original
+
+ subject.highlight("Content")
+ end
+
+ it 'utilizes longer timeout for sidekiq' do
+ allow(Sidekiq).to receive(:server?).and_return(true)
+ expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original
+
+ subject.highlight("Content")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 5bd4d6c6a48..0295138fc3a 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -65,6 +65,144 @@ describe Gitlab::RepositoryCacheAdapter do
end
end
+ describe '#cache_method_output_asymmetrically', :use_clean_rails_memory_store_caching, :request_store do
+ let(:request_store_cache) { repository.send(:request_store_cache) }
+
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) } # No repository
+ let(:object) { double }
+
+ subject do
+ repository.cache_method_output_asymmetrically(:cats) do
+ object.cats_call_stub
+ end
+ end
+
+ it 'returns the output of the original method' do
+ expect(object).to receive(:cats_call_stub).and_return('output')
+
+ expect(subject).to eq('output')
+ end
+ end
+
+ context 'with a method throwing a non-existing-repository error' do
+ subject do
+ repository.cache_method_output_asymmetrically(:cats) do
+ raise Gitlab::Git::Repository::NoRepository
+ end
+ end
+
+ it 'returns nil' do
+ expect(subject).to eq(nil)
+ end
+
+ it 'does not cache the data' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ expect(cache.exist?(:cats)).to eq(false)
+ end
+ end
+
+ context 'with an existing repository' do
+ let(:object) { double }
+
+ context 'when it returns truthy' do
+ before do
+ expect(object).to receive(:cats).once.and_return('truthy output')
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { request_store_cache.read(:cats) }.from(nil).to('truthy output')
+ end
+
+ it 'caches the output in RepositoryCache' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { cache.read(:cats) }.from(nil).to('truthy output')
+ end
+ end
+
+ context 'when it returns false' do
+ before do
+ expect(object).to receive(:cats).once.and_return(false)
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.to change { request_store_cache.read(:cats) }.from(nil).to(false)
+ end
+
+ it 'does NOT cache the output in RepositoryCache' do
+ expect do
+ repository.cache_method_output_asymmetrically(:cats) { object.cats }
+ end.not_to change { cache.read(:cats) }.from(nil)
+ end
+ end
+ end
+ end
+
+ describe '#memoize_method_output' do
+ let(:fallback) { 10 }
+
+ context 'with a non-existing repository' do
+ let(:project) { create(:project) } # No repository
+
+ subject do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ repository.cats_call_stub
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'avoids calling the original method' do
+ expect(repository).not_to receive(:cats_call_stub)
+
+ subject
+ end
+
+ it 'does not set the instance variable' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ end
+ end
+
+ context 'with a method throwing a non-existing-repository error' do
+ subject do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ raise Gitlab::Git::Repository::NoRepository
+ end
+ end
+
+ it 'returns the fallback value' do
+ expect(subject).to eq(fallback)
+ end
+
+ it 'does not set the instance variable' do
+ subject
+
+ expect(repository.instance_variable_defined?(:@cats)).to eq(false)
+ end
+ end
+
+ context 'with an existing repository' do
+ it 'sets the instance variable' do
+ repository.memoize_method_output(:cats, fallback: fallback) do
+ 'block output'
+ end
+
+ expect(repository.instance_variable_get(:@cats)).to eq('block output')
+ end
+ end
+ end
+
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
diff --git a/spec/lib/gitlab/repository_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index fc259cf1208..741ee12633f 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -47,4 +47,89 @@ describe Gitlab::RepositoryCache do
expect(backend).to have_received(:fetch).with("baz:#{namespace}", &p)
end
end
+
+ describe '#fetch_without_caching_false', :use_clean_rails_memory_store_caching do
+ let(:key) { :foo }
+ let(:backend) { Rails.cache }
+
+ it 'requires a block' do
+ expect do
+ cache.fetch_without_caching_false(key)
+ end.to raise_error(LocalJumpError)
+ end
+
+ context 'when the key does not exist in the cache' do
+ context 'when the result of the block is truthy' do
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key) { true }
+
+ expect(result).to be true
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", true)
+
+ cache.fetch_without_caching_false(key) { true }
+ end
+ end
+
+ context 'when the result of the block is falsey' do
+ let(:p) { -> { false } }
+
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key, &p)
+
+ expect(result).to be false
+ end
+
+ it 'does not cache the value' do
+ expect(backend).not_to receive(:write).with("#{key}:#{namespace}", true)
+
+ cache.fetch_without_caching_false(key, &p)
+ end
+ end
+ end
+
+ context 'when the cached value is truthy' do
+ before do
+ backend.write("#{key}:#{namespace}", true)
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch_without_caching_false(key) { 'block result' }
+
+ expect(result).to be true
+ end
+
+ it 'does not execute the block' do
+ expect do |b|
+ cache.fetch_without_caching_false(key, &b)
+ end.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch_without_caching_false(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is falsey' do
+ before do
+ backend.write("#{key}:#{namespace}", false)
+ end
+
+ it 'returns the result of the block' do
+ result = cache.fetch_without_caching_false(key) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'writes the truthy value to the cache' do
+ expect(backend).to receive(:write).with("#{key}:#{namespace}", 'block result')
+
+ cache.fetch_without_caching_false(key) { 'block result' }
+ end
+ end
+ end
end
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index 5ed2f4400bc..fbaa8d47a71 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -40,13 +40,14 @@ describe BlobViewer::PackageJson do
end
context 'when package.json has "private": true' do
+ let(:homepage) { 'http://example.com' }
let(:data) do
<<-SPEC.strip_heredoc
{
"name": "module-name",
"version": "10.3.1",
"private": true,
- "homepage": "myawesomepackage.com"
+ "homepage": #{homepage.to_json}
}
SPEC
end
@@ -54,10 +55,22 @@ describe BlobViewer::PackageJson do
subject { described_class.new(blob) }
describe '#package_url' do
- it 'returns homepage if any' do
- expect(subject).to receive(:prepare!)
+ context 'when the homepage has a valid URL' do
+ it 'returns homepage URL' do
+ expect(subject).to receive(:prepare!)
+
+ expect(subject.package_url).to eq(homepage)
+ end
+ end
+
+ context 'when the homepage has an invalid URL' do
+ let(:homepage) { 'javascript:alert()' }
+
+ it 'returns nil' do
+ expect(subject).to receive(:prepare!)
- expect(subject.package_url).to eq('myawesomepackage.com')
+ expect(subject.package_url).to be_nil
+ end
end
end
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index 889c243c8d8..03d09cb31d6 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -5,4 +5,13 @@ describe Ci::PipelineVariable do
it { is_expected.to include_module(HasVariable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) }
+
+ describe '#hook_attrs' do
+ let(:variable) { create(:ci_pipeline_variable, key: 'foo', value: 'bar') }
+
+ subject { variable.hook_attrs }
+
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to eq({ key: 'foo', value: 'bar' }) }
+ end
end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index c1eac4fa489..81748681528 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -148,9 +148,14 @@ describe Event do
let(:admin) { create(:admin) }
let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
+ let(:project_snippet) { create(:project_snippet, :public, project: project, author: author) }
+ let(:personal_snippet) { create(:personal_snippet, :public, author: author) }
let(:note_on_commit) { create(:note_on_commit, project: project) }
let(:note_on_issue) { create(:note_on_issue, noteable: issue, project: project) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project) }
+ let(:note_on_project_snippet) { create(:note_on_project_snippet, author: author, noteable: project_snippet, project: project) }
+ let(:note_on_personal_snippet) { create(:note_on_personal_snippet, author: author, noteable: personal_snippet, project: nil) }
+ let(:milestone_on_project) { create(:milestone, project: project) }
let(:event) { described_class.new(project: project, target: target, author_id: author.id) }
before do
@@ -268,6 +273,125 @@ describe Event do
end
end
end
+
+ context 'milestone event' do
+ let(:target) { milestone_on_project }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_truthy
+ expect(event.visible_to_user?(non_member)).to be_truthy
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+
+ context 'on public project with private issue tracker and merge requests' do
+ let(:project) { create(:project, :public, :issues_private, :merge_requests_private) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_falsy
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+ end
+
+ context 'on private project' do
+ let(:project) { create(:project, :private) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_falsy
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+ end
+ end
+
+ context 'project snippet note event' do
+ let(:target) { note_on_project_snippet }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_truthy
+ expect(event.visible_to_user?(non_member)).to be_truthy
+ expect(event.visible_to_user?(author)).to be_truthy
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+
+ context 'on public project with private snippets' do
+ let(:project) { create(:project, :public, :snippets_private) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_falsy
+
+ # Normally, we'd expect the author of a comment to be able to view it.
+ # However, this doesn't seem to be the case for comments on snippets.
+ expect(event.visible_to_user?(author)).to be_falsy
+
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+ end
+
+ context 'on private project' do
+ let(:project) { create(:project, :private) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_falsy
+
+ # Normally, we'd expect the author of a comment to be able to view it.
+ # However, this doesn't seem to be the case for comments on snippets.
+ expect(event.visible_to_user?(author)).to be_falsy
+
+ expect(event.visible_to_user?(member)).to be_truthy
+ expect(event.visible_to_user?(guest)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+ end
+ end
+
+ context 'personal snippet note event' do
+ let(:target) { note_on_personal_snippet }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_truthy
+ expect(event.visible_to_user?(non_member)).to be_truthy
+ expect(event.visible_to_user?(author)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+
+ context 'on internal snippet' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: author) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_truthy
+ expect(event.visible_to_user?(author)).to be_truthy
+ expect(event.visible_to_user?(admin)).to be_truthy
+ end
+ end
+
+ context 'on private snippet' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: author) }
+
+ it do
+ expect(event.visible_to_user?(nil)).to be_falsy
+ expect(event.visible_to_user?(non_member)).to be_falsy
+ expect(event.visible_to_user?(author)).to be_truthy
+
+ # It is very unexpected that a private personal snippet is not visible
+ # to an instance administrator. This should be fixed in the future.
+ expect(event.visible_to_user?(admin)).to be_falsy
+ end
+ end
+ end
end
describe '.limit_recent' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 0729eb99e78..1bf8f89e126 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -169,22 +169,42 @@ describe Group do
end
end
- describe '.visible_to_user' do
- let!(:group) { create(:group) }
- let!(:user) { create(:user) }
+ describe '.public_or_visible_to_user' do
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
- subject { described_class.visible_to_user(user) }
+ subject { described_class.public_or_visible_to_user(user) }
- describe 'when the user has access to a group' do
- before do
- group.add_user(user, Gitlab::Access::MAINTAINER)
- end
+ context 'when user is nil' do
+ let!(:user) { nil }
- it { is_expected.to eq([group]) }
+ it { is_expected.to match_array([group]) }
end
- describe 'when the user does not have access to any groups' do
- it { is_expected.to eq([]) }
+ context 'when user' do
+ let!(:user) { create(:user) }
+
+ context 'when user does not have access to any private group' do
+ it { is_expected.to match_array([internal_group, group]) }
+ end
+
+ context 'when user is a member of private group' do
+ before do
+ private_group.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ it { is_expected.to match_array([private_group, internal_group, group]) }
+ end
+
+ context 'when user is a member of private subgroup', :postgresql do
+ let!(:private_subgroup) { create(:group, :private, parent: private_group) }
+
+ before do
+ private_subgroup.add_user(user, Gitlab::Access::DEVELOPER)
+ end
+
+ it { is_expected.to match_array([private_subgroup, internal_group, group]) }
+ end
end
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index a4181631f01..a308ac6e33a 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -57,6 +57,12 @@ describe WebHook do
end
end
+ describe 'encrypted attributes' do
+ subject { described_class.encrypted_attributes.keys }
+
+ it { is_expected.to contain_exactly(:token, :url) }
+ end
+
describe 'execute' do
let(:data) { { key: 'value' } }
let(:hook_name) { 'project hook' }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index dffac05152b..784d17e271e 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -188,6 +188,57 @@ describe Repository do
end
end
+ describe '#list_last_commits_for_tree' do
+ let(:path_to_commit) do
+ {
+ "encoding" => "913c66a37b4a45b9769037c55c2d238bd0942d2e",
+ "files" => "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
+ ".gitignore" => "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
+ ".gitmodules" => "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
+ "CHANGELOG" => "913c66a37b4a45b9769037c55c2d238bd0942d2e",
+ "CONTRIBUTING.md" => "6d394385cf567f80a8fd85055db1ab4c5295806f",
+ "Gemfile.zip" => "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
+ "LICENSE" => "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863",
+ "MAINTENANCE.md" => "913c66a37b4a45b9769037c55c2d238bd0942d2e",
+ "PROCESS.md" => "913c66a37b4a45b9769037c55c2d238bd0942d2e",
+ "README.md" => "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863",
+ "VERSION" => "913c66a37b4a45b9769037c55c2d238bd0942d2e",
+ "gitlab-shell" => "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
+ "six" => "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ }
+ end
+
+ subject { repository.list_last_commits_for_tree(sample_commit.id, '.').id }
+
+ it 'returns the last commits for every entry in the current path' do
+ result = repository.list_last_commits_for_tree(sample_commit.id, '.')
+
+ result.each do |key, value|
+ result[key] = value.id
+ end
+
+ expect(result).to include(path_to_commit)
+ end
+
+ it 'returns the last commits for every entry in the current path starting from the offset' do
+ result = repository.list_last_commits_for_tree(sample_commit.id, '.', offset: path_to_commit.size - 1)
+
+ expect(result.size).to eq(1)
+ end
+
+ it 'returns a limited number of last commits for every entry in the current path starting from the offset' do
+ result = repository.list_last_commits_for_tree(sample_commit.id, '.', limit: 1)
+
+ expect(result.size).to eq(1)
+ end
+
+ it 'returns an empty hash when offset is out of bounds' do
+ result = repository.list_last_commits_for_tree(sample_commit.id, '.', offset: path_to_commit.size)
+
+ expect(result.size).to eq(0)
+ end
+ end
+
describe '#last_commit_for_path' do
shared_examples 'getting last commit for path' do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
@@ -1044,6 +1095,47 @@ describe Repository do
expect_to_raise_storage_error { broken_repository.exists? }
end
end
+
+ context 'asymmetric caching', :use_clean_rails_memory_store_caching, :request_store do
+ let(:cache) { repository.send(:cache) }
+ let(:request_store_cache) { repository.send(:request_store_cache) }
+
+ context 'when it returns true' do
+ before do
+ expect(repository.raw_repository).to receive(:exists?).once.and_return(true)
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.exists?
+ end.to change { request_store_cache.read(:exists?) }.from(nil).to(true)
+ end
+
+ it 'caches the output in RepositoryCache' do
+ expect do
+ repository.exists?
+ end.to change { cache.read(:exists?) }.from(nil).to(true)
+ end
+ end
+
+ context 'when it returns false' do
+ before do
+ expect(repository.raw_repository).to receive(:exists?).once.and_return(false)
+ end
+
+ it 'caches the output in RequestStore' do
+ expect do
+ repository.exists?
+ end.to change { request_store_cache.read(:exists?) }.from(nil).to(false)
+ end
+
+ it 'does NOT cache the output in RepositoryCache' do
+ expect do
+ repository.exists?
+ end.not_to change { cache.read(:exists?) }.from(nil)
+ end
+ end
+ end
end
describe '#has_visible_content?' do
@@ -1716,12 +1808,19 @@ describe Repository do
describe '#expire_exists_cache' do
let(:cache) { repository.send(:cache) }
+ let(:request_store_cache) { repository.send(:request_store_cache) }
it 'expires the cache' do
expect(cache).to receive(:expire).with(:exists?)
repository.expire_exists_cache
end
+
+ it 'expires the request store cache', :request_store do
+ expect(request_store_cache).to receive(:expire).with(:exists?)
+
+ repository.expire_exists_cache
+ end
end
describe '#xcode_project?' do
@@ -1892,7 +1991,7 @@ describe Repository do
match[1].to_sym if match
end.compact
- expect(methods).to match_array(Repository::CACHED_METHODS + Repository::MEMOIZED_CACHED_METHODS)
+ expect(Repository::CACHED_METHODS + Repository::MEMOIZED_CACHED_METHODS).to include(*methods)
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 3a8948f8477..3802b5c6848 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -155,7 +155,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq(Group.visible_to_user(user1).order(:name).pluck(:name))
+ expect(response_groups).to eq(groups_visible_to_user(user1).order(:name).pluck(:name))
end
it "sorts in descending order when passed" do
@@ -164,7 +164,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq(Group.visible_to_user(user1).order(name: :desc).pluck(:name))
+ expect(response_groups).to eq(groups_visible_to_user(user1).order(name: :desc).pluck(:name))
end
it "sorts by path in order_by param" do
@@ -173,7 +173,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq(Group.visible_to_user(user1).order(:path).pluck(:name))
+ expect(response_groups).to eq(groups_visible_to_user(user1).order(:path).pluck(:name))
end
it "sorts by id in the order_by param" do
@@ -182,7 +182,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq(Group.visible_to_user(user1).order(:id).pluck(:name))
+ expect(response_groups).to eq(groups_visible_to_user(user1).order(:id).pluck(:name))
end
it "sorts also by descending id with pagination fix" do
@@ -191,7 +191,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
- expect(response_groups).to eq(Group.visible_to_user(user1).order(id: :desc).pluck(:name))
+ expect(response_groups).to eq(groups_visible_to_user(user1).order(id: :desc).pluck(:name))
end
it "sorts identical keys by id for good pagination" do
@@ -211,6 +211,10 @@ describe API::Groups do
expect(json_response).to be_an Array
expect(response_groups_ids).to eq(Group.select { |group| group['name'] == 'same-name' }.map { |group| group['id'] }.sort)
end
+
+ def groups_visible_to_user(user)
+ Group.where(id: user.authorized_groups.select(:id).reorder(nil))
+ end
end
context 'when using owned in the request' do
diff --git a/spec/requests/api/redacted_events_spec.rb b/spec/requests/api/redacted_events_spec.rb
new file mode 100644
index 00000000000..086dd3df9ba
--- /dev/null
+++ b/spec/requests/api/redacted_events_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe 'Redacted events in API::Events' do
+ shared_examples 'private events are redacted' do
+ it 'redacts events the user does not have access to' do
+ expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original
+
+ get api(path), user
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to contain_exactly(
+ 'project_id' => nil,
+ 'action_name' => nil,
+ 'target_id' => nil,
+ 'target_iid' => nil,
+ 'target_type' => nil,
+ 'author_id' => nil,
+ 'target_title' => 'Confidential event',
+ 'created_at' => nil,
+ 'author_username' => nil
+ )
+ end
+ end
+
+ describe '/users/:id/events' do
+ let(:project) { create(:project, :public) }
+ let(:path) { "/users/#{project.owner.id}/events" }
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ before do
+ EventCreateService.new.open_issue(issue, issue.author)
+ end
+
+ context 'unauthenticated user views another user with private events' do
+ let(:user) { nil }
+
+ include_examples 'private events are redacted'
+ end
+
+ context 'authenticated user without access views another user with private events' do
+ let(:user) { create(:user) }
+
+ include_examples 'private events are redacted'
+ end
+ end
+
+ describe '/projects/:id/events' do
+ let(:project) { create(:project, :public) }
+ let(:path) { "/projects/#{project.id}/events" }
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ before do
+ EventCreateService.new.open_issue(issue, issue.author)
+ end
+
+ context 'unauthenticated user views public project' do
+ let(:user) { nil }
+
+ include_examples 'private events are redacted'
+ end
+
+ context 'authenticated user without access views public project' do
+ let(:user) { create(:user) }
+
+ include_examples 'private events are redacted'
+ end
+ end
+end
diff --git a/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
new file mode 100644
index 00000000000..7b5235a3da7
--- /dev/null
+++ b/spec/rubocop/cop/group_public_or_visible_to_user_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/group_public_or_visible_to_user'
+
+describe RuboCop::Cop::GroupPublicOrVisibleToUser do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of Group.public_or_visible_to_user with a constant receiver' do
+ inspect_source('Group.public_or_visible_to_user')
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not flat the use of public_or_visible_to_user with a constant that is not Group' do
+ inspect_source('Project.public_or_visible_to_user')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ it 'does not flag the use of Group.public_or_visible_to_user with a send receiver' do
+ inspect_source('foo.public_or_visible_to_user')
+
+ expect(cop.offenses.size).to eq(0)
+ end
+end
diff --git a/spec/serializers/diff_line_entity_spec.rb b/spec/serializers/diff_line_entity_spec.rb
new file mode 100644
index 00000000000..2549f64bcd3
--- /dev/null
+++ b/spec/serializers/diff_line_entity_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DiffLineEntity do
+ include RepoHelpers
+
+ let(:code) { 'hello world' }
+ let(:line) { Gitlab::Diff::Line.new(code, 'new', 1, nil, 1) }
+ let(:entity) { described_class.new(line, request: {}) }
+
+ subject { entity.as_json }
+
+ it 'exposes correct attributes' do
+ expect(subject).to include(
+ :line_code, :type, :old_line, :new_line, :text, :meta_data, :rich_text
+ )
+ end
+
+ describe '#rich_text' do
+ let(:code) { '<h2 onmouseover="alert(2)">Test</h2>' }
+ let(:rich_text_value) { nil }
+
+ before do
+ line.instance_variable_set(:@rich_text, rich_text_value)
+ end
+
+ shared_examples 'escapes html tags' do
+ it do
+ expect(subject[:rich_text]).to eq html_escape(code)
+ expect(subject[:rich_text]).to be_html_safe
+ end
+ end
+
+ context 'when rich_line is present' do
+ let(:rich_text_value) { code }
+
+ it_behaves_like 'escapes html tags'
+ end
+
+ context 'when rich_line is not present' do
+ it_behaves_like 'escapes html tags'
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 986f11410fd..1a565bb734d 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -82,7 +82,7 @@ describe Clusters::Applications::CheckInstallationProgressService do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq(errors)
+ expect(application.status_reason).to eq("Installation failed")
end
end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index a744ec30b65..4bd19f5bd79 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -42,7 +42,7 @@ describe Clusters::Applications::InstallService do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to match(/kubernetes error:/i)
+ expect(application.status_reason).to match('Kubernetes error.')
end
end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index b987fe45138..051e8c87f39 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -14,6 +14,49 @@ describe Users::BuildService do
expect(service.execute).to be_valid
end
+ context 'allowed params' do
+ let(:params) do
+ {
+ access_level: 1,
+ admin: 1,
+ avatar: anything,
+ bio: 1,
+ can_create_group: 1,
+ color_scheme_id: 1,
+ email: 1,
+ external: 1,
+ force_random_password: 1,
+ hide_no_password: 1,
+ hide_no_ssh_key: 1,
+ linkedin: 1,
+ name: 1,
+ password: 1,
+ password_automatically_set: 1,
+ password_expires_at: 1,
+ projects_limit: 1,
+ remember_me: 1,
+ skip_confirmation: 1,
+ skype: 1,
+ theme_id: 1,
+ twitter: 1,
+ username: 1,
+ website_url: 1,
+ private_profile: 1,
+ organization: 1,
+ location: 1,
+ public_email: 1
+ }
+ end
+
+ it 'sets all allowed attributes' do
+ admin_user # call first so the admin gets created before setting `expect`
+
+ expect(User).to receive(:new).with(hash_including(params)).and_call_original
+
+ service.execute
+ end
+ end
+
context 'with "user_default_external" application setting' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb
index 4b4de540d9e..c8c9c4e773d 100644
--- a/spec/views/help/index.html.haml_spec.rb
+++ b/spec/views/help/index.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'help/index' do
render
expect(rendered).to match '8.0.2'
- expect(rendered).to have_link('abcdefg', href: 'https://gitlab.com/gitlab-org/gitlab-ce/commits/abcdefg')
+ expect(rendered).to have_link('8.0.2', href: 'https://gitlab.com/gitlab-org/gitlab-ce/tags/v8.0.2')
end
end
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 8befae39d3a..0206928a211 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -12,6 +12,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do
assign(:hidden_commit_count, 0)
assign(:total_commit_count, merge_request.commits.count)
assign(:project, merge_request.target_project)
+ assign(:mr_presenter, merge_request.present(current_user: merge_request.author))
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:url_for).and_return('#')
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 9b74a7e1946..c13eab30054 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -24,6 +24,7 @@ describe 'projects/merge_requests/edit.html.haml' do
before do
assign(:project, project)
assign(:merge_request, closed_merge_request)
+ assign(:mr_presenter, closed_merge_request.present(current_user: user))
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:current_user)
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 85b7d16db54..b36aabb9b3d 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -336,7 +336,6 @@ doorkeeper,4.3.2,MIT
doorkeeper-openid_connect,1.5.0,MIT
dot-prop,4.2.0,MIT
dropzone,4.2.0,MIT
-dropzonejs-rails,0.7.2,MIT
duplexer,0.1.1,MIT
duplexer3,0.1.4,New BSD
duplexify,3.5.3,MIT