Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-08-03 00:08:46 +00:00
parent 68d3938909
commit 7eeb48577d
44 changed files with 671 additions and 213 deletions

View File

@ -200,6 +200,20 @@ overrides:
no-unsanitized/method: off
no-unsanitized/property: off
local-rules/require-valid-help-page-path: off
no-restricted-imports:
- error
- paths:
- name: mousetrap
message: 'Import { Mousetrap } from ~/lib/mousetrap instead.'
- name: vuex
message: 'See our documentation on "Migrating from VueX" for tips on how to avoid adding new VueX stores.'
- name: '@sentry/browser'
message: Use "import * as Sentry from '~/sentry/sentry_browser_wrapper';" instead
- name: ~/locale
importNames:
- __
- s__
message: 'Do not externalize strings in specs: https://docs.gitlab.com/ee/development/i18n/externalization.html#test-files-jest'
- files:
- 'config/**/*'
- 'scripts/**/*'

View File

@ -160,7 +160,7 @@ workflow:
variables:
PG_VERSION: "14"
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-${NODE_VERSION}-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-${NODE_VERSION}-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-yarn-${YARN_VERSION}-graphicsmagick-${GRAPHICSMAGICK_VERSION}"
DEFAULT_JOB_TAG: "gitlab-org"
DEFAULT_RSPEC_PREDICTIVE_JOB_TAGS: "${DEFAULT_JOB_TAG}" # Separated by commas, overridden in JiHu
# We set $GITLAB_DEPENDENCY_PROXY to another variable (since it's set at the group level and has higher precedence than .gitlab-ci.yml)
@ -219,7 +219,7 @@ variables:
DOCS_REVIEW_APPS_DOMAIN: "docs.gitlab-review.app"
DOCS_GITLAB_REPO_SUFFIX: "ee"
REVIEW_APPS_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:gcloud-383-kubectl-1.28-helm-3.9"
REVIEW_APPS_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:gcloud-${GCLOUD_VERSION}-kubectl-${KUBECTL_VERSION}-helm-${HELM_VERSION}"
REVIEW_APPS_DOMAIN: "gitlab-review.app"
REVIEW_APPS_GCP_PROJECT: "gitlab-review-apps"
REVIEW_APPS_GCP_REGION: "us-central1"

View File

@ -11,7 +11,7 @@
- .default-retry
- .default-before_script
- .assets-compile-cache
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-node-${NODE_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-node-${NODE_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-lfs-${LFS_VERSION}-yarn-${YARN_VERSION}-graphicsmagick-${GRAPHICSMAGICK_VERSION}
variables:
SETUP_DB: "false"
WEBPACK_VENDOR_DLL: "true"

View File

@ -94,6 +94,7 @@ sync-as-if-jh-branch:
- RUST_VERSION
- PG_VERSION
- RUBYGEMS_VERSION
- GIT_VERSION
- CHROME_VERSION
- NODE_ENV
variables:

View File

@ -44,13 +44,19 @@
QA_EXPORT_TEST_METRICS: "true" # Export test metrics directly to influxdb by default
inherit:
variables:
- BUILD_OS
- CHROME_VERSION
- DOCKER_VERSION
- GCLOUD_VERSION
- GIT_VERSION
- GO_VERSION
- HELM_VERSION
- KUBECTL_VERSION
- LFS_VERSION
- OS_VERSION
- RUBY_VERSION_DEFAULT
- RUBY_VERSION_NEXT
- RUBY_VERSION
- DOCKER_VERSION
- BUILD_OS
- OS_VERSION
- RUBYGEMS_VERSION
- REGISTRY_GROUP
- REGISTRY_HOST
- OMNIBUS_GITLAB_CACHE_UPDATE

View File

@ -53,7 +53,7 @@ setup-test-env:
setup-test-env-fips:
extends:
- setup-test-env
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-exiftool-12.60
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-exiftool-${EXIFTOOL_VERSION}
variables:
BUILD_OS: "ubi"
OS_VERSION: ${UBI_VERSION}

View File

@ -86,19 +86,28 @@ start-review-app-pipeline:
# https://gitlab.com/gitlab-org/gitlab/-/issues/387183
inherit:
variables:
- REGISTRY_GROUP
- REGISTRY_HOST
- REVIEW_APPS_DOMAIN
- REVIEW_APPS_GCP_PROJECT
- REVIEW_APPS_GCP_REGION
- REVIEW_APPS_IMAGE
- BUILD_OS
- CHROME_VERSION
- DOCKER_VERSION
- EXIFTOOL_VERSION
- GCLOUD_VERSION
- GIT_VERSION
- GO_VERSION
- GRAPHICSMAGICK_VERSION
- HELM_VERSION
- KIND_VERSION
- KUBECTL_VERSION
- LFS_VERSION
- NODE_VERSION
- OS_VERSION
- RUBY_VERSION_DEFAULT
- RUBY_VERSION_NEXT
- RUBY_VERSION
- BUILD_OS
- OS_VERSION
- DOCKER_VERSION
- CHROME_VERSION
- RUBYGEMS_VERSION
- RUST_VERSION
- UBI_VERSION
- YARN_VERSION
- REGISTRY_GROUP
- REGISTRY_HOST
# 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

View File

@ -11,7 +11,7 @@ workflow:
- when: always
.cng-test:
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-kubectl-1.23-helm-3.14-kind-0.20"
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-kubectl-${KUBECTL_VERSION}-helm-${HELM_VERSION}-kind-${KIND_VERSION}"
stage: test
extends:
- .qa-cache

View File

@ -56,7 +56,7 @@ include:
- mv $CI_BUILDS_DIR/*.log $CI_PROJECT_DIR/
.gdk-qa-base:
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23"
image: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-${GCLOUD_VERSION}-kubectl-${KUBECTL_VERSION}-helm-${HELM_VERSION}"
extends:
- .qa-cache
- .docker-in-docker

View File

@ -1,12 +1,21 @@
variables:
BUILD_OS: "debian"
OS_VERSION: "bookworm"
UBI_VERSION: "8.6"
CHROME_VERSION: "123"
DOCKER_VERSION: "24.0.5"
RUBYGEMS_VERSION: "3.4"
EXIFTOOL_VERSION: "12.60"
GCLOUD_VERSION: "413"
GIT_VERSION: "2.45"
GO_VERSION: "1.22"
GRAPHICSMAGICK_VERSION: "1.3.36"
HELM_VERSION: "3.14"
KIND_VERSION: "0.20"
KUBECTL_VERSION: "1.28"
LFS_VERSION: "2.9"
NODE_VERSION: "20.12"
RUST_VERSION: "1.73"
OS_VERSION: "bookworm"
RUBY_VERSION_DEFAULT: "3.1.5"
RUBY_VERSION_NEXT: "3.2.4"
RUBYGEMS_VERSION: "3.4"
RUST_VERSION: "1.73"
UBI_VERSION: "8.6"
YARN_VERSION: "1.22"

View File

@ -15,7 +15,7 @@ workhorse:verify:
extends:
- .workhorse:rules:workhorse
- .gitaly-with-transactions
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-exiftool-12.60
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-exiftool-${EXIFTOOL_VERSION}
services:
- name: redis:${REDIS_VERSION}-alpine
variables:
@ -68,7 +68,7 @@ workhorse:test fips:
matrix:
- GO_VERSION: ["1.21", "1.22"]
REDIS_VERSION: ["7.0", "6.2"]
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-exiftool-12.60
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}-rust-${RUST_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-${GIT_VERSION}-exiftool-${EXIFTOOL_VERSION}
variables:
FIPS_MODE: 1
BUILD_OS: "ubi"

View File

@ -248,8 +248,8 @@
{"name":"google-cloud-artifact_registry-v1","version":"0.11.0","platform":"ruby","checksum":"ba80d2dce9767e663931ded7929b7f8bf5983a6e2ea68078e27e7ca9a940783e"},
{"name":"google-cloud-common","version":"1.1.0","platform":"ruby","checksum":"738db08fd144b4fe37b4578ffd63308b64a86fd59f6979d240048f917a6fb5fb"},
{"name":"google-cloud-compute-v1","version":"2.6.0","platform":"ruby","checksum":"b96059b33ffc2f25644d20161a0c1aa1331197073c2e44786b18f8b670f1141e"},
{"name":"google-cloud-core","version":"1.6.0","platform":"ruby","checksum":"ea1744cd5a3085d3072de3fab9106afc769cd198609ebb5c6eeb5f13da46b72a"},
{"name":"google-cloud-env","version":"1.6.0","platform":"ruby","checksum":"6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15"},
{"name":"google-cloud-core","version":"1.7.0","platform":"ruby","checksum":"748028a48530ea5bce159722eb7a02cd0562f1c52f0569e9ed69da3cba6b4f35"},
{"name":"google-cloud-env","version":"2.1.1","platform":"ruby","checksum":"cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999"},
{"name":"google-cloud-errors","version":"1.3.0","platform":"ruby","checksum":"450b681e24c089a20721a01acc4408bb4a7b0df28c175aaab488da917480d64b"},
{"name":"google-cloud-location","version":"0.6.0","platform":"ruby","checksum":"386c99ca156e5cac413731c055d7d9c55629860129ad7658a2bf39ea5004d2d0"},
{"name":"google-cloud-profiler-v2","version":"0.4.0","platform":"ruby","checksum":"53fc2ab175d08f54233c644310d47798feac996220916815c4fb44c937b5d3e3"},
@ -280,15 +280,15 @@
{"name":"graphql","version":"2.3.5","platform":"ruby","checksum":"9c367835f86541660d24c3d81632267ecee553d304577aaee070f8ac05860af1"},
{"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"},
{"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"},
{"name":"grpc","version":"1.65.2","platform":"aarch64-linux","checksum":"a541d5aeb721ac5e732284ca8b6b955b2e2e5135c3c23134b926a569eca68933"},
{"name":"grpc","version":"1.65.2","platform":"arm64-darwin","checksum":"1a7e762ae1b59c363f26dfbbd857a87bf8f806744438a1a85d828e92d57ba436"},
{"name":"grpc","version":"1.65.2","platform":"ruby","checksum":"9d696e4e742eb1a7f3b3b7a6b3ee1796e4a6a2b009513b5f048df50e823622cc"},
{"name":"grpc","version":"1.65.2","platform":"x64-mingw-ucrt","checksum":"b3341bfa6050b1e1790fcfa5560bc9b0d82ffe36d7bacfccb5c621401424198d"},
{"name":"grpc","version":"1.65.2","platform":"x64-mingw32","checksum":"ea5162774967da44606552d3ca0c8efb3769c6a7b094e0764c38edca5ecab3e5"},
{"name":"grpc","version":"1.65.2","platform":"x86-linux","checksum":"d7566459acb8964aff730f3c662e31122fca148befd76598fa11ea80efe8a892"},
{"name":"grpc","version":"1.65.2","platform":"x86-mingw32","checksum":"6c102550453269f1de35cedc001aee1315bfec7af862008a8232c3262dcf130d"},
{"name":"grpc","version":"1.65.2","platform":"x86_64-darwin","checksum":"4693e166dc84270f16e6b71d8f758cbbd367070f1d980d590d97e8b4b9b8d6ff"},
{"name":"grpc","version":"1.65.2","platform":"x86_64-linux","checksum":"229bd96065637e867a2c13a1c5edb34ebdd14d2250428f03df718fd7a541edb5"},
{"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"},
{"name":"grpc","version":"1.63.0","platform":"arm64-darwin","checksum":"91b93a354508a9d1772f095554f2e4c04358c2b32d7a670e3705b7fc4695c996"},
{"name":"grpc","version":"1.63.0","platform":"ruby","checksum":"5f4383c4ee2886e92c31b90422261b7527f26e3baa585d877e9804e715983686"},
{"name":"grpc","version":"1.63.0","platform":"x64-mingw-ucrt","checksum":"bbca63f19b45cca5a485f5c5eb363a8684d23a6d0c3421bde5e72e6227291488"},
{"name":"grpc","version":"1.63.0","platform":"x64-mingw32","checksum":"fb6251f497c8327eda92c4af293ec07fcaec4ffaa2514d3942a7c31406bfaf5b"},
{"name":"grpc","version":"1.63.0","platform":"x86-linux","checksum":"152140fa2c28e384d3c1ded454a66d5e22fb2ff1d2920c2ef2530b2d707de6fd"},
{"name":"grpc","version":"1.63.0","platform":"x86-mingw32","checksum":"eed13225b08e705421fef9d986de6c2310ec692df1d80f7a4d407de7c1f98525"},
{"name":"grpc","version":"1.63.0","platform":"x86_64-darwin","checksum":"a814414ff178e89ee3ad0cc2a826ce1ca96c68063effb81affe3e5ceff7b44cc"},
{"name":"grpc","version":"1.63.0","platform":"x86_64-linux","checksum":"41a90a597f44959c8dbb94619db2b0c0939a768569a5dfad41fffa227eb1287d"},
{"name":"grpc-google-iam-v1","version":"1.5.0","platform":"ruby","checksum":"cea356d150dac69751f6a4c71f1571c8022c69d9f4ce9c18139200932c19374e"},
{"name":"gssapi","version":"1.3.1","platform":"ruby","checksum":"c51cf30842ee39bd93ce7fc33e20405ff8a04cda9dec6092071b61258284aee1"},
{"name":"guard","version":"2.16.2","platform":"ruby","checksum":"71ba7abaddecc8be91ab77bbaf78f767246603652ebbc7b976fda497ebdc8fbb"},

View File

@ -822,11 +822,11 @@ GEM
gapic-common (>= 0.20.0, < 2.a)
google-cloud-common (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-core (1.7.0)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-env (2.1.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.3.0)
google-cloud-location (0.6.0)
gapic-common (>= 0.20.0, < 2.a)
@ -900,8 +900,8 @@ GEM
graphql (~> 2.0)
html-pipeline (~> 2.14, >= 2.14.3)
sass-embedded (~> 1.58)
grpc (1.65.2)
google-protobuf (>= 3.25, < 5.0)
grpc (1.63.0)
google-protobuf (~> 3.25)
googleapis-common-protos-types (~> 1.0)
grpc-google-iam-v1 (1.5.0)
google-protobuf (~> 3.18)

View File

@ -16,8 +16,10 @@ import HorizontalRule from './horizontal_rule';
import HTMLNodes from './html_nodes';
import Image from './image';
import Italic from './italic';
import InlineDiff from './inline_diff';
import Link from './link';
import ListItem from './list_item';
import MathInline from './math_inline';
import OrderedList from './ordered_list';
import Paragraph from './paragraph';
import ReferenceDefinition from './reference_definition';
@ -55,8 +57,10 @@ export default Extension.create({
HorizontalRule.name,
Image.name,
Italic.name,
InlineDiff.name,
Link.name,
ListItem.name,
MathInline.name,
OrderedList.name,
Paragraph.name,
ReferenceDefinition.name,

View File

@ -120,7 +120,17 @@ const createChangeTracker = (doc, pristineDoc) => {
if (node.attrs.sourceMapKey) {
pristineSourceMarkdownMap.set(`${node.attrs.sourceMapKey}${node.type.name}`, node);
}
node.marks?.forEach((mark) => {
if (mark.attrs.sourceMapKey) {
pristineSourceMarkdownMap.set(`${mark.attrs.sourceMapKey}${mark.type.name}`, {
mark,
node,
});
}
});
});
doc.descendants((node) => {
const pristineNode = pristineSourceMarkdownMap.get(
`${node.attrs.sourceMapKey}${node.type.name}`,
@ -129,6 +139,15 @@ const createChangeTracker = (doc, pristineDoc) => {
if (pristineNode) {
changeTracker.set(node, node.eq(pristineNode));
}
node.marks?.forEach((mark) => {
const { node: pristineNodeForMark, mark: pristineMark } =
pristineSourceMarkdownMap.get(`${mark.attrs.sourceMapKey}${mark.type.name}`) || {};
if (pristineMark) {
changeTracker.set(mark, mark.eq(pristineMark) && node.eq(pristineNodeForMark));
}
});
});
}

View File

@ -138,6 +138,49 @@ export function preserveUnchanged(configOrRender) {
};
}
export function preserveUnchangedMark({ open, close, ...restConfig }) {
// use a buffer to replace the content of the serialized mark with the sourceMarkdown
// when the mark is unchanged
let bufferStartPos = -1;
function startBuffer(state) {
bufferStartPos = state.out.length;
}
function bufferStarted() {
return bufferStartPos !== -1;
}
function endBuffer(state, replace) {
state.out = state.out.substring(0, bufferStartPos) + replace;
bufferStartPos = -1;
}
return {
...restConfig,
open: (state, mark, parent, index) => {
const same = state.options.changeTracker.get(mark);
if (same) {
startBuffer(state);
return '';
}
return open(state, mark, parent, index);
},
close: (state, mark, parent, index) => {
const { sourceMarkdown } = mark.attrs;
if (bufferStarted()) {
endBuffer(state, sourceMarkdown);
return '';
}
return close(state, mark, parent, index);
},
};
}
export const findChildWithMark = (mark, parent) => {
let child;
let offset;

View File

@ -1,28 +1,20 @@
import { openTag, closeTag } from '../serialization_helpers';
import { openTag, closeTag, preserveUnchangedMark } from '../serialization_helpers';
const generateBoldTags = (wrapTagName = openTag) => {
const generateBoldTag = (wrapTagName = openTag) => {
return (_, mark) => {
const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1];
const type = /^(\*\*|__).*/.exec(mark.attrs.sourceMarkdown)?.[1];
if (type === '**' || type === '__') return type;
if (mark.attrs.sourceTagName) return wrapTagName(mark.attrs.sourceTagName);
switch (type) {
case '**':
case '__':
return type;
// eslint-disable-next-line @gitlab/require-i18n-strings
case '<strong':
case '<b':
return wrapTagName(type.substring(1));
default:
return '**';
}
return '**';
};
};
const bold = {
open: generateBoldTags(),
close: generateBoldTags(closeTag),
const bold = preserveUnchangedMark({
open: generateBoldTag(),
close: generateBoldTag(closeTag),
mixable: true,
expelEnclosingWhitespace: true,
};
});
export default bold;

View File

@ -4,11 +4,8 @@ const generateCodeTag = (wrapTagName = openTag) => {
const isOpen = wrapTagName === openTag;
return (_, mark, parent) => {
const type = /^(`|<code).*/.exec(mark.attrs.sourceMarkdown)?.[1];
if (type === '<code') {
return wrapTagName(type.substring(1));
}
const { sourceTagName, sourceMarkdown } = mark.attrs;
if (sourceTagName && !sourceMarkdown) return wrapTagName(mark.attrs.sourceTagName);
const childText = getMarkText(mark, parent);
if (childText.includes('`')) {

View File

@ -1,4 +1,6 @@
const inlineDiff = {
import { preserveUnchangedMark } from '../serialization_helpers';
const inlineDiff = preserveUnchangedMark({
mixable: true,
open(_, mark) {
return mark.attrs.type === 'addition' ? '{+' : '{-';
@ -6,6 +8,6 @@ const inlineDiff = {
close(_, mark) {
return mark.attrs.type === 'addition' ? '+}' : '-}';
},
};
});
export default inlineDiff;

View File

@ -1,28 +1,21 @@
import { openTag, closeTag } from '../serialization_helpers';
import { openTag, closeTag, preserveUnchangedMark } from '../serialization_helpers';
const generateItalicTag = (wrapTagName = openTag) => {
return (_, mark) => {
const type = /^(\*|_|<em|<i).*/.exec(mark.attrs.sourceMarkdown)?.[1];
const type = /^(\*|_).*/.exec(mark.attrs.sourceMarkdown)?.[1];
if (type === '*' || type === '_') return type;
switch (type) {
case '*':
case '_':
return type;
// eslint-disable-next-line @gitlab/require-i18n-strings
case '<em':
case '<i':
return wrapTagName(type.substring(1));
default:
return '_';
}
if (mark.attrs.sourceTagName) return wrapTagName(mark.attrs.sourceTagName);
return '_';
};
};
const italic = {
const italic = preserveUnchangedMark({
open: generateItalicTag(),
close: generateItalicTag(closeTag),
mixable: true,
expelEnclosingWhitespace: true,
};
});
export default italic;

View File

@ -1,8 +1,11 @@
import { removeLastSlashInUrlPath, removeUrlProtocol } from '~/lib/utils/url_utility';
import { findChildWithMark, openTag, closeTag, getMarkText } from '../serialization_helpers';
const LINK_HTML = 'linkHtml';
const LINK_MARKDOWN = 'linkMarkdown';
import {
findChildWithMark,
openTag,
closeTag,
getMarkText,
preserveUnchangedMark,
} from '../serialization_helpers';
/**
* Validates that the provided URL is a valid GFM autolink
@ -63,41 +66,28 @@ function getLinkHref(mark, useCanonicalSrc = true) {
return href || '';
}
const linkType = (sourceMarkdown) => {
const expression = /^(\[|<a).*/.exec(sourceMarkdown)?.[1];
if (!expression || expression === '[') {
return LINK_MARKDOWN;
}
return LINK_HTML;
};
const link = {
const link = preserveUnchangedMark({
open(state, mark, parent) {
if (isAutoLink(mark, parent)) {
return isBracketAutoLink(mark.attrs.sourceMarkdown) ? '<' : '';
}
const { href, title, sourceMarkdown, isGollumLink } = mark.attrs;
const { href, title, isGollumLink, sourceTagName, sourceMarkdown } = mark.attrs;
// eslint-disable-next-line @gitlab/require-i18n-strings
if (href.startsWith('data:') || href.startsWith('blob:')) return '';
if (linkType(sourceMarkdown) === LINK_MARKDOWN) {
if (isGollumLink) return '[[';
return '[';
}
const attrs = {
href: state.esc(getLinkHref(mark, state.options.useCanonicalSrc)),
};
if (title) {
attrs.title = title;
attrs.title = state.esc(title);
}
return openTag('a', attrs);
if (sourceTagName && !sourceMarkdown) return openTag(sourceTagName, attrs);
if (isGollumLink) return '[[';
return '[';
},
close(state, mark, parent) {
if (isAutoLink(mark, parent)) {
@ -107,6 +97,7 @@ const link = {
const {
href = '',
title,
sourceTagName,
sourceMarkdown,
isReference,
isGollumLink,
@ -116,12 +107,12 @@ const link = {
// eslint-disable-next-line @gitlab/require-i18n-strings
if (href.startsWith('data:') || href.startsWith('blob:')) return '';
if (isReference) {
return `][${state.esc(getLinkHref(mark, state.options.useCanonicalSrc))}]`;
if (sourceTagName && !sourceMarkdown) {
return closeTag(sourceTagName);
}
if (linkType(sourceMarkdown) === LINK_HTML) {
return closeTag('a');
if (isReference) {
return `][${state.esc(getLinkHref(mark, state.options.useCanonicalSrc))}]`;
}
if (isGollumLink) {
@ -139,6 +130,6 @@ const link = {
title ? ` ${state.quote(title)}` : ''
})`;
},
};
});
export default link;

View File

@ -1,29 +1,20 @@
import { openTag, closeTag } from '../serialization_helpers';
import { openTag, closeTag, preserveUnchangedMark } from '../serialization_helpers';
const generateStrikeTag = (wrapTagName = openTag) => {
return (_, mark) => {
if (mark.attrs.sourceMarkdown) return '~~';
if (mark.attrs.sourceTagName) return wrapTagName(mark.attrs.sourceTagName);
if (mark.attrs.htmlTag) return wrapTagName(mark.attrs.htmlTag);
const type = /^(~~|<del|<strike|<s).*/.exec(mark.attrs.sourceMarkdown)?.[1];
switch (type) {
case '~~':
return type;
case '<del': // eslint-disable-line @gitlab/require-i18n-strings
case '<strike': // eslint-disable-line @gitlab/require-i18n-strings
case '<s':
return wrapTagName(type.substring(1));
default:
return '~~';
}
return '~~';
};
};
const strike = {
const strike = preserveUnchangedMark({
open: generateStrikeTag(),
close: generateStrikeTag(closeTag),
mixable: true,
expelEnclosingWhitespace: true,
};
});
export default strike;

View File

@ -359,6 +359,16 @@ class Repository
@branch_names_include = nil
end
def expire_branch_names_cache
expire_method_caches(%i[branch_names branch_count])
@local_branches = nil
end
def expire_tag_names_cache
expire_method_caches(%i[tag_names tag_count])
@tags = nil
end
def expire_protected_branches_cache
ProtectedBranches::CacheService.new(project).refresh if project # rubocop:disable CodeReuse/ServiceClass
end

View File

@ -9,6 +9,7 @@ class PostReceive
sidekiq_options retry: 3
include Gitlab::Experiment::Dsl
include ::Gitlab::ExclusiveLeaseHelpers
feature_category :source_code_management
urgency :high
@ -18,6 +19,8 @@ class PostReceive
def perform(gl_repository, identifier, changes, push_options = {})
container, project, repo_type = Gitlab::GlRepository.parse(gl_repository)
@project = project
@gl_repository = gl_repository
if container.nil? || (container.is_a?(ProjectSnippet) && project.nil?)
log("Triggered hook for non-existing gl_repository \"#{gl_repository}\"")
@ -111,8 +114,49 @@ class PostReceive
# Expire the repository status, branch, and tag cache once per push.
def expire_caches(post_received, repository)
repository.expire_status_cache if repository.empty?
repository.expire_branches_cache if post_received.includes_branches?
repository.expire_caches_for_tags if post_received.includes_tags?
expire_branch_cache(repository) if post_received.includes_branches?
expire_tag_cache(repository) if post_received.includes_tags?
end
def expire_branch_cache(repository)
repository.expire_branches_cache
# Consider the scenario where multiple pushes happen in close succession:
#
# 1. Job 1 expires cache.
# 2. Job 1 starts computing branch list.
# 3. Job 2 starts.
# 4. Job 2 expires cache (no-op because nothing is there).
# 5. Job 1 finishes computing branch list, persists cache.
# 6. Job 2 reads from stale cache instead of loading a fresh branch list.
#
# To avoid this, atomically expire and refresh the branch name cache
# so that tasks such as pipeline creation will find the branch.
if Feature.enabled?(:post_receive_sync_refresh_cache, @project)
in_lock(lease_key(:branch), ttl: cache_ttl) do
repository.expire_branch_names_cache
repository.branch_names
end
end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
log("Failed to obtain lease for expiring branch name cache")
end
def expire_tag_cache(repository)
repository.expire_caches_for_tags
if Feature.enabled?(:post_receive_sync_refresh_cache, @project)
in_lock(lease_key(:tag), ttl: cache_ttl) do
repository.expire_tag_names_cache
repository.tag_names
end
end
rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
log("Failed to obtain lease for expiring tag name cache")
end
def lease_key(ref_type)
"post_receive:#{@gl_repository}:#{ref_type}"
end
# Schedule an update for the repository size and commit count if necessary.
@ -152,6 +196,10 @@ class PostReceive
def log(message)
Gitlab::GitLogger.error("POST-RECEIVE: #{message}")
end
def cache_ttl
::Gitlab::GitalyClient.fast_timeout * 2
end
end
PostReceive.prepend_mod_with('PostReceive')

View File

@ -0,0 +1,9 @@
---
name: post_receive_sync_refresh_cache
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/20891
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/160946
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/474741
milestone: '17.3'
group: group::pipeline execution
type: gitlab_com_derisk
default_enabled: false

View File

@ -1,11 +1,16 @@
ARG DOCKER_VERSION=24.0.5
ARG CHROME_VERSION=123
ARG RUBY_VERSION=3.2.4
ARG BUILD_OS=debian
ARG CHROME_VERSION=123
ARG DOCKER_VERSION=24.0.5
ARG GCLOUD_VERSION=413
ARG GIT_VERSION=2.45
ARG HELM_VERSION=3.14
ARG KUBECTL_VERSION=1.28
ARG LFS_VERSION=2.9
ARG OS_VERSION=bookworm
ARG QA_BUILD_TARGET=ee
ARG RUBY_VERSION=3.2.4
FROM registry.gitlab.com/gitlab-org/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS foss
FROM registry.gitlab.com/gitlab-org/gitlab-build-images/${BUILD_OS}-${OS_VERSION}-ruby-${RUBY_VERSION}:git-${GIT_VERSION}-lfs-${LFS_VERSION}-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-${GCLOUD_VERSION}-kubectl-${KUBECTL_VERSION}-helm-${HELM_VERSION} AS foss
LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
ENV DEBIAN_FRONTEND="noninteractive"

View File

@ -84,12 +84,17 @@ docker buildx build \
--cache-from="$QA_IMAGE_BRANCH" \
--cache-from="$QA_IMAGE_MASTER" \
--platform=${ARCH:-amd64} \
--build-arg=BUILD_OS="${BUILD_OS}" \
--build-arg=CHROME_VERSION="${CHROME_VERSION}" \
--build-arg=DOCKER_VERSION="${DOCKER_VERSION}" \
--build-arg=RUBY_VERSION="${RUBY_VERSION}" \
--build-arg=BUILD_OS="${BUILD_OS}" \
--build-arg=GCLOUD_VERSION="${GCLOUD_VERSION}" \
--build-arg=GIT_VERSION="${GIT_VERSION}" \
--build-arg=HELM_VERSION="${HELM_VERSION}" \
--build-arg=KUBECTL_VERSION="${KUBECTL_VERSION}" \
--build-arg=LFS_VERSION="${LFS_VERSION}" \
--build-arg=OS_VERSION="${OS_VERSION}" \
--build-arg=QA_BUILD_TARGET="${QA_BUILD_TARGET}" \
--build-arg=RUBY_VERSION="${RUBY_VERSION}" \
--file="${CI_PROJECT_DIR}/qa/Dockerfile" \
--push \
--provenance=false \

View File

@ -282,7 +282,14 @@ RSpec.describe '.gitlab/ci/rules.gitlab-ci.yml', feature_category: :tooling do
next unless name.end_with?('patterns')
# Ignore EE-only patterns list when in FOSS context
relevant_patterns = foss_context ? patterns.reject { |pattern| pattern =~ %r|^{?ee/| } : patterns
relevant_patterns = if foss_context
patterns.reject do |pattern|
pattern =~ %r|^{?ee/| || pattern == '.tool-versions'
end
else
patterns
end
next if relevant_patterns.empty?
next if foss_context && name == '.custom-roles-patterns'

View File

@ -1,3 +1,4 @@
import { uniqueId } from 'lodash';
import { builders } from 'prosemirror-test-builder';
import Sourcemap from '~/content_editor/extensions/sourcemap';
import MarkdownSerializer from '~/content_editor/services/markdown_serializer';
@ -11,7 +12,18 @@ const b = builders(tiptapEditor.schema);
export { b as builders };
export const serializeWithOptions = (options, ...content) =>
new MarkdownSerializer().serialize({ doc: b.doc(...content) }, options);
new MarkdownSerializer().serialize(
{ doc: b.doc(...content), pristineDoc: options.pristineDoc && b.doc(options.pristineDoc) },
options,
);
export const serialize = (...content) =>
new MarkdownSerializer().serialize({ doc: b.doc(...content) });
new MarkdownSerializer().serialize({ doc: b.doc(...content), pristineDoc: b.doc(...content) });
export const source = (sourceMarkdown, sourceTagName) => ({
sourceMarkdown,
sourceMapKey: uniqueId('key-'),
sourceTagName,
});
export const sourceTag = (sourceTagName) => ({ sourceTagName });

View File

@ -1,7 +1,49 @@
import { serialize, builders } from '../../serialization_utils';
import {
serialize,
builders,
source,
sourceTag,
serializeWithOptions,
} from '../../serialization_utils';
const { paragraph, bold } = builders;
it('correctly serializes bold', () => {
expect(serialize(paragraph(bold('bold')))).toBe('**bold**');
});
it.each`
boldStyle
${'**'}
${'__'}
`('correctly serializes bold with sourcemap including $boldStyle', ({ boldStyle }) => {
const sourceMarkdown = source(`${boldStyle}bolded\ncontent${boldStyle}`, 'strong');
expect(serialize(paragraph(bold(sourceMarkdown, 'bolded content')))).toBe(
`${boldStyle}bolded\ncontent${boldStyle}`,
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(bold(sourceMarkdown, 'bolded content')) },
paragraph(bold(sourceMarkdown, 'new content')),
),
).toBe(`${boldStyle}new content${boldStyle}`);
});
it.each`
htmlTag
${'strong'}
${'b'}
`('correctly preserves bold with a htmlTag $htmlTag', ({ htmlTag }) => {
expect(serialize(paragraph(bold(sourceTag(htmlTag), 'bolded content')))).toBe(
`<${htmlTag}>bolded content</${htmlTag}>`,
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(bold(sourceTag(htmlTag), 'bolded content')) },
paragraph(bold(sourceTag(htmlTag), 'new content')),
),
).toBe(`<${htmlTag}>new content</${htmlTag}>`);
});

View File

@ -1,4 +1,10 @@
import { serialize, builders } from '../../serialization_utils';
import {
serialize,
source,
serializeWithOptions,
builders,
sourceTag,
} from '../../serialization_utils';
const { paragraph, code, italic, bold, strike } = builders;
@ -23,3 +29,25 @@ it('correctly serializes inline code wrapped by italics and bold marks', () => {
expect(serialize(paragraph(strike(code(content))))).toBe(`~~\`${content}\`~~`);
expect(serialize(paragraph(code(strike(content))))).toBe(`~~\`${content}\`~~`);
});
it('correctly serializes code with sourcemap including `', () => {
const sourceMarkdown = source('`code content`', 'code');
expect(
serializeWithOptions(
{ pristineDoc: paragraph(code(sourceMarkdown, 'code content')) },
paragraph(code(sourceMarkdown, 'new content')),
),
).toBe(`\`new content\``);
});
it('correctly preserves code with a html tag <code>', () => {
expect(serialize(paragraph(code(sourceTag('code'), 'code')))).toBe(`<code>code</code>`);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(code(sourceTag('code'), 'code content')) },
paragraph(code(sourceTag('code'), 'new content')),
),
).toBe(`<code>new content</code>`);
});

View File

@ -1,4 +1,4 @@
import { serialize, builders } from '../../serialization_utils';
import { serialize, serializeWithOptions, builders, source } from '../../serialization_utils';
const { paragraph, inlineDiff } = builders;
@ -12,3 +12,21 @@ it('correctly serializes inline diff', () => {
),
).toBe('{++30 lines+}{--10 lines-}');
});
it.each`
type | start | end
${'addition'} | ${'{+'} | ${'+}'}
${'deletion'} | ${'{-'} | ${'-}'}
`('correctly serializes inline diff with sourcemap', ({ type, start, end }) => {
const sourceMarkdown = source(`${start}+30 lines${end}`);
const diffAttrs = { ...sourceMarkdown, type };
expect(serialize(paragraph(inlineDiff(diffAttrs, '+30 lines')))).toBe(`${start}+30 lines${end}`);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(inlineDiff(diffAttrs, '+30 lines')) },
paragraph(inlineDiff(diffAttrs, '+40 lines')),
),
).toBe(`${start}+40 lines${end}`);
});

View File

@ -1,7 +1,49 @@
import { serialize, builders } from '../../serialization_utils';
import {
serialize,
builders,
source,
sourceTag,
serializeWithOptions,
} from '../../serialization_utils';
const { paragraph, italic } = builders;
it('correctly serializes italics', () => {
expect(serialize(paragraph(italic('italics')))).toBe('_italics_');
});
it.each`
italicStyle
${'*'}
${'_'}
`('correctly serializes italic with sourcemap including $italicStyle', ({ italicStyle }) => {
const sourceMarkdown = source(`${italicStyle}italic\ncontent${italicStyle}`, 'em');
expect(serialize(paragraph(italic(sourceMarkdown, 'italic content')))).toBe(
`${italicStyle}italic\ncontent${italicStyle}`,
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(italic(sourceMarkdown, 'italic content')) },
paragraph(italic(sourceMarkdown, 'new content')),
),
).toBe(`${italicStyle}new content${italicStyle}`);
});
it.each`
htmlTag
${'em'}
${'i'}
`('correctly preserves italic with a htmlTag $htmlTag', ({ htmlTag }) => {
expect(serialize(paragraph(italic(sourceTag(htmlTag), 'italic content')))).toBe(
`<${htmlTag}>italic content</${htmlTag}>`,
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(italic(sourceTag(htmlTag), 'italic content')) },
paragraph(italic(sourceTag(htmlTag), 'new content')),
),
).toBe(`<${htmlTag}>new content</${htmlTag}>`);
});

View File

@ -1,4 +1,10 @@
import { serialize, builders } from '../../serialization_utils';
import {
serialize,
serializeWithOptions,
builders,
source,
sourceTag,
} from '../../serialization_utils';
const { paragraph, link } = builders;
@ -95,3 +101,42 @@ it.each`
).toBe(serialized);
},
);
it('correctly serializes links with sourcemap', () => {
const sourceMarkdown = source('[link\nwith\nwhitespace](https://link.url "title")');
const linkAttrs = {
...sourceMarkdown,
href: 'https://link.url',
title: 'title',
};
expect(serialize(paragraph(link(linkAttrs, 'link with whitespace')))).toBe(
'[link\nwith\nwhitespace](https://link.url "title")',
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(link(linkAttrs, 'link with whitespace')) },
paragraph(link(linkAttrs, 'new content')),
),
).toBe('[new content](https://link.url "title")');
});
it('correctly serializes links as an HTML tag', () => {
const linkAttrs = {
href: 'https://example.com',
title: 'example',
...sourceTag('a'),
};
expect(serialize(paragraph(link(linkAttrs, 'example url')))).toBe(
'<a href="https://example.com" title="example">example url</a>',
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(link(linkAttrs, 'example url')) },
paragraph(link(linkAttrs, 'new content')),
),
).toBe('<a href="https://example.com" title="example">new content</a>');
});

View File

@ -0,0 +1,7 @@
import { serialize, builders } from '../../serialization_utils';
const { paragraph, mathInline } = builders;
it('correctly serializes inline math', () => {
expect(serialize(paragraph(mathInline('a^2 + b^2')))).toBe('$`a^2 + b^2`$');
});

View File

@ -1,4 +1,10 @@
import { serialize, builders } from '../../serialization_utils';
import {
serialize,
serializeWithOptions,
builders,
source,
sourceTag,
} from '../../serialization_utils';
const { paragraph, strike } = builders;
@ -7,11 +13,36 @@ it('correctly serializes strikethrough', () => {
});
it.each`
strikeTag
${'s'}
${'strike'}
`('correctly serializes strikethrough with "$strikeTag" tag', ({ strikeTag }) => {
expect(serialize(paragraph(strike({ htmlTag: strikeTag }, 'deleted content')))).toBe(
`<${strikeTag}>deleted content</${strikeTag}>`,
attrs | tagName
${sourceTag('s')} | ${'s'}
${sourceTag('strike')} | ${'strike'}
${sourceTag('del')} | ${'del'}
${{ htmlTag: 's' }} | ${'s'}
${{ htmlTag: 'strike' }} | ${'strike'}
`('correctly serializes strikethrough with a attrs $attrs', ({ attrs, tagName }) => {
expect(serialize(paragraph(strike(attrs, 'deleted content')))).toBe(
`<${tagName}>deleted content</${tagName}>`,
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(strike(attrs, 'deleted content')) },
paragraph(strike(attrs, 'new content')),
),
).toBe(`<${tagName}>new content</${tagName}>`);
});
it('correctly serializes strikethrough with sourcemap', () => {
const sourceMarkdown = source('~~deleted\ncontent~~');
expect(serialize(paragraph(strike(sourceMarkdown, 'deleted content')))).toBe(
'~~deleted\ncontent~~',
);
expect(
serializeWithOptions(
{ pristineDoc: paragraph(strike(sourceMarkdown, 'deleted content')) },
paragraph(strike(sourceMarkdown, 'new content')),
),
).toBe('~~new content~~');
});

View File

@ -1,5 +1,6 @@
import { DATE_TIME_FORMATS, localeDateFormat } from '~/lib/utils/datetime/locale_dateformat';
import { setLanguage } from 'jest/__helpers__/locale_helper';
// eslint-disable-next-line no-restricted-imports
import * as localeFns from '~/locale';
describe('localeDateFormat (en-US)', () => {

View File

@ -1,6 +1,5 @@
// Copied to ee/spec/frontend/notes/mock_data.js
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { __ } from '~/locale';
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-foss/issues/26/discussions.json',
@ -1300,15 +1299,15 @@ export const draftDiffDiscussion = {
export const notesFilters = [
{
title: __('Show all activity'),
title: 'Show all activity',
value: 0,
},
{
title: __('Show comments only'),
title: 'Show comments only',
value: 1,
},
{
title: __('Show history only'),
title: 'Show history only',
value: 2,
},
];

View File

@ -5,7 +5,6 @@ import {
SAST_IAC_SHORT_NAME,
PRE_RECEIVE_SECRET_DETECTION,
} from '~/security_configuration/constants';
import { __, s__ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
import {
@ -23,36 +22,31 @@ export const testTrainingUrls = [
'https://www.vendornamethree.com/url',
];
const SAST_DESCRIPTION = __('Analyze your source code for known vulnerabilities.');
const SAST_DESCRIPTION = 'Analyze your source code for known vulnerabilities.';
const SAST_HELP_PATH = helpPagePath('user/application_security/sast/index');
const SAST_CONFIG_HELP_PATH = helpPagePath('user/application_security/sast/index', {
anchor: 'configuration',
});
const BAS_BADGE_TEXT = s__('SecurityConfiguration|Incubating feature');
const BAS_BADGE_TOOLTIP = s__(
'SecurityConfiguration|Breach and Attack Simulation is an incubating feature extending existing security testing by simulating adversary activity.',
);
const BAS_DESCRIPTION = s__(
'SecurityConfiguration|Simulate breach and attack scenarios against your running application by attempting to detect and exploit known vulnerabilities.',
);
const BAS_BADGE_TEXT = 'Incubating feature';
const BAS_BADGE_TOOLTIP =
'Breach and Attack Simulation is an incubating feature extending existing security testing by simulating adversary activity.';
const BAS_DESCRIPTION =
'Simulate breach and attack scenarios against your running application by attempting to detect and exploit known vulnerabilities.';
const BAS_HELP_PATH = helpPagePath('user/application_security/breach_and_attack_simulation/index');
const BAS_NAME = s__('SecurityConfiguration|Breach and Attack Simulation (BAS)');
const BAS_SHORT_NAME = s__('SecurityConfiguration|BAS');
const BAS_DAST_FEATURE_FLAG_DESCRIPTION = s__(
'SecurityConfiguration|Enable incubating Breach and Attack Simulation focused features such as callback attacks in your DAST scans.',
);
const BAS_NAME = 'Breach and Attack Simulation (BAS)';
const BAS_SHORT_NAME = 'BAS';
const BAS_DAST_FEATURE_FLAG_DESCRIPTION =
'Enable incubating Breach and Attack Simulation focused features such as callback attacks in your DAST scans.';
const BAS_DAST_FEATURE_FLAG_HELP_PATH = helpPagePath(
'user/application_security/breach_and_attack_simulation/index',
{ anchor: 'extend-dynamic-application-security-testing-dast' },
);
const BAS_DAST_FEATURE_FLAG_NAME = s__(
'SecurityConfiguration|Out-of-Band Application Security Testing (OAST)',
);
const BAS_DAST_FEATURE_FLAG_NAME =
'SecurityConfiguration|Out-of-Band Application Security Testing (OAST)';
const SAST_IAC_DESCRIPTION = __(
'Analyze your infrastructure as code configuration files for known vulnerabilities.',
);
const SAST_IAC_DESCRIPTION =
'Analyze your infrastructure as code configuration files for known vulnerabilities.';
const SAST_IAC_HELP_PATH = helpPagePath('user/application_security/iac_scanning/index');
const SAST_IAC_CONFIG_HELP_PATH = helpPagePath('user/application_security/iac_scanning/index', {
anchor: 'configuration',

View File

@ -1,5 +1,3 @@
import { s__ } from '~/locale';
export const getIssueCrmContactsQueryResponse = {
data: {
issue: {
@ -72,42 +70,42 @@ export const issueCrmContactsUpdateResponse = {
};
export const mockSuggestedColors = {
'#009966': s__('SuggestedColors|Green-cyan'),
'#8fbc8f': s__('SuggestedColors|Dark sea green'),
'#3cb371': s__('SuggestedColors|Medium sea green'),
'#00b140': s__('SuggestedColors|Green screen'),
'#013220': s__('SuggestedColors|Dark green'),
'#6699cc': s__('SuggestedColors|Blue-gray'),
'#0000ff': s__('SuggestedColors|Blue'),
'#e6e6fa': s__('SuggestedColors|Lavender'),
'#9400d3': s__('SuggestedColors|Dark violet'),
'#330066': s__('SuggestedColors|Deep violet'),
'#808080': s__('SuggestedColors|Gray'),
'#36454f': s__('SuggestedColors|Charcoal grey'),
'#f7e7ce': s__('SuggestedColors|Champagne'),
'#c21e56': s__('SuggestedColors|Rose red'),
'#cc338b': s__('SuggestedColors|Magenta-pink'),
'#dc143c': s__('SuggestedColors|Crimson'),
'#ff0000': s__('SuggestedColors|Red'),
'#cd5b45': s__('SuggestedColors|Dark coral'),
'#eee600': s__('SuggestedColors|Titanium yellow'),
'#ed9121': s__('SuggestedColors|Carrot orange'),
'#c39953': s__('SuggestedColors|Aztec Gold'),
'#009966': 'Green-cyan',
'#8fbc8f': 'Dark sea green',
'#3cb371': 'Medium sea green',
'#00b140': 'Green screen',
'#013220': 'Dark green',
'#6699cc': 'Blue-gray',
'#0000ff': 'Blue',
'#e6e6fa': 'Lavender',
'#9400d3': 'Dark violet',
'#330066': 'Deep violet',
'#808080': 'Gray',
'#36454f': 'Charcoal grey',
'#f7e7ce': 'Champagne',
'#c21e56': 'Rose red',
'#cc338b': 'Magenta-pink',
'#dc143c': 'Crimson',
'#ff0000': 'Red',
'#cd5b45': 'Dark coral',
'#eee600': 'Titanium yellow',
'#ed9121': 'Carrot orange',
'#c39953': 'Aztec Gold',
};
export const mockSuggestedEpicColors = [
{ '#E9BE74': s__('WorkItem|Apricot') },
{ '#D99530': s__('WorkItem|Copper') },
{ '#C17D10': s__('WorkItem|Rust') },
{ '#F57F6C': s__('WorkItem|Pink') },
{ '#EC5941': s__('WorkItem|Vermilion') },
{ '#DD2B0E': s__('WorkItem|Red') },
{ '#C91C00': s__('WorkItem|Dark red') },
{ '#52B87A': s__('WorkItem|Teal') },
{ '#2DA160': s__('WorkItem|Green') },
{ '#108548': s__('WorkItem|Forest green') },
{ '#63A6E9': s__('WorkItem|Sky blue') },
{ '#428FDC': s__('WorkItem|Royal blue') },
{ '#1F75CB': s__('WorkItem|Blue') },
{ '#1068BF': s__('WorkItem|Midnight blue') },
{ '#E9BE74': 'Apricot' },
{ '#D99530': 'Copper' },
{ '#C17D10': 'Rust' },
{ '#F57F6C': 'Pink' },
{ '#EC5941': 'Vermilion' },
{ '#DD2B0E': 'Red' },
{ '#C91C00': 'Dark red' },
{ '#52B87A': 'Teal' },
{ '#2DA160': 'Green' },
{ '#108548': 'Forest green' },
{ '#63A6E9': 'Sky blue' },
{ '#428FDC': 'Royal blue' },
{ '#1F75CB': 'Blue' },
{ '#1068BF': 'Midnight blue' },
];

View File

@ -2699,6 +2699,16 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
end
describe '#expire_branch_names_cache' do
it 'expires the cache' do
expect(repository).to receive(:expire_method_caches)
.with(%i[branch_names branch_count])
.and_call_original
repository.expire_branch_names_cache
end
end
describe '#expire_protected_branches_cache' do
it 'expires the cache' do
expect_next_instance_of(ProtectedBranches::CacheService) do |cache_service|
@ -2729,6 +2739,16 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
end
describe '#expire_tag_names_cache' do
it 'expires the cache' do
expect(repository).to receive(:expire_method_caches)
.with(%i[tag_names tag_count])
.and_call_original
repository.expire_tag_names_cache
end
end
describe '#add_tag' do
let(:user) { build_stubbed(:user) }

View File

@ -148,7 +148,7 @@ RSpec.configure do |config|
metadata[:type] = :feature
end
config.define_derived_metadata(file_path: %r{spec/dot_gitlab_ci/}) do |metadata|
config.define_derived_metadata(file_path: %r{spec/dot_gitlab_ci/job_dependency_spec.rb}) do |metadata|
metadata[:ci_config_validation] = true
end

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe PostReceive, feature_category: :source_code_management do
RSpec.describe PostReceive, :clean_gitlab_redis_shared_state, feature_category: :source_code_management do
include AfterNextHelpers
let(:changes) do
@ -448,6 +448,37 @@ RSpec.describe PostReceive, feature_category: :source_code_management do
perform
end
context 'with post_receive_sync_refresh_cache feature flag enabled' do
it 'refreshes branch names cache' do
expect(snippet.repository).to receive(:expire_branch_names_cache).and_call_original
expect(snippet.repository).to receive(:branch_names).and_call_original
perform
end
context 'when exclusive lease fails' do
it 'logs a message' do
expect(snippet.repository).to receive(:branch_names).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: Failed to obtain lease for expiring branch name cache")
perform
end
end
end
context 'with post_receive_sync_refresh_cache feature flag disabled' do
before do
stub_feature_flags(post_receive_sync_refresh_cache: false)
end
it 'does not expire branch names cache' do
expect(snippet.repository).not_to receive(:expire_tag_names_cache)
expect(snippet.repository).not_to receive(:tag_names)
perform
end
end
end
context 'tags' do
@ -472,6 +503,37 @@ RSpec.describe PostReceive, feature_category: :source_code_management do
perform
end
end
context 'with post_receive_sync_refresh_cache feature flag enabled' do
it 'refreshes the tag names cache' do
expect(snippet.repository).to receive(:expire_tag_names_cache).and_call_original
expect(snippet.repository).to receive(:tag_names).and_call_original
perform
end
context 'when exclusive lease fails' do
it 'logs a message' do
expect(snippet.repository).to receive(:tag_names).and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: Failed to obtain lease for expiring tag name cache")
perform
end
end
end
context 'with post_receive_sync_refresh_cache feature flag disabled' do
before do
stub_feature_flags(post_receive_sync_refresh_cache: false)
end
it 'does not expire tag names cache' do
expect(snippet.repository).not_to receive(:expire_tag_names_cache)
expect(snippet.repository).not_to receive(:tag_names)
perform
end
end
end
it_behaves_like 'an idempotent worker'

View File

@ -37,6 +37,10 @@ mapping:
- source: 'rubocop/(?<rest>.+)\.rb'
test: 'spec/rubocop/%{rest}_spec.rb'
# .gitlab/ci related specs
- source: '.gitlab/ci/(?<rest>.+)\.gitlab-ci\.yml'
test: 'spec/dot_gitlab_ci/rules_spec.rb'
# Map config to respective specs
- source: 'config/(?<rest>.+)\..*'
test: 'spec/config/%{rest}_spec.rb'