New upstream version 15.9.7+ds1
This commit is contained in:
parent
081c1a8e73
commit
fc1cb7f6a6
126 changed files with 2705 additions and 974 deletions
|
@ -12,6 +12,7 @@ stages:
|
||||||
- post-qa
|
- post-qa
|
||||||
- pages
|
- pages
|
||||||
- notify
|
- notify
|
||||||
|
- release-environments
|
||||||
|
|
||||||
# always use `gitlab-org` runners, however
|
# always use `gitlab-org` runners, however
|
||||||
# in cases where jobs require Docker-in-Docker, the job
|
# in cases where jobs require Docker-in-Docker, the job
|
||||||
|
@ -32,6 +33,8 @@ default:
|
||||||
|
|
||||||
.ruby2-variables: &ruby2-variables
|
.ruby2-variables: &ruby2-variables
|
||||||
RUBY_VERSION: "2.7"
|
RUBY_VERSION: "2.7"
|
||||||
|
OMNIBUS_GITLAB_RUBY2_BUILD: "true"
|
||||||
|
OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY2"
|
||||||
|
|
||||||
.default-branch-incident-variables: &default-branch-incident-variables
|
.default-branch-incident-variables: &default-branch-incident-variables
|
||||||
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
|
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
|
||||||
|
@ -45,8 +48,8 @@ workflow:
|
||||||
# If `$FORCE_GITLAB_CI` is set, create a pipeline.
|
# If `$FORCE_GITLAB_CI` is set, create a pipeline.
|
||||||
- if: '$FORCE_GITLAB_CI'
|
- if: '$FORCE_GITLAB_CI'
|
||||||
variables:
|
variables:
|
||||||
<<: *ruby3-variables
|
<<: *ruby2-variables
|
||||||
PIPELINE_NAME: 'Ruby 3 forced pipeline'
|
PIPELINE_NAME: 'Ruby 2 forced pipeline'
|
||||||
# As part of the process of creating RCs automatically, we update stable
|
# As part of the process of creating RCs automatically, we update stable
|
||||||
# branches with the changes of the most recent production deployment. The
|
# branches with the changes of the most recent production deployment. The
|
||||||
# merge requests used for this merge a branch release-tools/X into a stable
|
# merge requests used for this merge a branch release-tools/X into a stable
|
||||||
|
@ -61,14 +64,14 @@ workflow:
|
||||||
PIPELINE_NAME: 'Ruby 2 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
|
PIPELINE_NAME: 'Ruby 2 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
|
||||||
- if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/'
|
- if: '$CI_MERGE_REQUEST_LABELS =~ /Community contribution/'
|
||||||
variables:
|
variables:
|
||||||
<<: *ruby3-variables
|
<<: *ruby2-variables
|
||||||
GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
|
GITLAB_DEPENDENCY_PROXY_ADDRESS: ""
|
||||||
PIPELINE_NAME: 'Ruby 3 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline (community contribution)'
|
PIPELINE_NAME: 'Ruby 2 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline (community contribution)'
|
||||||
# For (detached) merge request pipelines.
|
# For (detached) merge request pipelines.
|
||||||
- if: '$CI_MERGE_REQUEST_IID'
|
- if: '$CI_MERGE_REQUEST_IID'
|
||||||
variables:
|
variables:
|
||||||
<<: *ruby3-variables
|
<<: *ruby2-variables
|
||||||
PIPELINE_NAME: 'Ruby 3 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
|
PIPELINE_NAME: 'Ruby 2 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
|
||||||
# For the scheduled pipelines, we set specific variables.
|
# For the scheduled pipelines, we set specific variables.
|
||||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule"'
|
||||||
variables:
|
variables:
|
||||||
|
@ -168,6 +171,7 @@ variables:
|
||||||
|
|
||||||
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
|
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
|
||||||
ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200"
|
ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200"
|
||||||
|
BUNDLER_CHECKSUM_VERIFICATION_OPT_IN: "1"
|
||||||
CACHE_CLASSES: "true"
|
CACHE_CLASSES: "true"
|
||||||
CHECK_PRECOMPILED_ASSETS: "true"
|
CHECK_PRECOMPILED_ASSETS: "true"
|
||||||
FF_USE_FASTZIP: "true"
|
FF_USE_FASTZIP: "true"
|
||||||
|
@ -184,6 +188,7 @@ variables:
|
||||||
REVIEW_APPS_GCP_PROJECT: "gitlab-review-apps"
|
REVIEW_APPS_GCP_PROJECT: "gitlab-review-apps"
|
||||||
REVIEW_APPS_GCP_REGION: "us-central1"
|
REVIEW_APPS_GCP_REGION: "us-central1"
|
||||||
|
|
||||||
|
CACHE_ASSETS_AS_PACKAGE: "true"
|
||||||
BUILD_ASSETS_IMAGE: "true" # Set it to "false" to disable assets image building, used in `build-assets-image`
|
BUILD_ASSETS_IMAGE: "true" # Set it to "false" to disable assets image building, used in `build-assets-image`
|
||||||
SIMPLECOV: "true"
|
SIMPLECOV: "true"
|
||||||
|
|
||||||
|
|
23
.gitlab/ci/release-environments.gitlab-ci.yml
Normal file
23
.gitlab/ci/release-environments.gitlab-ci.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
start-release-environments-pipeline:
|
||||||
|
allow_failure: true
|
||||||
|
extends:
|
||||||
|
- .release-environments:rules:start-release-environments-pipeline
|
||||||
|
stage: release-environments
|
||||||
|
# We do not want to have ALL global variables passed as trigger variables,
|
||||||
|
# as they cannot be overridden. See this issue for more context:
|
||||||
|
#
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/387183
|
||||||
|
inherit:
|
||||||
|
variables:
|
||||||
|
- RUBY_VERSION
|
||||||
|
|
||||||
|
# These variables are set in the pipeline schedules.
|
||||||
|
# They need to be explicitly passed on to the child pipeline.
|
||||||
|
# https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword
|
||||||
|
variables:
|
||||||
|
# This is needed by `release-environments-build-cng-env` (`.gitlab/ci/release-environments/main.gitlab-ci.yml`).
|
||||||
|
PARENT_PIPELINE_ID: $CI_PIPELINE_ID
|
||||||
|
trigger:
|
||||||
|
strategy: depend
|
||||||
|
include: .gitlab/ci/release-environments/main.gitlab-ci.yml
|
62
.gitlab/ci/release-environments/main.gitlab-ci.yml
Normal file
62
.gitlab/ci/release-environments/main.gitlab-ci.yml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
default:
|
||||||
|
interruptible: true
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- prepare
|
||||||
|
|
||||||
|
include:
|
||||||
|
- local: .gitlab/ci/global.gitlab-ci.yml
|
||||||
|
|
||||||
|
release-environments-build-cng-env:
|
||||||
|
allow_failure: true
|
||||||
|
image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-alpine3.16
|
||||||
|
stage: prepare
|
||||||
|
needs:
|
||||||
|
# We need this job because we need its `cached-assets-hash.txt` artifact, so that we can pass the assets image tag to the downstream CNG pipeline.
|
||||||
|
- pipeline: $PARENT_PIPELINE_ID
|
||||||
|
job: build-assets-image
|
||||||
|
variables:
|
||||||
|
BUILD_ENV: build.env
|
||||||
|
before_script:
|
||||||
|
- source ./scripts/utils.sh
|
||||||
|
- install_gitlab_gem
|
||||||
|
script:
|
||||||
|
- 'ruby -r./scripts/trigger-build.rb -e "puts Trigger.variables_for_env_file(Trigger::CNG.new.variables)" > $BUILD_ENV'
|
||||||
|
- echo "GITLAB_ASSETS_TAG=$(assets_image_tag)" >> $BUILD_ENV
|
||||||
|
- ruby -e 'puts "FULL_RUBY_VERSION=#{RUBY_VERSION}"' >> build.env
|
||||||
|
- cat $BUILD_ENV
|
||||||
|
artifacts:
|
||||||
|
reports:
|
||||||
|
dotenv: $BUILD_ENV
|
||||||
|
paths:
|
||||||
|
- $BUILD_ENV
|
||||||
|
expire_in: 7 days
|
||||||
|
when: always
|
||||||
|
|
||||||
|
release-environments-build-cng:
|
||||||
|
allow_failure: true
|
||||||
|
stage: prepare
|
||||||
|
needs: ["release-environments-build-cng-env"]
|
||||||
|
inherit:
|
||||||
|
variables: false
|
||||||
|
variables:
|
||||||
|
GITLAB_REF_SLUG: "${GITLAB_REF_SLUG}"
|
||||||
|
# CNG pipeline specific variables
|
||||||
|
GITLAB_VERSION: "${GITLAB_VERSION}"
|
||||||
|
GITLAB_TAG: "${GITLAB_TAG}"
|
||||||
|
GITLAB_ASSETS_TAG: "${GITLAB_ASSETS_TAG}"
|
||||||
|
FORCE_RAILS_IMAGE_BUILDS: "${FORCE_RAILS_IMAGE_BUILDS}"
|
||||||
|
CE_PIPELINE: "${CE_PIPELINE}" # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$CE_PIPELINE'` will evaluate to `false` when this variable is empty
|
||||||
|
EE_PIPELINE: "${EE_PIPELINE}" # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$EE_PIPELINE'` will evaluate to `false` when this variable is empty
|
||||||
|
GITLAB_ELASTICSEARCH_INDEXER_VERSION: "${GITLAB_ELASTICSEARCH_INDEXER_VERSION}"
|
||||||
|
GITLAB_KAS_VERSION: "${GITLAB_KAS_VERSION}"
|
||||||
|
GITLAB_METRICS_EXPORTER_VERSION: "${GITLAB_METRICS_EXPORTER_VERSION}"
|
||||||
|
GITLAB_PAGES_VERSION: "${GITLAB_PAGES_VERSION}"
|
||||||
|
GITLAB_SHELL_VERSION: "${GITLAB_SHELL_VERSION}"
|
||||||
|
GITALY_SERVER_VERSION: "${GITALY_SERVER_VERSION}"
|
||||||
|
RUBY_VERSION: "${FULL_RUBY_VERSION}"
|
||||||
|
trigger:
|
||||||
|
project: gitlab-org/build/CNG-mirror
|
||||||
|
branch: $TRIGGER_BRANCH
|
||||||
|
strategy: depend
|
|
@ -1901,6 +1901,13 @@
|
||||||
when: never
|
when: never
|
||||||
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
|
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
|
||||||
|
|
||||||
|
.releases:rules:canonical-dot-com-gitlab-stable-branch-only-setup-test-env-patterns:
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/'
|
||||||
|
when: never
|
||||||
|
- if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable-ee$/'
|
||||||
|
changes: *setup-test-env-patterns
|
||||||
|
|
||||||
.releases:rules:canonical-dot-com-security-gitlab-stable-branch-only:
|
.releases:rules:canonical-dot-com-security-gitlab-stable-branch-only:
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/'
|
- if: '$CI_COMMIT_MESSAGE =~ /\[merge-train skip\]/'
|
||||||
|
@ -2295,3 +2302,14 @@
|
||||||
- <<: *if-dot-com-gitlab-org-merge-request
|
- <<: *if-dot-com-gitlab-org-merge-request
|
||||||
changes: *feature-flag-development-config-patterns
|
changes: *feature-flag-development-config-patterns
|
||||||
allow_failure: true # See https://gitlab.com/gitlab-org/gitlab/-/issues/351136
|
allow_failure: true # See https://gitlab.com/gitlab-org/gitlab/-/issues/351136
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# release-environments rules #
|
||||||
|
##############################
|
||||||
|
.release-environments:rules:start-release-environments-pipeline:
|
||||||
|
rules:
|
||||||
|
- <<: *if-not-ee
|
||||||
|
when: never
|
||||||
|
- <<: *if-merge-request-labels-pipeline-expedite
|
||||||
|
when: never
|
||||||
|
- !reference [".releases:rules:canonical-dot-com-gitlab-stable-branch-only-setup-test-env-patterns", rules]
|
||||||
|
|
35
.gitlab/merge_request_templates/Stable Branch.md
Normal file
35
.gitlab/merge_request_templates/Stable Branch.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<!--
|
||||||
|
Merging into stable branches in canonical projects is reserved for
|
||||||
|
GitLab patch releases https://docs.gitlab.com/ee/policy/maintenance.html#patch-releases
|
||||||
|
|
||||||
|
If you're backporting a security fix, please refer to the security merge request
|
||||||
|
template https://gitlab.com/gitlab-org/security/gitlab/blob/master/.gitlab/merge_request_templates/Security%20Release.md.
|
||||||
|
Security backport merge requests should not be opened on the GitLab canonical project.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## What does this MR do and why?
|
||||||
|
|
||||||
|
_Describe in detail what merge request is being backported and why_
|
||||||
|
|
||||||
|
## MR acceptance checklist
|
||||||
|
|
||||||
|
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
|
||||||
|
|
||||||
|
* [ ] This MR is backporting a bug fix, documentation update, or spec fix, previously merged in the default branch.
|
||||||
|
* [ ] The original MR has been deployed to GitLab.com (not applicable for documentation or spec changes).
|
||||||
|
* [ ] This MR has a [severity label] assigned (if applicable).
|
||||||
|
* [ ] Ensure the `e2e:package-and-test` job has either succeeded or been approved by a Software Engineer in Test.
|
||||||
|
|
||||||
|
#### Note to the merge request author and maintainer
|
||||||
|
|
||||||
|
The process of backporting bug fixes into stable branches is tracked as part of an
|
||||||
|
[internal pilot]. If you have questions about this process, please:
|
||||||
|
|
||||||
|
* Refer to the [internal pilot] issue for feedback or questions.
|
||||||
|
* Refer to the [patch release runbook for engineers and maintainers] for guidance.
|
||||||
|
|
||||||
|
[severity label]: https://about.gitlab.com/handbook/engineering/quality/issue-triage/#severity
|
||||||
|
[internal pilot]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/2886
|
||||||
|
[patch release runbook for engineers and maintainers]: https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/patch/process_new.md
|
||||||
|
|
||||||
|
/assign me
|
55
CHANGELOG.md
55
CHANGELOG.md
|
@ -2,6 +2,61 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 15.9.7 (2023-05-03)
|
||||||
|
|
||||||
|
### Security (1 change)
|
||||||
|
|
||||||
|
- [Only maintainers of projects should be able to assign runners to them](gitlab-org/security/gitlab@695748314b758ca2d9992df7509025a6ac868000) ([merge request](gitlab-org/security/gitlab!3236))
|
||||||
|
|
||||||
|
## 15.9.6 (2023-05-01)
|
||||||
|
|
||||||
|
### Security (8 changes)
|
||||||
|
|
||||||
|
- [Resolve ambiguous references for archive metadata](gitlab-org/security/gitlab@233b0f78baf8eb9adcfd77e4d1aa606d54472d34) ([merge request](gitlab-org/security/gitlab!3203))
|
||||||
|
- [Commit trailers now only match public user email addresses](gitlab-org/security/gitlab@e360774721bb9b5f6a2da9908ef08d92ad5a79cd) ([merge request](gitlab-org/security/gitlab!3209))
|
||||||
|
- [Handle invalid URLs in asset proxy](gitlab-org/security/gitlab@ee6df7196b14014b5416f090a684e3b6ba600b5a) ([merge request](gitlab-org/security/gitlab!3213))
|
||||||
|
- [Relay state to check for only allowing sub paths](gitlab-org/security/gitlab@c690eec0a2f8aa506b8ff3ffadf306aa91501648) ([merge request](gitlab-org/security/gitlab!3221))
|
||||||
|
- [Prohibit 40 character hex sets at beginning of path-based branch name](gitlab-org/security/gitlab@889683b6b1884bfc36208dfae899d0fb9437246c) ([merge request](gitlab-org/security/gitlab!3195))
|
||||||
|
- [Update policy to prevent banned members from accessing public projects](gitlab-org/security/gitlab@1abcbdc23881dab5f675e858afa31be87d5d47ce) ([merge request](gitlab-org/security/gitlab!3187))
|
||||||
|
- [Use dummy filename as filename when viewing raw xml files](gitlab-org/security/gitlab@33563159bcc7d46c95f013bf089ed94128f10379) ([merge request](gitlab-org/security/gitlab!3193))
|
||||||
|
- [Authorize access to vulnerabilitiesCountByDay resolver](gitlab-org/security/gitlab@4b0825f79b0a27eeddabaee0b3a7f627b2487706) ([merge request](gitlab-org/security/gitlab!3181))
|
||||||
|
|
||||||
|
## 15.9.5 (2023-04-21)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- [Fix automatically-retried jobs stuck in pending state](gitlab-org/gitlab@752fbfcd613259b71af37f62a83321e8f573219b) ([merge request](gitlab-org/gitlab!117281))
|
||||||
|
|
||||||
|
## 15.9.4 (2023-03-30)
|
||||||
|
|
||||||
|
### Security (16 changes)
|
||||||
|
|
||||||
|
- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@98b1bd243f454bd28c262131be616ee2060c3a78) ([merge request](gitlab-org/security/gitlab!3104))
|
||||||
|
- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@0f0c0f21dffe300a56abf1e07a2fefb17160faeb) ([merge request](gitlab-org/security/gitlab!3133))
|
||||||
|
- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@12498f791f9c5fe833f5202b06cc818d4dcf965b) ([merge request](gitlab-org/security/gitlab!3124))
|
||||||
|
- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@440a7989ff46ca333f86a38aefa47f74301e66fc) ([merge request](gitlab-org/security/gitlab!3163))
|
||||||
|
- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@9dd0dff69d3941e827c461c67b9af10da07d69f8) ([merge request](gitlab-org/security/gitlab!3084))
|
||||||
|
- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@cd20b44dd5b075827203330802e331b896448265) ([merge request](gitlab-org/security/gitlab!3140))
|
||||||
|
- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@76975082c41870265e1285fa8f4e053eb6ff11ae) ([merge request](gitlab-org/security/gitlab!3136))
|
||||||
|
- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@7fcc4a0d010d3a428e803f95ef47904c4c7178a8) ([merge request](gitlab-org/security/gitlab!3149))
|
||||||
|
- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@4e7313536e4cdb3ecef37100b5a73720eabfbc79) ([merge request](gitlab-org/security/gitlab!3108))
|
||||||
|
- [Check access to target project before looking for branch](gitlab-org/security/gitlab@f55edf39e52af9eecb19caf8ed5d4cb8524ef64d) ([merge request](gitlab-org/security/gitlab!3040))
|
||||||
|
- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@be73600e8c43c22cda1ace5910eb2052b2741972) ([merge request](gitlab-org/security/gitlab!3120))
|
||||||
|
- [Use UntrustedRegexp to limit scan of HTML comments](gitlab-org/security/gitlab@d5e65583debcae71787e171643275bc9b9d4393e) ([merge request](gitlab-org/security/gitlab!3142))
|
||||||
|
- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@54045b508a9ba9ae18f5992b77970240774b28a7) ([merge request](gitlab-org/security/gitlab!3111))
|
||||||
|
- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@bc033cd3a98c9a1468545811a8180604f7f8aee3) ([merge request](gitlab-org/security/gitlab!3101))
|
||||||
|
- [Fix security report authorization](gitlab-org/security/gitlab@a01cf9d8383ffc4c0e29514f71d49bf345e1f7c2) ([merge request](gitlab-org/security/gitlab!3106))
|
||||||
|
- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@3cefb16a5e369ee99f4c3ccbaa02cead6faf1a99) ([merge request](gitlab-org/security/gitlab!3130))
|
||||||
|
|
||||||
|
## 15.9.3 (2023-03-09)
|
||||||
|
|
||||||
|
### Fixed (4 changes)
|
||||||
|
|
||||||
|
- [Fix foreign_key_exists? migration helper](gitlab-org/gitlab@7b51239b18779acfe9876fb9467f1231f56d47b4) ([merge request](gitlab-org/gitlab!114005))
|
||||||
|
- [Enable Geo::RepositoryRegistrySyncWorker on Geo secondary site](gitlab-org/gitlab@57b542b4377bcc991b65f34a37397ac1d08846d9) ([merge request](gitlab-org/gitlab!114005)) **GitLab Enterprise Edition**
|
||||||
|
- [Guard against dropped columns when finalizing user details migration](gitlab-org/gitlab@939d646e2cbbbabf870e15fae384c0380d371111) ([merge request](gitlab-org/gitlab!114005))
|
||||||
|
- [Fix object deletion not working with Azure Blob Storage](gitlab-org/gitlab@9515c7a334a43c0e580543029a8da5061bdc19ce) ([merge request](gitlab-org/gitlab!114005))
|
||||||
|
|
||||||
## 15.9.2 (2023-03-02)
|
## 15.9.2 (2023-03-02)
|
||||||
|
|
||||||
### Security (12 changes)
|
### Security (12 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
15.9.2
|
15.9.7
|
|
@ -1 +1 @@
|
||||||
15.9.2
|
15.9.7
|
2
Gemfile
2
Gemfile
|
@ -158,7 +158,7 @@ gem 'fog-rackspace', '~> 0.1.1'
|
||||||
# We may want to update this dependency if this is ever addressed upstream, e.g. via
|
# We may want to update this dependency if this is ever addressed upstream, e.g. via
|
||||||
# https://github.com/aliyun/aliyun-oss-ruby-sdk/pull/93
|
# https://github.com/aliyun/aliyun-oss-ruby-sdk/pull/93
|
||||||
gem 'fog-aliyun', '~> 0.4'
|
gem 'fog-aliyun', '~> 0.4'
|
||||||
gem 'gitlab-fog-azure-rm', '~> 1.4.0', require: 'fog/azurerm'
|
gem 'gitlab-fog-azure-rm', '~> 1.7.0', require: 'fog/azurerm'
|
||||||
|
|
||||||
# for Google storage
|
# for Google storage
|
||||||
gem 'google-cloud-storage', '~> 1.44.0'
|
gem 'google-cloud-storage', '~> 1.44.0'
|
||||||
|
|
|
@ -202,7 +202,7 @@
|
||||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||||
{"name":"gitlab-dangerfiles","version":"3.7.0","platform":"ruby","checksum":"35c5bc42e60c575ab5701192ca2384ab414b14c2963602b39e143b1aaeb7e54d"},
|
{"name":"gitlab-dangerfiles","version":"3.7.0","platform":"ruby","checksum":"35c5bc42e60c575ab5701192ca2384ab414b14c2963602b39e143b1aaeb7e54d"},
|
||||||
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
|
{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
|
||||||
{"name":"gitlab-fog-azure-rm","version":"1.4.0","platform":"ruby","checksum":"af4163c32b028aa5208814a3f4765a5817d50527e6c61931f766bf18a2e0eb7e"},
|
{"name":"gitlab-fog-azure-rm","version":"1.7.0","platform":"ruby","checksum":"969c67943c54ad4c259a6acd040493f13922fbdf2211bb4eca00e71505263dc2"},
|
||||||
{"name":"gitlab-labkit","version":"0.30.1","platform":"ruby","checksum":"bdedbd86014c83dfd6a50d20dbc1709697bba2bb9e3666383e5f28cbd312b113"},
|
{"name":"gitlab-labkit","version":"0.30.1","platform":"ruby","checksum":"bdedbd86014c83dfd6a50d20dbc1709697bba2bb9e3666383e5f28cbd312b113"},
|
||||||
{"name":"gitlab-license","version":"2.2.1","platform":"ruby","checksum":"39fcf6be8b2887df8afe01b5dcbae8d08b7c5d937ff56b0fb40484a8c4f02d30"},
|
{"name":"gitlab-license","version":"2.2.1","platform":"ruby","checksum":"39fcf6be8b2887df8afe01b5dcbae8d08b7c5d937ff56b0fb40484a8c4f02d30"},
|
||||||
{"name":"gitlab-mail_room","version":"0.0.9","platform":"ruby","checksum":"6700374b5c0aa9d9ad4e711aeb677f0b7d415a6d01d3baa699efab25349d851c"},
|
{"name":"gitlab-mail_room","version":"0.0.9","platform":"ruby","checksum":"6700374b5c0aa9d9ad4e711aeb677f0b7d415a6d01d3baa699efab25349d851c"},
|
||||||
|
|
|
@ -577,7 +577,7 @@ GEM
|
||||||
gitlab-experiment (0.7.1)
|
gitlab-experiment (0.7.1)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
request_store (>= 1.0)
|
request_store (>= 1.0)
|
||||||
gitlab-fog-azure-rm (1.4.0)
|
gitlab-fog-azure-rm (1.7.0)
|
||||||
azure-storage-blob (~> 2.0)
|
azure-storage-blob (~> 2.0)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
fog-core (= 2.1.0)
|
fog-core (= 2.1.0)
|
||||||
|
@ -1678,7 +1678,7 @@ DEPENDENCIES
|
||||||
gitlab-chronic (~> 0.10.5)
|
gitlab-chronic (~> 0.10.5)
|
||||||
gitlab-dangerfiles (~> 3.7.0)
|
gitlab-dangerfiles (~> 3.7.0)
|
||||||
gitlab-experiment (~> 0.7.1)
|
gitlab-experiment (~> 0.7.1)
|
||||||
gitlab-fog-azure-rm (~> 1.4.0)
|
gitlab-fog-azure-rm (~> 1.7.0)
|
||||||
gitlab-labkit (~> 0.30.1)
|
gitlab-labkit (~> 0.30.1)
|
||||||
gitlab-license (~> 2.2.1)
|
gitlab-license (~> 2.2.1)
|
||||||
gitlab-mail_room (~> 0.0.9)
|
gitlab-mail_room (~> 0.0.9)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
15.9.2
|
15.9.7
|
|
@ -7,23 +7,36 @@ export function getFrameSrc(url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mountVueComponent = (element) => {
|
const mountVueComponent = (element) => {
|
||||||
const url = [element.dataset.frameUrl];
|
const { frameUrl, observabilityUrl } = element.dataset;
|
||||||
|
|
||||||
return new Vue({
|
try {
|
||||||
el: element,
|
if (
|
||||||
render(h) {
|
!observabilityUrl ||
|
||||||
return h('iframe', {
|
!frameUrl ||
|
||||||
style: {
|
new URL(frameUrl)?.host !== new URL(observabilityUrl).host
|
||||||
height: '366px',
|
)
|
||||||
width: '768px',
|
return;
|
||||||
},
|
|
||||||
attrs: {
|
// eslint-disable-next-line no-new
|
||||||
src: getFrameSrc(url),
|
new Vue({
|
||||||
frameBorder: '0',
|
el: element,
|
||||||
},
|
render(h) {
|
||||||
});
|
return h('iframe', {
|
||||||
},
|
style: {
|
||||||
});
|
height: '366px',
|
||||||
|
width: '768px',
|
||||||
|
},
|
||||||
|
attrs: {
|
||||||
|
src: getFrameSrc(frameUrl),
|
||||||
|
frameBorder: '0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function renderObservability(elements) {
|
export default function renderObservability(elements) {
|
||||||
|
|
|
@ -143,7 +143,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
skip() {
|
skip() {
|
||||||
return !this.workItemId || !this.workItemsMvcEnabled;
|
return !this.workItemId;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
workItemTypes: {
|
workItemTypes: {
|
||||||
|
@ -156,15 +156,9 @@ export default {
|
||||||
update(data) {
|
update(data) {
|
||||||
return data.workspace?.workItemTypes?.nodes;
|
return data.workspace?.workItemTypes?.nodes;
|
||||||
},
|
},
|
||||||
skip() {
|
|
||||||
return !this.workItemsMvcEnabled;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
workItemsMvcEnabled() {
|
|
||||||
return this.glFeatures.workItemsMvc;
|
|
||||||
},
|
|
||||||
taskWorkItemType() {
|
taskWorkItemType() {
|
||||||
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
|
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
|
||||||
},
|
},
|
||||||
|
@ -194,8 +188,7 @@ export default {
|
||||||
|
|
||||||
this.renderGFM();
|
this.renderGFM();
|
||||||
this.updateTaskStatusText();
|
this.updateTaskStatusText();
|
||||||
|
if (this.workItemId) {
|
||||||
if (this.workItemId && this.workItemsMvcEnabled) {
|
|
||||||
const taskLink = this.$el.querySelector(
|
const taskLink = this.$el.querySelector(
|
||||||
`.gfm-issue[data-issue="${getIdFromGraphQLId(this.workItemId)}"]`,
|
`.gfm-issue[data-issue="${getIdFromGraphQLId(this.workItemId)}"]`,
|
||||||
);
|
);
|
||||||
|
@ -228,9 +221,7 @@ export default {
|
||||||
|
|
||||||
this.renderSortableLists();
|
this.renderSortableLists();
|
||||||
|
|
||||||
if (this.workItemsMvcEnabled) {
|
this.renderTaskListItemActions();
|
||||||
this.renderTaskListItemActions();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderSortableLists() {
|
renderSortableLists() {
|
||||||
|
|
|
@ -110,9 +110,10 @@ export default class Project {
|
||||||
const urlParams = { [fieldName]: ref };
|
const urlParams = { [fieldName]: ref };
|
||||||
if (params.group === BRANCH_GROUP_NAME) {
|
if (params.group === BRANCH_GROUP_NAME) {
|
||||||
urlParams.ref_type = BRANCH_REF_TYPE;
|
urlParams.ref_type = BRANCH_REF_TYPE;
|
||||||
} else {
|
} else if (params.group === TAG_GROUP_NAME) {
|
||||||
urlParams.ref_type = TAG_REF_TYPE;
|
urlParams.ref_type = TAG_REF_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
link.href = mergeUrlParams(urlParams, linkTarget);
|
link.href = mergeUrlParams(urlParams, linkTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex';
|
||||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||||
import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
|
import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
|
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
|
||||||
import PerformancePlugin from '~/performance/vue_performance_plugin';
|
import PerformancePlugin from '~/performance/vue_performance_plugin';
|
||||||
|
@ -121,7 +121,7 @@ export default function setupVueRepositoryList() {
|
||||||
|
|
||||||
if (!refSwitcherEl) return false;
|
if (!refSwitcherEl) return false;
|
||||||
|
|
||||||
const { projectId, projectRootPath } = refSwitcherEl.dataset;
|
const { projectId, projectRootPath, refType } = refSwitcherEl.dataset;
|
||||||
|
|
||||||
return new Vue({
|
return new Vue({
|
||||||
el: refSwitcherEl,
|
el: refSwitcherEl,
|
||||||
|
@ -129,7 +129,8 @@ export default function setupVueRepositoryList() {
|
||||||
return createElement(RefSelector, {
|
return createElement(RefSelector, {
|
||||||
props: {
|
props: {
|
||||||
projectId,
|
projectId,
|
||||||
value: ref,
|
value: refType ? joinPaths('refs', refType, ref) : ref,
|
||||||
|
useSymbolicRefNames: true,
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
input(selectedRef) {
|
input(selectedRef) {
|
||||||
|
|
|
@ -16,22 +16,29 @@ const getNamespaceTargetRegex = (ref) => new RegExp(`(/-/(blob|tree))/${ref}/(.*
|
||||||
* @param {string} selectedRef - The selected ref from the ref dropdown.
|
* @param {string} selectedRef - The selected ref from the ref dropdown.
|
||||||
*/
|
*/
|
||||||
export function generateRefDestinationPath(projectRootPath, ref, selectedRef) {
|
export function generateRefDestinationPath(projectRootPath, ref, selectedRef) {
|
||||||
const currentPath = window.location.pathname;
|
const url = new URL(window.location.href);
|
||||||
const encodedHash = '%23';
|
const currentPath = url.pathname;
|
||||||
|
let refType = null;
|
||||||
let namespace = '/-/tree';
|
let namespace = '/-/tree';
|
||||||
let target;
|
let target;
|
||||||
|
let actualRef = selectedRef;
|
||||||
|
|
||||||
|
const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/);
|
||||||
|
if (matches) {
|
||||||
|
[, refType, actualRef] = matches;
|
||||||
|
}
|
||||||
|
if (refType) {
|
||||||
|
url.searchParams.set('ref_type', refType);
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete('ref_type');
|
||||||
|
}
|
||||||
|
|
||||||
const NAMESPACE_TARGET_REGEX = getNamespaceTargetRegex(ref);
|
const NAMESPACE_TARGET_REGEX = getNamespaceTargetRegex(ref);
|
||||||
const match = NAMESPACE_TARGET_REGEX.exec(currentPath);
|
const match = NAMESPACE_TARGET_REGEX.exec(currentPath);
|
||||||
if (match) {
|
if (match) {
|
||||||
[, namespace, , target] = match;
|
[, namespace, , target] = match;
|
||||||
}
|
}
|
||||||
|
url.pathname = joinPaths(projectRootPath, namespace, actualRef, target);
|
||||||
|
|
||||||
const destinationPath = joinPaths(
|
return url.toString();
|
||||||
projectRootPath,
|
|
||||||
namespace,
|
|
||||||
encodeURI(selectedRef).replace(/#/g, encodedHash),
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${destinationPath}${window.location.hash}`;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ConfirmEmailWarning
|
module ConfirmEmailWarning
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
@ -17,11 +18,9 @@ module ConfirmEmailWarning
|
||||||
return unless current_user
|
return unless current_user
|
||||||
return if current_user.confirmed?
|
return if current_user.confirmed?
|
||||||
|
|
||||||
email = current_user.unconfirmed_email || current_user.email
|
|
||||||
|
|
||||||
flash.now[:warning] = format(
|
flash.now[:warning] = format(
|
||||||
confirm_warning_message,
|
confirm_warning_message,
|
||||||
email: email,
|
email: email_to_display,
|
||||||
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
|
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
|
||||||
update_link: view_context.link_to(_('Update it'), profile_path)
|
update_link: view_context.link_to(_('Update it'), profile_path)
|
||||||
).html_safe
|
).html_safe
|
||||||
|
@ -29,7 +28,16 @@ module ConfirmEmailWarning
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def email
|
||||||
|
current_user.unconfirmed_email || current_user.email
|
||||||
|
end
|
||||||
|
strong_memoize_attr :email
|
||||||
|
|
||||||
def confirm_warning_message
|
def confirm_warning_message
|
||||||
_("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.")
|
_("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_to_display
|
||||||
|
html_escape(email)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
|
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
|
||||||
|
|
||||||
before_action :commit, except: [:new, :create]
|
before_action :commit, except: [:new, :create]
|
||||||
|
before_action :check_for_ambiguous_ref, only: [:show]
|
||||||
before_action :blob, except: [:new, :create]
|
before_action :blob, except: [:new, :create]
|
||||||
before_action :require_branch_head, only: [:edit, :update]
|
before_action :require_branch_head, only: [:edit, :update]
|
||||||
before_action :editor_variables, except: [:show, :preview, :diff]
|
before_action :editor_variables, except: [:show, :preview, :diff]
|
||||||
|
@ -145,6 +146,15 @@ class Projects::BlobController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_for_ambiguous_ref
|
||||||
|
@ref_type = ref_type
|
||||||
|
|
||||||
|
if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
|
||||||
|
branch = @project.repository.find_branch(@ref)
|
||||||
|
redirect_to project_blob_path(@project, File.join(branch.target, @path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def commit
|
def commit
|
||||||
@commit ||= @repository.commit(@ref)
|
@commit ||= @repository.commit(@ref)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ class Projects::RefsController < Projects::ApplicationController
|
||||||
when "tree"
|
when "tree"
|
||||||
project_tree_path(@project, @id)
|
project_tree_path(@project, @id)
|
||||||
when "blob"
|
when "blob"
|
||||||
project_blob_path(@project, @id)
|
project_blob_path(@project, @id, ref_type: ref_type)
|
||||||
when "graph"
|
when "graph"
|
||||||
project_network_path(@project, @id, ref_type: ref_type)
|
project_network_path(@project, @id, ref_type: ref_type)
|
||||||
when "graphs"
|
when "graphs"
|
||||||
|
|
|
@ -28,6 +28,15 @@ class Projects::TreeController < Projects::ApplicationController
|
||||||
def show
|
def show
|
||||||
return render_404 unless @commit
|
return render_404 unless @commit
|
||||||
|
|
||||||
|
@ref_type = ref_type
|
||||||
|
if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
|
||||||
|
branch = @project.repository.find_branch(@ref)
|
||||||
|
if branch
|
||||||
|
redirect_to project_tree_path(@project, branch.target)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if tree.entries.empty?
|
if tree.entries.empty?
|
||||||
if @repository.blob_at(@commit.id, @path)
|
if @repository.blob_at(@commit.id, @path)
|
||||||
redirect_to project_blob_path(@project, File.join(@ref, @path))
|
redirect_to project_blob_path(@project, File.join(@ref, @path))
|
||||||
|
|
|
@ -171,11 +171,19 @@ class ProjectsController < Projects::ApplicationController
|
||||||
flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
|
flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if ambiguous_ref?(@project, @ref)
|
||||||
|
branch = @project.repository.find_branch(@ref)
|
||||||
|
|
||||||
|
# The files view would render a ref other than the default branch
|
||||||
|
# This redirect can be removed once the view is fixed
|
||||||
|
redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
@notification_setting = current_user.notification_settings_for(@project) if current_user
|
@notification_setting = current_user.notification_settings_for(@project) if current_user
|
||||||
@project = @project.present(current_user: current_user)
|
@project = @project.present(current_user: current_user)
|
||||||
|
|
||||||
render_landing_page
|
render_landing_page
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -32,18 +32,9 @@ module Environments
|
||||||
end
|
end
|
||||||
|
|
||||||
def namespace_environments
|
def namespace_environments
|
||||||
# We assume reporter access is needed for the :read_environment permission
|
|
||||||
# here. This expection is also present in
|
|
||||||
# IssuableFinder::Params#min_access_level, which is used for filtering out
|
|
||||||
# merge requests that don't have the right permissions.
|
|
||||||
#
|
|
||||||
# We use this approach so we don't need to load every project into memory
|
|
||||||
# just to verify if we can see their environments. Doing so would not be
|
|
||||||
# efficient, and possibly mess up pagination if certain projects are not
|
|
||||||
# meant to be visible.
|
|
||||||
projects = project_or_group
|
projects = project_or_group
|
||||||
.all_projects
|
.all_projects
|
||||||
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
|
.filter_by_feature_visibility(:environments, current_user)
|
||||||
|
|
||||||
Environment.for_project(projects)
|
Environment.for_project(projects)
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,6 +30,7 @@ class NotesFinder
|
||||||
notes = init_collection
|
notes = init_collection
|
||||||
notes = since_fetch_at(notes)
|
notes = since_fetch_at(notes)
|
||||||
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
|
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
|
||||||
|
notes = redact_internal(notes)
|
||||||
sort(notes)
|
sort(notes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -181,6 +182,13 @@ class NotesFinder
|
||||||
|
|
||||||
notes.order_by(sort)
|
notes.order_by(sort)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def redact_internal(notes)
|
||||||
|
subject = @project || target
|
||||||
|
return notes if Ability.allowed?(@current_user, :read_internal_note, subject)
|
||||||
|
|
||||||
|
notes.not_internal
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
NotesFinder.prepend_mod_with('NotesFinder')
|
NotesFinder.prepend_mod_with('NotesFinder')
|
||||||
|
|
|
@ -116,7 +116,7 @@ module AvatarsHelper
|
||||||
private
|
private
|
||||||
|
|
||||||
def avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path:)
|
def avatar_icon_by_user_email_or_gravatar(email, size, scale, only_path:)
|
||||||
user = User.find_by_any_email(email)
|
user = User.with_public_email(email).first
|
||||||
|
|
||||||
if user
|
if user
|
||||||
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
avatar_icon_for_user(user, size, scale, only_path: only_path)
|
||||||
|
|
|
@ -24,25 +24,37 @@ module Taskable
|
||||||
(\s.+) # followed by whitespace and some text.
|
(\s.+) # followed by whitespace and some text.
|
||||||
}x.freeze
|
}x.freeze
|
||||||
|
|
||||||
|
ITEM_PATTERN_UNTRUSTED =
|
||||||
|
'^' \
|
||||||
|
'(?:(?:>\s{0,4})*)' \
|
||||||
|
'(?P<prefix>(?:\s*(?:[-+*]|(?:\d+\.)))+)' \
|
||||||
|
'\s+' \
|
||||||
|
'(?P<checkbox>' \
|
||||||
|
"#{COMPLETE_PATTERN.source}|#{INCOMPLETE_PATTERN.source}" \
|
||||||
|
')' \
|
||||||
|
'(?P<label>\s.+)'.freeze
|
||||||
|
|
||||||
# ignore tasks in code or html comment blocks. HTML blocks
|
# ignore tasks in code or html comment blocks. HTML blocks
|
||||||
# are ok as we allow tasks inside <detail> blocks
|
# are ok as we allow tasks inside <detail> blocks
|
||||||
REGEX = %r{
|
REGEX =
|
||||||
#{::Gitlab::Regex.markdown_code_or_html_comments}
|
"#{::Gitlab::Regex.markdown_code_or_html_comments_untrusted}" \
|
||||||
|
|
"|" \
|
||||||
(?<task_item>
|
"(?P<task_item>" \
|
||||||
#{ITEM_PATTERN}
|
"#{ITEM_PATTERN_UNTRUSTED}" \
|
||||||
)
|
")".freeze
|
||||||
}mx.freeze
|
|
||||||
|
|
||||||
def self.get_tasks(content)
|
def self.get_tasks(content)
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
content.to_s.scan(REGEX) do
|
regex = Gitlab::UntrustedRegexp.new(REGEX, multiline: true)
|
||||||
next unless $~[:task_item]
|
regex.scan(content.to_s).each do |match|
|
||||||
|
next unless regex.extract_named_group(:task_item, match)
|
||||||
|
|
||||||
$~[:task_item].scan(ITEM_PATTERN) do |prefix, checkbox, label|
|
prefix = regex.extract_named_group(:prefix, match)
|
||||||
items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
|
checkbox = regex.extract_named_group(:checkbox, match)
|
||||||
end
|
label = regex.extract_named_group(:label, match)
|
||||||
|
|
||||||
|
items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
|
||||||
end
|
end
|
||||||
|
|
||||||
items
|
items
|
||||||
|
|
|
@ -41,7 +41,7 @@ class WebHook < ApplicationRecord
|
||||||
after_initialize :initialize_url_variables
|
after_initialize :initialize_url_variables
|
||||||
|
|
||||||
before_validation :reset_token
|
before_validation :reset_token
|
||||||
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }
|
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }, on: :update
|
||||||
before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
|
before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
|
||||||
validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
|
validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
|
||||||
validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
|
validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
|
||||||
|
@ -150,7 +150,7 @@ class WebHook < ApplicationRecord
|
||||||
# See app/validators/json_schemas/web_hooks_url_variables.json
|
# See app/validators/json_schemas/web_hooks_url_variables.json
|
||||||
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
|
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
|
||||||
|
|
||||||
def interpolated_url
|
def interpolated_url(url = self.url, url_variables = self.url_variables)
|
||||||
return url unless url.include?('{')
|
return url unless url.include?('{')
|
||||||
|
|
||||||
vars = url_variables
|
vars = url_variables
|
||||||
|
@ -176,7 +176,19 @@ class WebHook < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_url_variables
|
def reset_url_variables
|
||||||
self.url_variables = {} if url_changed? && !encrypted_url_variables_changed?
|
interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were)
|
||||||
|
|
||||||
|
return if url_variables_were.empty? || interpolated_url_was == interpolated_url
|
||||||
|
|
||||||
|
self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def decrypt_url_was
|
||||||
|
self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was))
|
||||||
|
end
|
||||||
|
|
||||||
|
def url_variables_were
|
||||||
|
self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was)
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_failure_count
|
def next_failure_count
|
||||||
|
|
|
@ -36,7 +36,8 @@ class ProjectFeature < ApplicationRecord
|
||||||
merge_requests: Gitlab::Access::REPORTER,
|
merge_requests: Gitlab::Access::REPORTER,
|
||||||
metrics_dashboard: Gitlab::Access::REPORTER,
|
metrics_dashboard: Gitlab::Access::REPORTER,
|
||||||
container_registry: Gitlab::Access::REPORTER,
|
container_registry: Gitlab::Access::REPORTER,
|
||||||
package_registry: Gitlab::Access::REPORTER
|
package_registry: Gitlab::Access::REPORTER,
|
||||||
|
environments: Gitlab::Access::REPORTER
|
||||||
}.freeze
|
}.freeze
|
||||||
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
|
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,9 @@ module Uploads
|
||||||
private
|
private
|
||||||
|
|
||||||
def delete_object(key)
|
def delete_object(key)
|
||||||
connection.delete_object(bucket_name, key)
|
return unless available?
|
||||||
|
|
||||||
|
connection.delete_object(bucket_name, object_key(key))
|
||||||
|
|
||||||
# So far, only GoogleCloudStorage raises an exception when the file is not found.
|
# So far, only GoogleCloudStorage raises an exception when the file is not found.
|
||||||
# Other providers support idempotent requests and does not raise an error
|
# Other providers support idempotent requests and does not raise an error
|
||||||
|
@ -35,11 +37,16 @@ module Uploads
|
||||||
end
|
end
|
||||||
|
|
||||||
def bucket_name
|
def bucket_name
|
||||||
return unless available?
|
|
||||||
|
|
||||||
object_store.remote_directory
|
object_store.remote_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def object_key(key)
|
||||||
|
# We allow administrators to create "sub buckets" by setting a prefix.
|
||||||
|
# This makes it possible to deploy GitLab with only one object storage
|
||||||
|
# bucket. This mirrors the implementation in app/uploaders/object_storage.rb.
|
||||||
|
File.join([object_store.bucket_prefix, key].compact)
|
||||||
|
end
|
||||||
|
|
||||||
def connection
|
def connection
|
||||||
return unless available?
|
return unless available?
|
||||||
|
|
||||||
|
|
|
@ -412,7 +412,6 @@ class ProjectPolicy < BasePolicy
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { can?(:metrics_dashboard) }.policy do
|
rule { can?(:metrics_dashboard) }.policy do
|
||||||
enable :read_prometheus
|
|
||||||
enable :read_deployment
|
enable :read_deployment
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def clone!(job, variables: [], enqueue_if_actionable: false)
|
def clone!(job, variables: [], enqueue_if_actionable: false, start_pipeline: false)
|
||||||
# Cloning a job requires a strict type check to ensure
|
# Cloning a job requires a strict type check to ensure
|
||||||
# the attributes being used for the clone are taken straight
|
# the attributes being used for the clone are taken straight
|
||||||
# from the model and not overridden by other abstractions.
|
# from the model and not overridden by other abstractions.
|
||||||
|
@ -32,7 +32,11 @@ module Ci
|
||||||
new_job.set_enqueue_immediately!
|
new_job.set_enqueue_immediately!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
start_pipeline_proc = -> { start_pipeline(job, new_job) } if start_pipeline
|
||||||
|
|
||||||
new_job.run_after_commit do
|
new_job.run_after_commit do
|
||||||
|
start_pipeline_proc&.call
|
||||||
|
|
||||||
::Ci::CopyCrossDatabaseAssociationsService.new.execute(job, new_job)
|
::Ci::CopyCrossDatabaseAssociationsService.new.execute(job, new_job)
|
||||||
|
|
||||||
::Deployments::CreateForBuildService.new.execute(new_job)
|
::Deployments::CreateForBuildService.new.execute(new_job)
|
||||||
|
@ -59,15 +63,12 @@ module Ci
|
||||||
def check_assignable_runners!(job); end
|
def check_assignable_runners!(job); end
|
||||||
|
|
||||||
def retry_job(job, variables: [])
|
def retry_job(job, variables: [])
|
||||||
clone!(job, variables: variables, enqueue_if_actionable: true).tap do |new_job|
|
clone!(job, variables: variables, enqueue_if_actionable: true, start_pipeline: true).tap do |new_job|
|
||||||
check_assignable_runners!(new_job) if new_job.is_a?(Ci::Build)
|
check_assignable_runners!(new_job) if new_job.is_a?(Ci::Build)
|
||||||
|
|
||||||
next if new_job.failed?
|
next if new_job.failed?
|
||||||
|
|
||||||
ResetSkippedJobsService.new(project, current_user).execute(job)
|
ResetSkippedJobsService.new(project, current_user).execute(job)
|
||||||
|
|
||||||
Ci::PipelineCreation::StartPipelineService.new(job.pipeline).execute
|
|
||||||
new_job.reset
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -76,6 +77,11 @@ module Ci
|
||||||
raise Gitlab::Access::AccessDeniedError, '403 Forbidden'
|
raise Gitlab::Access::AccessDeniedError, '403 Forbidden'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def start_pipeline(job, new_job)
|
||||||
|
Ci::PipelineCreation::StartPipelineService.new(job.pipeline).execute
|
||||||
|
new_job.reset
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,9 @@ module Ci
|
||||||
current_project_ids = runner.projects.ids
|
current_project_ids = runner.projects.ids
|
||||||
# rubocop:enable CodeReuse/ActiveRecord
|
# rubocop:enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
unless associate_new_projects(new_project_ids, current_project_ids)
|
response = associate_new_projects(new_project_ids, current_project_ids)
|
||||||
response = ServiceResponse.error(message: 'failed to assign projects to runner')
|
response = disassociate_old_projects(new_project_ids, current_project_ids) if response.success?
|
||||||
raise ActiveRecord::Rollback, response.errors
|
raise ActiveRecord::Rollback, response.errors unless response.success?
|
||||||
end
|
|
||||||
|
|
||||||
unless disassociate_old_projects(new_project_ids, current_project_ids)
|
|
||||||
response = ServiceResponse.error(message: 'failed to destroy runner project')
|
|
||||||
raise ActiveRecord::Rollback, response.errors
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -49,16 +43,29 @@ module Ci
|
||||||
|
|
||||||
def associate_new_projects(new_project_ids, current_project_ids)
|
def associate_new_projects(new_project_ids, current_project_ids)
|
||||||
missing_projects = Project.id_in(new_project_ids - current_project_ids)
|
missing_projects = Project.id_in(new_project_ids - current_project_ids)
|
||||||
missing_projects.all? { |project| runner.assign_to(project, current_user) }
|
|
||||||
|
unless missing_projects.all? { |project| current_user.can?(:register_project_runners, project) }
|
||||||
|
return ServiceResponse.error(message: 'user is not authorized to add runners to project')
|
||||||
|
end
|
||||||
|
|
||||||
|
unless missing_projects.all? { |project| runner.assign_to(project, current_user) }
|
||||||
|
return ServiceResponse.error(message: 'failed to assign projects to runner')
|
||||||
|
end
|
||||||
|
|
||||||
|
ServiceResponse.success
|
||||||
end
|
end
|
||||||
|
|
||||||
def disassociate_old_projects(new_project_ids, current_project_ids)
|
def disassociate_old_projects(new_project_ids, current_project_ids)
|
||||||
projects_to_be_deleted = current_project_ids - new_project_ids
|
projects_to_be_deleted = current_project_ids - new_project_ids
|
||||||
return true if projects_to_be_deleted.empty?
|
return ServiceResponse.success if projects_to_be_deleted.empty?
|
||||||
|
|
||||||
Ci::RunnerProject
|
all_destroyed =
|
||||||
.destroy_by(project_id: projects_to_be_deleted)
|
Ci::RunnerProject
|
||||||
.all?(&:destroyed?)
|
.destroy_by(project_id: projects_to_be_deleted)
|
||||||
|
.all?(&:destroyed?)
|
||||||
|
return ServiceResponse.success if all_destroyed
|
||||||
|
|
||||||
|
ServiceResponse.error(message: 'failed to destroy runner project')
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :runner, :current_user, :project_ids
|
attr_reader :runner, :current_user, :project_ids
|
||||||
|
|
|
@ -54,7 +54,15 @@ module MergeRequests
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_service
|
def validate_service
|
||||||
errors << 'User is required' if current_user.nil?
|
if current_user.nil?
|
||||||
|
errors << 'User is required'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
unless current_user&.can?(:read_code, target_project)
|
||||||
|
errors << 'User access was denied'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
unless target_project.merge_requests_enabled?
|
unless target_project.merge_requests_enabled?
|
||||||
errors << "Merge requests are not enabled for project #{target_project.full_path}"
|
errors << "Merge requests are not enabled for project #{target_project.full_path}"
|
||||||
|
|
|
@ -18,5 +18,5 @@
|
||||||
%h5= _("Maximum page reached")
|
%h5= _("Maximum page reached")
|
||||||
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
|
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
|
||||||
|
|
||||||
= render Pajamas::ButtonComponent.new(href: request.params.merge(page: @max_page_number)) do
|
= render Pajamas::ButtonComponent.new(href: safe_params.merge(page: @max_page_number)) do
|
||||||
= _("Back to page %{number}") % { number: @max_page_number }
|
= _("Back to page %{number}") % { number: @max_page_number }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
|
.tree-ref-container.gl-display-flex.gl-flex-wrap.gl-gap-2.mb-2.mb-md-0
|
||||||
.tree-ref-holder.gl-max-w-26
|
.tree-ref-holder.gl-max-w-26
|
||||||
#js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } }
|
#js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
|
||||||
|
|
||||||
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
|
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
|
||||||
|
|
||||||
|
|
|
@ -628,11 +628,16 @@ production: &base
|
||||||
geo_secondary_registry_consistency_worker:
|
geo_secondary_registry_consistency_worker:
|
||||||
cron: "* * * * *"
|
cron: "* * * * *"
|
||||||
|
|
||||||
# GitLab Geo registry sync worker (for backfilling)
|
# GitLab Geo blob registry sync worker (for backfilling)
|
||||||
# NOTE: This will only take effect if Geo is enabled (secondary nodes only)
|
# NOTE: This will only take effect if Geo is enabled (secondary nodes only)
|
||||||
geo_registry_sync_worker:
|
geo_registry_sync_worker:
|
||||||
cron: "*/1 * * * *"
|
cron: "*/1 * * * *"
|
||||||
|
|
||||||
|
# GitLab Geo repository registry sync worker (for backfilling)
|
||||||
|
# NOTE: This will only take effect if Geo is enabled (secondary nodes only)
|
||||||
|
geo_repository_registry_sync_worker:
|
||||||
|
cron: "*/1 * * * *"
|
||||||
|
|
||||||
# Elasticsearch bulk updater for incremental updates.
|
# Elasticsearch bulk updater for incremental updates.
|
||||||
# NOTE: This will only take effect if elasticsearch is enabled.
|
# NOTE: This will only take effect if elasticsearch is enabled.
|
||||||
elastic_index_bulk_cron_worker:
|
elastic_index_bulk_cron_worker:
|
||||||
|
|
|
@ -721,6 +721,9 @@ Gitlab.ee do
|
||||||
Settings.cron_jobs['geo_registry_sync_worker'] ||= Settingslogic.new({})
|
Settings.cron_jobs['geo_registry_sync_worker'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['geo_registry_sync_worker']['cron'] ||= '*/1 * * * *'
|
Settings.cron_jobs['geo_registry_sync_worker']['cron'] ||= '*/1 * * * *'
|
||||||
Settings.cron_jobs['geo_registry_sync_worker']['job_class'] ||= 'Geo::RegistrySyncWorker'
|
Settings.cron_jobs['geo_registry_sync_worker']['job_class'] ||= 'Geo::RegistrySyncWorker'
|
||||||
|
Settings.cron_jobs['geo_repository_registry_sync_worker'] ||= Settingslogic.new({})
|
||||||
|
Settings.cron_jobs['geo_repository_registry_sync_worker']['cron'] ||= '*/1 * * * *'
|
||||||
|
Settings.cron_jobs['geo_repository_registry_sync_worker']['job_class'] ||= 'Geo::RepositoryRegistrySyncWorker'
|
||||||
Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
|
Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
|
||||||
Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
|
Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
|
||||||
Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
|
Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
return if helper.stable_branch?
|
||||||
|
|
||||||
data_qa_selectors = /qa_selector|data-qa-selector/
|
data_qa_selectors = /qa_selector|data-qa-selector/
|
||||||
|
|
||||||
deprecated_qa_selectors = /(?!.*\bdata-qa-)(?=class=.*qa-.*|class: .*qa-.*)/
|
deprecated_qa_selectors = /(?!.*\bdata-qa-)(?=class=.*qa-.*|class: .*qa-.*)/
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
if stable_branch.non_security_stable_branch?
|
if stable_branch.encourage_package_and_qa_execution?
|
||||||
markdown(<<~MARKDOWN)
|
markdown(<<~MARKDOWN)
|
||||||
### QA `e2e:package-and-test`
|
## QA `e2e:package-and-test`
|
||||||
|
|
||||||
**@#{helper.mr_author}, the `package-and-test` job must complete before merging this merge request.***
|
**@#{helper.mr_author}, the `package-and-test` job must complete before merging this merge request.***
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,3 @@ has_milestone = !gitlab.mr_json["milestone"].nil?
|
||||||
unless has_milestone || (helper.security_mr? && helper.mr_target_branch == default_branch)
|
unless has_milestone || (helper.security_mr? && helper.mr_target_branch == default_branch)
|
||||||
warn "This merge request does not refer to an existing milestone.", sticky: false
|
warn "This merge request does not refer to an existing milestone.", sticky: false
|
||||||
end
|
end
|
||||||
|
|
||||||
has_pick_into_stable_label = helper.mr_labels.find { |label| label.start_with?('Pick into') }
|
|
||||||
|
|
||||||
if helper.mr_target_branch != default_branch && !has_pick_into_stable_label && !helper.security_mr?
|
|
||||||
warn "Most of the time, merge requests should target `#{default_branch}`. Otherwise, please set the relevant `Pick into X.Y` label."
|
|
||||||
end
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ require './spec/support/sidekiq_middleware'
|
||||||
|
|
||||||
Sidekiq::Testing.inline! do
|
Sidekiq::Testing.inline! do
|
||||||
Gitlab::Seeder.quiet do
|
Gitlab::Seeder.quiet do
|
||||||
User.not_mass_generated.sample(10).each do |user|
|
User.humans.not_mass_generated.sample(10).each do |user|
|
||||||
source_project = Project.not_mass_generated.public_only.sample
|
source_project = Project.not_mass_generated.public_only.sample
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class FinalizeBackfillUserDetailsFields < Gitlab::Database::Migration[2.0]
|
||||||
|
BACKFILL_MIGRATION = 'BackfillUserDetailsFields'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||||
|
|
||||||
|
def up
|
||||||
|
# If the 20230116160904_remove_user_details_fields_from_user.rb migration already ran,
|
||||||
|
# finalizing this background migration will fail.
|
||||||
|
return unless column_exists?(:users, :linkedin)
|
||||||
|
|
||||||
|
ensure_batched_background_migration_is_finished(
|
||||||
|
job_class_name: BACKFILL_MIGRATION,
|
||||||
|
table_name: :users,
|
||||||
|
column_name: :id,
|
||||||
|
job_arguments: [],
|
||||||
|
finalize: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down; end
|
||||||
|
end
|
|
@ -1,13 +1,11 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RemoveTempIndexForUserDetailsFields < Gitlab::Database::Migration[2.0]
|
class RemoveTempIndexForUserDetailsFields < Gitlab::Database::Migration[2.0]
|
||||||
BACKFILL_MIGRATION = 'BackfillUserDetailsFields'
|
|
||||||
INDEX_NAME = 'tmp_idx_where_user_details_fields_filled'
|
INDEX_NAME = 'tmp_idx_where_user_details_fields_filled'
|
||||||
|
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
finalize_background_migration BACKFILL_MIGRATION
|
|
||||||
remove_concurrent_index_by_name :users, INDEX_NAME
|
remove_concurrent_index_by_name :users, INDEX_NAME
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class NullifyLastErrorFromProjectMirrorData < Gitlab::Database::Migration[2.1]
|
||||||
|
MIGRATION = 'NullifyLastErrorFromProjectMirrorData'
|
||||||
|
INTERVAL = 2.minutes
|
||||||
|
BATCH_SIZE = 10_000
|
||||||
|
SUB_BATCH_SIZE = 1_000
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||||
|
|
||||||
|
def up
|
||||||
|
queue_batched_background_migration(
|
||||||
|
MIGRATION,
|
||||||
|
:project_mirror_data,
|
||||||
|
:id,
|
||||||
|
job_interval: INTERVAL,
|
||||||
|
batch_size: BATCH_SIZE,
|
||||||
|
sub_batch_size: SUB_BATCH_SIZE
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
delete_batched_background_migration(MIGRATION, :project_mirror_data, :id, [])
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20221102231130
Normal file
1
db/schema_migrations/20221102231130
Normal file
|
@ -0,0 +1 @@
|
||||||
|
8678040a9fa8da1d455489db89e00084943d1dced6dd01cbf3517afd1a47bac5
|
1
db/schema_migrations/20230208131808
Normal file
1
db/schema_migrations/20230208131808
Normal file
|
@ -0,0 +1 @@
|
||||||
|
784f8f189eee7b5cf3136f0a859874a1d170d2b148f4c260f968b144816f1322
|
|
@ -1353,7 +1353,7 @@ If you have installed GitLab using the Linux package (Omnibus) and have configur
|
||||||
- `15.6.0`-`15.6.3`
|
- `15.6.0`-`15.6.3`
|
||||||
- `15.7.0`-`15.7.1`
|
- `15.7.0`-`15.7.1`
|
||||||
|
|
||||||
This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/).
|
This is due to [a bug introduced in the included version of cURL](https://github.com/curl/curl/issues/10122) shipped with Omnibus GitLab 15.4.6 and later. You are encouraged to upgrade to a later version where this has been [fixed](https://about.gitlab.com/releases/2023/01/09/security-release-gitlab-15-7-2-released/).
|
||||||
|
|
||||||
The bug causes all wildcard domains (`.example.com`) to be ignored except for the last on in the `no_proxy` environment variable list. Therefore, if for any reason you cannot upgrade to a newer version, you can work around the issue by moving your wildcard domain to the end of the list:
|
The bug causes all wildcard domains (`.example.com`) to be ignored except for the last on in the `no_proxy` environment variable list. Therefore, if for any reason you cannot upgrade to a newer version, you can work around the issue by moving your wildcard domain to the end of the list:
|
||||||
|
|
||||||
|
@ -1363,12 +1363,13 @@ The bug causes all wildcard domains (`.example.com`) to be ignored except for th
|
||||||
gitaly['env'] = {
|
gitaly['env'] = {
|
||||||
"no_proxy" => "sever.yourdomain.org, .yourdomain.com",
|
"no_proxy" => "sever.yourdomain.org, .yourdomain.com",
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
1. Reconfigure GitLab:
|
1. Reconfigure GitLab:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo gitlab-ctl reconfigure
|
sudo gitlab-ctl reconfigure
|
||||||
```
|
```
|
||||||
|
|
||||||
You can have only one wildcard domain in the `no_proxy` list.
|
You can have only one wildcard domain in the `no_proxy` list.
|
||||||
|
|
||||||
|
|
|
@ -258,7 +258,7 @@ One way to generate the initial list is to run the Rake task `rubocop:todo:gener
|
||||||
bundle exec rake rubocop:todo:generate
|
bundle exec rake rubocop:todo:generate
|
||||||
```
|
```
|
||||||
|
|
||||||
To generate TODO list for specific RuboCop rules, pass them comma-seperated as
|
To generate TODO list for specific RuboCop rules, pass them comma-separated as
|
||||||
argument to the Rake task:
|
argument to the Rake task:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -117,20 +117,14 @@ Install the required packages (needed to compile Ruby and native extensions to R
|
||||||
```shell
|
```shell
|
||||||
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
|
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
|
||||||
libreadline-dev libncurses5-dev libffi-dev curl openssh-server libxml2-dev libxslt-dev \
|
libreadline-dev libncurses5-dev libffi-dev curl openssh-server libxml2-dev libxslt-dev \
|
||||||
libcurl4-openssl-dev libicu-dev logrotate rsync python3-docutils pkg-config cmake runit-systemd
|
libcurl4-openssl-dev libicu-dev libkrb5-dev logrotate rsync python3-docutils pkg-config cmake \
|
||||||
|
runit-systemd
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
GitLab requires OpenSSL version 1.1. If your Linux distribution includes a different version of OpenSSL,
|
GitLab requires OpenSSL version 1.1. If your Linux distribution includes a different version of OpenSSL,
|
||||||
you might have to install 1.1 manually.
|
you might have to install 1.1 manually.
|
||||||
|
|
||||||
If you want to use Kerberos for user authentication, install `libkrb5-dev`
|
|
||||||
(if you don't know what Kerberos is, you can assume you don't need it):
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo apt-get install libkrb5-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git
|
### Git
|
||||||
|
|
||||||
From GitLab 13.6, we recommend you use the
|
From GitLab 13.6, we recommend you use the
|
||||||
|
|
|
@ -3115,7 +3115,7 @@ such as the following:
|
||||||
| Encrypt SAML assertion | Optional | Uses TLS between your identity provider, the user's browser, and GitLab. |
|
| Encrypt SAML assertion | Optional | Uses TLS between your identity provider, the user's browser, and GitLab. |
|
||||||
| Sign SAML assertion | Optional | Validates the integrity of a SAML assertion. When active, signs the whole response. |
|
| Sign SAML assertion | Optional | Validates the integrity of a SAML assertion. When active, signs the whole response. |
|
||||||
| Check SAML request signature | Optional | Checks the signature on the SAML response. |
|
| Check SAML request signature | Optional | Checks the signature on the SAML response. |
|
||||||
| Default RelayState | Optional | Specifies the URL users should end up on after successfully signing in through SAML at your IdP. |
|
| Default RelayState | Optional | Specifies the sub-paths of the base URL that users should end up on after successfully signing in through SAML at your IdP. |
|
||||||
| NameID format | Persistent | See [NameID format details](../user/group/saml_sso/index.md#nameid-format). |
|
| NameID format | Persistent | See [NameID format details](../user/group/saml_sso/index.md#nameid-format). |
|
||||||
| Additional URLs | Optional | May include the issuer, identifier, or assertion consumer service URL in other fields on some providers. |
|
| Additional URLs | Optional | May include the issuer, identifier, or assertion consumer service URL in other fields on some providers. |
|
||||||
|
|
||||||
|
|
|
@ -266,7 +266,7 @@ arguments until the status query returns no rows.
|
||||||
|
|
||||||
1. Run a reconfigure:
|
1. Run a reconfigure:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
sudo gitlab-ctl reconfigure
|
sudo gitlab-ctl reconfigure
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -325,6 +325,37 @@ The results from the query can be plugged into the command:
|
||||||
sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[["id"]\, ["id_convert_to_bigint"]]']
|
sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[["id"]\, ["id_convert_to_bigint"]]']
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Mark a batched migration finished
|
||||||
|
|
||||||
|
There can be cases where the background migration fails: when jumping too many version upgrades,
|
||||||
|
or backward-incompatible database schema changes. (For an example, see [issue 393216](https://gitlab.com/gitlab-org/gitlab/-/issues/393216)).
|
||||||
|
Failed background migrations prevent further application upgrades.
|
||||||
|
|
||||||
|
When the background migration is determined to be "safe" to skip, the migration can be manually marked finished:
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
Make sure you create a backup before proceeding.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
# Start the rails console
|
||||||
|
|
||||||
|
connection = ApplicationRecord.connection # or Ci::ApplicationRecord.connection, depending on which DB was the migration scheduled
|
||||||
|
|
||||||
|
Gitlab::Database::SharedModel.using_connection(connection) do
|
||||||
|
migration = Gitlab::Database::BackgroundMigration::BatchedMigration.find_for_configuration(
|
||||||
|
Gitlab::Database.gitlab_schemas_for_connection(connection),
|
||||||
|
'BackfillUserDetailsFields',
|
||||||
|
:users,
|
||||||
|
:id,
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
# mark all jobs completed
|
||||||
|
migration.batched_jobs.update_all(status: Gitlab::Database::BackgroundMigration::BatchedJob.state_machine.states['succeeded'].value)
|
||||||
|
migration.update_attribute(:status, Gitlab::Database::BackgroundMigration::BatchedMigration.state_machine.states[:finished].value)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
### The `BackfillNamespaceIdForNamespaceRoute` batched migration job fails
|
### The `BackfillNamespaceIdForNamespaceRoute` batched migration job fails
|
||||||
|
|
||||||
In GitLab 14.8, the `BackfillNamespaceIdForNamespaceRoute` batched background migration job
|
In GitLab 14.8, the `BackfillNamespaceIdForNamespaceRoute` batched background migration job
|
||||||
|
|
|
@ -376,10 +376,10 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
|
||||||
- GitLab Runner 15.7.0 introduced a breaking change that impacts CI/CD jobs: [Correctly handle expansion of job file variables](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3613).
|
- GitLab Runner 15.7.0 introduced a breaking change that impacts CI/CD jobs: [Correctly handle expansion of job file variables](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3613).
|
||||||
Previously, job-defined variables that referred to
|
Previously, job-defined variables that referred to
|
||||||
[file type variables](../ci/variables/index.md#use-file-type-cicd-variables)
|
[file type variables](../ci/variables/index.md#use-file-type-cicd-variables)
|
||||||
were expanded to the value of the file variable (its content). This behavior did not
|
were expanded to the value of the file variable (its content). This behavior did not
|
||||||
respect the typical rules of shell variable expansion. There was also the potential
|
respect the typical rules of shell variable expansion. There was also the potential
|
||||||
that secrets or sensitive information could leak if the file variable and its
|
that secrets or sensitive information could leak if the file variable and its
|
||||||
contents printed. For example, if they were printed in an echo output. For more information,
|
contents printed. For example, if they were printed in an echo output. For more information,
|
||||||
see [Understanding the file type variable expansion change in GitLab 15.7](https://about.gitlab.com/blog/2023/02/13/impact-of-the-file-type-variable-change-15-7/).
|
see [Understanding the file type variable expansion change in GitLab 15.7](https://about.gitlab.com/blog/2023/02/13/impact-of-the-file-type-variable-change-15-7/).
|
||||||
- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.0 - 15.6.6 and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.6.7, 15.7.3, or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover.
|
- Geo: [Container registry push events are rejected](https://gitlab.com/gitlab-org/gitlab/-/issues/386389) by the `/api/v4/container_registry_event/events` endpoint resulting in Geo secondary sites not being aware of updates to container registry images and subsequently not replicating the updates. Secondary sites may contain out of date container images after a failover as a consequence. This impacts versions 15.6.0 - 15.6.6 and 15.7.0 - 15.7.2. If you're using Geo with container repositories, you are advised to upgrade to GitLab 15.6.7, 15.7.3, or 15.8.0 which contain a fix for this issue and avoid potential data loss after a failover.
|
||||||
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
|
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
|
||||||
|
@ -524,6 +524,7 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
|
||||||
|
|
||||||
### 15.4.6
|
### 15.4.6
|
||||||
|
|
||||||
|
- Due to a [bug introduced in curl in GitLab 15.4.6](https://github.com/curl/curl/issues/10122), the [`no_proxy` environment variable may not work properly](../administration/geo/replication/troubleshooting.md#secondary-site-returns-received-http-code-403-from-proxy-after-connect). Either downgrade to GitLab 15.4.5, or upgrade to GitLab 15.5.7 or a later version.
|
||||||
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
|
- Due to [a bug introduced in GitLab 15.4](https://gitlab.com/gitlab-org/gitlab/-/issues/390155), if one or more Git repositories in Gitaly Cluster is [unavailable](../administration/gitaly/recovery.md#unavailable-repositories), then [Repository checks](../administration/repository_checks.md#repository-checks) and [Geo replication and verification](../administration/geo/index.md) stop running for all project or project wiki repositories in the affected Gitaly Cluster. The bug was fixed by [reverting the change in GitLab 15.9.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110823). Before upgrading to this version, check if you have any "unavailable" repositories. See [the bug issue](https://gitlab.com/gitlab-org/gitlab/-/issues/390155) for more information.
|
||||||
|
|
||||||
### 15.4.5
|
### 15.4.5
|
||||||
|
|
|
@ -422,6 +422,14 @@ Example:
|
||||||
Additional instructions here.
|
Additional instructions here.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### 15.9.0
|
||||||
|
|
||||||
|
With the addition of `gitlab-sshd` the Kerberos headers are needed to build GitLab Shell.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt install libkrb5-dev
|
||||||
|
```
|
||||||
|
|
||||||
### 15.0.0
|
### 15.0.0
|
||||||
|
|
||||||
Support for more than one database has been added to GitLab. [As part of this](https://gitlab.com/gitlab-org/gitlab/-/issues/338182),
|
Support for more than one database has been added to GitLab. [As part of this](https://gitlab.com/gitlab-org/gitlab/-/issues/338182),
|
||||||
|
|
|
@ -549,6 +549,77 @@ To enable group file templates:
|
||||||
1. Choose a project to act as the template repository.
|
1. Choose a project to act as the template repository.
|
||||||
1. Select **Save changes**.
|
1. Select **Save changes**.
|
||||||
|
|
||||||
|
## Group merge checks settings **(PREMIUM)**
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372040) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) name `support_group_level_merge_checks_setting`. Disabled by default.
|
||||||
|
|
||||||
|
FLAG:
|
||||||
|
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to
|
||||||
|
[enable the feature flag](../../administration/feature_flags.md) named `support_group_level_merge_checks_setting`. On GitLab.com, this feature is not
|
||||||
|
available.
|
||||||
|
|
||||||
|
Group owners can set up merge request checks on a top-level group, which apply to all subgroups and projects.
|
||||||
|
|
||||||
|
If the settings are inherited by a subgroup or project, they cannot be changed in the subgroup or project
|
||||||
|
that inherited them.
|
||||||
|
|
||||||
|
### Require a successful pipeline for merge
|
||||||
|
|
||||||
|
You can configure all child projects in your group to require a complete and successful pipeline before
|
||||||
|
merge.
|
||||||
|
|
||||||
|
See also [the project-level setting](../project/merge_requests/merge_when_pipeline_succeeds.md#require-a-successful-pipeline-for-merge).
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- You must be the owner of the group.
|
||||||
|
|
||||||
|
To enable this setting:
|
||||||
|
|
||||||
|
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||||
|
1. On the left sidebar, select **Settings > General**.
|
||||||
|
1. Expand **Merge requests**.
|
||||||
|
1. Under **Merge checks**, select **Pipelines must succeed**.
|
||||||
|
This setting also prevents merge requests from being merged if there is no pipeline.
|
||||||
|
1. Select **Save changes**.
|
||||||
|
|
||||||
|
#### Allow merge after skipped pipelines
|
||||||
|
|
||||||
|
You can configure [skipped pipelines](../../ci/pipelines/index.md#skip-a-pipeline) from preventing merge requests from being merged.
|
||||||
|
|
||||||
|
See also [the project-level setting](../project/merge_requests/merge_when_pipeline_succeeds.md#allow-merge-after-skipped-pipelines).
|
||||||
|
|
||||||
|
Prerequisite:
|
||||||
|
|
||||||
|
- You must be the owner of the group.
|
||||||
|
|
||||||
|
To change this behavior:
|
||||||
|
|
||||||
|
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||||
|
1. On the left sidebar, select **Settings > General**.
|
||||||
|
1. Expand **Merge requests**.
|
||||||
|
1. Under **Merge checks**:
|
||||||
|
- Select **Pipelines must succeed**.
|
||||||
|
- Select **Skipped pipelines are considered successful**.
|
||||||
|
1. Select **Save changes**.
|
||||||
|
|
||||||
|
### Prevent merge unless all threads are resolved
|
||||||
|
|
||||||
|
You can prevent merge requests from being merged until all threads are resolved. When this setting is enabled, for all child projects in your group, the
|
||||||
|
**Unresolved threads** count in a merge request is shown in orange when at least one thread remains unresolved.
|
||||||
|
|
||||||
|
Prerequisite:
|
||||||
|
|
||||||
|
- You must be the owner of the group.
|
||||||
|
|
||||||
|
To enable this setting:
|
||||||
|
|
||||||
|
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||||
|
1. On the left sidebar, select **Settings > General**.
|
||||||
|
1. Expand **Merge requests**.
|
||||||
|
1. Under **Merge checks**, select **All threads must be resolved**.
|
||||||
|
1. Select **Save changes**.
|
||||||
|
|
||||||
## Group merge request approval settings **(PREMIUM)**
|
## Group merge request approval settings **(PREMIUM)**
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285458) in GitLab 13.9. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../administration/feature_flags.md), disabled by default.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285458) in GitLab 13.9. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../administration/feature_flags.md), disabled by default.
|
||||||
|
|
|
@ -24,13 +24,7 @@ To edit an issue:
|
||||||
|
|
||||||
### Remove a task list item
|
### Remove a task list item
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `work_items_mvc`. Disabled by default.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9.
|
||||||
|
|
||||||
FLAG:
|
|
||||||
On self-managed GitLab, by default this feature is not available.
|
|
||||||
To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `work_items_mvc`.
|
|
||||||
On GitLab.com, this feature is not available.
|
|
||||||
The feature is not ready for production use.
|
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
|
|
|
@ -58,15 +58,9 @@ To create a task:
|
||||||
1. Enter the task title.
|
1. Enter the task title.
|
||||||
1. Select **Create task**.
|
1. Select **Create task**.
|
||||||
|
|
||||||
### Create a task from a task list item
|
### From a task list item
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9 [with a flag](../administration/feature_flags.md) named `work_items_mvc`. Disabled by default.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377307) in GitLab 15.9.
|
||||||
|
|
||||||
FLAG:
|
|
||||||
On self-managed GitLab, by default this feature is not available.
|
|
||||||
To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc`.
|
|
||||||
On GitLab.com, this feature is not available.
|
|
||||||
The feature is not ready for production use.
|
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
|
|
|
@ -7028,7 +7028,7 @@ references and their corresponding code points.</p>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p> &amp; © Æ Ď</span>
|
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p> &amp; © Æ Ď</span>
|
||||||
<span id="LC2" class="line" lang="plaintext">¾ ℋ ⅆ</span>
|
<span id="LC2" class="line" lang="plaintext">¾ ℋ ⅆ</span>
|
||||||
<span id="LC3" class="line" lang="plaintext">∲ ≧̸</p></span></code></pre>
|
<span id="LC3" class="line" lang="plaintext">∲ ≧̸</p></span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
|
@ -7344,11 +7344,11 @@ stripped in this way:</p>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="#example-343">Example 343</a></div>
|
<div><a href="#example-343">Example 343</a></div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
|
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> b </code></p></span></code></pre>
|
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> b </code></p></span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7356,12 +7356,12 @@ stripped in this way:</p>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="#example-344">Example 344</a></div>
|
<div><a href="#example-344">Example 344</a></div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
|
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
|
||||||
<span id="LC2" class="line" lang="plaintext">` `</span></code></pre>
|
<span id="LC2" class="line" lang="plaintext">` `</span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> </code></span>
|
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p><code> </code></span>
|
||||||
<span id="LC2" class="line" lang="plaintext"><code> </code></p></span></code></pre>
|
<span id="LC2" class="line" lang="plaintext"><code> </code></p></span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7832,11 +7832,11 @@ not part of a [left-flanking delimiter run]:</p>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="#example-363">Example 363</a></div>
|
<div><a href="#example-363">Example 363</a></div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
|
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p>* a *</p></span></code></pre>
|
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext"><p>* a *</p></span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9790,7 +9790,7 @@ Other [Unicode whitespace] like non-breaking space doesn't work.</p>
|
||||||
<div>
|
<div>
|
||||||
<div><a href="#example-515">Example 515</a></div>
|
<div><a href="#example-515">Example 515</a></div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
|
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
|
||||||
<copy-code></copy-code>
|
<copy-code></copy-code>
|
||||||
</div>
|
</div>
|
||||||
<div class="gl-relative markdown-code-block js-markdown-code">
|
<div class="gl-relative markdown-code-block js-markdown-code">
|
||||||
|
|
|
@ -203,6 +203,10 @@ module API
|
||||||
render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
|
render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless can?(current_user, :read_code, target_project)
|
||||||
|
forbidden!("You don't have access to this fork's parent project")
|
||||||
|
end
|
||||||
|
|
||||||
cache_key = compare_cache_key(current_user, user_project, target_project, declared_params)
|
cache_key = compare_cache_key(current_user, user_project, target_project, declared_params)
|
||||||
|
|
||||||
cache_action(cache_key, expires_in: 1.minute) do
|
cache_action(cache_key, expires_in: 1.minute) do
|
||||||
|
|
|
@ -6,11 +6,35 @@ module Banzai
|
||||||
# as well as hiding the customer's IP address when requesting images.
|
# as well as hiding the customer's IP address when requesting images.
|
||||||
# Copies the original img `src` to `data-canonical-src` then replaces the
|
# Copies the original img `src` to `data-canonical-src` then replaces the
|
||||||
# `src` with a new url to the proxy server.
|
# `src` with a new url to the proxy server.
|
||||||
class AssetProxyFilter < HTML::Pipeline::CamoFilter
|
#
|
||||||
|
# Based on https://github.com/gjtorikian/html-pipeline/blob/v2.14.3/lib/html/pipeline/camo_filter.rb
|
||||||
|
class AssetProxyFilter < HTML::Pipeline::Filter
|
||||||
def initialize(text, context = nil, result = nil)
|
def initialize(text, context = nil, result = nil)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
return doc unless asset_proxy_enabled?
|
||||||
|
|
||||||
|
doc.search('img').each do |element|
|
||||||
|
original_src = element['src']
|
||||||
|
next unless original_src
|
||||||
|
|
||||||
|
begin
|
||||||
|
uri = URI.parse(original_src)
|
||||||
|
rescue StandardError
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
next if uri.host.nil? && !original_src.start_with?('///')
|
||||||
|
next if asset_host_allowed?(uri.host)
|
||||||
|
|
||||||
|
element['src'] = asset_proxy_url(original_src)
|
||||||
|
element['data-canonical-src'] = original_src
|
||||||
|
end
|
||||||
|
doc
|
||||||
|
end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
|
needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
|
||||||
end
|
end
|
||||||
|
@ -63,6 +87,24 @@ module Banzai
|
||||||
application_settings.try(:asset_proxy_whitelist).presence ||
|
application_settings.try(:asset_proxy_whitelist).presence ||
|
||||||
[Gitlab.config.gitlab.host]
|
[Gitlab.config.gitlab.host]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def asset_proxy_enabled?
|
||||||
|
!context[:disable_asset_proxy]
|
||||||
|
end
|
||||||
|
|
||||||
|
def asset_proxy_url(url)
|
||||||
|
"#{context[:asset_proxy]}/#{asset_url_hash(url)}/#{hexencode(url)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def asset_url_hash(url)
|
||||||
|
OpenSSL::HMAC.hexdigest('sha1', context[:asset_proxy_secret_key], url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def hexencode(str)
|
||||||
|
str.unpack1('H*')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
module Banzai
|
module Banzai
|
||||||
module Filter
|
module Filter
|
||||||
class InlineObservabilityFilter < ::Banzai::Filter::InlineEmbedsFilter
|
class InlineObservabilityFilter < ::Banzai::Filter::InlineEmbedsFilter
|
||||||
|
@ -15,7 +17,8 @@ module Banzai
|
||||||
doc.document.create_element(
|
doc.document.create_element(
|
||||||
'div',
|
'div',
|
||||||
class: 'js-render-observability',
|
class: 'js-render-observability',
|
||||||
'data-frame-url': url
|
'data-frame-url': url,
|
||||||
|
'data-observability-url': Gitlab::Observability.observability_url
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,8 +31,15 @@ module Banzai
|
||||||
# obtained from the target link
|
# obtained from the target link
|
||||||
def element_to_embed(node)
|
def element_to_embed(node)
|
||||||
url = node['href']
|
url = node['href']
|
||||||
|
uri = URI.parse(url)
|
||||||
|
observability_uri = URI.parse(Gitlab::Observability.observability_url)
|
||||||
|
|
||||||
create_element(url)
|
if uri.scheme == observability_uri.scheme &&
|
||||||
|
uri.port == observability_uri.port &&
|
||||||
|
uri.host.casecmp?(observability_uri.host) &&
|
||||||
|
uri.path.downcase.exclude?("auth/start")
|
||||||
|
create_element(url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
# Can be extended for different types of repository object, e.g. Project or Snippet
|
# Can be extended for different types of repository object, e.g. Project or Snippet
|
||||||
module ExtractsRef
|
module ExtractsRef
|
||||||
InvalidPathError = Class.new(StandardError)
|
InvalidPathError = Class.new(StandardError)
|
||||||
|
BRANCH_REF_TYPE = 'heads'
|
||||||
|
TAG_REF_TYPE = 'tags'
|
||||||
# Given a string containing both a Git tree-ish, such as a branch or tag, and
|
# Given a string containing both a Git tree-ish, such as a branch or tag, and
|
||||||
# a filesystem path joined by forward slashes, attempts to separate the two.
|
# a filesystem path joined by forward slashes, attempts to separate the two.
|
||||||
#
|
#
|
||||||
|
@ -91,7 +92,7 @@ module ExtractsRef
|
||||||
def ref_type
|
def ref_type
|
||||||
return unless params[:ref_type].present?
|
return unless params[:ref_type].present?
|
||||||
|
|
||||||
params[:ref_type] == 'tags' ? 'tags' : 'heads'
|
params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -154,4 +155,13 @@ module ExtractsRef
|
||||||
def repository_container
|
def repository_container
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ambiguous_ref?(project, ref)
|
||||||
|
return true if project.repository.ambiguous_ref?(ref)
|
||||||
|
|
||||||
|
return false unless ref&.starts_with?('refs/')
|
||||||
|
|
||||||
|
unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '')
|
||||||
|
project.repository.commit(unprefixed_ref).present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
# Nullifies last_error value from project_mirror_data table as they
|
||||||
|
# potentially included sensitive data.
|
||||||
|
# https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3041
|
||||||
|
class NullifyLastErrorFromProjectMirrorData < BatchedMigrationJob
|
||||||
|
feature_category :source_code_management
|
||||||
|
operation_name :update_all
|
||||||
|
|
||||||
|
def perform
|
||||||
|
each_sub_batch { |rel| rel.update_all(last_error: nil) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,7 +42,7 @@ module Gitlab
|
||||||
def prohibited_branch_checks
|
def prohibited_branch_checks
|
||||||
return if deletion?
|
return if deletion?
|
||||||
|
|
||||||
if branch_name =~ /\A\h{40}\z/
|
if branch_name =~ %r{\A\h{40}(/|\z)}
|
||||||
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -367,12 +367,12 @@ module Gitlab
|
||||||
|
|
||||||
def foreign_key_exists?(source, target = nil, **options)
|
def foreign_key_exists?(source, target = nil, **options)
|
||||||
# This if block is necessary because foreign_key_exists? is called in down migrations that may execute before
|
# This if block is necessary because foreign_key_exists? is called in down migrations that may execute before
|
||||||
# the postgres_foreign_keys view had necessary columns added, or even before the view existed.
|
# the postgres_foreign_keys view had necessary columns added.
|
||||||
# In that case, we revert to the previous behavior of this method.
|
# In that case, we revert to the previous behavior of this method.
|
||||||
# The behavior in the if block has a bug: it always returns false if the fk being checked has multiple columns.
|
# The behavior in the if block has a bug: it always returns false if the fk being checked has multiple columns.
|
||||||
# This can be removed after init_schema.rb passes 20221122210711_add_columns_to_postgres_foreign_keys.rb
|
# This can be removed after init_schema.rb passes 20221122210711_add_columns_to_postgres_foreign_keys.rb
|
||||||
# Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/386796
|
# Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/386796
|
||||||
if ActiveRecord::Migrator.current_version < 20221122210711
|
unless connection.column_exists?('postgres_foreign_keys', 'constrained_table_name')
|
||||||
return foreign_keys(source).any? do |foreign_key|
|
return foreign_keys(source).any? do |foreign_key|
|
||||||
tables_match?(target.to_s, foreign_key.to_table.to_s) &&
|
tables_match?(target.to_s, foreign_key.to_table.to_s) &&
|
||||||
options_match?(foreign_key.options, options)
|
options_match?(foreign_key.options, options)
|
||||||
|
|
|
@ -262,7 +262,11 @@ module Gitlab
|
||||||
|
|
||||||
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
|
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
|
||||||
ref ||= root_ref
|
ref ||= root_ref
|
||||||
commit = Gitlab::Git::Commit.find(self, ref)
|
|
||||||
|
commit_id = extract_commit_id_from_ref(ref)
|
||||||
|
return {} if commit_id.nil?
|
||||||
|
|
||||||
|
commit = Gitlab::Git::Commit.find(self, commit_id)
|
||||||
return {} if commit.nil?
|
return {} if commit.nil?
|
||||||
|
|
||||||
prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)
|
prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)
|
||||||
|
@ -1233,6 +1237,26 @@ module Gitlab
|
||||||
def gitaly_delete_refs(*ref_names)
|
def gitaly_delete_refs(*ref_names)
|
||||||
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
|
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The order is based on git priority to resolve ambiguous references
|
||||||
|
#
|
||||||
|
# `git show <ref>`
|
||||||
|
#
|
||||||
|
# In case of name clashes, it uses this order:
|
||||||
|
# 1. Commit
|
||||||
|
# 2. Tag
|
||||||
|
# 3. Branch
|
||||||
|
def extract_commit_id_from_ref(ref)
|
||||||
|
return ref if Gitlab::Git.commit_id?(ref)
|
||||||
|
|
||||||
|
tag = find_tag(ref)
|
||||||
|
return tag.dereferenced_target.sha if tag
|
||||||
|
|
||||||
|
branch = find_branch(ref)
|
||||||
|
return branch.dereferenced_target.sha if branch
|
||||||
|
|
||||||
|
ref
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -448,6 +448,17 @@ module Gitlab
|
||||||
)
|
)
|
||||||
}mx.freeze
|
}mx.freeze
|
||||||
|
|
||||||
|
# Code blocks:
|
||||||
|
# ```
|
||||||
|
# Anything, including `>>>` blocks which are ignored by this filter
|
||||||
|
# ```
|
||||||
|
MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED =
|
||||||
|
'(?P<code>' \
|
||||||
|
'^```\n' \
|
||||||
|
'(?:\n|.)*?' \
|
||||||
|
'\n```\ *$' \
|
||||||
|
')'.freeze
|
||||||
|
|
||||||
MARKDOWN_HTML_BLOCK_REGEX = %r{
|
MARKDOWN_HTML_BLOCK_REGEX = %r{
|
||||||
(?<html>
|
(?<html>
|
||||||
# HTML block:
|
# HTML block:
|
||||||
|
@ -461,27 +472,19 @@ module Gitlab
|
||||||
)
|
)
|
||||||
}mx.freeze
|
}mx.freeze
|
||||||
|
|
||||||
MARKDOWN_HTML_COMMENT_LINE_REGEX = %r{
|
# HTML comment line:
|
||||||
(?<html_comment_line>
|
# <!-- some commented text -->
|
||||||
# HTML comment line:
|
MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED =
|
||||||
# <!-- some commented text -->
|
'(?P<html_comment_line>' \
|
||||||
|
'^<!--\ .*?\ -->\ *$' \
|
||||||
|
')'.freeze
|
||||||
|
|
||||||
^<!--\ .*\ -->\ *$
|
MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED =
|
||||||
)
|
'(?P<html_comment_block>' \
|
||||||
}mx.freeze
|
'^<!--.*?\n' \
|
||||||
|
'(?:\n|.)*?' \
|
||||||
MARKDOWN_HTML_COMMENT_BLOCK_REGEX = %r{
|
'\n.*?-->\ *$' \
|
||||||
(?<html_comment_block>
|
')'.freeze
|
||||||
# HTML comment block:
|
|
||||||
# <!-- some commented text
|
|
||||||
# additional text
|
|
||||||
# -->
|
|
||||||
|
|
||||||
^<!--.*\n
|
|
||||||
.+?
|
|
||||||
\n-->\ *$
|
|
||||||
)
|
|
||||||
}mx.freeze
|
|
||||||
|
|
||||||
def markdown_code_or_html_blocks
|
def markdown_code_or_html_blocks
|
||||||
@markdown_code_or_html_blocks ||= %r{
|
@markdown_code_or_html_blocks ||= %r{
|
||||||
|
@ -491,14 +494,13 @@ module Gitlab
|
||||||
}mx.freeze
|
}mx.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def markdown_code_or_html_comments
|
def markdown_code_or_html_comments_untrusted
|
||||||
@markdown_code_or_html_comments ||= %r{
|
@markdown_code_or_html_comments_untrusted ||=
|
||||||
#{MARKDOWN_CODE_BLOCK_REGEX}
|
"#{MARKDOWN_CODE_BLOCK_REGEX_UNTRUSTED}" \
|
||||||
|
|
"|" \
|
||||||
#{MARKDOWN_HTML_COMMENT_LINE_REGEX}
|
"#{MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED}" \
|
||||||
|
|
"|" \
|
||||||
#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX}
|
"#{MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED}"
|
||||||
}mx.freeze
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Based on Jira's project key format
|
# Based on Jira's project key format
|
||||||
|
|
|
@ -9,6 +9,12 @@ module Gitlab
|
||||||
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
|
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
|
||||||
BIDI_REGEXP = /\p{Bidi Control}/.freeze
|
BIDI_REGEXP = /\p{Bidi Control}/.freeze
|
||||||
|
|
||||||
|
# Regular expression for identifying space characters
|
||||||
|
#
|
||||||
|
# In web browsers space characters can be confused with simple
|
||||||
|
# spaces which may be misleading
|
||||||
|
SPACE_REGEXP = /\p{Space_Separator}/.freeze
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Warning message used to highlight bidi characters in the GUI
|
# Warning message used to highlight bidi characters in the GUI
|
||||||
def bidi_warning
|
def bidi_warning
|
||||||
|
|
|
@ -47,6 +47,17 @@ module Gitlab
|
||||||
RE2.Replace(text, regexp, rewrite)
|
RE2.Replace(text, regexp, rewrite)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# #scan returns an array of the groups captured, rather than MatchData.
|
||||||
|
# Use this to give the capture group name and grab the proper value
|
||||||
|
def extract_named_group(name, match)
|
||||||
|
return unless match
|
||||||
|
|
||||||
|
match_position = regexp.named_capturing_groups[name.to_s]
|
||||||
|
raise RegexpError, "Invalid named capture group: #{name}" unless match_position
|
||||||
|
|
||||||
|
match[match_position - 1]
|
||||||
|
end
|
||||||
|
|
||||||
def ==(other)
|
def ==(other)
|
||||||
self.source == other.source
|
self.source == other.source
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,15 +2,37 @@
|
||||||
|
|
||||||
module Gitlab
|
module Gitlab
|
||||||
class UrlSanitizer
|
class UrlSanitizer
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
||||||
ALLOWED_WEB_SCHEMES = %w[http https].freeze
|
ALLOWED_WEB_SCHEMES = %w[http https].freeze
|
||||||
|
SCHEMIFIED_SCHEME = 'glschemelessuri'
|
||||||
|
SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://".freeze
|
||||||
|
# URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or
|
||||||
|
# relative URLs. This section will match schemeless URIs with userinfo
|
||||||
|
# e.g. user:pass@gitlab.com but will not match scp-style URIs e.g.
|
||||||
|
# user@server:path/to/file)
|
||||||
|
#
|
||||||
|
# The userinfo part is very loose compared to URI's implementation so we
|
||||||
|
# also match non-escaped userinfo e.g foo:b?r@gitlab.com which should be
|
||||||
|
# encoded as foo:b%3Fr@gitlab.com
|
||||||
|
URI_REGEXP = %r{
|
||||||
|
(?:
|
||||||
|
#{URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)}
|
||||||
|
|
|
||||||
|
(?:(?:(?!@)[%#{URI::REGEXP::PATTERN::UNRESERVED}#{URI::REGEXP::PATTERN::RESERVED}])+(?:@))
|
||||||
|
(?# negative lookahead ensures this isn't an SCP-style URL: [host]:[rel_path|abs_path] server:path/to/file)
|
||||||
|
(?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH}))
|
||||||
|
#{URI::REGEXP::PATTERN::HOSTPORT}
|
||||||
|
)
|
||||||
|
}x
|
||||||
|
|
||||||
def self.sanitize(content)
|
def self.sanitize(content)
|
||||||
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
|
content.gsub(URI_REGEXP) do |url|
|
||||||
|
new(url).masked_url
|
||||||
content.gsub(regexp) { |url| new(url).masked_url }
|
rescue Addressable::URI::InvalidURIError
|
||||||
rescue Addressable::URI::InvalidURIError
|
''
|
||||||
content.gsub(regexp, '')
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
|
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
|
||||||
|
@ -37,17 +59,6 @@ module Gitlab
|
||||||
@url = parse_url(url)
|
@url = parse_url(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitized_url
|
|
||||||
@sanitized_url ||= safe_url.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def masked_url
|
|
||||||
url = @url.dup
|
|
||||||
url.password = "*****" if url.password.present?
|
|
||||||
url.user = "*****" if url.user.present?
|
|
||||||
url.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def credentials
|
def credentials
|
||||||
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
|
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
|
||||||
end
|
end
|
||||||
|
@ -56,15 +67,37 @@ module Gitlab
|
||||||
credentials[:user]
|
credentials[:user]
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_url
|
def sanitized_url
|
||||||
@full_url ||= generate_full_url.to_s
|
safe_url = @url.dup
|
||||||
|
safe_url.password = nil
|
||||||
|
safe_url.user = nil
|
||||||
|
reverse_schemify(safe_url.to_s)
|
||||||
end
|
end
|
||||||
|
strong_memoize_attr :sanitized_url
|
||||||
|
|
||||||
|
def masked_url
|
||||||
|
url = @url.dup
|
||||||
|
url.password = "*****" if url.password.present?
|
||||||
|
url.user = "*****" if url.user.present?
|
||||||
|
reverse_schemify(url.to_s)
|
||||||
|
end
|
||||||
|
strong_memoize_attr :masked_url
|
||||||
|
|
||||||
|
def full_url
|
||||||
|
return reverse_schemify(@url.to_s) unless valid_credentials?
|
||||||
|
|
||||||
|
url = @url.dup
|
||||||
|
url.password = encode_percent(credentials[:password]) if credentials[:password].present?
|
||||||
|
url.user = encode_percent(credentials[:user]) if credentials[:user].present?
|
||||||
|
reverse_schemify(url.to_s)
|
||||||
|
end
|
||||||
|
strong_memoize_attr :full_url
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parse_url(url)
|
def parse_url(url)
|
||||||
url = url.to_s.strip
|
url = schemify(url.to_s.strip)
|
||||||
match = url.match(%r{\A(?:git|ssh|http(?:s?))\://(?:(.+)(?:@))?(.+)})
|
match = url.match(%r{\A(?:(?:#{SCHEMIFIED_SCHEME}|git|ssh|http(?:s?)):)?//(?:(.+)(?:@))?(.+)}o)
|
||||||
raw_credentials = match[1] if match
|
raw_credentials = match[1] if match
|
||||||
|
|
||||||
if raw_credentials.present?
|
if raw_credentials.present?
|
||||||
|
@ -83,24 +116,19 @@ module Gitlab
|
||||||
url
|
url
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_full_url
|
def schemify(url)
|
||||||
return @url unless valid_credentials?
|
# Prepend the placeholder scheme unless the URL has a scheme or is relative
|
||||||
|
url.prepend(SCHEMIFY_PLACEHOLDER) unless url.starts_with?(%r{(?:#{URI::REGEXP::PATTERN::SCHEME}:)?//}o)
|
||||||
@url.dup.tap do |generated|
|
url
|
||||||
generated.password = encode_percent(credentials[:password]) if credentials[:password].present?
|
|
||||||
generated.user = encode_percent(credentials[:user]) if credentials[:user].present?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def safe_url
|
def reverse_schemify(url)
|
||||||
safe_url = @url.dup
|
url.slice!(SCHEMIFY_PLACEHOLDER) if url.starts_with?(SCHEMIFY_PLACEHOLDER)
|
||||||
safe_url.password = nil
|
url
|
||||||
safe_url.user = nil
|
|
||||||
safe_url
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_credentials?
|
def valid_credentials?
|
||||||
credentials && credentials.is_a?(Hash) && credentials.any?
|
credentials.is_a?(Hash) && credentials.values.any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_percent(string)
|
def encode_percent(string)
|
||||||
|
|
|
@ -25,7 +25,10 @@ module Rouge
|
||||||
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
|
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
|
||||||
|
|
||||||
line.each do |token, value|
|
line.each do |token, value|
|
||||||
yield highlight_unicode_control_characters(span(token, value.chomp! || value))
|
value = value.chomp! || value
|
||||||
|
value = replace_space_characters(value)
|
||||||
|
|
||||||
|
yield highlight_unicode_control_characters(span(token, value))
|
||||||
end
|
end
|
||||||
|
|
||||||
yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present?
|
yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present?
|
||||||
|
@ -42,6 +45,10 @@ module Rouge
|
||||||
%(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>)
|
%(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_space_characters(text)
|
||||||
|
text.gsub(Gitlab::Unicode::SPACE_REGEXP, ' ')
|
||||||
|
end
|
||||||
|
|
||||||
def highlight_unicode_control_characters(text)
|
def highlight_unicode_control_characters(text)
|
||||||
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
|
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
|
||||||
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
|
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
|
||||||
|
|
|
@ -42698,6 +42698,9 @@ msgstr ""
|
||||||
msgid "The default branch for this project has been changed. Please update your bookmarks."
|
msgid "The default branch for this project has been changed. Please update your bookmarks."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The default branch of this project clashes with another ref"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "The dependency list details information about the components used within your project."
|
msgid "The dependency list details information about the components used within your project."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -170,8 +170,6 @@ class CreatePipelineFailureIncident
|
||||||
Additionally, a message can be posted in `#backend_maintainers` or `#frontend_maintainers` to get a maintainer take a look at the fix ASAP.
|
Additionally, a message can be posted in `#backend_maintainers` or `#frontend_maintainers` to get a maintainer take a look at the fix ASAP.
|
||||||
- Cherry picking a change that was used to fix a similar master-broken issue.
|
- Cherry picking a change that was used to fix a similar master-broken issue.
|
||||||
|
|
||||||
In both cases, make sure to add the ~"pipeline:expedite" label to speed up the `stable`-fixing pipelines.
|
|
||||||
|
|
||||||
### Resolution
|
### Resolution
|
||||||
|
|
||||||
Add a comment to this issue describing how this incident could have been prevented earlier in the Merge Request pipeline (rather than the merge commit pipeline).
|
Add a comment to this issue describing how this incident could have been prevented earlier in the Merge Request pipeline (rather than the merge commit pipeline).
|
||||||
|
|
|
@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do
|
||||||
enable_ssl_verification: false,
|
enable_ssl_verification: false,
|
||||||
url_variables: [
|
url_variables: [
|
||||||
{ key: 'token', value: 'some secret value' },
|
{ key: 'token', value: 'some secret value' },
|
||||||
|
{ key: 'baz', value: 'qux' },
|
||||||
{ key: 'foo', value: nil }
|
{ key: 'foo', value: nil }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do
|
||||||
expect(flash[:notice]).to include('was updated')
|
expect(flash[:notice]).to include('was updated')
|
||||||
expect(hook).to have_attributes(hook_params.except(:url_variables))
|
expect(hook).to have_attributes(hook_params.except(:url_variables))
|
||||||
expect(hook).to have_attributes(
|
expect(hook).to have_attributes(
|
||||||
url_variables: { 'token' => 'some secret value', 'baz' => 'woo' }
|
url_variables: { 'token' => 'some secret value', 'baz' => 'qux' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe ConfirmEmailWarning do
|
RSpec.describe ConfirmEmailWarning, feature_category: :system_access do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(soft_email_confirmation: true)
|
stub_feature_flags(soft_email_confirmation: true)
|
||||||
end
|
end
|
||||||
|
@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do
|
||||||
it { is_expected.to set_confirm_warning_for(user.email) }
|
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user is being impersonated' do
|
||||||
|
let(:impersonator) { create(:admin) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id })
|
||||||
|
|
||||||
|
get :index
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||||
|
|
||||||
|
context 'when impersonated user email has html in their email' do
|
||||||
|
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||||
|
|
||||||
|
it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not being impersonated' do
|
||||||
|
before do
|
||||||
|
get :index
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to set_confirm_warning_for(user.email) }
|
||||||
|
|
||||||
|
context 'when user email has html in their email' do
|
||||||
|
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||||
|
|
||||||
|
it { is_expected.to set_confirm_warning_for("malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::BlobController do
|
RSpec.describe Projects::BlobController, feature_category: :source_code_management do
|
||||||
include ProjectForksHelper
|
include ProjectForksHelper
|
||||||
|
|
||||||
let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) }
|
let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) }
|
||||||
let(:previous_default_branch) { nil }
|
let(:previous_default_branch) { nil }
|
||||||
|
|
||||||
describe "GET show" do
|
describe "GET show" do
|
||||||
def request
|
let(:params) { { namespace_id: project.namespace, project_id: project, id: id } }
|
||||||
get(:show, params: { namespace_id: project.namespace, project_id: project, id: id })
|
let(:request) do
|
||||||
|
get(:show, params: params)
|
||||||
end
|
end
|
||||||
|
|
||||||
render_views
|
render_views
|
||||||
|
@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do
|
||||||
context 'with file path' do
|
context 'with file path' do
|
||||||
before do
|
before do
|
||||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||||
|
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
|
||||||
|
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
|
||||||
request
|
request
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the ref is ambiguous' do
|
||||||
|
let(:ref) { 'ambiguous_ref' }
|
||||||
|
let(:path) { 'README.md' }
|
||||||
|
let(:id) { "#{ref}/#{path}" }
|
||||||
|
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
|
||||||
|
|
||||||
|
context 'and explicitly requesting a branch' do
|
||||||
|
let(:ref_type) { 'heads' }
|
||||||
|
|
||||||
|
it 'redirects to blob#show with sha for the branch' do
|
||||||
|
expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'and explicitly requesting a tag' do
|
||||||
|
let(:ref_type) { 'tags' }
|
||||||
|
|
||||||
|
it 'responds with success' do
|
||||||
|
expect(response).to be_ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "valid branch, valid file" do
|
context "valid branch, valid file" do
|
||||||
let(:id) { 'master/README.md' }
|
let(:id) { 'master/README.md' }
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
|
||||||
include GoogleApi::CloudPlatformHelpers
|
include GoogleApi::CloudPlatformHelpers
|
||||||
include KubernetesHelpers
|
include KubernetesHelpers
|
||||||
|
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be_with_reload(:project) { create(:project) }
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController, feature_category: :kubernetes_manag
|
||||||
expect(response).to redirect_to(new_user_session_path)
|
expect(response).to redirect_to(new_user_session_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a public project' do
|
||||||
|
before do
|
||||||
|
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||||
|
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with guest user' do
|
||||||
|
let(:prometheus_body) { nil }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
get :prometheus_proxy, params: prometheus_proxy_params
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::Environments::PrometheusApiController do
|
RSpec.describe Projects::Environments::PrometheusApiController do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be_with_reload(:project) { create(:project) }
|
||||||
let_it_be(:proxyable) { create(:environment, project: project) }
|
let_it_be(:proxyable) { create(:environment, project: project) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do
|
||||||
expect(response).to redirect_to(new_user_session_path)
|
expect(response).to redirect_to(new_user_session_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a public project' do
|
||||||
|
before do
|
||||||
|
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||||
|
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with guest user' do
|
||||||
|
let(:prometheus_body) { nil }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
get :prometheus_proxy, params: prometheus_proxy_params
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme
|
||||||
'tree' | nil | lazy { project_tree_path(project, id) }
|
'tree' | nil | lazy { project_tree_path(project, id) }
|
||||||
'tree' | 'heads' | lazy { project_tree_path(project, id) }
|
'tree' | 'heads' | lazy { project_tree_path(project, id) }
|
||||||
'blob' | nil | lazy { project_blob_path(project, id) }
|
'blob' | nil | lazy { project_blob_path(project, id) }
|
||||||
'blob' | 'heads' | lazy { project_blob_path(project, id) }
|
'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') }
|
||||||
'graph' | nil | lazy { project_network_path(project, id) }
|
'graph' | nil | lazy { project_network_path(project, id) }
|
||||||
'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') }
|
'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') }
|
||||||
'graphs' | nil | lazy { project_graph_path(project, id) }
|
'graphs' | nil | lazy { project_graph_path(project, id) }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::TreeController do
|
RSpec.describe Projects::TreeController, feature_category: :source_code_management do
|
||||||
let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) }
|
let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) }
|
||||||
let(:previous_default_branch) { nil }
|
let(:previous_default_branch) { nil }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
@ -15,18 +15,41 @@ RSpec.describe Projects::TreeController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET show" do
|
describe "GET show" do
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
namespace_id: project.namespace.to_param, project_id: project, id: id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
# Make sure any errors accessing the tree in our views bubble up to this spec
|
# Make sure any errors accessing the tree in our views bubble up to this spec
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
before do
|
before do
|
||||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||||
|
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
|
||||||
|
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
|
||||||
|
get :show, params: params
|
||||||
|
end
|
||||||
|
|
||||||
get(:show,
|
context 'when the ref is ambiguous' do
|
||||||
params: {
|
let(:id) { 'ambiguous_ref' }
|
||||||
namespace_id: project.namespace.to_param,
|
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
|
||||||
project_id: project,
|
|
||||||
id: id
|
context 'and explicitly requesting a branch' do
|
||||||
})
|
let(:ref_type) { 'heads' }
|
||||||
|
|
||||||
|
it 'redirects to blob#show with sha for the branch' do
|
||||||
|
expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'and explicitly requesting a tag' do
|
||||||
|
let(:ref_type) { 'tags' }
|
||||||
|
|
||||||
|
it 'responds with success' do
|
||||||
|
expect(response).to be_ok
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "valid branch, no path" do
|
context "valid branch, no path" do
|
||||||
|
|
|
@ -163,6 +163,69 @@ RSpec.describe ProjectsController, feature_category: :projects do
|
||||||
expect(assigns(:notification_setting).level).to eq("watch")
|
expect(assigns(:notification_setting).level).to eq("watch")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when there is a tag with the same name as the default branch' do
|
||||||
|
let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) }
|
||||||
|
let(:tree_with_default_branch) do
|
||||||
|
branch = tagged_project.repository.find_branch(tagged_project.default_branch)
|
||||||
|
project_tree_path(tagged_project, branch.target)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
tagged_project.repository.create_file(
|
||||||
|
tagged_project.creator,
|
||||||
|
'file_for_tag',
|
||||||
|
'content for file',
|
||||||
|
message: "Automatically created file",
|
||||||
|
branch_name: 'branch-to-tag'
|
||||||
|
)
|
||||||
|
|
||||||
|
tagged_project.repository.add_tag(
|
||||||
|
tagged_project.creator,
|
||||||
|
tagged_project.default_branch, # tag name
|
||||||
|
'branch-to-tag' # target
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to tree view for the default branch' do
|
||||||
|
get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project }
|
||||||
|
expect(response).to redirect_to(tree_with_default_branch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the default branch name can resolve to another ref' do
|
||||||
|
let!(:project_with_default_branch) do
|
||||||
|
create(:project, :public, :custom_repo, files: ['somefile']).tap do |p|
|
||||||
|
p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master')
|
||||||
|
p.change_head("refs/heads/#{other_ref}")
|
||||||
|
end.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:other_ref) { 'branch-name' }
|
||||||
|
|
||||||
|
context 'but there is no other ref' do
|
||||||
|
it 'responds with ok' do
|
||||||
|
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
|
||||||
|
expect(response).to be_ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'and that other ref exists' do
|
||||||
|
let(:tree_with_default_branch) do
|
||||||
|
branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch)
|
||||||
|
project_tree_path(project_with_default_branch, branch.target)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
project_with_default_branch.repository.create_branch(other_ref, 'master')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to tree view for the default branch' do
|
||||||
|
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
|
||||||
|
expect(response).to redirect_to(tree_with_default_branch)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "when project repository is disabled" do
|
describe "when project repository is disabled" do
|
||||||
|
|
|
@ -7,7 +7,7 @@ FactoryBot.define do
|
||||||
project
|
project
|
||||||
|
|
||||||
trait :url_variables do
|
trait :url_variables do
|
||||||
url_variables { { 'abc' => 'supers3cret' } }
|
url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
trait :token do
|
trait :token do
|
||||||
|
|
|
@ -21,6 +21,7 @@ require_relative 'support/rspec'
|
||||||
require_relative '../lib/gitlab/utils'
|
require_relative '../lib/gitlab/utils'
|
||||||
require_relative '../lib/gitlab/utils/strong_memoize'
|
require_relative '../lib/gitlab/utils/strong_memoize'
|
||||||
require 'active_support/all'
|
require 'active_support/all'
|
||||||
|
require 'pry'
|
||||||
|
|
||||||
require_relative 'simplecov_env'
|
require_relative 'simplecov_env'
|
||||||
SimpleCovEnv.start!
|
SimpleCovEnv.start!
|
||||||
|
|
|
@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
|
||||||
icon = first('[data-testid="incognito-icon"]')
|
icon = first('[data-testid="incognito-icon"]')
|
||||||
expect(icon).not_to be nil
|
expect(icon).not_to be nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when viewing the confirm email warning', :js do
|
||||||
|
let_it_be(:another_user) { create(:user, :unconfirmed) }
|
||||||
|
|
||||||
|
let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') }
|
||||||
|
let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } }
|
||||||
|
|
||||||
|
context 'with an email that does not contain HTML' do
|
||||||
|
before do
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays the warning alert including the email' do
|
||||||
|
expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an email that contains HTML' do
|
||||||
|
let(:malicious_email) { "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>" }
|
||||||
|
let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays the impersonation alert, excludes email, and disables links' do
|
||||||
|
expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'ending impersonation' do
|
context 'ending impersonation' do
|
||||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let!(:group) { create(:group) }
|
let!(:group) { create(:group) }
|
||||||
let!(:public_project) { create(:project, :public, namespace: group) }
|
let!(:public_project) { create(:project, :public, namespace: group) }
|
||||||
|
let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) }
|
||||||
let!(:private_project) { create(:project, :private, namespace: group) }
|
let!(:private_project) { create(:project, :private, namespace: group) }
|
||||||
let!(:user) { create(:user) }
|
let!(:user) { create(:user) }
|
||||||
|
|
||||||
|
@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
create(:environment, name: 'gprd', project: public_project)
|
create(:environment, name: 'gprd', project: public_project)
|
||||||
create(:environment, name: 'gprd', project: private_project)
|
create(:environment, name: 'gprd', project: private_project)
|
||||||
create(:environment, name: 'gcny', project: private_project)
|
create(:environment, name: 'gcny', project: private_project)
|
||||||
|
create(:environment, name: 'gprivprd', project: public_project_with_private_environments)
|
||||||
|
create(:environment, name: 'gprivstg', project: public_project_with_private_environments)
|
||||||
|
|
||||||
|
public_project_with_private_environments.update!(namespace: group)
|
||||||
|
public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'using a group' do
|
context 'using a group' do
|
||||||
|
@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
|
|
||||||
names = described_class.new(group, user).execute
|
names = described_class.new(group, user).execute
|
||||||
|
|
||||||
expect(names).to eq(%w[gcny gprd gstg])
|
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
|
|
||||||
names = described_class.new(group, user).execute
|
names = described_class.new(group, user).execute
|
||||||
|
|
||||||
expect(names).to eq(%w[gcny gprd gstg])
|
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a public project reporter which has private environments' do
|
||||||
|
it 'returns environment names for public projects' do
|
||||||
|
public_project_with_private_environments.add_reporter(user)
|
||||||
|
|
||||||
|
names = described_class.new(group, user).execute
|
||||||
|
|
||||||
|
expect(names).to eq(%w[gprd gprivprd gprivstg gstg])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a group guest' do
|
context 'with a group guest' do
|
||||||
it 'returns environment names for all public projects' do
|
it 'returns environment names for public projects' do
|
||||||
group.add_guest(user)
|
group.add_guest(user)
|
||||||
|
|
||||||
names = described_class.new(group, user).execute
|
names = described_class.new(group, user).execute
|
||||||
|
@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a non-member' do
|
context 'with a non-member' do
|
||||||
it 'returns environment names for all public projects' do
|
it 'returns environment names for only public projects with public environments' do
|
||||||
names = described_class.new(group, user).execute
|
names = described_class.new(group, user).execute
|
||||||
|
|
||||||
expect(names).to eq(%w[gprd gstg])
|
expect(names).to eq(%w[gprd gstg])
|
||||||
|
@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'without a user' do
|
context 'without a user' do
|
||||||
it 'returns environment names for all public projects' do
|
it 'returns environment names for only public projects with public environments' do
|
||||||
names = described_class.new(group).execute
|
names = described_class.new(group).execute
|
||||||
|
|
||||||
expect(names).to eq(%w[gprd gstg])
|
expect(names).to eq(%w[gprd gstg])
|
||||||
|
|
|
@ -106,6 +106,26 @@ RSpec.describe NotesFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'for notes on public issue in public project' do
|
||||||
|
let_it_be(:public_project) { create(:project, :public) }
|
||||||
|
let_it_be(:guest_member) { create(:user) }
|
||||||
|
let_it_be(:reporter_member) { create(:user) }
|
||||||
|
let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) }
|
||||||
|
let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) }
|
||||||
|
let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) }
|
||||||
|
let_it_be(:public_note) { create(:note_on_issue, project: public_project) }
|
||||||
|
|
||||||
|
it 'shows all notes when the current_user has reporter access' do
|
||||||
|
notes = described_class.new(reporter_member, project: public_project).execute
|
||||||
|
expect(notes).to contain_exactly internal_note, public_note
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows only public notes when the current_user has guest access' do
|
||||||
|
notes = described_class.new(guest_member, project: public_project).execute
|
||||||
|
expect(notes).to contain_exactly public_note
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'for target type' do
|
context 'for target type' do
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let!(:note1) { create :note_on_issue, project: project }
|
let!(:note1) { create :note_on_issue, project: project }
|
||||||
|
|
588
spec/fixtures/emails/valid_reply_signed_smime.eml
vendored
588
spec/fixtures/emails/valid_reply_signed_smime.eml
vendored
|
@ -1,294 +1,294 @@
|
||||||
User-Agent: Microsoft-MacOutlook/10.22.0.200209
|
User-Agent: Microsoft-MacOutlook/10.22.0.200209
|
||||||
Date: Mon, 17 Feb 2020 22:56:47 +0100
|
Date: Mon, 17 Feb 2020 22:56:47 +0100
|
||||||
Subject: Re: htmltest | test issue (#1)
|
Subject: Re: htmltest | test issue (#1)
|
||||||
From: "Louzan Martinez, Diego (ext) (SI BP R&D ZG)"
|
From: "Louzan Martinez, Diego (ext) (SI BP R&D ZG)"
|
||||||
<diego.louzan.ext@siemens.com>
|
<diego.louzan.ext@siemens.com>
|
||||||
To: Administrator / htmltest
|
To: Administrator / htmltest
|
||||||
<dlouzan.dummy+c034670b1623e617e15a3df64223d363@gmail.com>
|
<dlouzan.dummy+c034670b1623e617e15a3df64223d363@gmail.com>
|
||||||
Message-ID: <012E37D9-2A3F-4AC8-B79A-871F42914D86@siemens.com>
|
Message-ID: <012E37D9-2A3F-4AC8-B79A-871F42914D86@siemens.com>
|
||||||
Thread-Topic: htmltest | test issue (#1)
|
Thread-Topic: htmltest | test issue (#1)
|
||||||
References: <reply-c034670b1623e617e15a3df64223d363@169.254.169.254>
|
References: <reply-c034670b1623e617e15a3df64223d363@169.254.169.254>
|
||||||
<issue_451@169.254.169.254>
|
<issue_451@169.254.169.254>
|
||||||
<note_1797@169.254.169.254>
|
<note_1797@169.254.169.254>
|
||||||
In-Reply-To: <note_1797@169.254.169.254>
|
In-Reply-To: <note_1797@169.254.169.254>
|
||||||
Content-type: multipart/signed;
|
Content-type: multipart/signed;
|
||||||
protocol="application/pkcs7-signature";
|
protocol="application/pkcs7-signature";
|
||||||
micalg=sha256;
|
micalg=sha256;
|
||||||
boundary="B_3664825007_1904734766"
|
boundary="B_3664825007_1904734766"
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
|
|
||||||
--B_3664825007_1904734766
|
--B_3664825007_1904734766
|
||||||
Content-type: multipart/mixed;
|
Content-type: multipart/mixed;
|
||||||
boundary="B_3664825007_384940722"
|
boundary="B_3664825007_384940722"
|
||||||
|
|
||||||
|
|
||||||
--B_3664825007_384940722
|
--B_3664825007_384940722
|
||||||
Content-type: multipart/alternative;
|
Content-type: multipart/alternative;
|
||||||
boundary="B_3664825007_1519466360"
|
boundary="B_3664825007_1519466360"
|
||||||
|
|
||||||
|
|
||||||
--B_3664825007_1519466360
|
--B_3664825007_1519466360
|
||||||
Content-type: text/plain;
|
Content-type: text/plain;
|
||||||
charset="UTF-8"
|
charset="UTF-8"
|
||||||
Content-transfer-encoding: quoted-printable
|
Content-transfer-encoding: quoted-printable
|
||||||
|
|
||||||
Me too, with an attachment
|
Me too, with an attachment
|
||||||
|
|
||||||
=20
|
=20
|
||||||
|
|
||||||
From: Administrator <dlouzan.dummy@gmail.com>
|
From: Administrator <dlouzan.dummy@gmail.com>
|
||||||
Reply to: Administrator / htmltest <dlouzan.dummy+c034670b1623e617e15a3df64=
|
Reply to: Administrator / htmltest <dlouzan.dummy+c034670b1623e617e15a3df64=
|
||||||
223d363@gmail.com>
|
223d363@gmail.com>
|
||||||
Date: Monday, 17 February 2020 at 22:55
|
Date: Monday, 17 February 2020 at 22:55
|
||||||
To: "Louzan Martinez, Diego (ext) (SOP IT STG XS)" <diego.louzan.ext@siemen=
|
To: "Louzan Martinez, Diego (ext) (SOP IT STG XS)" <diego.louzan.ext@siemen=
|
||||||
s.com>
|
s.com>
|
||||||
Subject: Re: htmltest | test issue (#1)
|
Subject: Re: htmltest | test issue (#1)
|
||||||
|
|
||||||
=20
|
=20
|
||||||
|
|
||||||
Administrator commented:=20
|
Administrator commented:=20
|
||||||
|
|
||||||
I pity the foo !!!
|
I pity the foo !!!
|
||||||
|
|
||||||
=E2=80=94=20
|
=E2=80=94=20
|
||||||
Reply to this email directly or view it on GitLab.=20
|
Reply to this email directly or view it on GitLab.=20
|
||||||
You're receiving this email because of your account on 169.254.169.254. If =
|
You're receiving this email because of your account on 169.254.169.254. If =
|
||||||
you'd like to receive fewer emails, you can unsubscribe from this thread or =
|
you'd like to receive fewer emails, you can unsubscribe from this thread or =
|
||||||
adjust your notification settings.=20
|
adjust your notification settings.=20
|
||||||
|
|
||||||
|
|
||||||
--B_3664825007_1519466360
|
--B_3664825007_1519466360
|
||||||
Content-type: text/html;
|
Content-type: text/html;
|
||||||
charset="UTF-8"
|
charset="UTF-8"
|
||||||
Content-transfer-encoding: quoted-printable
|
Content-transfer-encoding: quoted-printable
|
||||||
|
|
||||||
<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" xmlns:w=3D"urn:schema=
|
<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" xmlns:w=3D"urn:schema=
|
||||||
s-microsoft-com:office:word" xmlns:m=3D"http://schemas.microsoft.com/office/20=
|
s-microsoft-com:office:word" xmlns:m=3D"http://schemas.microsoft.com/office/20=
|
||||||
04/12/omml" xmlns=3D"http://www.w3.org/TR/REC-html40"><head><meta http-equiv=3DC=
|
04/12/omml" xmlns=3D"http://www.w3.org/TR/REC-html40"><head><meta http-equiv=3DC=
|
||||||
ontent-Type content=3D"text/html; charset=3Dutf-8"><meta name=3DGenerator content=3D=
|
ontent-Type content=3D"text/html; charset=3Dutf-8"><meta name=3DGenerator content=3D=
|
||||||
"Microsoft Word 15 (filtered medium)"><title>GitLab</title><style><!--
|
"Microsoft Word 15 (filtered medium)"><title>GitLab</title><style><!--
|
||||||
/* Font Definitions */
|
/* Font Definitions */
|
||||||
@font-face
|
@font-face
|
||||||
{font-family:"Cambria Math";
|
{font-family:"Cambria Math";
|
||||||
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
panose-1:2 4 5 3 5 4 6 3 2 4;}
|
||||||
@font-face
|
@font-face
|
||||||
{font-family:Calibri;
|
{font-family:Calibri;
|
||||||
panose-1:2 15 5 2 2 2 4 3 2 4;}
|
panose-1:2 15 5 2 2 2 4 3 2 4;}
|
||||||
/* Style Definitions */
|
/* Style Definitions */
|
||||||
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
p.MsoNormal, li.MsoNormal, div.MsoNormal
|
||||||
{margin:0cm;
|
{margin:0cm;
|
||||||
margin-bottom:.0001pt;
|
margin-bottom:.0001pt;
|
||||||
font-size:11.0pt;
|
font-size:11.0pt;
|
||||||
font-family:"Calibri",sans-serif;}
|
font-family:"Calibri",sans-serif;}
|
||||||
a:link, span.MsoHyperlink
|
a:link, span.MsoHyperlink
|
||||||
{mso-style-priority:99;
|
{mso-style-priority:99;
|
||||||
color:blue;
|
color:blue;
|
||||||
text-decoration:underline;}
|
text-decoration:underline;}
|
||||||
span.EmailStyle19
|
span.EmailStyle19
|
||||||
{mso-style-type:personal-reply;
|
{mso-style-type:personal-reply;
|
||||||
font-family:"Calibri",sans-serif;
|
font-family:"Calibri",sans-serif;
|
||||||
color:windowtext;}
|
color:windowtext;}
|
||||||
.MsoChpDefault
|
.MsoChpDefault
|
||||||
{mso-style-type:export-only;
|
{mso-style-type:export-only;
|
||||||
font-size:10.0pt;}
|
font-size:10.0pt;}
|
||||||
@page WordSection1
|
@page WordSection1
|
||||||
{size:612.0pt 792.0pt;
|
{size:612.0pt 792.0pt;
|
||||||
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
|
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
|
||||||
div.WordSection1
|
div.WordSection1
|
||||||
{page:WordSection1;}
|
{page:WordSection1;}
|
||||||
--></style></head><body lang=3Den-ES link=3Dblue vlink=3Dpurple><div class=3DWordSe=
|
--></style></head><body lang=3Den-ES link=3Dblue vlink=3Dpurple><div class=3DWordSe=
|
||||||
ction1><p class=3DMsoNormal><span lang=3DEN-US style=3D'mso-fareast-language:EN-US=
|
ction1><p class=3DMsoNormal><span lang=3DEN-US style=3D'mso-fareast-language:EN-US=
|
||||||
'>Me too, with an attachment<o:p></o:p></span></p><p class=3DMsoNormal><span s=
|
'>Me too, with an attachment<o:p></o:p></span></p><p class=3DMsoNormal><span s=
|
||||||
tyle=3D'mso-fareast-language:EN-US'><o:p> </o:p></span></p><div style=3D'bo=
|
tyle=3D'mso-fareast-language:EN-US'><o:p> </o:p></span></p><div style=3D'bo=
|
||||||
rder:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0cm 0cm 0cm'><p class=
|
rder:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0cm 0cm 0cm'><p class=
|
||||||
=3DMsoNormal><b><span style=3D'font-size:12.0pt;color:black'>From: </span></b><s=
|
=3DMsoNormal><b><span style=3D'font-size:12.0pt;color:black'>From: </span></b><s=
|
||||||
pan style=3D'font-size:12.0pt;color:black'>Administrator <dlouzan.dummy@gma=
|
pan style=3D'font-size:12.0pt;color:black'>Administrator <dlouzan.dummy@gma=
|
||||||
il.com><br><b>Reply to: </b>Administrator / htmltest <dlouzan.dummy+c0=
|
il.com><br><b>Reply to: </b>Administrator / htmltest <dlouzan.dummy+c0=
|
||||||
34670b1623e617e15a3df64223d363@gmail.com><br><b>Date: </b>Monday, 17 Febr=
|
34670b1623e617e15a3df64223d363@gmail.com><br><b>Date: </b>Monday, 17 Febr=
|
||||||
uary 2020 at 22:55<br><b>To: </b>"Louzan Martinez, Diego (ext) (SOP IT =
|
uary 2020 at 22:55<br><b>To: </b>"Louzan Martinez, Diego (ext) (SOP IT =
|
||||||
STG XS)" <diego.louzan.ext@siemens.com><br><b>Subject: </b>Re: ht=
|
STG XS)" <diego.louzan.ext@siemens.com><br><b>Subject: </b>Re: ht=
|
||||||
mltest | test issue (#1)<o:p></o:p></span></p></div><div><p class=3DMsoNormal>=
|
mltest | test issue (#1)<o:p></o:p></span></p></div><div><p class=3DMsoNormal>=
|
||||||
<o:p> </o:p></p></div><div><p><span style=3D'color:#777777'><a href=3D"http=
|
<o:p> </o:p></p></div><div><p><span style=3D'color:#777777'><a href=3D"http=
|
||||||
://localhost:3000/root">Administrator</a> commented: <o:p></o:p></span></p><=
|
://localhost:3000/root">Administrator</a> commented: <o:p></o:p></span></p><=
|
||||||
div><p>I pity the foo !!!<o:p></o:p></p></div></div><div style=3D'margin-top:7=
|
div><p>I pity the foo !!!<o:p></o:p></p></div></div><div style=3D'margin-top:7=
|
||||||
.5pt'><p><span style=3D'font-size:12.0pt;color:#777777'>=E2=80=94 <br>Reply to this =
|
.5pt'><p><span style=3D'font-size:12.0pt;color:#777777'>=E2=80=94 <br>Reply to this =
|
||||||
email directly or <a href=3D"http://localhost:3000/root/htmltest/issues/1#note=
|
email directly or <a href=3D"http://localhost:3000/root/htmltest/issues/1#note=
|
||||||
_1797">view it on GitLab</a>. <br>You're receiving this email because of you=
|
_1797">view it on GitLab</a>. <br>You're receiving this email because of you=
|
||||||
r account on 169.254.169.254. If you'd like to receive fewer emails, you can=
|
r account on 169.254.169.254. If you'd like to receive fewer emails, you can=
|
||||||
<a href=3D"http://localhost:3000/sent_notifications/c034670b1623e617e15a3df64=
|
<a href=3D"http://localhost:3000/sent_notifications/c034670b1623e617e15a3df64=
|
||||||
223d363/unsubscribe">unsubscribe</a> from this thread or adjust your notific=
|
223d363/unsubscribe">unsubscribe</a> from this thread or adjust your notific=
|
||||||
ation settings. <o:p></o:p></span></p></div></div></body></html>
|
ation settings. <o:p></o:p></span></p></div></div></body></html>
|
||||||
|
|
||||||
--B_3664825007_1519466360--
|
--B_3664825007_1519466360--
|
||||||
|
|
||||||
|
|
||||||
--B_3664825007_384940722
|
--B_3664825007_384940722
|
||||||
Content-type: image/png; name="gitlab_logo.png";
|
Content-type: image/png; name="gitlab_logo.png";
|
||||||
x-mac-creator="4F50494D";
|
x-mac-creator="4F50494D";
|
||||||
x-mac-type="504E4766"
|
x-mac-type="504E4766"
|
||||||
Content-disposition: attachment;
|
Content-disposition: attachment;
|
||||||
filename="gitlab_logo.png"
|
filename="gitlab_logo.png"
|
||||||
Content-transfer-encoding: base64
|
Content-transfer-encoding: base64
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
--B_3664825007_384940722--
|
--B_3664825007_384940722--
|
||||||
|
|
||||||
--B_3664825007_1904734766
|
--B_3664825007_1904734766
|
||||||
Content-type: application/pkcs7-signature; name="smime.p7s"
|
Content-type: application/pkcs7-signature; name="smime.p7s"
|
||||||
Content-transfer-encoding: base64
|
Content-transfer-encoding: base64
|
||||||
Content-disposition: attachment;
|
Content-disposition: attachment;
|
||||||
filename="smime.p7s"
|
filename="smime.p7s"
|
||||||
|
|
||||||
MIIRpwYJKoZIhvcNAQcCoIIRmDCCEZQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0B
|
MIIRpwYJKoZIhvcNAQcCoIIRmDCCEZQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0B
|
||||||
BwGggg8VMIIHojCCBYqgAwIBAgIEZ5a6PTANBgkqhkiG9w0BAQsFADCBtjELMAkGA1UEBhMC
|
BwGggg8VMIIHojCCBYqgAwIBAgIEZ5a6PTANBgkqhkiG9w0BAQsFADCBtjELMAkGA1UEBhMC
|
||||||
REUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1l
|
REUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1l
|
||||||
bnMxETAPBgNVBAUTCFpaWlpaWkE2MR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRlcjE/
|
bnMxETAPBgNVBAUTCFpaWlpaWkE2MR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRlcjE/
|
||||||
MD0GA1UEAww2U2llbWVucyBJc3N1aW5nIENBIE1lZGl1bSBTdHJlbmd0aCBBdXRoZW50aWNh
|
MD0GA1UEAww2U2llbWVucyBJc3N1aW5nIENBIE1lZGl1bSBTdHJlbmd0aCBBdXRoZW50aWNh
|
||||||
dGlvbiAyMDE2MB4XDTE5MTEyMTE0NDQ0N1oXDTIwMTEyMTE0NDQ0N1owdzERMA8GA1UEBRMI
|
dGlvbiAyMDE2MB4XDTE5MTEyMTE0NDQ0N1oXDTIwMTEyMTE0NDQ0N1owdzERMA8GA1UEBRMI
|
||||||
WjAwM0gwOFQxDjAMBgNVBCoMBURpZWdvMRgwFgYDVQQEDA9Mb3V6YW4gTWFydGluZXoxGDAW
|
WjAwM0gwOFQxDjAMBgNVBCoMBURpZWdvMRgwFgYDVQQEDA9Mb3V6YW4gTWFydGluZXoxGDAW
|
||||||
BgNVBAoMD1NpZW1lbnMtUGFydG5lcjEeMBwGA1UEAwwVTG91emFuIE1hcnRpbmV6IERpZWdv
|
BgNVBAoMD1NpZW1lbnMtUGFydG5lcjEeMBwGA1UEAwwVTG91emFuIE1hcnRpbmV6IERpZWdv
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuInpNaC7NRYD+0pOpHDz2pk9xmPt
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuInpNaC7NRYD+0pOpHDz2pk9xmPt
|
||||||
JGj860SF6Nmn6Eu9EMYKEDfneC6z5QcH+mPS2d0VWgqVVGbRXSPsxJtbi9TCWjQUZdHglEZK
|
JGj860SF6Nmn6Eu9EMYKEDfneC6z5QcH+mPS2d0VWgqVVGbRXSPsxJtbi9TCWjQUZdHglEZK
|
||||||
z9zxoFDh2dvW5/+TOT5Jf78FXyqak0YtY6+oMjQ/i9RUqPL7sIlyXLrBYrILzQ9Afo+7bXZg
|
z9zxoFDh2dvW5/+TOT5Jf78FXyqak0YtY6+oMjQ/i9RUqPL7sIlyXLrBYrILzQ9Afo+7bXZg
|
||||||
v3ypp6xtqAV2ctHzQWFi0onJzxLVYguiVb7fFF9rBEMvSZonuw5tvOwJIhbe5FDFOrDcfbyU
|
v3ypp6xtqAV2ctHzQWFi0onJzxLVYguiVb7fFF9rBEMvSZonuw5tvOwJIhbe5FDFOrDcfbyU
|
||||||
ofZ/wikIZ+A+CE5GryXuuQmGxJaC2QqOkRAWQDzLDx9nG+rKiEs5OvlfEZC7EV1PyjZ93coM
|
ofZ/wikIZ+A+CE5GryXuuQmGxJaC2QqOkRAWQDzLDx9nG+rKiEs5OvlfEZC7EV1PyjZ93coM
|
||||||
faCVdlAgcFZ5fvd37CjyjKl+1QIDAQABo4IC9DCCAvAwggEEBggrBgEFBQcBAQSB9zCB9DAy
|
faCVdlAgcFZ5fvd37CjyjKl+1QIDAQABo4IC9DCCAvAwggEEBggrBgEFBQcBAQSB9zCB9DAy
|
||||||
BggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5jcnQwQQYI
|
BggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5jcnQwQQYI
|
||||||
KwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jQUNl
|
KwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jQUNl
|
||||||
cnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049WlpaWlpa
|
cnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049WlpaWlpa
|
||||||
QTYsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRwOi8vb2Nz
|
QTYsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRwOi8vb2Nz
|
||||||
cC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAU+BVdRwxsd3tyxAIXkWii
|
cC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAU+BVdRwxsd3tyxAIXkWii
|
||||||
tvdqCUQwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGDSsGAQQBoWkHAgIEAQMwKTAnBggr
|
tvdqCUQwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGDSsGAQQBoWkHAgIEAQMwKTAnBggr
|
||||||
BgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHKBgNVHR8EgcIwgb8wgbyg
|
BgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHKBgNVHR8EgcIwgb8wgbyg
|
||||||
gbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTYuY3JshkFsZGFwOi8v
|
gbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTYuY3JshkFsZGFwOi8v
|
||||||
Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTYsTD1QS0k/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
|
Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTYsTD1QS0k/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
|
||||||
TGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkE2LG89VHJ1c3RjZW50ZXI/
|
TGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkE2LG89VHJ1c3RjZW50ZXI/
|
||||||
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
|
||||||
Cztca8vI1jUjQzVhS+0dWpvpfhkVumbvOnid8DI9lapYsX8dpZFsa3ya+T3tjUdGSOOKi0kg
|
Cztca8vI1jUjQzVhS+0dWpvpfhkVumbvOnid8DI9lapYsX8dpZFsa3ya+T3tjUdGSOOKi0kg
|
||||||
lWf/XYyyfhmDAgMBAAGjggLVMIIC0TAdBgNVHQ4EFgQUprhTCDwNLfPImpSfWdq+QvPTo9Mw
|
lWf/XYyyfhmDAgMBAAGjggLVMIIC0TAdBgNVHQ4EFgQUprhTCDwNLfPImpSfWdq+QvPTo9Mw
|
||||||
JwYDVR0RBCAwHoEcZGllZ28ubG91emFuLmV4dEBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMC
|
JwYDVR0RBCAwHoEcZGllZ28ubG91emFuLmV4dEBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMC
|
||||||
BDAwLAYDVR0lBCUwIwYIKwYBBQUHAwQGCisGAQQBgjcKAwQGCysGAQQBgjcKAwQBMIHKBgNV
|
BDAwLAYDVR0lBCUwIwYIKwYBBQUHAwQGCisGAQQBgjcKAwQGCysGAQQBgjcKAwQBMIHKBgNV
|
||||||
HR8EgcIwgb8wgbyggbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTMu
|
HR8EgcIwgb8wgbyggbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTMu
|
||||||
Y3JshkFsZGFwOi8vY2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTMsTD1QS0k/Y2VydGlmaWNh
|
Y3JshkFsZGFwOi8vY2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTMsTD1QS0k/Y2VydGlmaWNh
|
||||||
dGVSZXZvY2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEzLG89
|
dGVSZXZvY2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEzLG89
|
||||||
VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8MDoGDSsG
|
VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8MDoGDSsG
|
||||||
AQQBoWkHAgIEAQMwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kv
|
AQQBoWkHAgIEAQMwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kv
|
||||||
MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUoassbqB68NPCTeof8R4hivwMre8wggEEBggr
|
MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUoassbqB68NPCTeof8R4hivwMre8wggEEBggr
|
||||||
BgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9a
|
BgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9a
|
||||||
WlpaWlpBMy5jcnQwQQYIKwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpa
|
WlpaWlpBMy5jcnQwQQYIKwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpa
|
||||||
WlpBMyxMPVBLST9jQUNlcnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVu
|
WlpBMyxMPVBLST9jQUNlcnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVu
|
||||||
cy5jb20vQ049WlpaWlpaQTMsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUF
|
cy5jb20vQ049WlpaWlpaQTMsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUF
|
||||||
BzABhiRodHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL
|
BzABhiRodHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL
|
||||||
BQADggIBAF98ZMNg28LgkwdjOdvOGbC1QitsWjZTyotmQESF0nClDLUhb0O5675vVixntbrf
|
BQADggIBAF98ZMNg28LgkwdjOdvOGbC1QitsWjZTyotmQESF0nClDLUhb0O5675vVixntbrf
|
||||||
eB8xy1+KRiadk40GnAIJ0YzmNl4Tav6hPYv9VBWe5olsWG7C4qB3Q/SwhvW/e+owxv1cBra8
|
eB8xy1+KRiadk40GnAIJ0YzmNl4Tav6hPYv9VBWe5olsWG7C4qB3Q/SwhvW/e+owxv1cBra8
|
||||||
R3oRudiN81eTZQHyNghRephVqQG/dpPYqydoANfIhEpHa79QlpaCAeYl4896AZOS8HYbkDFs
|
R3oRudiN81eTZQHyNghRephVqQG/dpPYqydoANfIhEpHa79QlpaCAeYl4896AZOS8HYbkDFs
|
||||||
hLdv7sEHtl79YuSWI1wBjbJl70c0Sb4wLRgCPuHyQj2Uw/vQ5xJlEvBDZAIXXe1TP/nqiuY6
|
hLdv7sEHtl79YuSWI1wBjbJl70c0Sb4wLRgCPuHyQj2Uw/vQ5xJlEvBDZAIXXe1TP/nqiuY6
|
||||||
7nweJbbeqfFE6ZP3kCe+mEIWGSaO0iThZyLGer8fHs1XiEmhhPgvC7P7KodzpXU6+hX+ZzbD
|
7nweJbbeqfFE6ZP3kCe+mEIWGSaO0iThZyLGer8fHs1XiEmhhPgvC7P7KodzpXU6+hX+ZzbD
|
||||||
DxEjFfetV5sh0aNSXG9xx4hZmS9bpImBGR8MvZ7cgxqItvLtY2xvfUbYW244d4RcWesaCDq3
|
DxEjFfetV5sh0aNSXG9xx4hZmS9bpImBGR8MvZ7cgxqItvLtY2xvfUbYW244d4RcWesaCDq3
|
||||||
ZEIo6uCIzOzJAwjUdLIac+lLV0rxiHmb7O3cQ19kjpWDB31hmfrus/TKJ55pBKVWBX5m/mFv
|
ZEIo6uCIzOzJAwjUdLIac+lLV0rxiHmb7O3cQ19kjpWDB31hmfrus/TKJ55pBKVWBX5m/mFv
|
||||||
K8Ep5USpGrNS0EzOP7I1kQZv2VsvAhSxk/m5FMLpDy8T0O8YgbLypTXoeJFWCF6RduSjVsaZ
|
K8Ep5USpGrNS0EzOP7I1kQZv2VsvAhSxk/m5FMLpDy8T0O8YgbLypTXoeJFWCF6RduSjVsaZ
|
||||||
lkAtTQYud683pjyOMxJXaQUYGU1PmEYSOonMkVsT9aBcxYkXLp+Ln/+8G0OCYu7dRdwnj+Ut
|
lkAtTQYud683pjyOMxJXaQUYGU1PmEYSOonMkVsT9aBcxYkXLp+Ln/+8G0OCYu7dRdwnj+Ut
|
||||||
7yR/ltxtgDcaFApCb0qBTKbgbqZk1fASmkOp+kbdYmoUMYICVjCCAlICAQEwgb8wgbYxCzAJ
|
7yR/ltxtgDcaFApCb0qBTKbgbqZk1fASmkOp+kbdYmoUMYICVjCCAlICAQEwgb8wgbYxCzAJ
|
||||||
BgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQK
|
BgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQK
|
||||||
DAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVzdCBD
|
DAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVzdCBD
|
||||||
ZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3RyZW5ndGggQXV0
|
ZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3RyZW5ndGggQXV0
|
||||||
aGVudGljYXRpb24gMjAxNgIEZ5a6PTANBglghkgBZQMEAgEFAKBpMC8GCSqGSIb3DQEJBDEi
|
aGVudGljYXRpb24gMjAxNgIEZ5a6PTANBglghkgBZQMEAgEFAKBpMC8GCSqGSIb3DQEJBDEi
|
||||||
BCAOR58AbNfSrI+vtMs+dgAQtn3IVZ3RjYC5hz3j9k+6TTAYBgkqhkiG9w0BCQMxCwYJKoZI
|
BCAOR58AbNfSrI+vtMs+dgAQtn3IVZ3RjYC5hz3j9k+6TTAYBgkqhkiG9w0BCQMxCwYJKoZI
|
||||||
hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDAyMTcyMTU2NDdaMA0GCSqGSIb3DQEBAQUABIIB
|
hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDAyMTcyMTU2NDdaMA0GCSqGSIb3DQEBAQUABIIB
|
||||||
AHLSBcFHhNHPevbwqvA2ecuVb/aKnj45CFF6l8esP1H5DRm1ee5qMKuIS84NFuFC9RUENNhW
|
AHLSBcFHhNHPevbwqvA2ecuVb/aKnj45CFF6l8esP1H5DRm1ee5qMKuIS84NFuFC9RUENNhW
|
||||||
DBzsB+BVGz64o1f8QgIklYVrIJ4JZ0q1abNG7NbkVKWIpS3CQo//YWShUTYg+JpKx4YbahGR
|
DBzsB+BVGz64o1f8QgIklYVrIJ4JZ0q1abNG7NbkVKWIpS3CQo//YWShUTYg+JpKx4YbahGR
|
||||||
sP5zbufbU4eagrrqBChjPTLy+njdjwCNu0XPykBTKOOf6BMjnS33AYjHJyh83JOY7rw3IDLx
|
sP5zbufbU4eagrrqBChjPTLy+njdjwCNu0XPykBTKOOf6BMjnS33AYjHJyh83JOY7rw3IDLx
|
||||||
8POQH4g5EMRpl9354s0rEkIezMt7pfUAsqY3QnQ8hvlE4KTikPQ+tvLMK1l/ffcLAP8BdBNI
|
8POQH4g5EMRpl9354s0rEkIezMt7pfUAsqY3QnQ8hvlE4KTikPQ+tvLMK1l/ffcLAP8BdBNI
|
||||||
YA3ikb3qCoGNSLKieYzNnBPhNOIJELUtEEaljAFZYMQzMKCbI4JdiDs=
|
YA3ikb3qCoGNSLKieYzNnBPhNOIJELUtEEaljAFZYMQzMKCbI4JdiDs=
|
||||||
|
|
||||||
--B_3664825007_1904734766--
|
--B_3664825007_1904734766--
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe('Observability iframe renderer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders an observability iframe', () => {
|
it('renders an observability iframe', () => {
|
||||||
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
|
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/" data-observability-url="https://observe.gitlab.com/" ></div>`;
|
||||||
|
|
||||||
expect(findObservabilityIframes()).toHaveLength(0);
|
expect(findObservabilityIframes()).toHaveLength(0);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ describe('Observability iframe renderer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders iframe with dark param when GL has dark theme', () => {
|
it('renders iframe with dark param when GL has dark theme', () => {
|
||||||
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
|
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/" data-observability-url="https://observe.gitlab.com/"></div>`;
|
||||||
jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true);
|
jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true);
|
||||||
|
|
||||||
expect(findObservabilityIframes('dark')).toHaveLength(0);
|
expect(findObservabilityIframes('dark')).toHaveLength(0);
|
||||||
|
@ -35,4 +35,12 @@ describe('Observability iframe renderer', () => {
|
||||||
|
|
||||||
expect(findObservabilityIframes('dark')).toHaveLength(1);
|
expect(findObservabilityIframes('dark')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not render if url is different from observability url', () => {
|
||||||
|
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://example.com/" data-observability-url="https://observe.gitlab.com/"></div>`;
|
||||||
|
|
||||||
|
renderEmbeddedObservability();
|
||||||
|
|
||||||
|
expect(findObservabilityIframes()).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -310,69 +310,58 @@ describe('Description component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with work_items_mvc feature flag enabled', () => {
|
describe('empty description', () => {
|
||||||
describe('empty description', () => {
|
beforeEach(() => {
|
||||||
beforeEach(() => {
|
createComponent({
|
||||||
createComponent({
|
props: {
|
||||||
props: {
|
descriptionHtml: '',
|
||||||
descriptionHtml: '',
|
},
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
glFeatures: {
|
|
||||||
workItemsMvc: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return nextTick();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders without error', () => {
|
|
||||||
expect(findTaskActionButtons()).toHaveLength(0);
|
|
||||||
});
|
});
|
||||||
|
return nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('description with checkboxes', () => {
|
it('renders without error', () => {
|
||||||
beforeEach(() => {
|
expect(findTaskActionButtons()).toHaveLength(0);
|
||||||
createComponent({
|
});
|
||||||
props: {
|
});
|
||||||
descriptionHtml: descriptionHtmlWithCheckboxes,
|
|
||||||
},
|
|
||||||
provide: {
|
|
||||||
glFeatures: {
|
|
||||||
workItemsMvc: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return nextTick();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
|
describe('description with checkboxes', () => {
|
||||||
expect(findTaskActionButtons()).toHaveLength(3);
|
beforeEach(() => {
|
||||||
});
|
createComponent({
|
||||||
|
props: {
|
||||||
it('does not show a modal by default', () => {
|
descriptionHtml: descriptionHtmlWithCheckboxes,
|
||||||
expect(findModal().exists()).toBe(false);
|
},
|
||||||
});
|
|
||||||
|
|
||||||
it('shows toast after delete success', async () => {
|
|
||||||
const newDesc = 'description';
|
|
||||||
findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
|
|
||||||
|
|
||||||
expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
|
|
||||||
expect($toast.show).toHaveBeenCalledWith('Task deleted');
|
|
||||||
});
|
});
|
||||||
|
return nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('task list item actions', () => {
|
it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
|
||||||
describe('converting the task list item to a task', () => {
|
expect(findTaskActionButtons()).toHaveLength(3);
|
||||||
describe('when successful', () => {
|
});
|
||||||
let createWorkItemMutationHandler;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
it('does not show a modal by default', () => {
|
||||||
createWorkItemMutationHandler = jest
|
expect(findModal().exists()).toBe(false);
|
||||||
.fn()
|
});
|
||||||
.mockResolvedValue(createWorkItemMutationResponse);
|
|
||||||
const descriptionText = `Tasks
|
it('shows toast after delete success', async () => {
|
||||||
|
const newDesc = 'description';
|
||||||
|
findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
|
||||||
|
|
||||||
|
expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
|
||||||
|
expect($toast.show).toHaveBeenCalledWith('Task deleted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('task list item actions', () => {
|
||||||
|
describe('converting the task list item to a task', () => {
|
||||||
|
describe('when successful', () => {
|
||||||
|
let createWorkItemMutationHandler;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
createWorkItemMutationHandler = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue(createWorkItemMutationResponse);
|
||||||
|
const descriptionText = `Tasks
|
||||||
|
|
||||||
1. [ ] item 1
|
1. [ ] item 1
|
||||||
1. [ ] item 2
|
1. [ ] item 2
|
||||||
|
@ -381,218 +370,207 @@ describe('Description component', () => {
|
||||||
|
|
||||||
1. [ ] item 3
|
1. [ ] item 3
|
||||||
1. [ ] item 4;`;
|
1. [ ] item 4;`;
|
||||||
createComponent({
|
createComponent({
|
||||||
props: { descriptionText },
|
props: { descriptionText },
|
||||||
provide: { glFeatures: { workItemsMvc: true } },
|
createWorkItemMutationHandler,
|
||||||
createWorkItemMutationHandler,
|
|
||||||
});
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
eventHub.$emit('convert-task-list-item', '4:4-8:19');
|
|
||||||
await waitForPromises();
|
|
||||||
});
|
});
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
it('emits an event to update the description with the deleted task list item omitted', () => {
|
eventHub.$emit('convert-task-list-item', '4:4-8:19');
|
||||||
const newDescriptionText = `Tasks
|
await waitForPromises();
|
||||||
|
|
||||||
1. [ ] item 1
|
|
||||||
1. [ ] item 3
|
|
||||||
1. [ ] item 4;`;
|
|
||||||
|
|
||||||
expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls a mutation to create a task', () => {
|
|
||||||
const {
|
|
||||||
confidential,
|
|
||||||
iteration,
|
|
||||||
milestone,
|
|
||||||
} = issueDetailsResponse.data.workspace.issuable;
|
|
||||||
expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
|
|
||||||
input: {
|
|
||||||
confidential,
|
|
||||||
description: '\nparagraph text\n',
|
|
||||||
hierarchyWidget: {
|
|
||||||
parentId: 'gid://gitlab/WorkItem/1',
|
|
||||||
},
|
|
||||||
iterationWidget: {
|
|
||||||
iterationId: IS_EE ? iteration.id : null,
|
|
||||||
},
|
|
||||||
milestoneWidget: {
|
|
||||||
milestoneId: milestone.id,
|
|
||||||
},
|
|
||||||
projectPath: 'gitlab-org/gitlab-test',
|
|
||||||
title: 'item 2',
|
|
||||||
workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows a toast to confirm the creation of the task', () => {
|
|
||||||
expect($toast.show).toHaveBeenCalledWith('Converted to task', expect.any(Object));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when unsuccessful', () => {
|
it('emits an event to update the description with the deleted task list item omitted', () => {
|
||||||
beforeEach(async () => {
|
|
||||||
createComponent({
|
|
||||||
props: { descriptionText: 'description' },
|
|
||||||
provide: { glFeatures: { workItemsMvc: true } },
|
|
||||||
createWorkItemMutationHandler: jest
|
|
||||||
.fn()
|
|
||||||
.mockResolvedValue(createWorkItemMutationErrorResponse),
|
|
||||||
});
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
eventHub.$emit('convert-task-list-item', '1:1-1:11');
|
|
||||||
await waitForPromises();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows an alert with an error message', () => {
|
|
||||||
expect(createAlert).toHaveBeenCalledWith({
|
|
||||||
message: 'Something went wrong when creating task. Please try again.',
|
|
||||||
error: new Error('an error'),
|
|
||||||
captureError: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deleting the task list item', () => {
|
|
||||||
it('emits an event to update the description with the deleted task list item', () => {
|
|
||||||
const descriptionText = `Tasks
|
|
||||||
|
|
||||||
1. [ ] item 1
|
|
||||||
1. [ ] item 2
|
|
||||||
1. [ ] item 3
|
|
||||||
1. [ ] item 4;`;
|
|
||||||
const newDescriptionText = `Tasks
|
const newDescriptionText = `Tasks
|
||||||
|
|
||||||
1. [ ] item 1
|
1. [ ] item 1
|
||||||
1. [ ] item 3
|
1. [ ] item 3
|
||||||
1. [ ] item 4;`;
|
1. [ ] item 4;`;
|
||||||
createComponent({
|
|
||||||
props: { descriptionText },
|
|
||||||
provide: { glFeatures: { workItemsMvc: true } },
|
|
||||||
});
|
|
||||||
|
|
||||||
eventHub.$emit('delete-task-list-item', '4:4-5:19');
|
|
||||||
|
|
||||||
expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
|
expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('work items detail', () => {
|
it('calls a mutation to create a task', () => {
|
||||||
describe('when opening and closing', () => {
|
const {
|
||||||
beforeEach(() => {
|
confidential,
|
||||||
|
iteration,
|
||||||
|
milestone,
|
||||||
|
} = issueDetailsResponse.data.workspace.issuable;
|
||||||
|
expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
|
||||||
|
input: {
|
||||||
|
confidential,
|
||||||
|
description: '\nparagraph text\n',
|
||||||
|
hierarchyWidget: {
|
||||||
|
parentId: 'gid://gitlab/WorkItem/1',
|
||||||
|
},
|
||||||
|
iterationWidget: {
|
||||||
|
iterationId: IS_EE ? iteration.id : null,
|
||||||
|
},
|
||||||
|
milestoneWidget: {
|
||||||
|
milestoneId: milestone.id,
|
||||||
|
},
|
||||||
|
projectPath: 'gitlab-org/gitlab-test',
|
||||||
|
title: 'item 2',
|
||||||
|
workItemTypeId: 'gid://gitlab/WorkItems::Type/3',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows a toast to confirm the creation of the task', () => {
|
||||||
|
expect($toast.show).toHaveBeenCalledWith('Converted to task', expect.any(Object));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when unsuccessful', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
createComponent({
|
createComponent({
|
||||||
props: {
|
props: { descriptionText: 'description' },
|
||||||
descriptionHtml: descriptionHtmlWithTask,
|
createWorkItemMutationHandler: jest
|
||||||
},
|
.fn()
|
||||||
provide: {
|
.mockResolvedValue(createWorkItemMutationErrorResponse),
|
||||||
glFeatures: { workItemsMvc: true },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return nextTick();
|
await waitForPromises();
|
||||||
|
|
||||||
|
eventHub.$emit('convert-task-list-item', '1:1-1:11');
|
||||||
|
await waitForPromises();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('opens when task button is clicked', async () => {
|
it('shows an alert with an error message', () => {
|
||||||
await findTaskLink().trigger('click');
|
expect(createAlert).toHaveBeenCalledWith({
|
||||||
|
message: 'Something went wrong when creating task. Please try again.',
|
||||||
expect(showDetailsModal).toHaveBeenCalled();
|
error: new Error('an error'),
|
||||||
expect(updateHistory).toHaveBeenCalledWith({
|
captureError: true,
|
||||||
url: `${TEST_HOST}/?work_item_id=2`,
|
|
||||||
replace: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes from an open state', async () => {
|
|
||||||
await findTaskLink().trigger('click');
|
|
||||||
|
|
||||||
findWorkItemDetailModal().vm.$emit('close');
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(updateHistory).toHaveBeenLastCalledWith({
|
|
||||||
url: `${TEST_HOST}/`,
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('tracks when opened', async () => {
|
|
||||||
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
|
||||||
|
|
||||||
await findTaskLink().trigger('click');
|
|
||||||
|
|
||||||
expect(trackingSpy).toHaveBeenCalledWith(
|
|
||||||
TRACKING_CATEGORY_SHOW,
|
|
||||||
'viewed_work_item_from_modal',
|
|
||||||
{
|
|
||||||
category: TRACKING_CATEGORY_SHOW,
|
|
||||||
label: 'work_item_view',
|
|
||||||
property: 'type_task',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when url query `work_item_id` exists', () => {
|
|
||||||
it.each`
|
|
||||||
behavior | workItemId | modalOpened
|
|
||||||
${'opens'} | ${'2'} | ${1}
|
|
||||||
${'does not open'} | ${'123'} | ${0}
|
|
||||||
${'does not open'} | ${'123e'} | ${0}
|
|
||||||
${'does not open'} | ${'12e3'} | ${0}
|
|
||||||
${'does not open'} | ${'1e23'} | ${0}
|
|
||||||
${'does not open'} | ${'x'} | ${0}
|
|
||||||
${'does not open'} | ${'undefined'} | ${0}
|
|
||||||
`(
|
|
||||||
'$behavior when url contains `work_item_id=$workItemId`',
|
|
||||||
async ({ workItemId, modalOpened }) => {
|
|
||||||
setWindowLocation(`?work_item_id=${workItemId}`);
|
|
||||||
|
|
||||||
createComponent({
|
|
||||||
props: { descriptionHtml: descriptionHtmlWithTask },
|
|
||||||
provide: { glFeatures: { workItemsMvc: true } },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when hovering task links', () => {
|
describe('deleting the task list item', () => {
|
||||||
|
it('emits an event to update the description with the deleted task list item', () => {
|
||||||
|
const descriptionText = `Tasks
|
||||||
|
|
||||||
|
1. [ ] item 1
|
||||||
|
1. [ ] item 2
|
||||||
|
1. [ ] item 3
|
||||||
|
1. [ ] item 4;`;
|
||||||
|
const newDescriptionText = `Tasks
|
||||||
|
|
||||||
|
1. [ ] item 1
|
||||||
|
1. [ ] item 3
|
||||||
|
1. [ ] item 4;`;
|
||||||
|
createComponent({
|
||||||
|
props: { descriptionText },
|
||||||
|
});
|
||||||
|
|
||||||
|
eventHub.$emit('delete-task-list-item', '4:4-5:19');
|
||||||
|
|
||||||
|
expect(wrapper.emitted('saveDescription')).toEqual([[newDescriptionText]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('work items detail', () => {
|
||||||
|
describe('when opening and closing', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createComponent({
|
createComponent({
|
||||||
props: {
|
props: {
|
||||||
descriptionHtml: descriptionHtmlWithTask,
|
descriptionHtml: descriptionHtmlWithTask,
|
||||||
},
|
},
|
||||||
provide: {
|
|
||||||
glFeatures: { workItemsMvc: true },
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return nextTick();
|
return nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prefetches work item detail after work item link is hovered for 150ms', async () => {
|
it('opens when task button is clicked', async () => {
|
||||||
await findTaskLink().trigger('mouseover');
|
await findTaskLink().trigger('click');
|
||||||
jest.advanceTimersByTime(150);
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(queryHandler).toHaveBeenCalledWith({
|
expect(showDetailsModal).toHaveBeenCalled();
|
||||||
id: 'gid://gitlab/WorkItem/2',
|
expect(updateHistory).toHaveBeenCalledWith({
|
||||||
|
url: `${TEST_HOST}/?work_item_id=2`,
|
||||||
|
replace: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not work item detail after work item link is hovered for less than 150ms', async () => {
|
it('closes from an open state', async () => {
|
||||||
await findTaskLink().trigger('mouseover');
|
await findTaskLink().trigger('click');
|
||||||
await findTaskLink().trigger('mouseout');
|
|
||||||
jest.advanceTimersByTime(150);
|
|
||||||
await waitForPromises();
|
|
||||||
|
|
||||||
expect(queryHandler).not.toHaveBeenCalled();
|
findWorkItemDetailModal().vm.$emit('close');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
expect(updateHistory).toHaveBeenLastCalledWith({
|
||||||
|
url: `${TEST_HOST}/`,
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('tracks when opened', async () => {
|
||||||
|
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
|
||||||
|
await findTaskLink().trigger('click');
|
||||||
|
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(
|
||||||
|
TRACKING_CATEGORY_SHOW,
|
||||||
|
'viewed_work_item_from_modal',
|
||||||
|
{
|
||||||
|
category: TRACKING_CATEGORY_SHOW,
|
||||||
|
label: 'work_item_view',
|
||||||
|
property: 'type_task',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when url query `work_item_id` exists', () => {
|
||||||
|
it.each`
|
||||||
|
behavior | workItemId | modalOpened
|
||||||
|
${'opens'} | ${'2'} | ${1}
|
||||||
|
${'does not open'} | ${'123'} | ${0}
|
||||||
|
${'does not open'} | ${'123e'} | ${0}
|
||||||
|
${'does not open'} | ${'12e3'} | ${0}
|
||||||
|
${'does not open'} | ${'1e23'} | ${0}
|
||||||
|
${'does not open'} | ${'x'} | ${0}
|
||||||
|
${'does not open'} | ${'undefined'} | ${0}
|
||||||
|
`(
|
||||||
|
'$behavior when url contains `work_item_id=$workItemId`',
|
||||||
|
async ({ workItemId, modalOpened }) => {
|
||||||
|
setWindowLocation(`?work_item_id=${workItemId}`);
|
||||||
|
|
||||||
|
createComponent({
|
||||||
|
props: { descriptionHtml: descriptionHtmlWithTask },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when hovering task links', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent({
|
||||||
|
props: {
|
||||||
|
descriptionHtml: descriptionHtmlWithTask,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return nextTick();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prefetches work item detail after work item link is hovered for 150ms', async () => {
|
||||||
|
await findTaskLink().trigger('mouseover');
|
||||||
|
jest.advanceTimersByTime(150);
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(queryHandler).toHaveBeenCalledWith({
|
||||||
|
id: 'gid://gitlab/WorkItem/2',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not work item detail after work item link is hovered for less than 150ms', async () => {
|
||||||
|
await findTaskLink().trigger('mouseover');
|
||||||
|
await findTaskLink().trigger('mouseout');
|
||||||
|
jest.advanceTimersByTime(150);
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(queryHandler).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
|
||||||
import setWindowLocation from 'helpers/set_window_location_helper';
|
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||||
|
import { TEST_HOST } from 'spec/test_constants';
|
||||||
import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
|
import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
|
||||||
|
|
||||||
const projectRootPath = 'root/Project1';
|
const projectRootPath = 'root/Project1';
|
||||||
|
@ -16,16 +17,38 @@ describe('generateRefDestinationPath', () => {
|
||||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`}
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`}
|
||||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`}
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`}
|
||||||
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
|
||||||
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
|
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
|
||||||
setWindowLocation(currentPath);
|
setWindowLocation(currentPath);
|
||||||
expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(result);
|
expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(
|
||||||
|
`${TEST_HOST}/${result}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when using symbolic ref names', () => {
|
||||||
|
it.each`
|
||||||
|
currentPath | nextRef | result
|
||||||
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`}
|
||||||
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||||
|
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
|
||||||
|
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||||
|
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
|
||||||
|
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`}
|
||||||
|
`(
|
||||||
|
'generates the correct destination path for $currentPath with ref type when it can be extracted',
|
||||||
|
({ currentPath, result, nextRef }) => {
|
||||||
|
setWindowLocation(currentPath);
|
||||||
|
expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe(
|
||||||
|
`${TEST_HOST}/${result}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('encodes the selected ref', () => {
|
it('encodes the selected ref', () => {
|
||||||
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
|
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
|
||||||
|
|
||||||
expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
|
expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
|
||||||
result,
|
`${TEST_HOST}/${result}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,10 +46,17 @@ RSpec.describe Mutations::Ci::Runner::Update, feature_category: :runner_fleet do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user can update runner', :enable_admin_mode do
|
context 'when user can update runner' do
|
||||||
let_it_be(:admin_user) { create(:user, :admin) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
let(:current_ctx) { { current_user: admin_user } }
|
let(:original_projects) { [project1, project2] }
|
||||||
|
let(:projects_with_maintainer_access) { original_projects }
|
||||||
|
|
||||||
|
let(:current_ctx) { { current_user: user } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
projects_with_maintainer_access.each { |project| project.add_maintainer(user) }
|
||||||
|
end
|
||||||
|
|
||||||
context 'with valid arguments' do
|
context 'with valid arguments' do
|
||||||
let(:mutation_params) do
|
let(:mutation_params) do
|
||||||
|
@ -82,27 +89,22 @@ RSpec.describe Mutations::Ci::Runner::Update, feature_category: :runner_fleet do
|
||||||
|
|
||||||
context 'with associatedProjects argument' do
|
context 'with associatedProjects argument' do
|
||||||
let_it_be(:project3) { create(:project) }
|
let_it_be(:project3) { create(:project) }
|
||||||
|
let_it_be(:project4) { create(:project) }
|
||||||
|
|
||||||
|
let(:new_projects) { [project3, project4] }
|
||||||
|
let(:mutation_params) do
|
||||||
|
{
|
||||||
|
id: runner.to_global_id,
|
||||||
|
description: 'updated description',
|
||||||
|
associated_projects: new_projects.map { |project| project.to_global_id.to_s }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
context 'with id set to project runner' do
|
context 'with id set to project runner' do
|
||||||
let(:mutation_params) do
|
let(:projects_with_maintainer_access) { original_projects + new_projects }
|
||||||
{
|
|
||||||
id: runner.to_global_id,
|
|
||||||
description: 'updated description',
|
|
||||||
associated_projects: [project3.to_global_id.to_s]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'updates runner attributes and project relationships', :aggregate_failures do
|
it 'updates runner attributes and project relationships', :aggregate_failures do
|
||||||
expect_next_instance_of(
|
setup_service_expectations
|
||||||
::Ci::Runners::SetRunnerAssociatedProjectsService,
|
|
||||||
{
|
|
||||||
runner: runner,
|
|
||||||
current_user: admin_user,
|
|
||||||
project_ids: [project3.id]
|
|
||||||
}
|
|
||||||
) do |service|
|
|
||||||
expect(service).to receive(:execute).and_call_original
|
|
||||||
end
|
|
||||||
|
|
||||||
expected_attributes = mutation_params.except(:id, :associated_projects)
|
expected_attributes = mutation_params.except(:id, :associated_projects)
|
||||||
|
|
||||||
|
@ -112,57 +114,32 @@ RSpec.describe Mutations::Ci::Runner::Update, feature_category: :runner_fleet do
|
||||||
expect(response[:runner]).to be_an_instance_of(Ci::Runner)
|
expect(response[:runner]).to be_an_instance_of(Ci::Runner)
|
||||||
expect(response[:runner]).to have_attributes(expected_attributes)
|
expect(response[:runner]).to have_attributes(expected_attributes)
|
||||||
expect(runner.reload).to have_attributes(expected_attributes)
|
expect(runner.reload).to have_attributes(expected_attributes)
|
||||||
expect(runner.projects).to match_array([project1, project3])
|
expect(runner.projects).to match_array([project1] + new_projects)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with user not allowed to assign runner' do
|
context 'with missing permissions on one of the new projects' do
|
||||||
before do
|
let(:projects_with_maintainer_access) { original_projects + [project3] }
|
||||||
allow(admin_user).to receive(:can?).with(:assign_runner, runner).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not update runner', :aggregate_failures do
|
it 'does not update runner', :aggregate_failures do
|
||||||
expect_next_instance_of(
|
setup_service_expectations
|
||||||
::Ci::Runners::SetRunnerAssociatedProjectsService,
|
|
||||||
{
|
|
||||||
runner: runner,
|
|
||||||
current_user: admin_user,
|
|
||||||
project_ids: [project3.id]
|
|
||||||
}
|
|
||||||
) do |service|
|
|
||||||
expect(service).to receive(:execute).and_call_original
|
|
||||||
end
|
|
||||||
|
|
||||||
expected_attributes = mutation_params.except(:id, :associated_projects)
|
expected_attributes = mutation_params.except(:id, :associated_projects)
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
||||||
expect(response[:errors]).to match_array(['user not allowed to assign runner'])
|
expect(response[:errors]).to match_array(['user is not authorized to add runners to project'])
|
||||||
expect(response[:runner]).to be_nil
|
expect(response[:runner]).to be_nil
|
||||||
expect(runner.reload).not_to have_attributes(expected_attributes)
|
expect(runner.reload).not_to have_attributes(expected_attributes)
|
||||||
expect(runner.projects).to match_array([project1, project2])
|
expect(runner.projects).to match_array(original_projects)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an empty list of projects' do
|
context 'with an empty list of projects' do
|
||||||
let(:mutation_params) do
|
let(:new_projects) { [] }
|
||||||
{
|
|
||||||
id: runner.to_global_id,
|
|
||||||
associated_projects: []
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'removes project relationships', :aggregate_failures do
|
it 'removes project relationships', :aggregate_failures do
|
||||||
expect_next_instance_of(
|
setup_service_expectations
|
||||||
::Ci::Runners::SetRunnerAssociatedProjectsService,
|
|
||||||
{
|
|
||||||
runner: runner,
|
|
||||||
current_user: admin_user,
|
|
||||||
project_ids: []
|
|
||||||
}
|
|
||||||
) do |service|
|
|
||||||
expect(service).to receive(:execute).and_call_original
|
|
||||||
end
|
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
||||||
|
@ -172,15 +149,9 @@ RSpec.describe Mutations::Ci::Runner::Update, feature_category: :runner_fleet do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with id set to instance runner' do
|
context 'with id set to instance runner', :enable_admin_mode do
|
||||||
let(:instance_runner) { create(:ci_runner, :instance) }
|
let_it_be(:user) { create(:user, :admin) }
|
||||||
let(:mutation_params) do
|
let_it_be(:runner) { create(:ci_runner, :instance) }
|
||||||
{
|
|
||||||
id: instance_runner.to_global_id,
|
|
||||||
description: 'updated description',
|
|
||||||
associated_projects: [project2.to_global_id.to_s]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'raises error', :aggregate_failures do
|
it 'raises error', :aggregate_failures do
|
||||||
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do
|
||||||
|
@ -188,6 +159,19 @@ RSpec.describe Mutations::Ci::Runner::Update, feature_category: :runner_fleet do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setup_service_expectations
|
||||||
|
expect_next_instance_of(
|
||||||
|
::Ci::Runners::SetRunnerAssociatedProjectsService,
|
||||||
|
{
|
||||||
|
runner: runner,
|
||||||
|
current_user: user,
|
||||||
|
project_ids: new_projects.map(&:id)
|
||||||
|
}
|
||||||
|
) do |service|
|
||||||
|
expect(service).to receive(:execute).and_call_original
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with non-existing project ID in associatedProjects argument' do
|
context 'with non-existing project ID in associatedProjects argument' do
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe AvatarsHelper do
|
RSpec.describe AvatarsHelper, feature_category: :source_code_management do
|
||||||
include UploadHelpers
|
include UploadHelpers
|
||||||
|
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
@ -88,7 +88,7 @@ RSpec.describe AvatarsHelper do
|
||||||
describe '#avatar_icon_for' do
|
describe '#avatar_icon_for' do
|
||||||
let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') }
|
let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') }
|
||||||
let(:email) { 'foo@example.com' }
|
let(:email) { 'foo@example.com' }
|
||||||
let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) }
|
let!(:another_user) { create(:user, :public_email, avatar: File.open(uploaded_image_temp_path), email: email) }
|
||||||
|
|
||||||
it 'prefers the user to retrieve the avatar_url' do
|
it 'prefers the user to retrieve the avatar_url' do
|
||||||
expect(helper.avatar_icon_for(user, email).to_s)
|
expect(helper.avatar_icon_for(user, email).to_s)
|
||||||
|
@ -102,7 +102,7 @@ RSpec.describe AvatarsHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#avatar_icon_for_email', :clean_gitlab_redis_cache do
|
describe '#avatar_icon_for_email', :clean_gitlab_redis_cache do
|
||||||
let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) }
|
let(:user) { create(:user, :public_email, avatar: File.open(uploaded_image_temp_path)) }
|
||||||
|
|
||||||
subject { helper.avatar_icon_for_email(user.email).to_s }
|
subject { helper.avatar_icon_for_email(user.email).to_s }
|
||||||
|
|
||||||
|
@ -114,6 +114,14 @@ RSpec.describe AvatarsHelper do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when a private email is used' do
|
||||||
|
it 'calls gravatar_icon' do
|
||||||
|
expect(helper).to receive(:gravatar_icon).with(user.commit_email, 20, 2)
|
||||||
|
|
||||||
|
helper.avatar_icon_for_email(user.commit_email, 20, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when no user exists for the email' do
|
context 'when no user exists for the email' do
|
||||||
it 'calls gravatar_icon' do
|
it 'calls gravatar_icon' do
|
||||||
expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
|
expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2)
|
||||||
|
@ -136,7 +144,7 @@ RSpec.describe AvatarsHelper do
|
||||||
it_behaves_like "returns avatar for email"
|
it_behaves_like "returns avatar for email"
|
||||||
|
|
||||||
it "caches the request" do
|
it "caches the request" do
|
||||||
expect(User).to receive(:find_by_any_email).once.and_call_original
|
expect(User).to receive(:with_public_email).once.and_call_original
|
||||||
|
|
||||||
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
||||||
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url)
|
||||||
|
|
|
@ -26,7 +26,7 @@ RSpec.describe HooksHelper do
|
||||||
it 'returns proper data' do
|
it 'returns proper data' do
|
||||||
expect(subject).to match(
|
expect(subject).to match(
|
||||||
url: project_hook.url,
|
url: project_hook.url,
|
||||||
url_variables: Gitlab::Json.dump([{ key: 'abc' }])
|
url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -80,6 +80,15 @@ RSpec.describe Banzai::Filter::AssetProxyFilter, feature_category: :team_plannin
|
||||||
expect(doc.at_css('img')['data-canonical-src']).to eq src
|
expect(doc.at_css('img')['data-canonical-src']).to eq src
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'replaces invalid URLs' do
|
||||||
|
src = '///example.com/test.png'
|
||||||
|
new_src = 'https://assets.example.com/3368d2c7b9bed775bdd1e811f36a4b80a0dcd8ab/2f2f2f6578616d706c652e636f6d2f746573742e706e67'
|
||||||
|
doc = filter(image(src), @context)
|
||||||
|
|
||||||
|
expect(doc.at_css('img')['src']).to eq new_src
|
||||||
|
expect(doc.at_css('img')['data-canonical-src']).to eq src
|
||||||
|
end
|
||||||
|
|
||||||
it 'skips internal images' do
|
it 'skips internal images' do
|
||||||
src = "#{Gitlab.config.gitlab.url}/test.png"
|
src = "#{Gitlab.config.gitlab.url}/test.png"
|
||||||
doc = filter(image(src), @context)
|
doc = filter(image(src), @context)
|
||||||
|
|
|
@ -218,7 +218,7 @@ RSpec.describe Banzai::Filter::CommitTrailersFilter, feature_category: :source_c
|
||||||
# any path-only link will automatically be prefixed
|
# any path-only link will automatically be prefixed
|
||||||
# with the path of its repository.
|
# with the path of its repository.
|
||||||
# See: "build_relative_path" in "lib/banzai/filter/relative_link_filter.rb"
|
# See: "build_relative_path" in "lib/banzai/filter/relative_link_filter.rb"
|
||||||
let(:user_with_avatar) { create(:user, :with_avatar, username: 'foobar') }
|
let(:user_with_avatar) { create(:user, :public_email, :with_avatar, username: 'foobar') }
|
||||||
|
|
||||||
it 'returns a full path for avatar urls' do
|
it 'returns a full path for avatar urls' do
|
||||||
_, message_html = build_commit_message(
|
_, message_html = build_commit_message(
|
||||||
|
|
|
@ -34,6 +34,58 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the document contains an embeddable observability link with redirect' do
|
||||||
|
let(:url) { 'https://observe.gitlab.com@example.com/12345' }
|
||||||
|
|
||||||
|
it 'leaves the original link unchanged' do
|
||||||
|
expect(doc.at_css('a').to_s).to eq(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not append an observability charts placeholder' do
|
||||||
|
node = doc.at_css('.js-render-observability')
|
||||||
|
|
||||||
|
expect(node).not_to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the document contains an embeddable observability link with different port' do
|
||||||
|
let(:url) { 'https://observe.gitlab.com:3000/12345' }
|
||||||
|
let(:observe_url) { 'https://observe.gitlab.com:3001' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'leaves the original link unchanged' do
|
||||||
|
expect(doc.at_css('a').to_s).to eq(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not append an observability charts placeholder' do
|
||||||
|
node = doc.at_css('.js-render-observability')
|
||||||
|
|
||||||
|
expect(node).not_to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the document contains an embeddable observability link with auth/start' do
|
||||||
|
let(:url) { 'https://observe.gitlab.com/auth/start' }
|
||||||
|
let(:observe_url) { 'https://observe.gitlab.com' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'leaves the original link unchanged' do
|
||||||
|
expect(doc.at_css('a').to_s).to eq(input)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not append an observability charts placeholder' do
|
||||||
|
node = doc.at_css('.js-render-observability')
|
||||||
|
|
||||||
|
expect(node).not_to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when feature flag is disabled' do
|
context 'when feature flag is disabled' do
|
||||||
let(:url) { 'https://observe.gitlab.com/12345' }
|
let(:url) { 'https://observe.gitlab.com/12345' }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength
|
||||||
|
it 'nullifies last_error column on all rows' do
|
||||||
|
namespaces = table(:namespaces)
|
||||||
|
projects = table(:projects)
|
||||||
|
project_import_states = table(:project_mirror_data)
|
||||||
|
|
||||||
|
group = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||||
|
|
||||||
|
project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||||
|
project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||||
|
project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
|
||||||
|
|
||||||
|
project_1 = projects.create!(
|
||||||
|
namespace_id: group.id,
|
||||||
|
project_namespace_id: project_namespace_1.id,
|
||||||
|
name: 'test1'
|
||||||
|
)
|
||||||
|
project_2 = projects.create!(
|
||||||
|
namespace_id: group.id,
|
||||||
|
project_namespace_id: project_namespace_2.id,
|
||||||
|
name: 'test2'
|
||||||
|
)
|
||||||
|
project_3 = projects.create!(
|
||||||
|
namespace_id: group.id,
|
||||||
|
project_namespace_id: project_namespace_3.id,
|
||||||
|
name: 'test3'
|
||||||
|
)
|
||||||
|
|
||||||
|
project_import_state_1 = project_import_states.create!(
|
||||||
|
project_id: project_1.id,
|
||||||
|
status: 0,
|
||||||
|
last_update_started_at: 1.hour.ago,
|
||||||
|
last_update_scheduled_at: 1.hour.ago,
|
||||||
|
last_update_at: 1.hour.ago,
|
||||||
|
last_successful_update_at: 2.days.ago,
|
||||||
|
last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength
|
||||||
|
correlation_id_value: SecureRandom.uuid,
|
||||||
|
jid: SecureRandom.uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
project_import_states.create!(
|
||||||
|
project_id: project_2.id,
|
||||||
|
status: 1,
|
||||||
|
last_update_started_at: 1.hour.ago,
|
||||||
|
last_update_scheduled_at: 1.hour.ago,
|
||||||
|
last_update_at: 1.hour.ago,
|
||||||
|
last_successful_update_at: nil,
|
||||||
|
next_execution_timestamp: 1.day.from_now,
|
||||||
|
last_error: '',
|
||||||
|
correlation_id_value: SecureRandom.uuid,
|
||||||
|
jid: SecureRandom.uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
project_import_state_3 = project_import_states.create!(
|
||||||
|
project_id: project_3.id,
|
||||||
|
status: 2,
|
||||||
|
last_update_started_at: 1.hour.ago,
|
||||||
|
last_update_scheduled_at: 1.hour.ago,
|
||||||
|
last_update_at: 1.hour.ago,
|
||||||
|
last_successful_update_at: 1.hour.ago,
|
||||||
|
next_execution_timestamp: 1.day.from_now,
|
||||||
|
last_error: nil,
|
||||||
|
correlation_id_value: SecureRandom.uuid,
|
||||||
|
jid: SecureRandom.uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
migration = described_class.new(
|
||||||
|
start_id: project_import_state_1.id,
|
||||||
|
end_id: project_import_state_3.id,
|
||||||
|
batch_table: :project_mirror_data,
|
||||||
|
batch_column: :id,
|
||||||
|
sub_batch_size: 1,
|
||||||
|
pause_ms: 0,
|
||||||
|
connection: ApplicationRecord.connection
|
||||||
|
)
|
||||||
|
|
||||||
|
w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord
|
||||||
|
expect { migration.perform }.to change(&w_last_error_count).from(2).to(0)
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,8 +26,14 @@ RSpec.describe Gitlab::Checks::BranchCheck do
|
||||||
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "prohibits 40-character hexadecimal branch names as the start of a path" do
|
||||||
|
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e/test")
|
||||||
|
|
||||||
|
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
|
||||||
|
end
|
||||||
|
|
||||||
it "doesn't prohibit a nested hexadecimal in a branch name" do
|
it "doesn't prohibit a nested hexadecimal in a branch name" do
|
||||||
allow(subject).to receive(:branch_name).and_return("fix-267208abfe40e546f5e847444276f7d43a39503e")
|
allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
|
||||||
|
|
||||||
expect { subject.validate! }.not_to raise_error
|
expect { subject.validate! }.not_to raise_error
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,7 +116,8 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
||||||
let(:expected_extension) { 'tar.gz' }
|
let(:expected_extension) { 'tar.gz' }
|
||||||
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
|
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
|
||||||
let(:expected_path) { File.join(storage_path, cache_key, "@v2", expected_filename) }
|
let(:expected_path) { File.join(storage_path, cache_key, "@v2", expected_filename) }
|
||||||
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{TestEnv::BRANCH_SHA['master']}" }
|
let(:expected_prefix) { "gitlab-git-test-#{ref.tr('/', '-')}-#{expected_prefix_sha}" }
|
||||||
|
let(:expected_prefix_sha) { TestEnv::BRANCH_SHA['master'] }
|
||||||
|
|
||||||
subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) }
|
subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) }
|
||||||
|
|
||||||
|
@ -173,6 +174,73 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
|
||||||
it { expect(metadata['ArchivePath']).to eq(expected_path) }
|
it { expect(metadata['ArchivePath']).to eq(expected_path) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when references are ambiguous' do
|
||||||
|
let_it_be(:ambiguous_project) { create(:project, :repository) }
|
||||||
|
let_it_be(:repository) { ambiguous_project.repository.raw }
|
||||||
|
let_it_be(:branch_merged_commit_id) { ambiguous_project.repository.find_branch('branch-merged').dereferenced_target.id }
|
||||||
|
let_it_be(:branch_master_commit_id) { ambiguous_project.repository.find_branch('master').dereferenced_target.id }
|
||||||
|
let_it_be(:tag_1_0_0_commit_id) { ambiguous_project.repository.find_tag('v1.0.0').dereferenced_target.id }
|
||||||
|
|
||||||
|
context 'when tag is ambiguous' do
|
||||||
|
before do
|
||||||
|
ambiguous_project.repository.add_tag(user, ref, 'master', 'foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
ambiguous_project.repository.rm_tag(user, ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
where(:ref, :expected_commit_id, :desc) do
|
||||||
|
'refs/heads/branch-merged' | ref(:branch_master_commit_id) | 'when tag looks like a branch'
|
||||||
|
'branch-merged' | ref(:branch_master_commit_id) | 'when tag has the same name as a branch'
|
||||||
|
ref(:branch_merged_commit_id) | ref(:branch_merged_commit_id) | 'when tag looks like a commit id'
|
||||||
|
'v0.0.0' | ref(:branch_master_commit_id) | 'when tag looks like a normal tag'
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it 'selects the correct commit' do
|
||||||
|
expect(metadata['CommitId']).to eq(expected_commit_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when branch is ambiguous' do
|
||||||
|
before do
|
||||||
|
ambiguous_project.repository.add_branch(user, ref, 'master')
|
||||||
|
end
|
||||||
|
|
||||||
|
where(:ref, :expected_commit_id, :desc) do
|
||||||
|
'refs/tags/v1.0.0' | ref(:branch_master_commit_id) | 'when branch looks like a tag'
|
||||||
|
'v1.0.0' | ref(:tag_1_0_0_commit_id) | 'when branch has the same name as a tag'
|
||||||
|
ref(:branch_merged_commit_id) | ref(:branch_merged_commit_id) | 'when branch looks like a commit id'
|
||||||
|
'just-a-normal-branch' | ref(:branch_master_commit_id) | 'when branch looks like a normal branch'
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
it 'selects the correct commit' do
|
||||||
|
expect(metadata['CommitId']).to eq(expected_commit_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ref is HEAD' do
|
||||||
|
let(:ref) { 'HEAD' }
|
||||||
|
|
||||||
|
it 'selects commit id from HEAD ref' do
|
||||||
|
expect(metadata['CommitId']).to eq(branch_master_commit_id)
|
||||||
|
expect(metadata['ArchivePrefix']).to eq(expected_prefix)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ref is not found' do
|
||||||
|
let(:ref) { 'unknown-ref-cannot-be-found' }
|
||||||
|
|
||||||
|
it 'returns empty metadata' do
|
||||||
|
expect(metadata).to eq({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#size' do
|
describe '#size' do
|
||||||
|
|
|
@ -1140,9 +1140,9 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'HTML comment lines' do
|
context 'HTML comment lines' do
|
||||||
subject { described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX }
|
subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_LINE_REGEX_UNTRUSTED, multiline: true) }
|
||||||
|
|
||||||
let(:expected) { %(<!-- an HTML comment -->) }
|
let(:expected) { [['<!-- an HTML comment -->'], ['<!-- another HTML comment -->']] }
|
||||||
let(:markdown) do
|
let(:markdown) do
|
||||||
<<~MARKDOWN
|
<<~MARKDOWN
|
||||||
Regular text
|
Regular text
|
||||||
|
@ -1150,26 +1150,28 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do
|
||||||
<!-- an HTML comment -->
|
<!-- an HTML comment -->
|
||||||
|
|
||||||
more text
|
more text
|
||||||
|
|
||||||
|
<!-- another HTML comment -->
|
||||||
MARKDOWN
|
MARKDOWN
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to match(%(<!-- single line comment -->)) }
|
it { is_expected.to match(%(<!-- single line comment -->)) }
|
||||||
it { is_expected.not_to match(%(<!--\nblock comment\n-->)) }
|
it { is_expected.not_to match(%(<!--\nblock comment\n-->)) }
|
||||||
it { is_expected.not_to match(%(must start in first column <!-- comment -->)) }
|
it { is_expected.not_to match(%(must start in first column <!-- comment -->)) }
|
||||||
it { expect(subject.match(markdown)[:html_comment_line]).to eq expected }
|
it { expect(subject.scan(markdown)).to eq expected }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'HTML comment blocks' do
|
context 'HTML comment blocks' do
|
||||||
subject { described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX }
|
subject { Gitlab::UntrustedRegexp.new(described_class::MARKDOWN_HTML_COMMENT_BLOCK_REGEX_UNTRUSTED, multiline: true) }
|
||||||
|
|
||||||
let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\n-->) }
|
let(:expected) { %(<!-- the start of an HTML comment\n- [ ] list item commented out\nmore text -->) }
|
||||||
let(:markdown) do
|
let(:markdown) do
|
||||||
<<~MARKDOWN
|
<<~MARKDOWN
|
||||||
Regular text
|
Regular text
|
||||||
|
|
||||||
<!-- the start of an HTML comment
|
<!-- the start of an HTML comment
|
||||||
- [ ] list item commented out
|
- [ ] list item commented out
|
||||||
-->
|
more text -->
|
||||||
MARKDOWN
|
MARKDOWN
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ RSpec.describe ::Gitlab::Seeders::Ci::Runner::RunnerFleetPipelineSeeder, feature
|
||||||
context 'with job_count specified' do
|
context 'with job_count specified' do
|
||||||
let(:job_count) { 20 }
|
let(:job_count) { 20 }
|
||||||
|
|
||||||
it 'creates expected jobs', :aggregate_failures do
|
it 'creates expected jobs', :aggregate_failures,
|
||||||
|
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/394721' do
|
||||||
expect { seeder.seed }.to change { Ci::Build.count }.by(job_count)
|
expect { seeder.seed }.to change { Ci::Build.count }.by(job_count)
|
||||||
.and change { Ci::Pipeline.count }.by(4)
|
.and change { Ci::Pipeline.count }.by(4)
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue