Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
03cd4f8da4
commit
b267f3a3ac
117 changed files with 1911 additions and 323 deletions
|
@ -32,7 +32,6 @@ Rails/SaveBang:
|
|||
- 'ee/spec/models/approval_project_rule_spec.rb'
|
||||
- 'ee/spec/models/burndown_spec.rb'
|
||||
- 'ee/spec/models/elasticsearch_indexed_namespace_spec.rb'
|
||||
- 'ee/spec/models/epic_spec.rb'
|
||||
- 'ee/spec/models/gitlab_subscription_spec.rb'
|
||||
- 'ee/spec/models/issue_spec.rb'
|
||||
- 'ee/spec/models/label_note_spec.rb'
|
||||
|
|
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -2,6 +2,24 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 14.4.1 (2021-10-28)
|
||||
|
||||
### Security (13 changes)
|
||||
|
||||
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@cef762a270783780112c7bf318e353a39de1aa1e) ([merge request](gitlab-org/security/gitlab!1937))
|
||||
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@9a891cbe465a302f260f0f81fc490cacb9e8c70e) ([merge request](gitlab-org/security/gitlab!1929))
|
||||
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@bdf8b6e90d0a1f719c0f389f29ea5dc41c22f119) ([merge request](gitlab-org/security/gitlab!1916))
|
||||
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@324fe6286b266c3990676bc93b3f6ab03eea5f6b) ([merge request](gitlab-org/security/gitlab!1927))
|
||||
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@4e2c4d2a88acf7167e1078e8a27679545ab90c9c) ([merge request](gitlab-org/security/gitlab!1922))
|
||||
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@34ffcb55a70ad6db38292f79fe73c05fb2655738) ([merge request](gitlab-org/security/gitlab!1919))
|
||||
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@0aee710db4bbab84c78b9e38f459bfca606aaf80) ([merge request](gitlab-org/security/gitlab!1913))
|
||||
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@de405edc9de4519656675ed6825534aac6b738da) ([merge request](gitlab-org/security/gitlab!1911))
|
||||
- [Do not display the root password by default](gitlab-org/security/gitlab@138a62f89ce6616d63e3cf18eeda291a380b9ebc) ([merge request](gitlab-org/security/gitlab!1909))
|
||||
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@43d19f580543d0203b1d841f921536474ca4be38) ([merge request](gitlab-org/security/gitlab!1906)) **GitLab Enterprise Edition**
|
||||
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@f96258f3622cf72b46158f22c4660ff60a2c25ae) ([merge request](gitlab-org/security/gitlab!1903))
|
||||
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@b93683df51ce85f909d5072ec2a0e7756d64038e) ([merge request](gitlab-org/security/gitlab!1899))
|
||||
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@874aa74a23fc3c44f390500bc8379c30ebc51452) ([merge request](gitlab-org/security/gitlab!1872))
|
||||
|
||||
## 14.4.0 (2021-10-21)
|
||||
|
||||
### Added (79 changes)
|
||||
|
@ -391,6 +409,24 @@ entry.
|
|||
- [Cleanup bigint conversion for ci_builds](gitlab-org/gitlab@176992aa2b2e76b22637a07d5bafbd6541324a7d) ([merge request](gitlab-org/gitlab!70351))
|
||||
- [Drop support for data-track-event](gitlab-org/gitlab@ac6027fbef6adf41643412a84945fda6f15c9666) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70234))
|
||||
|
||||
## 14.3.4 (2021-10-28)
|
||||
|
||||
### Security (13 changes)
|
||||
|
||||
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@0b9bcafa73bc12ad873f75584b993f7b94f1f2e7) ([merge request](gitlab-org/security/gitlab!1938))
|
||||
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@6599afd4d7357ab356fcb773af19f8388978b3ed) ([merge request](gitlab-org/security/gitlab!1930))
|
||||
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@faad71f44a1b1048b73897d450c923a18ec18c0b) ([merge request](gitlab-org/security/gitlab!1917))
|
||||
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@293931500c84ef7ea9a2117d3ddf094f8ac15dcf) ([merge request](gitlab-org/security/gitlab!1923))
|
||||
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@5027cb2b0303645a921b95d324d3d55dcf7632e4) ([merge request](gitlab-org/security/gitlab!1920))
|
||||
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@c18c2ddfa34a4c3e476136ab3eba9be7f265ad59) ([merge request](gitlab-org/security/gitlab!1914))
|
||||
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@3d6664461da720fb256d8e139961b383e33a3b90) ([merge request](gitlab-org/security/gitlab!1907)) **GitLab Enterprise Edition**
|
||||
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@124ca62c02bfa8ef6f7de7b328f80756fd01c052) ([merge request](gitlab-org/security/gitlab!1904))
|
||||
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@8e6ffd52f50170a5cf2761e50a3d6efaca5fe64f) ([merge request](gitlab-org/security/gitlab!1900))
|
||||
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@6f2a2b2240eb7590bbc773f35d3927d4854a31b5) ([merge request](gitlab-org/security/gitlab!1894))
|
||||
- [Do not display the root password by default](gitlab-org/security/gitlab@87893548183fc4a111e12c0bdb3e409175a41668) ([merge request](gitlab-org/security/gitlab!1803))
|
||||
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@0e77e1cd938f876f3e9c049a84486c8c90cd0f3f) ([merge request](gitlab-org/security/gitlab!1879))
|
||||
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@1362f7481aad5e4295da11f0db53e31600c7c7b5) ([merge request](gitlab-org/security/gitlab!1866))
|
||||
|
||||
## 14.3.3 (2021-10-12)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
@ -939,6 +975,24 @@ entry.
|
|||
- [Remove the FF ci_reset_bridge_with_subsequent_jobs](gitlab-org/gitlab@a4a75095b9b0250d0b1bdadea90c8a4cd24449b2) ([merge request](gitlab-org/gitlab!68295))
|
||||
- [Removes ci_same_stage_job_needs ff](gitlab-org/gitlab@5e509cf7aa90041a541b19dda563120a359f0bf9) ([merge request](gitlab-org/gitlab!68041))
|
||||
|
||||
## 14.2.6 (2021-10-28)
|
||||
|
||||
### Security (13 changes)
|
||||
|
||||
- [Highlight usage of unicode bidi characters](gitlab-org/security/gitlab@18a768bb3cd19b6dc780bb85d91a93605ec8aa4f) ([merge request](gitlab-org/security/gitlab!1939))
|
||||
- [Fix dompurify.js to prevent path traversal attacks](gitlab-org/security/gitlab@cfd7c715162c22060b9b80268ef501a9e604421a) ([merge request](gitlab-org/security/gitlab!1931))
|
||||
- [Refresh authorizations on transfer of groups having project shares](gitlab-org/security/gitlab@3fc08eb869156a090b015e78da79c8ced16a7162) ([merge request](gitlab-org/security/gitlab!1918))
|
||||
- [Do not allow Applications API to create apps with blank scopes](gitlab-org/security/gitlab@c4ffc8c0ee5356bcb9b76dbfa92517589b4225a8) ([merge request](gitlab-org/security/gitlab!1924))
|
||||
- [Don't allow author to resolve discussions when MR is locked via GraphQL](gitlab-org/security/gitlab@fe2d0b6f250b60619da97f162c93c9e645daf4af) ([merge request](gitlab-org/security/gitlab!1921))
|
||||
- [Workhorse: Allow uploading only a single file](gitlab-org/security/gitlab@89b04599592b7dfc0e4883cfde5d3ecd9ea855b2) ([merge request](gitlab-org/security/gitlab!1915))
|
||||
- [Group owners should see SCIM token only once](gitlab-org/security/gitlab@d52c1e41f38039db075a7a3418b8eb9ed8474c2a) ([merge request](gitlab-org/security/gitlab!1908)) **GitLab Enterprise Edition**
|
||||
- [Respect visibility level settings when updating project via API](gitlab-org/security/gitlab@3051d6a00d1a56133a77ecd24313bafb4565d576) ([merge request](gitlab-org/security/gitlab!1905))
|
||||
- [Avoid decoding the whole tiff image on isTIFF check](gitlab-org/security/gitlab@bab7f45def8fc81fe4b0961a21b4c90a60358ff9) ([merge request](gitlab-org/security/gitlab!1901))
|
||||
- [Adding a '[redacted]' to mask private email addresses](gitlab-org/security/gitlab@8eb9749f40b87b9b49b034bceb263219a4d3b114) ([merge request](gitlab-org/security/gitlab!1895))
|
||||
- [Do not display the root password by default](gitlab-org/security/gitlab@4ccf08b6645b9f616657edd266d9d31e3602d170) ([merge request](gitlab-org/security/gitlab!1802))
|
||||
- [Set PipelineSchedules to inactive](gitlab-org/security/gitlab@ebee16945325d22ceb5c07b7ba48df6fd0b2f067) ([merge request](gitlab-org/security/gitlab!1878))
|
||||
- [Remove external_webhook_token from exported project](gitlab-org/security/gitlab@f3ef12185902f3ed5c9d62ffce07418fd704a753) ([merge request](gitlab-org/security/gitlab!1865))
|
||||
|
||||
## 14.2.5 (2021-09-30)
|
||||
|
||||
### Security (28 changes)
|
||||
|
|
|
@ -1 +1 @@
|
|||
bf9abd731ba2dab65e8c275c51d578d95dd3d506
|
||||
80d41b90597e1ca027cc6f02a09a6d3607d75ba2
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { sanitize as dompurifySanitize, addHook } from 'dompurify';
|
||||
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
|
||||
import { getNormalizedURL, getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
|
||||
|
||||
const defaultConfig = {
|
||||
// Safely allow SVG <use> tags
|
||||
|
@ -11,12 +11,14 @@ const defaultConfig = {
|
|||
|
||||
// Only icons urls from `gon` are allowed
|
||||
const getAllowedIconUrls = (gon = window.gon) =>
|
||||
[gon.sprite_file_icons, gon.sprite_icons].filter(Boolean);
|
||||
[gon.sprite_file_icons, gon.sprite_icons]
|
||||
.filter(Boolean)
|
||||
.map((path) => relativePathToAbsolute(path, getBaseURL()));
|
||||
|
||||
const isUrlAllowed = (url) => getAllowedIconUrls().some((allowedUrl) => url.startsWith(allowedUrl));
|
||||
const isUrlAllowed = (url) =>
|
||||
getAllowedIconUrls().some((allowedUrl) => getNormalizedURL(url).startsWith(allowedUrl));
|
||||
|
||||
const isHrefSafe = (url) =>
|
||||
isUrlAllowed(url) || isUrlAllowed(relativePathToAbsolute(url, getBaseURL())) || url.match(/^#/);
|
||||
const isHrefSafe = (url) => url.match(/^#/) || isUrlAllowed(url);
|
||||
|
||||
const removeUnsafeHref = (node, attr) => {
|
||||
if (!node.hasAttribute(attr)) {
|
||||
|
@ -36,13 +38,14 @@ const removeUnsafeHref = (node, attr) => {
|
|||
* <use href="/assets/icons-xxx.svg#icon_name"></use>
|
||||
* </svg>
|
||||
*
|
||||
* It validates both href & xlink:href attributes.
|
||||
* Note that `xlink:href` is deprecated, but still in use
|
||||
* https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
|
||||
*
|
||||
* @param {Object} node - Node to sanitize
|
||||
*/
|
||||
const sanitizeSvgIcon = (node) => {
|
||||
removeUnsafeHref(node, 'href');
|
||||
|
||||
// Note: `xlink:href` is deprecated, but still in use
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
|
||||
removeUnsafeHref(node, 'xlink:href');
|
||||
};
|
||||
|
||||
|
|
|
@ -399,6 +399,24 @@ export function isSafeURL(url) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized url
|
||||
*
|
||||
* https://gitlab.com/foo/../baz => https://gitlab.com/baz
|
||||
*
|
||||
* @param {String} url - URL to be transformed
|
||||
* @param {String?} baseUrl - current base URL
|
||||
* @returns {String}
|
||||
*/
|
||||
export const getNormalizedURL = (url, baseUrl) => {
|
||||
const base = baseUrl || getBaseURL();
|
||||
try {
|
||||
return new URL(url, base).href;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export function getWebSocketProtocol() {
|
||||
return window.location.protocol.replace('http', 'ws');
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<script>
|
||||
import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
import {
|
||||
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
|
||||
DEPENDENCY_PROXY_DOCS_PATH,
|
||||
} from '~/packages_and_registries/settings/group/constants';
|
||||
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
|
||||
|
||||
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
|
||||
|
||||
|
@ -22,11 +23,16 @@ export default {
|
|||
},
|
||||
inject: ['groupPath', 'dependencyProxyAvailable'],
|
||||
i18n: {
|
||||
proxyNotAvailableText: __('Dependency Proxy feature is limited to public groups for now.'),
|
||||
proxyDisabledText: __('Dependency Proxy disabled. To enable it, contact the group owner.'),
|
||||
proxyImagePrefix: __('Dependency Proxy image prefix'),
|
||||
copyImagePrefixText: __('Copy prefix'),
|
||||
blobCountAndSize: __('Contains %{count} blobs of images (%{size})'),
|
||||
proxyNotAvailableText: s__(
|
||||
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
|
||||
),
|
||||
proxyDisabledText: s__(
|
||||
'DependencyProxy|Dependency Proxy disabled. To enable it, contact the group owner.',
|
||||
),
|
||||
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
|
||||
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
|
||||
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
|
||||
pageTitle: s__('DependencyProxy|Dependency Proxy'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -40,7 +46,7 @@ export default {
|
|||
return !this.dependencyProxyAvailable;
|
||||
},
|
||||
variables() {
|
||||
return { fullPath: this.groupPath };
|
||||
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -62,7 +68,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<title-area :title="__('Dependency Proxy')" :info-messages="infoMessages" />
|
||||
<title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages" />
|
||||
<gl-alert
|
||||
v-if="!dependencyProxyAvailable"
|
||||
:dismissible="false"
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<script>
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'ManifestRow',
|
||||
components: {
|
||||
GlSprintf,
|
||||
ListItem,
|
||||
TimeagoTooltip,
|
||||
},
|
||||
props: {
|
||||
manifest: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
name() {
|
||||
return this.manifest?.imageName.split(':')[0];
|
||||
},
|
||||
version() {
|
||||
return this.manifest?.imageName.split(':')[1];
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
cachedAgoMessage: s__('DependencyProxy|Cached %{time}'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<list-item>
|
||||
<template #left-primary> {{ name }} </template>
|
||||
<template #left-secondary> {{ version }} </template>
|
||||
<template #right-primary> </template>
|
||||
<template #right-secondary>
|
||||
<timeago-tooltip :time="manifest.createdAt" data-testid="cached-message">
|
||||
<template #default="{ timeAgo }">
|
||||
<gl-sprintf :message="$options.i18n.cachedAgoMessage">
|
||||
<template #time>{{ timeAgo }}</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
</timeago-tooltip>
|
||||
</template>
|
||||
</list-item>
|
||||
</template>
|
|
@ -0,0 +1,46 @@
|
|||
<script>
|
||||
import { GlKeysetPagination } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
|
||||
|
||||
export default {
|
||||
name: 'ManifestsLists',
|
||||
components: {
|
||||
ManifestRow,
|
||||
GlKeysetPagination,
|
||||
},
|
||||
props: {
|
||||
manifests: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
pagination: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
listTitle: s__('DependencyProxy|Manifest list'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-mt-5">
|
||||
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
|
||||
<div
|
||||
class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
|
||||
>
|
||||
<manifest-row v-for="(manifest, index) in manifests" :key="index" :manifest="manifest" />
|
||||
</div>
|
||||
<div class="gl-display-flex gl-justify-content-center">
|
||||
<gl-keyset-pagination
|
||||
v-bind="pagination"
|
||||
class="gl-mt-3"
|
||||
@prev="$emit('prev-page')"
|
||||
@next="$emit('next-page')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1 @@
|
|||
export const GRAPHQL_PAGE_SIZE = 20;
|
|
@ -1,4 +1,12 @@
|
|||
query getDependencyProxyDetails($fullPath: ID!) {
|
||||
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
|
||||
|
||||
query getDependencyProxyDetails(
|
||||
$fullPath: ID!
|
||||
$first: Int
|
||||
$last: Int
|
||||
$after: String
|
||||
$before: String
|
||||
) {
|
||||
group(fullPath: $fullPath) {
|
||||
dependencyProxyBlobCount
|
||||
dependencyProxyTotalSize
|
||||
|
@ -6,5 +14,14 @@ query getDependencyProxyDetails($fullPath: ID!) {
|
|||
dependencyProxySetting {
|
||||
enabled
|
||||
}
|
||||
dependencyProxyManifests(after: $after, before: $before, first: $first, last: $last) {
|
||||
nodes {
|
||||
createdAt
|
||||
imageName
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
|
@ -29,10 +24,14 @@ export default {
|
|||
<div class="project-feature-row">
|
||||
<label v-if="label" class="label-bold">
|
||||
{{ label }}
|
||||
<a v-if="helpPath" :href="helpPath" target="_blank">
|
||||
<gl-icon name="question-o" />
|
||||
</a>
|
||||
</label>
|
||||
<span v-if="helpText" class="form-text text-muted"> {{ helpText }} </span> <slot></slot>
|
||||
<div>
|
||||
<span v-if="helpText" class="text-muted"> {{ helpText }} </span>
|
||||
<span v-if="helpPath"
|
||||
><a :href="helpPath" target="_blank">{{ __('Learn more') }}</a
|
||||
>.</span
|
||||
>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -37,6 +37,10 @@ export default {
|
|||
securityAndComplianceLabel: s__('ProjectSettings|Security & Compliance'),
|
||||
snippetsLabel: s__('ProjectSettings|Snippets'),
|
||||
wikiLabel: s__('ProjectSettings|Wiki'),
|
||||
pucWarningLabel: s__('ProjectSettings|Warn about Potentially Unwanted Characters'),
|
||||
pucWarningHelpText: s__(
|
||||
'ProjectSettings|Highlight the usage of hidden unicode characters. These have innocent uses for right-to-left languages, but can also be used in potential exploits.',
|
||||
),
|
||||
},
|
||||
|
||||
components: {
|
||||
|
@ -178,6 +182,7 @@ export default {
|
|||
securityAndComplianceAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
|
||||
operationsAccessLevel: featureAccessLevel.EVERYONE,
|
||||
containerRegistryAccessLevel: featureAccessLevel.EVERYONE,
|
||||
warnAboutPotentiallyUnwantedCharacters: true,
|
||||
lfsEnabled: true,
|
||||
requestAccessEnabled: true,
|
||||
highlightChangesClass: false,
|
||||
|
@ -395,6 +400,9 @@ export default {
|
|||
ref="project-visibility-settings"
|
||||
:help-path="visibilityHelpPath"
|
||||
:label="s__('ProjectSettings|Project visibility')"
|
||||
:help-text="
|
||||
s__('ProjectSettings|Manage who can see the project in the public access directory.')
|
||||
"
|
||||
>
|
||||
<div class="project-feature-controls gl-display-flex gl-align-items-center gl-my-3 gl-mx-0">
|
||||
<div class="select-wrapper gl-flex-grow-1">
|
||||
|
@ -752,5 +760,19 @@ export default {
|
|||
}}</template>
|
||||
</gl-form-checkbox>
|
||||
</project-setting-row>
|
||||
<project-setting-row class="gl-mb-5">
|
||||
<input
|
||||
:value="warnAboutPotentiallyUnwantedCharacters"
|
||||
type="hidden"
|
||||
name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"
|
||||
/>
|
||||
<gl-form-checkbox
|
||||
v-model="warnAboutPotentiallyUnwantedCharacters"
|
||||
name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"
|
||||
>
|
||||
{{ $options.i18n.pucWarningLabel }}
|
||||
<template #help>{{ $options.i18n.pucWarningHelpText }}</template>
|
||||
</gl-form-checkbox>
|
||||
</project-setting-row>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -66,7 +66,13 @@ export default {
|
|||
data-qa-selector="clone_button"
|
||||
/>
|
||||
</div>
|
||||
<snippet-blob v-for="blob in blobs" :key="blob.path" :snippet="snippet" :blob="blob" />
|
||||
<snippet-blob
|
||||
v-for="blob in blobs"
|
||||
:key="blob.path"
|
||||
:snippet="snippet"
|
||||
:blob="blob"
|
||||
class="project-highlight-puc"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -85,3 +85,9 @@
|
|||
td.line-numbers {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.project-highlight-puc .unicode-bidi::before {
|
||||
content: '<EFBFBD>';
|
||||
cursor: pointer;
|
||||
text-decoration: underline wavy $red-500;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,6 @@ class JiraConnect::ApplicationController < ApplicationController
|
|||
end
|
||||
|
||||
def signed_install_active?
|
||||
Feature.enabled?(:jira_connect_asymmetric_jwt)
|
||||
Feature.enabled?(:jira_connect_asymmetric_jwt, default_enabled: :yaml)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -105,8 +105,7 @@ class Projects::BranchesController < Projects::ApplicationController
|
|||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def destroy
|
||||
@branch_name = Addressable::URI.unescape(params[:id])
|
||||
result = ::Branches::DeleteService.new(project, current_user).execute(@branch_name)
|
||||
result = ::Branches::DeleteService.new(project, current_user).execute(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
@ -409,6 +409,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
show_default_award_emojis
|
||||
squash_option
|
||||
mr_default_target_self
|
||||
warn_about_potentially_unwanted_characters
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ module Mutations
|
|||
argument :severity, Types::IssuableSeverityEnum, required: true,
|
||||
description: 'Set the incident severity level.'
|
||||
|
||||
authorize :admin_issue
|
||||
|
||||
def resolve(project_path:, iid:, severity:)
|
||||
issue = authorized_find!(project_path: project_path, iid: iid)
|
||||
project = issue.project
|
||||
|
|
|
@ -10,7 +10,14 @@ module LearnGitlabHelper
|
|||
def onboarding_actions_data(project)
|
||||
attributes = onboarding_progress(project).attributes.symbolize_keys
|
||||
|
||||
action_urls.to_h do |action, url|
|
||||
urls_to_use = nil
|
||||
|
||||
experiment(:change_continuous_onboarding_link_urls) do |e|
|
||||
e.use { urls_to_use = action_urls }
|
||||
e.try { urls_to_use = new_action_urls(project) }
|
||||
end
|
||||
|
||||
urls_to_use.to_h do |action, url|
|
||||
[
|
||||
action,
|
||||
url: url,
|
||||
|
@ -46,6 +53,17 @@ module LearnGitlabHelper
|
|||
.merge(LearnGitlab::Onboarding::ACTION_DOC_URLS)
|
||||
end
|
||||
|
||||
def new_action_urls(project)
|
||||
action_urls.merge(
|
||||
issue_created: project_issues_path(project),
|
||||
git_write: project_path(project),
|
||||
pipeline_created: project_pipelines_path(project),
|
||||
merge_request_created: project_merge_requests_path(project),
|
||||
user_added: project_members_url(project),
|
||||
security_scan_enabled: project_security_configuration_path(project)
|
||||
)
|
||||
end
|
||||
|
||||
def learn_gitlab_project
|
||||
@learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project
|
||||
end
|
||||
|
@ -54,3 +72,5 @@ module LearnGitlabHelper
|
|||
OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper')
|
||||
|
|
|
@ -376,6 +376,12 @@ module ProjectsHelper
|
|||
}
|
||||
end
|
||||
|
||||
def project_classes(project)
|
||||
return "project-highlight-puc" if project.warn_about_potentially_unwanted_characters?
|
||||
|
||||
""
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tab_ability_map
|
||||
|
@ -532,6 +538,7 @@ module ProjectsHelper
|
|||
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
|
||||
operationsAccessLevel: feature.operations_access_level,
|
||||
showDefaultAwardEmojis: project.show_default_award_emojis?,
|
||||
warnAboutPotentiallyUnwantedCharacters: project.warn_about_potentially_unwanted_characters?,
|
||||
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
|
||||
containerRegistryAccessLevel: feature.container_registry_access_level
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ module Ci
|
|||
|
||||
serialize :config_options, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :config_variables, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
|
||||
serialize :runtime_runner_features, Serializers::SymbolizedJson # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
chronic_duration_attr_reader :timeout_human_readable, :timeout
|
||||
|
||||
|
@ -47,6 +48,14 @@ module Ci
|
|||
update(timeout: timeout.value, timeout_source: timeout.source)
|
||||
end
|
||||
|
||||
def set_cancel_gracefully
|
||||
runtime_runner_features.merge!( { cancel_gracefully: true } )
|
||||
end
|
||||
|
||||
def cancel_gracefully?
|
||||
runtime_runner_features[:cancel_gracefully] == true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_build_project
|
||||
|
|
|
@ -20,6 +20,8 @@ module Ci
|
|||
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
|
||||
delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
|
||||
delegate :cancel_gracefully?, to: :metadata, prefix: false, allow_nil: false
|
||||
before_create :ensure_metadata
|
||||
end
|
||||
|
||||
|
|
|
@ -81,8 +81,7 @@ module ResolvableDiscussion
|
|||
return false unless current_user
|
||||
return false unless resolvable?
|
||||
|
||||
current_user == self.noteable.try(:author) ||
|
||||
current_user.can?(:resolve_note, self.project)
|
||||
current_user.can?(:resolve_note, self.noteable)
|
||||
end
|
||||
|
||||
def resolve!(current_user)
|
||||
|
|
|
@ -34,6 +34,8 @@ class Namespace < ApplicationRecord
|
|||
SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_ENABLED].freeze
|
||||
URL_MAX_LENGTH = 255
|
||||
|
||||
PATH_TRAILING_VIOLATIONS = %w[.git .atom .].freeze
|
||||
|
||||
cache_markdown_field :description, pipeline: :description
|
||||
|
||||
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
@ -203,9 +205,14 @@ class Namespace < ApplicationRecord
|
|||
# Remove everything that's not in the list of allowed characters.
|
||||
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
|
||||
# Remove trailing violations ('.atom', '.git', or '.')
|
||||
path.gsub!(/(\.atom|\.git|\.)*\z/, "")
|
||||
loop do
|
||||
orig = path
|
||||
PATH_TRAILING_VIOLATIONS.each { |ext| path = path.chomp(ext) }
|
||||
break if orig == path
|
||||
end
|
||||
|
||||
# Remove leading violations ('-')
|
||||
path.gsub!(/\A\-+/, "")
|
||||
path.gsub!(/\A\-+/, "")
|
||||
|
||||
# Users with the great usernames of "." or ".." would end up with a blank username.
|
||||
# Work around that by setting their username to "blank", followed by a counter.
|
||||
|
@ -531,21 +538,23 @@ class Namespace < ApplicationRecord
|
|||
# Until we compare the inconsistency rates of the new specialized worker and
|
||||
# the old approach, we still run AuthorizedProjectsWorker
|
||||
# but with some delay and lower urgency as a safety net.
|
||||
Group
|
||||
.joins(project_group_links: :project)
|
||||
.where(projects: { namespace_id: id })
|
||||
.distinct
|
||||
.find_each do |group|
|
||||
group.refresh_members_authorized_projects(
|
||||
blocking: false,
|
||||
priority: UserProjectAccessChangedService::LOW_PRIORITY
|
||||
)
|
||||
end
|
||||
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::LOW_PRIORITY)
|
||||
else
|
||||
Group
|
||||
.joins(project_group_links: :project)
|
||||
.where(projects: { namespace_id: id })
|
||||
.find_each(&:refresh_members_authorized_projects)
|
||||
enqueue_jobs_for_groups_requiring_authorizations_refresh(priority: UserProjectAccessChangedService::HIGH_PRIORITY)
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_jobs_for_groups_requiring_authorizations_refresh(priority:)
|
||||
groups_requiring_authorizations_refresh = Group
|
||||
.joins(project_group_links: :project)
|
||||
.where(projects: { namespace_id: id })
|
||||
.distinct
|
||||
|
||||
groups_requiring_authorizations_refresh.find_each do |group|
|
||||
group.refresh_members_authorized_projects(
|
||||
blocking: false,
|
||||
priority: priority
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -423,8 +423,8 @@ class Project < ApplicationRecord
|
|||
:container_registry_access_level, :container_registry_enabled?,
|
||||
to: :project_feature, allow_nil: true
|
||||
alias_method :container_registry_enabled, :container_registry_enabled?
|
||||
delegate :show_default_award_emojis, :show_default_award_emojis=,
|
||||
:show_default_award_emojis?,
|
||||
delegate :show_default_award_emojis, :show_default_award_emojis=, :show_default_award_emojis?,
|
||||
:warn_about_potentially_unwanted_characters, :warn_about_potentially_unwanted_characters=, :warn_about_potentially_unwanted_characters?,
|
||||
to: :project_setting, allow_nil: true
|
||||
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
|
||||
prefix: :import, to: :import_state, allow_nil: true
|
||||
|
@ -2724,8 +2724,23 @@ class Project < ApplicationRecord
|
|||
self.errors.add(:base, _("Could not change HEAD: branch '%{branch}' does not exist") % { branch: branch })
|
||||
end
|
||||
|
||||
def visible_group_links(for_user:)
|
||||
user = for_user
|
||||
links = project_group_links_with_preload
|
||||
user.max_member_access_for_group_ids(links.map(&:group_id)) if user && links.any?
|
||||
|
||||
DeclarativePolicy.user_scope do
|
||||
links.select { Ability.allowed?(user, :read_group, _1.group) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# overridden in EE
|
||||
def project_group_links_with_preload
|
||||
project_group_links
|
||||
end
|
||||
|
||||
def save_topics
|
||||
return if @topic_list.nil?
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ class ProjectGroupLink < ApplicationRecord
|
|||
validate :different_group
|
||||
|
||||
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
|
||||
scope :in_group, -> (group_ids) { where(group_id: group_ids) }
|
||||
|
||||
alias_method :shared_with_group, :group
|
||||
|
||||
|
|
|
@ -15,13 +15,21 @@ module Uploads
|
|||
end
|
||||
|
||||
def delete_keys(keys)
|
||||
keys.each do |key|
|
||||
connection.delete_object(bucket_name, key)
|
||||
end
|
||||
keys.each { |key| delete_object(key) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_object(key)
|
||||
connection.delete_object(bucket_name, key)
|
||||
|
||||
# So far, only GoogleCloudStorage raises an exception when the file is not found.
|
||||
# Other providers support idempotent requests and does not raise an error
|
||||
# when the file is missing.
|
||||
rescue ::Google::Apis::ClientError => e
|
||||
Gitlab::ErrorTracking.log_exception(e)
|
||||
end
|
||||
|
||||
def object_store
|
||||
Gitlab.config.uploads.object_store
|
||||
end
|
||||
|
|
|
@ -1434,7 +1434,7 @@ class User < ApplicationRecord
|
|||
name: name,
|
||||
username: username,
|
||||
avatar_url: avatar_url(only_path: false),
|
||||
email: email
|
||||
email: public_email.presence || _('[REDACTED]')
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ class IssuablePolicy < BasePolicy
|
|||
@user && @subject.assignee_or_author?(@user)
|
||||
end
|
||||
|
||||
condition(:is_author) { @subject&.author == @user }
|
||||
|
||||
rule { can?(:guest_access) & assignee_or_author }.policy do
|
||||
enable :read_issue
|
||||
enable :update_issue
|
||||
|
@ -20,6 +22,10 @@ class IssuablePolicy < BasePolicy
|
|||
enable :reopen_merge_request
|
||||
end
|
||||
|
||||
rule { is_author }.policy do
|
||||
enable :resolve_note
|
||||
end
|
||||
|
||||
rule { locked & ~is_project_member }.policy do
|
||||
prevent :create_note
|
||||
prevent :admin_note
|
||||
|
|
|
@ -221,6 +221,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :set_note_created_at
|
||||
enable :set_emails_disabled
|
||||
enable :set_show_default_award_emojis
|
||||
enable :set_warn_about_potentially_unwanted_characters
|
||||
end
|
||||
|
||||
rule { can?(:guest_access) }.policy do
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UpdateVisibilityLevel
|
||||
# check that user is allowed to set specified visibility_level
|
||||
def valid_visibility_level_change?(target, new_visibility)
|
||||
# check that user is allowed to set specified visibility_level
|
||||
if new_visibility && new_visibility.to_i != target.visibility_level
|
||||
unless can?(current_user, :change_visibility_level, target) &&
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
|
||||
return true unless new_visibility
|
||||
|
||||
deny_visibility_level(target, new_visibility)
|
||||
new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility)
|
||||
|
||||
if new_visibility_level != target.visibility_level_value
|
||||
unless can?(current_user, :change_visibility_level, target) &&
|
||||
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility_level)
|
||||
|
||||
deny_visibility_level(target, new_visibility_level)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -182,6 +182,14 @@ module Groups
|
|||
|
||||
# schedule refreshing projects for all the members of the group
|
||||
@group.refresh_members_authorized_projects
|
||||
|
||||
# When a group is transferred, it also affects who gets access to the projects shared to
|
||||
# the subgroups within its hierarchy, so we also schedule jobs that refresh authorizations for all such shared projects.
|
||||
project_group_shares_within_the_hierarchy = ProjectGroupLink.in_group(group.self_and_descendants.select(:id))
|
||||
|
||||
project_group_shares_within_the_hierarchy.find_each do |project_group_link|
|
||||
AuthorizedProjectUpdate::ProjectRecalculateWorker.perform_async(project_group_link.project_id)
|
||||
end
|
||||
end
|
||||
|
||||
def raise_transfer_error(message)
|
||||
|
|
|
@ -15,7 +15,7 @@ module Groups
|
|||
return false
|
||||
end
|
||||
|
||||
return false unless valid_visibility_level_change?(group, params[:visibility_level])
|
||||
return false unless valid_visibility_level_change?(group, group.visibility_attribute_value(params))
|
||||
|
||||
return false unless valid_share_with_group_lock_change?
|
||||
|
||||
|
@ -77,7 +77,7 @@ module Groups
|
|||
end
|
||||
|
||||
def after_update
|
||||
if group.previous_changes.include?(:visibility_level) && group.private?
|
||||
if group.previous_changes.include?(group.visibility_level_field) && group.private?
|
||||
# don't enqueue immediately to prevent todos removal in case of a mistake
|
||||
TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id)
|
||||
end
|
||||
|
|
|
@ -142,6 +142,7 @@ class IssuableBaseService < ::BaseProjectService
|
|||
def filter_severity(issuable)
|
||||
severity = params.delete(:severity)
|
||||
return unless severity && issuable.supports_severity?
|
||||
return unless can_admin_issuable?(issuable)
|
||||
|
||||
severity = IssuableSeverity::DEFAULT unless IssuableSeverity.severities.key?(severity)
|
||||
return if severity == issuable.severity
|
||||
|
|
|
@ -49,7 +49,7 @@ module Projects
|
|||
private
|
||||
|
||||
def validate!
|
||||
unless valid_visibility_level_change?(project, params[:visibility_level])
|
||||
unless valid_visibility_level_change?(project, project.visibility_attribute_value(params))
|
||||
raise ValidationError, s_('UpdateProject|New visibility level not allowed!')
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
'external-id' => @aws_role.role_external_id,
|
||||
'role-arn' => @aws_role.role_arn,
|
||||
'instance-types' => @instance_types,
|
||||
'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'),
|
||||
'kubernetes-integration-help-path' => help_page_path('user/infrastructure/clusters/index.md'),
|
||||
'account-and-external-ids-help-path' => help_page_path('user/project/clusters/add_eks_clusters.md', anchor: 'how-to-create-a-new-cluster-on-eks-through-cluster-certificates-deprecated'),
|
||||
'create-role-arn-help-path' => help_page_path('user/project/clusters/add_eks_clusters.md', anchor: 'how-to-create-a-new-cluster-on-eks-through-cluster-certificates-deprecated'),
|
||||
'external-link-icon' => sprite_icon('external-link') } }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
|
||||
- machine_type_link_url = 'https://cloud.google.com/compute/docs/machine-types'
|
||||
- pricing_link_url = 'https://cloud.google.com/compute/pricing#machinetype'
|
||||
- kubernetes_integration_url = help_page_path('user/project/clusters/index')
|
||||
- kubernetes_integration_url = help_page_path('user/infrastructure/clusters/index.md')
|
||||
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
|
||||
- help_link_end = ' %{external_link_icon}</a>'.html_safe % { external_link_icon: external_link_icon }
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
cluster_status: @cluster.status_name,
|
||||
cluster_status_reason: @cluster.status_reason,
|
||||
provider_type: @cluster.provider_type,
|
||||
help_path: help_page_path('user/project/clusters/index.md'),
|
||||
help_path: help_page_path('user/infrastructure/clusters/index.md'),
|
||||
environments_help_path: help_page_path('ci/environments/index.md', anchor: 'create-a-static-environment'),
|
||||
clusters_help_path: help_page_path('user/project/clusters/deploy_to_cluster.md'),
|
||||
deploy_boards_help_path: help_page_path('user/project/deploy_boards.md', anchor: 'enabling-deploy-boards'),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
- page_title _("Dependency Proxy")
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- dependency_proxy_available = Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true) || @group.public?
|
||||
|
||||
#js-dependency-proxy{ data: { group_path: @group.full_path,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
- display_subscription_banner!
|
||||
- display_namespace_storage_limit_alert!
|
||||
- @left_sidebar = true
|
||||
- @content_class = [@content_class, project_classes(@project)].compact.join(" ")
|
||||
|
||||
- content_for :project_javascripts do
|
||||
- project = @target_project || @project
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342808
|
|||
milestone: '14.4'
|
||||
type: development
|
||||
group: group::integrations
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: change_continuous_onboarding_link_urls
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71408
|
||||
rollout_issue_url:
|
||||
milestone: '14.5'
|
||||
type: experiment
|
||||
group: group::conversion
|
||||
default_enabled: false
|
|
@ -176,8 +176,10 @@ production: &base
|
|||
## Application settings cache expiry in seconds (default: 60)
|
||||
# application_settings_cache_seconds: 60
|
||||
|
||||
## Print initial root password to stdout during initialization (default: true)
|
||||
# display_initial_root_password: true
|
||||
## Print initial root password to stdout during initialization (default: false)
|
||||
# WARNING: setting this to true means that the root password will be printed in
|
||||
# plaintext. This can be a security risk.
|
||||
# display_initial_root_password: false
|
||||
|
||||
## Reply by email
|
||||
# Allow users to comment on issues and merge requests by replying to notification emails.
|
||||
|
|
|
@ -218,8 +218,7 @@ Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config'
|
|||
Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
|
||||
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
|
||||
Settings.gitlab['max_request_duration_seconds'] ||= 57
|
||||
|
||||
Settings.gitlab['display_initial_root_password'] = true if Settings.gitlab['display_initial_root_password'].nil?
|
||||
Settings.gitlab['display_initial_root_password'] = false if Settings.gitlab['display_initial_root_password'].nil?
|
||||
|
||||
Gitlab.ee do
|
||||
Settings.gitlab['mirror_max_delay'] ||= 300
|
||||
|
|
|
@ -26,7 +26,7 @@ if user.persisted?
|
|||
if ::Settings.gitlab['display_initial_root_password']
|
||||
puts "password: #{user_args[:password]}".color(:green)
|
||||
else
|
||||
puts "password: *** - You opted not to display initial root password to STDOUT."
|
||||
puts "password: ******".color(:green)
|
||||
end
|
||||
else
|
||||
puts "password: You'll be prompted to create one on your first visit.".color(:green)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class AddWarnAboutPotentiallyUnwantedCharactersToProjectSettings < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
add_column :project_settings, :warn_about_potentially_unwanted_characters, :boolean, null: false, default: true
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :project_settings, :warn_about_potentially_unwanted_characters
|
||||
end
|
||||
end
|
1
db/schema_migrations/20210929144453
Normal file
1
db/schema_migrations/20210929144453
Normal file
|
@ -0,0 +1 @@
|
|||
0f808c27d19e6a38d4aa31f2dd820fe226681af84e05c4af47213409b2043e5a
|
|
@ -18262,6 +18262,7 @@ CREATE TABLE project_settings (
|
|||
cve_id_request_enabled boolean DEFAULT true NOT NULL,
|
||||
mr_default_target_self boolean DEFAULT false NOT NULL,
|
||||
previous_default_branch text,
|
||||
warn_about_potentially_unwanted_characters boolean DEFAULT true NOT NULL,
|
||||
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
|
||||
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL))
|
||||
);
|
||||
|
|
|
@ -23,6 +23,14 @@ We have two kinds of changes related to JH:
|
|||
|
||||
If needed, review the corresponding JH merge request located at [JH repository](https://gitlab.com/gitlab-jh/gitlab)
|
||||
|
||||
## When to merge files to the GitLab Inc. repository
|
||||
|
||||
Files that are added to the `gitlab-jh` repository outside of `jh/` must be mirrored in the GitLab Inc. repository.
|
||||
|
||||
If code that is added to the GitLab Inc. repository references (for example, `render_if_exists`) any `gitlab-jh` file that does not
|
||||
exist in the GitLab Inc. codebase, add a comment with a link to the JiHu merge request or file. This is to prevent
|
||||
the reference from being misidentified as a missing partial and subsequently deleted in the `gitlab` codebase.
|
||||
|
||||
## Process overview
|
||||
|
||||
See the [merge request process](https://about.gitlab.com/handbook/ceo/chief-of-staff-team/jihu-support/#merge-request-process)
|
||||
|
|
|
@ -6,62 +6,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Connect a cluster to GitLab **(FREE)**
|
||||
|
||||
You can create new or connect existing clusters to GitLab through different [levels](#cluster-levels),
|
||||
using different [methods](#methods-to-connect-a-cluster-to-gitlab).
|
||||
The [certificate-based Kubernetes integration with GitLab](../index.md)
|
||||
was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8)
|
||||
in GitLab 14.5. To connect your clusters, use the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
|
||||
|
||||
Before getting started:
|
||||
|
||||
1. Check the [supported Kubernetes cluster versions](#supported-cluster-versions).
|
||||
1. Define the [cluster level](#cluster-levels) according to your case.
|
||||
|
||||
After that:
|
||||
|
||||
1. Choose the [method](#methods-to-connect-a-cluster-to-gitlab)
|
||||
to connect your cluster according to your case.
|
||||
1. [View your clusters](#view-your-clusters) connected to GitLab.
|
||||
|
||||
## Methods to connect a cluster to GitLab
|
||||
|
||||
GitLab offers three methods to connect existing and create new clusters:
|
||||
|
||||
- **GitLab Kubernetes Agent**: the best solution to
|
||||
[connect existing clusters](#connect-existing-clusters-to-gitlab).
|
||||
- **Infrastructure as Code**: it's a broader infrastructure management
|
||||
toolset that includes managing your cluster. It's the recommended
|
||||
solution to [create a new cluster](#create-new-clusters-from-gitlab)
|
||||
from GitLab.
|
||||
- **Certificate-based method**: our first and legacy solution uses
|
||||
cluster certificates to connect your cluster to GitLab. It is no longer
|
||||
recommended for [security implications](#security-implications-for-clusters-connected-with-certificates).
|
||||
|
||||
### Connect existing clusters to GitLab
|
||||
|
||||
To safely connect and configure an existing cluster on the **project level**,
|
||||
we **recommend** using the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
|
||||
We are working to support [the Agent for connecting a cluster at the group level](https://gitlab.com/groups/gitlab-org/-/epics/5784).
|
||||
|
||||
Alternatively, you can use [cluster certificates](../../../project/clusters/add_existing_cluster.md)
|
||||
to connect clusters in all levels (projects, group, instance). However,
|
||||
for [security implications](#security-implications-for-clusters-connected-with-certificates),
|
||||
we don't recommend using this method.
|
||||
|
||||
### Create new clusters from GitLab
|
||||
|
||||
To safely create new clusters from GitLab, use
|
||||
[Infrastructure as Code](../../iac/index.md#create-a-new-cluster-through-iac).
|
||||
|
||||
The [certificate-based method to create a new cluster](../../../project/clusters/add_remove_clusters.md)
|
||||
is still available through the GitLab UI but was **deprecated** in GitLab 14.0.
|
||||
If possible, we don't recommend using this method.
|
||||
|
||||
### Connect multiple clusters to a single project
|
||||
|
||||
To connect multiple clusters to a single project in GitLab,
|
||||
we **recommend** using the [GitLab Kubernetes Agent](../../../clusters/agent/index.md).
|
||||
|
||||
You can also use the [certificate-based method](../../../project/clusters/multiple_kubernetes_clusters.md),
|
||||
but, for [security implications](#security-implications-for-clusters-connected-with-certificates),
|
||||
we don't recommend using this method.
|
||||
<!-- TBA: (We need to resolve https://gitlab.com/gitlab-org/gitlab/-/issues/343660 before adding this line)
|
||||
If you don't have a cluster yet, create one and connect it to GitLab through the Agent.
|
||||
You can also create a new cluster from GitLab using [Infrastructure as Code](../../iac/index.md#create-a-new-cluster-through-iac).
|
||||
-->
|
||||
|
||||
## Supported cluster versions
|
||||
|
||||
|
@ -85,7 +37,13 @@ Kubernetes version to any supported version at any time:
|
|||
|
||||
Some GitLab features may support versions outside the range provided here.
|
||||
|
||||
## Cluster levels
|
||||
## Cluster levels (DEPRECATED)
|
||||
|
||||
> [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
|
||||
|
||||
WARNING:
|
||||
The [concept of cluster levels was deprecated](../index.md#cluster-levels)
|
||||
in GitLab 14.5.
|
||||
|
||||
Choose your cluster's level according to its purpose:
|
||||
|
||||
|
@ -118,6 +76,8 @@ your cluster's level.
|
|||
|
||||
## Security implications for clusters connected with certificates
|
||||
|
||||
> Connecting clusters to GitLab through cluster certificates was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
|
||||
|
||||
WARNING:
|
||||
The whole cluster security is based on a model where [developers](../../../permissions.md)
|
||||
are trusted, so **only trusted users should be allowed to control your clusters**.
|
||||
|
|
|
@ -14,7 +14,7 @@ This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/e
|
|||
in GitLab 14.5. To connect clusters to GitLab, use the
|
||||
[GitLab Kubernetes Agent](../../clusters/agent/index.md).
|
||||
|
||||
[Project-level Kubernetes clusters](../../infrastructure/clusters/index.md#cluster-levels)
|
||||
[Project-level](../../infrastructure/clusters/connect/index.md#cluster-levels-deprecated) Kubernetes clusters
|
||||
allow you to connect a Kubernetes cluster to a project in GitLab.
|
||||
|
||||
You can also [connect multiple clusters](multiple_kubernetes_clusters.md)
|
||||
|
|
|
@ -15,7 +15,7 @@ module API
|
|||
params do
|
||||
requires :name, type: String, desc: 'Application name'
|
||||
requires :redirect_uri, type: String, desc: 'Application redirect URI'
|
||||
requires :scopes, type: String, desc: 'Application scopes'
|
||||
requires :scopes, type: String, desc: 'Application scopes', allow_blank: false
|
||||
|
||||
optional :confidential, type: Boolean, default: true,
|
||||
desc: 'Application will be used where the client secret is confidential'
|
||||
|
|
|
@ -100,7 +100,9 @@ module API
|
|||
expose :build_coverage_regex
|
||||
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
||||
expose :shared_with_groups do |project, options|
|
||||
SharedGroupWithProject.represent(project.project_group_links, options)
|
||||
user = options[:current_user]
|
||||
|
||||
SharedGroupWithProject.represent(project.visible_group_links(for_user: user), options)
|
||||
end
|
||||
expose :only_allow_merge_if_pipeline_succeeds
|
||||
expose :allow_merge_on_skipped_pipeline
|
||||
|
|
|
@ -39,6 +39,7 @@ module API
|
|||
|
||||
optional :emails_disabled, type: Boolean, desc: 'Disable email notifications'
|
||||
optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis'
|
||||
optional :warn_about_potentially_unwanted_characters, type: Boolean, desc: 'Warn about Potentially Unwanted Characters'
|
||||
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
|
||||
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
|
||||
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
|
||||
|
|
|
@ -9,6 +9,8 @@ module API
|
|||
|
||||
feature_category :importers
|
||||
|
||||
before { authenticate! unless route.settings[:skip_authentication] }
|
||||
|
||||
helpers do
|
||||
def import_params
|
||||
declared_params(include_missing: false)
|
||||
|
@ -109,6 +111,7 @@ module API
|
|||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success Entities::ProjectImportStatus
|
||||
end
|
||||
route_setting :skip_authentication, true
|
||||
get ':id/import' do
|
||||
present user_project, with: Entities::ProjectImportStatus
|
||||
end
|
||||
|
|
|
@ -433,7 +433,7 @@ module API
|
|||
authorize_admin_project
|
||||
attrs = declared_params(include_missing: false)
|
||||
authorize! :rename_project, user_project if attrs[:name].present?
|
||||
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
|
||||
authorize! :change_visibility_level, user_project if user_project.visibility_attribute_present?(attrs)
|
||||
|
||||
attrs = translate_params_for_compatibility(attrs)
|
||||
filter_attributes_using_license!(attrs)
|
||||
|
|
|
@ -160,16 +160,40 @@ module Banzai
|
|||
def self.cacheless_render(text, context = {})
|
||||
return text.to_s unless text.present?
|
||||
|
||||
Gitlab::Metrics.measure(:banzai_cacheless_render) do
|
||||
result = render_result(text, context)
|
||||
real_start = Gitlab::Metrics::System.monotonic_time
|
||||
cpu_start = Gitlab::Metrics::System.cpu_time
|
||||
|
||||
output = result[:output]
|
||||
if output.respond_to?(:to_html)
|
||||
output.to_html
|
||||
else
|
||||
output.to_s
|
||||
end
|
||||
end
|
||||
result = render_result(text, context)
|
||||
|
||||
output = result[:output]
|
||||
rendered = if output.respond_to?(:to_html)
|
||||
output.to_html
|
||||
else
|
||||
output.to_s
|
||||
end
|
||||
|
||||
cpu_duration_histogram.observe({}, Gitlab::Metrics::System.cpu_time - cpu_start)
|
||||
real_duration_histogram.observe({}, Gitlab::Metrics::System.monotonic_time - real_start)
|
||||
|
||||
rendered
|
||||
end
|
||||
|
||||
def self.real_duration_histogram
|
||||
Gitlab::Metrics.histogram(
|
||||
:gitlab_banzai_cacheless_render_real_duration_seconds,
|
||||
'Duration of Banzai pipeline rendering in real time',
|
||||
{},
|
||||
[0.01, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10.0, 50, 100]
|
||||
)
|
||||
end
|
||||
|
||||
def self.cpu_duration_histogram
|
||||
Gitlab::Metrics.histogram(
|
||||
:gitlab_banzai_cacheless_render_cpu_duration_seconds,
|
||||
'Duration of Banzai pipeline rendering in cpu time',
|
||||
{},
|
||||
Gitlab::Metrics::EXECUTION_MEASUREMENT_BUCKETS
|
||||
)
|
||||
end
|
||||
|
||||
def self.full_cache_key(cache_key, pipeline_name)
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module BulkImports
|
||||
module Projects
|
||||
module Pipelines
|
||||
class MergeRequestsPipeline
|
||||
include NdjsonPipeline
|
||||
|
||||
relation_name 'merge_requests'
|
||||
|
||||
extractor ::BulkImports::Common::Extractors::NdjsonExtractor, relation: relation
|
||||
|
||||
def after_run(_)
|
||||
context.portable.merge_requests.set_latest_merge_request_diff_ids!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,10 +17,18 @@ module BulkImports
|
|||
def load(context, data)
|
||||
url = data['httpUrlToRepo']
|
||||
url = url.sub("://", "://oauth2:#{context.configuration.access_token}@")
|
||||
project = context.portable
|
||||
|
||||
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?, allow_localhost: allow_local_requests?)
|
||||
|
||||
context.portable.repository.import_repository(url)
|
||||
project.ensure_repository
|
||||
project.repository.fetch_as_mirror(url)
|
||||
end
|
||||
|
||||
# The initial fetch can bring in lots of loose refs and objects.
|
||||
# Running a `git gc` will make importing merge requests faster.
|
||||
def after_run(_)
|
||||
::Repositories::HousekeepingService.new(context.portable, :gc).execute
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -27,6 +27,10 @@ module BulkImports
|
|||
pipeline: BulkImports::Common::Pipelines::BoardsPipeline,
|
||||
stage: 4
|
||||
},
|
||||
merge_requests: {
|
||||
pipeline: BulkImports::Projects::Pipelines::MergeRequestsPipeline,
|
||||
stage: 4
|
||||
},
|
||||
uploads: {
|
||||
pipeline: BulkImports::Common::Pipelines::UploadsPipeline,
|
||||
stage: 5
|
||||
|
|
|
@ -11,6 +11,10 @@ module Gitlab
|
|||
|
||||
has_one :saml_provider
|
||||
|
||||
def root_saml_provider
|
||||
root_ancestor.saml_provider
|
||||
end
|
||||
|
||||
def self.declarative_policy_class
|
||||
"GroupPolicy"
|
||||
end
|
||||
|
|
|
@ -368,9 +368,11 @@ excluded_attributes:
|
|||
- :marked_for_deletion_by_user_id
|
||||
- :compliance_framework_setting
|
||||
- :show_default_award_emojis
|
||||
- :warn_about_potentially_unwanted_characters
|
||||
- :services
|
||||
- :exported_protected_branches
|
||||
- :repository_size_limit
|
||||
- :external_webhook_token
|
||||
namespaces:
|
||||
- :runners_token
|
||||
- :runners_token_encrypted
|
||||
|
@ -583,6 +585,8 @@ excluded_attributes:
|
|||
system_note_metadata:
|
||||
- :description_version_id
|
||||
- :note_id
|
||||
pipeline_schedules:
|
||||
- :active
|
||||
methods:
|
||||
notes:
|
||||
- :type
|
||||
|
|
|
@ -84,6 +84,7 @@ module Gitlab
|
|||
when :'Ci::Pipeline' then setup_pipeline
|
||||
when *BUILD_MODELS then setup_build
|
||||
when :issues then setup_issue
|
||||
when :'Ci::PipelineSchedule' then setup_pipeline_schedule
|
||||
end
|
||||
|
||||
update_project_references
|
||||
|
@ -143,6 +144,10 @@ module Gitlab
|
|||
@relation_hash['relative_position'] = compute_relative_position
|
||||
end
|
||||
|
||||
def setup_pipeline_schedule
|
||||
@relation_hash['active'] = false
|
||||
end
|
||||
|
||||
def compute_relative_position
|
||||
return unless max_relative_position
|
||||
|
||||
|
|
19
lib/gitlab/unicode.rb
Normal file
19
lib/gitlab/unicode.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class Unicode
|
||||
# Regular expression for identifying bidirectional control
|
||||
# characters in UTF-8 strings
|
||||
#
|
||||
# Documentation on how this works:
|
||||
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
|
||||
BIDI_REGEXP = /\p{Bidi Control}/.freeze
|
||||
|
||||
class << self
|
||||
# Warning message used to highlight bidi characters in the GUI
|
||||
def bidi_warning
|
||||
_("Potentially unwanted character detected: Unicode BiDi Control")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -155,6 +155,14 @@ module Gitlab
|
|||
false
|
||||
end
|
||||
|
||||
def visibility_attribute_value(attributes)
|
||||
visibility_level_attributes.each do |attr|
|
||||
return attributes[attr] if attributes.has_key?(attr)
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def visibility_level_attributes
|
||||
[visibility_level_field, visibility_level_field.to_s,
|
||||
:visibility, 'visibility']
|
||||
|
|
|
@ -21,12 +21,24 @@ module Rouge
|
|||
is_first = false
|
||||
|
||||
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
|
||||
line.each { |token, value| yield span(token, value.chomp! || value) }
|
||||
|
||||
line.each do |token, value|
|
||||
yield highlight_unicode_control_characters(span(token, value.chomp! || value))
|
||||
end
|
||||
|
||||
yield %(</span>)
|
||||
|
||||
@line_number += 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def highlight_unicode_control_characters(text)
|
||||
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
|
||||
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9178,9 +9178,6 @@ msgstr ""
|
|||
msgid "ContainerRegistry|You can add an image to this registry with the following commands:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Contains %{count} blobs of images (%{size})"
|
||||
msgstr ""
|
||||
|
||||
msgid "Content parsed with %{link}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -9370,9 +9367,6 @@ msgstr ""
|
|||
msgid "Copy link to chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11230,27 +11224,39 @@ msgstr ""
|
|||
msgid "Dependency Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependency Proxy disabled. To enable it, contact the group owner."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependency Proxy feature is limited to public groups for now."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependency Proxy image prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dependency Scanning"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Cached %{time}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Contains %{count} blobs of images (%{size})"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Copy prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies."
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Dependency Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Dependency Proxy disabled. To enable it, contact the group owner."
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Dependency Proxy feature is limited to public groups for now."
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Dependency Proxy image prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Enable Proxy"
|
||||
msgstr ""
|
||||
|
||||
msgid "DependencyProxy|Manifest list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Depends on %d merge request being merged"
|
||||
msgid_plural "Depends on %d merge requests being merged"
|
||||
msgstr[0] ""
|
||||
|
@ -25828,6 +25834,9 @@ msgstr ""
|
|||
msgid "Postman collection file path or URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Potentially unwanted character detected: Unicode BiDi Control"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pre-defined push rules."
|
||||
msgstr ""
|
||||
|
||||
|
@ -26953,6 +26962,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Global"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Highlight the usage of hidden unicode characters. These have innocent uses for right-to-left languages, but can also be used in potential exploits."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Internal"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26965,6 +26977,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|LFS objects from this repository are available to forks. %{linkStart}How do I remove them?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Manage who can see the project in the public access directory."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Manages large files such as audio, video, and graphics files."
|
||||
msgstr ""
|
||||
|
||||
|
@ -27142,6 +27157,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Visualize the project's performance metrics."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Warn about Potentially Unwanted Characters"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|What are badges?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39836,6 +39854,9 @@ msgstr ""
|
|||
msgid "[No reason]"
|
||||
msgstr ""
|
||||
|
||||
msgid "[REDACTED]"
|
||||
msgstr ""
|
||||
|
||||
msgid "[Redacted]"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ module QA
|
|||
}
|
||||
end
|
||||
|
||||
it "push and pull a npm package via CI using a #{params[:token_name]}", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1772' do
|
||||
it "push and pull a npm package via CI using a #{params[:token_name]}" do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
|
|
|
@ -85,7 +85,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do
|
||||
it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}" do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
|
|
|
@ -356,7 +356,7 @@ RSpec.describe Projects::BranchesController do
|
|||
context "valid branch name with encoded slashes" do
|
||||
let(:branch) { "improve%2Fawesome" }
|
||||
|
||||
it { expect(response).to have_gitlab_http_status(:ok) }
|
||||
it { expect(response).to have_gitlab_http_status(:not_found) }
|
||||
it { expect(response.body).to be_blank }
|
||||
end
|
||||
|
||||
|
@ -396,10 +396,10 @@ RSpec.describe Projects::BranchesController do
|
|||
let(:branch) { 'improve%2Fawesome' }
|
||||
|
||||
it 'returns JSON response with message' do
|
||||
expect(json_response).to eql('message' => 'Branch was deleted')
|
||||
expect(json_response).to eql('message' => 'No such branch')
|
||||
end
|
||||
|
||||
it { expect(response).to have_gitlab_http_status(:ok) }
|
||||
it { expect(response).to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'invalid branch name, valid ref' do
|
||||
|
|
|
@ -323,6 +323,34 @@ RSpec.describe ProjectsController do
|
|||
expect(response).to render_template('_files')
|
||||
expect(response.body).to have_content('LICENSE') # would be 'MIT license' if stub not works
|
||||
end
|
||||
|
||||
describe "PUC highlighting" do
|
||||
render_views
|
||||
|
||||
before do
|
||||
expect(controller).to receive(:find_routable!).and_return(public_project)
|
||||
end
|
||||
|
||||
context "option is enabled" do
|
||||
it "adds the highlighting class" do
|
||||
expect(public_project).to receive(:warn_about_potentially_unwanted_characters?).and_return(true)
|
||||
|
||||
get_show
|
||||
|
||||
expect(response.body).to have_css(".project-highlight-puc")
|
||||
end
|
||||
end
|
||||
|
||||
context "option is disabled" do
|
||||
it "doesn't add the highlighting class" do
|
||||
expect(public_project).to receive(:warn_about_potentially_unwanted_characters?).and_return(false)
|
||||
|
||||
get_show
|
||||
|
||||
expect(response.body).not_to have_css(".project-highlight-puc")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the url contains .atom" do
|
||||
|
|
|
@ -15,6 +15,10 @@ FactoryBot.define do
|
|||
admin { true }
|
||||
end
|
||||
|
||||
trait :public_email do
|
||||
public_email { email }
|
||||
end
|
||||
|
||||
trait :blocked do
|
||||
after(:build) { |user, _| user.block! }
|
||||
end
|
||||
|
|
|
@ -22,12 +22,16 @@ const safeUrls = {
|
|||
const unsafeUrls = [
|
||||
'/an/evil/url',
|
||||
'../../../evil/url',
|
||||
'https://evil.url/assets/icons-123a.svg',
|
||||
'https://evil.url/assets/icons-123a.svg#test',
|
||||
'https://evil.url/assets/icons-456b.svg',
|
||||
`https://evil.url/${rootGon.sprite_icons}`,
|
||||
`https://evil.url/${rootGon.sprite_file_icons}`,
|
||||
`https://evil.url/${absoluteGon.sprite_icons}`,
|
||||
`https://evil.url/${absoluteGon.sprite_file_icons}`,
|
||||
`${rootGon.sprite_icons}/../evil/path`,
|
||||
`${rootGon.sprite_file_icons}/../../evil/path`,
|
||||
`${absoluteGon.sprite_icons}/../evil/path`,
|
||||
`${absoluteGon.sprite_file_icons}/../../https://evil.url`,
|
||||
];
|
||||
|
||||
const forbiddenDataAttrs = ['data-remote', 'data-url', 'data-type', 'data-method'];
|
||||
|
|
|
@ -607,6 +607,27 @@ describe('URL utility', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getNormalizedURL', () => {
|
||||
it.each`
|
||||
url | base | result
|
||||
${'./foo'} | ${''} | ${'http://test.host/foo'}
|
||||
${'../john.md'} | ${''} | ${'http://test.host/john.md'}
|
||||
${'/images/img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/images/img.png'}
|
||||
${'/images/../img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/img.png'}
|
||||
${'/images/./img.png'} | ${'https://gitlab.com'} | ${'https://gitlab.com/images/img.png'}
|
||||
${'./images/img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/user/images/img.png'}
|
||||
${'../images/../img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/img.png'}
|
||||
${'/images/img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/images/img.png'}
|
||||
${'/images/../img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/img.png'}
|
||||
${'/images/./img.png'} | ${'https://gitlab.com/user/project'} | ${'https://gitlab.com/images/img.png'}
|
||||
`(
|
||||
'converts url "$url" with base "$base" to normalized url => "expected"',
|
||||
({ url, base, result }) => {
|
||||
expect(urlUtils.getNormalizedURL(url, base)).toBe(result);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('getWebSocketProtocol', () => {
|
||||
it.each`
|
||||
protocol | expectation
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import { GlKeysetPagination } from '@gitlab/ui';
|
||||
import { stripTypenames } from 'helpers/graphql_helpers';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
|
||||
|
||||
import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
|
||||
import {
|
||||
proxyManifests,
|
||||
pagination,
|
||||
} from 'jest/packages_and_registries/dependency_proxy/mock_data';
|
||||
|
||||
describe('Manifests List', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
manifests: proxyManifests(),
|
||||
pagination: stripTypenames(pagination()),
|
||||
};
|
||||
|
||||
const createComponent = (propsData = defaultProps) => {
|
||||
wrapper = shallowMountExtended(Component, {
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
const findRows = () => wrapper.findAllComponents(ManifestRow);
|
||||
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has the correct title', () => {
|
||||
expect(wrapper.text()).toContain(Component.i18n.listTitle);
|
||||
});
|
||||
|
||||
it('shows a row for every manifest', () => {
|
||||
expect(findRows().length).toBe(defaultProps.manifests.length);
|
||||
});
|
||||
|
||||
it('binds a manifest to each row', () => {
|
||||
expect(findRows().at(0).props()).toMatchObject({
|
||||
manifest: defaultProps.manifests[0],
|
||||
});
|
||||
});
|
||||
|
||||
describe('pagination', () => {
|
||||
it('has the correct props', () => {
|
||||
expect(findPagination().props()).toMatchObject({
|
||||
...defaultProps.pagination,
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the next-page event', () => {
|
||||
findPagination().vm.$emit('next');
|
||||
|
||||
expect(wrapper.emitted('next-page')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('emits the prev-page event', () => {
|
||||
findPagination().vm.$emit('prev');
|
||||
|
||||
expect(wrapper.emitted('prev-page')).toEqual([[]]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
import { GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import Component from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
|
||||
import { proxyManifests } from 'jest/packages_and_registries/dependency_proxy/mock_data';
|
||||
|
||||
describe('Manifest Row', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
manifest: proxyManifests()[0],
|
||||
};
|
||||
|
||||
const createComponent = (propsData = defaultProps) => {
|
||||
wrapper = shallowMountExtended(Component, {
|
||||
propsData,
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
TimeagoTooltip,
|
||||
ListItem,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findListItem = () => wrapper.findComponent(ListItem);
|
||||
const findCachedMessages = () => wrapper.findByTestId('cached-message');
|
||||
const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('has a list item', () => {
|
||||
expect(findListItem().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays the name', () => {
|
||||
expect(wrapper.text()).toContain('alpine');
|
||||
});
|
||||
|
||||
it('displays the version', () => {
|
||||
expect(wrapper.text()).toContain('latest');
|
||||
});
|
||||
|
||||
it('displays the cached time', () => {
|
||||
expect(findCachedMessages().text()).toContain('Cached');
|
||||
});
|
||||
|
||||
it('has a time ago tooltip component', () => {
|
||||
expect(findTimeAgoTooltip().props()).toMatchObject({
|
||||
time: defaultProps.manifest.createdAt,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,6 +7,20 @@ export const proxyData = () => ({
|
|||
|
||||
export const proxySettings = (extend = {}) => ({ enabled: true, ...extend });
|
||||
|
||||
export const proxyManifests = () => [
|
||||
{ createdAt: '2021-09-22T09:45:28Z', imageName: 'alpine:latest' },
|
||||
{ createdAt: '2021-09-21T09:45:28Z', imageName: 'alpine:stable' },
|
||||
];
|
||||
|
||||
export const pagination = (extend) => ({
|
||||
endCursor: 'eyJpZCI6IjIwNSIsIm5hbWUiOiJteS9jb21wYW55L2FwcC9teS1hcHAifQ',
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: true,
|
||||
startCursor: 'eyJpZCI6IjI0NyIsIm5hbWUiOiJ2ZXJzaW9uX3Rlc3QxIn0',
|
||||
__typename: 'PageInfo',
|
||||
...extend,
|
||||
});
|
||||
|
||||
export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
|
||||
data: {
|
||||
group: {
|
||||
|
@ -16,6 +30,10 @@ export const proxyDetailsQuery = ({ extendSettings = {} } = {}) => ({
|
|||
...proxySettings(extendSettings),
|
||||
__typename: 'DependencyProxySetting',
|
||||
},
|
||||
dependencyProxyManifests: {
|
||||
nodes: proxyManifests(),
|
||||
pageInfo: pagination(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -27,6 +27,7 @@ const defaultProps = {
|
|||
emailsDisabled: false,
|
||||
packagesEnabled: true,
|
||||
showDefaultAwardEmojis: true,
|
||||
warnAboutPotentiallyUnwantedCharacters: true,
|
||||
},
|
||||
isGitlabCom: true,
|
||||
canDisableEmails: true,
|
||||
|
@ -97,6 +98,10 @@ describe('Settings Panel', () => {
|
|||
const findEmailSettings = () => wrapper.find({ ref: 'email-settings' });
|
||||
const findShowDefaultAwardEmojis = () =>
|
||||
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
|
||||
const findWarnAboutPuc = () =>
|
||||
wrapper.find(
|
||||
'input[name="project[project_setting_attributes][warn_about_potentially_unwanted_characters]"]',
|
||||
);
|
||||
const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' });
|
||||
const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' });
|
||||
|
||||
|
@ -539,6 +544,14 @@ describe('Settings Panel', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Warn about potentially unwanted characters', () => {
|
||||
it('should have a "Warn about Potentially Unwanted Characters" input', () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
expect(findWarnAboutPuc().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Metrics dashboard', () => {
|
||||
it('should show the metrics dashboard access toggle', () => {
|
||||
wrapper = mountComponent();
|
||||
|
|
|
@ -7,6 +7,7 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
|
|||
described_class.new(object: nil, context: { current_user: user }, field: nil)
|
||||
end
|
||||
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
describe '#resolve' do
|
||||
|
@ -136,20 +137,37 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is the author and discussion is locked' do
|
||||
let(:user) { author }
|
||||
|
||||
before do
|
||||
issuable.update!(discussion_locked: true)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { mutation.resolve(id: id_arg, resolve: resolve_arg) }.to raise_error(
|
||||
Gitlab::Graphql::Errors::ResourceNotAvailable,
|
||||
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when discussion is on a merge request' do
|
||||
let_it_be(:noteable) { create(:merge_request, source_project: project) }
|
||||
let_it_be(:noteable) { create(:merge_request, source_project: project, author: author) }
|
||||
|
||||
let(:discussion) { create(:diff_note_on_merge_request, noteable: noteable, project: project).to_discussion }
|
||||
let(:issuable) { noteable }
|
||||
|
||||
it_behaves_like 'a working resolve method'
|
||||
end
|
||||
|
||||
context 'when discussion is on a design' do
|
||||
let_it_be(:noteable) { create(:design, :with_file, issue: create(:issue, project: project)) }
|
||||
let_it_be(:noteable) { create(:design, :with_file, issue: create(:issue, project: project, author: author)) }
|
||||
|
||||
let(:discussion) { create(:diff_note_on_design, noteable: noteable, project: project).to_discussion }
|
||||
let(:issuable) { noteable.issue }
|
||||
|
||||
it_behaves_like 'a working resolve method'
|
||||
end
|
||||
|
|
|
@ -3,26 +3,58 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Issues::SetSeverity do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:issue) { create(:incident) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
let_it_be(:issue) { create(:incident, project: project) }
|
||||
|
||||
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:update_issue) }
|
||||
specify { expect(described_class).to require_graphql_authorizations(:update_issue, :admin_issue) }
|
||||
|
||||
before_all do
|
||||
project.add_guest(guest)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
let(:severity) { 'critical' }
|
||||
let(:mutated_incident) { subject[:issue] }
|
||||
|
||||
subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, severity: severity) }
|
||||
subject(:resolve) do
|
||||
mutation.resolve(
|
||||
project_path: issue.project.full_path,
|
||||
iid: issue.iid,
|
||||
severity: severity
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'permission level for issue mutation is correctly verified'
|
||||
context 'as guest' do
|
||||
let(:user) { guest }
|
||||
|
||||
context 'when the user can update the issue' do
|
||||
before do
|
||||
issue.project.add_developer(user)
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
|
||||
context 'and also author' do
|
||||
let!(:issue) { create(:incident, project: project, author: user) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and also assignee' do
|
||||
let!(:issue) { create(:incident, project: project, assignee_ids: [user.id]) }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'as reporter' do
|
||||
let(:user) { reporter }
|
||||
|
||||
context 'when issue type is incident' do
|
||||
context 'when severity has a correct value' do
|
||||
it 'updates severity' do
|
||||
|
@ -48,9 +80,9 @@ RSpec.describe Mutations::Issues::SetSeverity do
|
|||
end
|
||||
|
||||
context 'when issue type is not incident' do
|
||||
let!(:issue) { create(:issue) }
|
||||
let!(:issue) { create(:issue, project: project) }
|
||||
|
||||
it 'does not updates the issue' do
|
||||
it 'does not update the issue' do
|
||||
expect { resolve }.not_to change { issue.updated_at }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,9 +11,6 @@ RSpec.describe LearnGitlabHelper do
|
|||
let_it_be(:namespace) { project.namespace }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
allow(helper).to receive(:user).and_return(user)
|
||||
allow_next_instance_of(LearnGitlab::Project) do |learn_gitlab|
|
||||
allow(learn_gitlab).to receive(:project).and_return(project)
|
||||
end
|
||||
|
@ -22,38 +19,115 @@ RSpec.describe LearnGitlabHelper do
|
|||
OnboardingProgress.register(namespace, :git_write)
|
||||
end
|
||||
|
||||
describe '.onboarding_actions_data' do
|
||||
describe '#onboarding_actions_data' do
|
||||
subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) }
|
||||
|
||||
it 'has all actions' do
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(
|
||||
:issue_created,
|
||||
:git_write,
|
||||
:pipeline_created,
|
||||
:merge_request_created,
|
||||
:user_added,
|
||||
:trial_started,
|
||||
:required_mr_approvals_enabled,
|
||||
:code_owners_enabled,
|
||||
:security_scan_enabled
|
||||
)
|
||||
shared_examples 'has all actions' do
|
||||
it 'has all actions' do
|
||||
expect(onboarding_actions_data.keys).to contain_exactly(
|
||||
:issue_created,
|
||||
:git_write,
|
||||
:pipeline_created,
|
||||
:merge_request_created,
|
||||
:user_added,
|
||||
:trial_started,
|
||||
:required_mr_approvals_enabled,
|
||||
:code_owners_enabled,
|
||||
:security_scan_enabled
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets correct path and completion status' do
|
||||
expect(onboarding_actions_data[:git_write]).to eq({
|
||||
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:git_write]),
|
||||
completed: true,
|
||||
svg: helper.image_path("learn_gitlab/git_write.svg")
|
||||
it_behaves_like 'has all actions'
|
||||
|
||||
it 'sets correct paths' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
trial_started: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
|
||||
),
|
||||
issue_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/4\z})
|
||||
),
|
||||
git_write: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/6\z})
|
||||
),
|
||||
pipeline_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/7\z})
|
||||
),
|
||||
user_added: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/8\z})
|
||||
),
|
||||
merge_request_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/9\z})
|
||||
),
|
||||
code_owners_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
|
||||
),
|
||||
required_mr_approvals_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
|
||||
),
|
||||
security_scan_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z})
|
||||
)
|
||||
})
|
||||
expect(onboarding_actions_data[:pipeline_created]).to eq({
|
||||
url: project_issue_url(project, LearnGitlab::Onboarding::ACTION_ISSUE_IDS[:pipeline_created]),
|
||||
completed: false,
|
||||
svg: helper.image_path("learn_gitlab/pipeline_created.svg")
|
||||
end
|
||||
|
||||
it 'sets correct completion statuses' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
issue_created: a_hash_including(completed: false),
|
||||
git_write: a_hash_including(completed: true),
|
||||
pipeline_created: a_hash_including(completed: false),
|
||||
merge_request_created: a_hash_including(completed: false),
|
||||
user_added: a_hash_including(completed: false),
|
||||
trial_started: a_hash_including(completed: false),
|
||||
required_mr_approvals_enabled: a_hash_including(completed: false),
|
||||
code_owners_enabled: a_hash_including(completed: false),
|
||||
security_scan_enabled: a_hash_including(completed: false)
|
||||
})
|
||||
end
|
||||
|
||||
context 'when in the new action URLs experiment' do
|
||||
before do
|
||||
stub_experiments(change_continuous_onboarding_link_urls: :candidate)
|
||||
end
|
||||
|
||||
it_behaves_like 'has all actions'
|
||||
|
||||
it 'sets mostly new paths' do
|
||||
expect(onboarding_actions_data).to match({
|
||||
trial_started: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/2\z})
|
||||
),
|
||||
issue_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues\z})
|
||||
),
|
||||
git_write: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab\z})
|
||||
),
|
||||
pipeline_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/pipelines\z})
|
||||
),
|
||||
user_added: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/project_members\z})
|
||||
),
|
||||
merge_request_created: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z})
|
||||
),
|
||||
code_owners_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/10\z})
|
||||
),
|
||||
required_mr_approvals_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/issues/11\z})
|
||||
),
|
||||
security_scan_enabled: a_hash_including(
|
||||
url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z})
|
||||
)
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.learn_gitlab_enabled?' do
|
||||
describe '#learn_gitlab_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
@ -89,7 +163,7 @@ RSpec.describe LearnGitlabHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.onboarding_sections_data' do
|
||||
describe '#onboarding_sections_data' do
|
||||
subject(:sections) { helper.onboarding_sections_data }
|
||||
|
||||
it 'has the right keys' do
|
||||
|
|
|
@ -961,4 +961,26 @@ RSpec.describe ProjectsHelper do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project_classes' do
|
||||
subject { helper.project_classes(project) }
|
||||
|
||||
it { is_expected.to be_a(String) }
|
||||
|
||||
context 'PUC highlighting enabled' do
|
||||
before do
|
||||
project.warn_about_potentially_unwanted_characters = true
|
||||
end
|
||||
|
||||
it { is_expected.to include('project-highlight-puc') }
|
||||
end
|
||||
|
||||
context 'PUC highlighting disabled' do
|
||||
before do
|
||||
project.warn_about_potentially_unwanted_characters = false
|
||||
end
|
||||
|
||||
it { is_expected.not_to include('project-highlight-puc') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
39
spec/lib/api/entities/project_spec.rb
Normal file
39
spec/lib/api/entities/project_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::API::Entities::Project do
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:current_user) { create(:user) }
|
||||
let(:options) { { current_user: current_user } }
|
||||
|
||||
let(:entity) do
|
||||
::API::Entities::Project.new(project, options)
|
||||
end
|
||||
|
||||
subject(:json) { entity.as_json }
|
||||
|
||||
describe '.shared_with_groups' do
|
||||
let(:group) { create(:group, :private) }
|
||||
|
||||
before do
|
||||
project.project_group_links.create!(group: group)
|
||||
end
|
||||
|
||||
context 'when the current user does not have access to the group' do
|
||||
it 'is empty' do
|
||||
expect(json[:shared_with_groups]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the current user has access to the group' do
|
||||
before do
|
||||
group.add_guest(current_user)
|
||||
end
|
||||
|
||||
it 'contains information about the shared group' do
|
||||
expect(json[:shared_with_groups]).to contain_exactly(include(group_id: group.id))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,6 +84,24 @@ RSpec.describe Banzai::Renderer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#cacheless_render' do
|
||||
context 'without cache' do
|
||||
let(:object) { fake_object(fresh: false) }
|
||||
let(:histogram) { double('prometheus histogram') }
|
||||
|
||||
it 'returns cacheless render field' do
|
||||
allow(renderer).to receive(:render_result).and_return(output: 'test')
|
||||
allow(renderer).to receive(:real_duration_histogram).and_return(histogram)
|
||||
allow(renderer).to receive(:cpu_duration_histogram).and_return(histogram)
|
||||
|
||||
expect(renderer).to receive(:render_result).with('test', {})
|
||||
expect(histogram).to receive(:observe).twice
|
||||
|
||||
renderer.cacheless_render('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#post_process' do
|
||||
let(:context_options) { {} }
|
||||
let(:html) { 'Consequatur aperiam et nesciunt modi aut assumenda quo id. '}
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, :repository, group: group) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
let_it_be(:entity) do
|
||||
create(
|
||||
:bulk_import_entity,
|
||||
:project_entity,
|
||||
project: project,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'My Destination Project',
|
||||
destination_namespace: group.full_path
|
||||
)
|
||||
end
|
||||
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
let(:mr) do
|
||||
{
|
||||
'iid' => 7,
|
||||
'author_id' => 22,
|
||||
'source_project_id' => 1234,
|
||||
'target_project_id' => 1234,
|
||||
'title' => 'Imported MR',
|
||||
'description' => 'Description',
|
||||
'state' => 'opened',
|
||||
'source_branch' => 'feature',
|
||||
'target_branch' => 'main',
|
||||
'source_branch_sha' => 'ABCD',
|
||||
'target_branch_sha' => 'DCBA',
|
||||
'created_at' => '2020-06-14T15:02:47.967Z',
|
||||
'updated_at' => '2020-06-14T15:03:47.967Z',
|
||||
'merge_request_diff' => {
|
||||
'state' => 'collected',
|
||||
'base_commit_sha' => 'ae73cb07c9eeaf35924a10f713b364d32b2dd34f',
|
||||
'head_commit_sha' => 'a97f74ddaa848b707bea65441c903ae4bf5d844d',
|
||||
'start_commit_sha' => '9eea46b5c72ead701c22f516474b95049c9d9462',
|
||||
'merge_request_diff_commits' => [
|
||||
{
|
||||
'sha' => 'COMMIT1',
|
||||
'relative_order' => 0,
|
||||
'message' => 'commit message',
|
||||
'authored_date' => '2014-08-06T08:35:52.000+02:00',
|
||||
'committed_date' => '2014-08-06T08:35:52.000+02:00',
|
||||
'commit_author' => {
|
||||
'name' => 'Commit Author',
|
||||
'email' => 'gitlab@example.com'
|
||||
},
|
||||
'committer' => {
|
||||
'name' => 'Committer',
|
||||
'email' => 'committer@example.com'
|
||||
}
|
||||
}
|
||||
],
|
||||
'merge_request_diff_files' => [
|
||||
{
|
||||
'relative_order' => 0,
|
||||
'utf8_diff' => '--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1 @@ test\n',
|
||||
'new_path' => '.gitignore',
|
||||
'old_path' => '.gitignore',
|
||||
'a_mode' => '100644',
|
||||
'b_mode' => '100644',
|
||||
'new_file' => false,
|
||||
'renamed_file' => false,
|
||||
'deleted_file' => false,
|
||||
'too_large' => false
|
||||
}
|
||||
]
|
||||
}
|
||||
}.merge(attributes)
|
||||
end
|
||||
|
||||
let(:attributes) { {} }
|
||||
let(:imported_mr) { project.merge_requests.find_by_title(mr['title']) }
|
||||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
describe '#run' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:remove_tmp_dir)
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[mr, 0]]))
|
||||
end
|
||||
|
||||
allow(project.repository).to receive(:fetch_source_branch!).and_return(true)
|
||||
allow(project.repository).to receive(:branch_exists?).and_return(false)
|
||||
allow(project.repository).to receive(:create_branch)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
it 'imports a merge request' do
|
||||
expect(project.merge_requests.count).to eq(1)
|
||||
expect(imported_mr.title).to eq(mr['title'])
|
||||
expect(imported_mr.description).to eq(mr['description'])
|
||||
expect(imported_mr.state).to eq(mr['state'])
|
||||
expect(imported_mr.iid).to eq(mr['iid'])
|
||||
expect(imported_mr.created_at).to eq(mr['created_at'])
|
||||
expect(imported_mr.updated_at).to eq(mr['updated_at'])
|
||||
expect(imported_mr.author).to eq(user)
|
||||
end
|
||||
|
||||
context 'merge request state' do
|
||||
context 'when mr is closed' do
|
||||
let(:attributes) { { 'state' => 'closed' } }
|
||||
|
||||
it 'imported mr as closed' do
|
||||
expect(imported_mr.state).to eq(attributes['state'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when mr is merged' do
|
||||
let(:attributes) { { 'state' => 'merged' } }
|
||||
|
||||
it 'imported mr as merged' do
|
||||
expect(imported_mr.state).to eq(attributes['state'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'source & target project' do
|
||||
it 'has the new project as target' do
|
||||
expect(imported_mr.target_project).to eq(project)
|
||||
end
|
||||
|
||||
it 'has the new project as source' do
|
||||
expect(imported_mr.source_project).to eq(project)
|
||||
end
|
||||
|
||||
context 'when source/target projects differ' do
|
||||
let(:attributes) { { 'source_project_id' => 4321 } }
|
||||
|
||||
it 'has no source' do
|
||||
expect(imported_mr.source_project).to be_nil
|
||||
end
|
||||
|
||||
context 'when diff_head_sha is present' do
|
||||
let(:attributes) { { 'diff_head_sha' => 'HEAD', 'source_project_id' => 4321 } }
|
||||
|
||||
it 'has the new project as source' do
|
||||
expect(imported_mr.source_project).to eq(project)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'resource label events' do
|
||||
let(:attributes) { { 'resource_label_events' => [{ 'action' => 'add', 'user_id' => 1 }] } }
|
||||
|
||||
it 'restores resource label events' do
|
||||
expect(imported_mr.resource_label_events.first.action).to eq('add')
|
||||
end
|
||||
end
|
||||
|
||||
context 'award emoji' do
|
||||
let(:attributes) { { 'award_emoji' => [{ 'name' => 'tada', 'user_id' => 22 }] } }
|
||||
|
||||
it 'has award emoji' do
|
||||
expect(imported_mr.award_emoji.first.name).to eq(attributes['award_emoji'].first['name'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'notes' do
|
||||
let(:note) { imported_mr.notes.first }
|
||||
let(:attributes) do
|
||||
{
|
||||
'notes' => [
|
||||
{
|
||||
'note' => 'Issue note',
|
||||
'note_html' => '<p>something else entirely</p>',
|
||||
'cached_markdown_version' => 917504,
|
||||
'author_id' => 22,
|
||||
'author' => { 'name' => 'User 22' },
|
||||
'created_at' => '2016-06-14T15:02:56.632Z',
|
||||
'updated_at' => '2016-06-14T15:02:47.770Z',
|
||||
'award_emoji' => [{ 'name' => 'clapper', 'user_id' => 22 }]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports mr note' do
|
||||
expect(note).to be_present
|
||||
expect(note.note).to include('By User 22')
|
||||
expect(note.note).to include(attributes['notes'].first['note'])
|
||||
expect(note.author).to eq(user)
|
||||
end
|
||||
|
||||
it 'has award emoji' do
|
||||
emoji = note.award_emoji.first
|
||||
|
||||
expect(emoji.name).to eq('clapper')
|
||||
expect(emoji.user).to eq(user)
|
||||
end
|
||||
|
||||
it 'does not import note_html' do
|
||||
expect(note.note_html).to match(attributes['notes'].first['note'])
|
||||
expect(note.note_html).not_to match(attributes['notes'].first['note_html'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'system note metadata' do
|
||||
let(:attributes) do
|
||||
{
|
||||
'notes' => [
|
||||
{
|
||||
'note' => 'added 3 commits',
|
||||
'system' => true,
|
||||
'author_id' => 22,
|
||||
'author' => { 'name' => 'User 22' },
|
||||
'created_at' => '2016-06-14T15:02:56.632Z',
|
||||
'updated_at' => '2016-06-14T15:02:47.770Z',
|
||||
'system_note_metadata' => { 'action' => 'commit', 'commit_count' => 3 }
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'restores system note metadata' do
|
||||
note = imported_mr.notes.first
|
||||
|
||||
expect(note.system).to eq(true)
|
||||
expect(note.noteable_type).to eq('MergeRequest')
|
||||
expect(note.system_note_metadata.action).to eq('commit')
|
||||
expect(note.system_note_metadata.commit_count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'diffs' do
|
||||
it 'imports merge request diff' do
|
||||
expect(imported_mr.merge_request_diff).to be_present
|
||||
end
|
||||
|
||||
it 'has the correct data for merge request latest_merge_request_diff' do
|
||||
expect(imported_mr.latest_merge_request_diff_id).to eq(imported_mr.merge_request_diffs.maximum(:id))
|
||||
end
|
||||
|
||||
it 'imports diff files' do
|
||||
expect(imported_mr.merge_request_diff.merge_request_diff_files.count).to eq(1)
|
||||
end
|
||||
|
||||
context 'diff commits' do
|
||||
it 'imports diff commits' do
|
||||
expect(imported_mr.merge_request_diff.merge_request_diff_commits.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'assigns committer and author details to diff commits' do
|
||||
commit = imported_mr.merge_request_diff.merge_request_diff_commits.first
|
||||
|
||||
expect(commit.commit_author_id).not_to be_nil
|
||||
expect(commit.committer_id).not_to be_nil
|
||||
end
|
||||
|
||||
it 'assigns the correct commit users to diff commits' do
|
||||
commit = MergeRequestDiffCommit.find_by(sha: 'COMMIT1')
|
||||
|
||||
expect(commit.commit_author.name).to eq('Commit Author')
|
||||
expect(commit.commit_author.email).to eq('gitlab@example.com')
|
||||
expect(commit.committer.name).to eq('Committer')
|
||||
expect(commit.committer.email).to eq('committer@example.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'labels' do
|
||||
let(:attributes) do
|
||||
{
|
||||
'label_links' => [
|
||||
{ 'label' => { 'title' => 'imported label 1', 'type' => 'ProjectLabel' } },
|
||||
{ 'label' => { 'title' => 'imported label 2', 'type' => 'ProjectLabel' } }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'imports labels' do
|
||||
expect(imported_mr.labels.pluck(:title)).to contain_exactly('imported label 1', 'imported label 2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'milestone' do
|
||||
let(:attributes) { { 'milestone' => { 'title' => 'imported milestone' } } }
|
||||
|
||||
it 'imports milestone' do
|
||||
expect(imported_mr.milestone.title).to eq(attributes.dig('milestone', 'title'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,71 +3,72 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::RepositoryPipeline do
|
||||
describe '#run' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:parent) { create(:project) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
let_it_be(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:parent) { create(:project) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
let_it_be(:bulk_import_configuration) { create(:bulk_import_configuration, bulk_import: bulk_import) }
|
||||
|
||||
let_it_be(:entity) do
|
||||
create(
|
||||
:bulk_import_entity,
|
||||
:project_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'My Destination Repository',
|
||||
destination_namespace: parent.full_path,
|
||||
project: parent
|
||||
)
|
||||
let_it_be(:entity) do
|
||||
create(
|
||||
:bulk_import_entity,
|
||||
:project_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: 'My Destination Repository',
|
||||
destination_namespace: parent.full_path,
|
||||
project: parent
|
||||
)
|
||||
end
|
||||
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: project_data) }
|
||||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(extracted_data)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
|
||||
|
||||
describe '#run' do
|
||||
context 'successfully imports repository' do
|
||||
let(:project_data) do
|
||||
{
|
||||
'httpUrlToRepo' => 'http://test.git'
|
||||
}
|
||||
end
|
||||
|
||||
subject { described_class.new(context) }
|
||||
let(:project_data) { { 'httpUrlToRepo' => 'http://test.git' } }
|
||||
|
||||
it 'imports new repository into destination project' do
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: project_data))
|
||||
end
|
||||
url = project_data['httpUrlToRepo'].sub("://", "://oauth2:#{bulk_import_configuration.access_token}@")
|
||||
|
||||
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |repository_service|
|
||||
url = project_data['httpUrlToRepo'].sub("://", "://oauth2:#{bulk_import_configuration.access_token}@")
|
||||
expect(repository_service).to receive(:import_repository).with(url).and_return 0
|
||||
end
|
||||
expect(context.portable).to receive(:ensure_repository)
|
||||
expect(context.portable.repository).to receive(:fetch_as_mirror).with(url)
|
||||
|
||||
subject.run
|
||||
pipeline.run
|
||||
end
|
||||
end
|
||||
|
||||
context 'blocked local networks' do
|
||||
let(:project_data) do
|
||||
{
|
||||
'httpUrlToRepo' => 'http://localhost/foo.git'
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab.config.gitlab).to receive(:host).and_return('notlocalhost.gitlab.com')
|
||||
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: project_data))
|
||||
end
|
||||
end
|
||||
|
||||
subject { described_class.new(context) }
|
||||
let(:project_data) { { 'httpUrlToRepo' => 'http://localhost/foo.git' } }
|
||||
|
||||
it 'imports new repository into destination project' do
|
||||
subject.run
|
||||
expect(context.entity.failed?).to be_truthy
|
||||
allow(Gitlab.config.gitlab).to receive(:host).and_return('notlocalhost.gitlab.com')
|
||||
allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
|
||||
|
||||
pipeline.run
|
||||
|
||||
expect(context.entity.failed?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#after_run' do
|
||||
it 'executes housekeeping service after import' do
|
||||
service = instance_double(Repositories::HousekeepingService)
|
||||
|
||||
expect(Repositories::HousekeepingService).to receive(:new).with(context.portable, :gc).and_return(service)
|
||||
expect(service).to receive(:execute)
|
||||
|
||||
pipeline.after_run(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ RSpec.describe BulkImports::Projects::Stage do
|
|||
[2, BulkImports::Common::Pipelines::LabelsPipeline],
|
||||
[3, BulkImports::Projects::Pipelines::IssuesPipeline],
|
||||
[4, BulkImports::Common::Pipelines::BoardsPipeline],
|
||||
[4, BulkImports::Projects::Pipelines::MergeRequestsPipeline],
|
||||
[5, BulkImports::Common::Pipelines::UploadsPipeline],
|
||||
[6, BulkImports::Common::Pipelines::EntityFinisher]
|
||||
]
|
||||
|
|
|
@ -280,7 +280,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
|
|||
end
|
||||
|
||||
context 'metrics' do
|
||||
let(:histogram) { double(:histogram) }
|
||||
let(:histogram) { double(:histogram).as_null_object }
|
||||
let(:counter) { double('counter', increment: true) }
|
||||
|
||||
before do
|
||||
|
@ -315,7 +315,6 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
|
|||
)
|
||||
|
||||
expect(counter).to receive(:increment)
|
||||
allow(histogram).to receive(:observe).with({ importer: :bitbucket_server_importer }, anything)
|
||||
|
||||
subject.execute
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::DataBuilder::Build do
|
||||
let!(:tag_names) { %w(tag-1 tag-2) }
|
||||
let(:runner) { create(:ci_runner, :instance, tag_list: tag_names.map { |n| ActsAsTaggableOn::Tag.create!(name: n)}) }
|
||||
let(:user) { create(:user) }
|
||||
let(:user) { create(:user, :public_email) }
|
||||
let(:build) { create(:ci_build, :running, runner: runner, user: user) }
|
||||
|
||||
describe '.build' do
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::DataBuilder::Pipeline do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user) { create(:user, :public_email) }
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
let_it_be_with_reload(:pipeline) do
|
||||
|
@ -46,7 +46,7 @@ RSpec.describe Gitlab::DataBuilder::Pipeline do
|
|||
name: user.name,
|
||||
username: user.username,
|
||||
avatar_url: user.avatar_url(only_path: false),
|
||||
email: user.email
|
||||
email: user.public_email
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -267,6 +267,35 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
|
|||
end
|
||||
end
|
||||
|
||||
context 'pipeline_schedule' do
|
||||
let(:relation_sym) { :pipeline_schedules }
|
||||
let(:relation_hash) do
|
||||
{
|
||||
"id": 3,
|
||||
"created_at": "2016-07-22T08:55:44.161Z",
|
||||
"updated_at": "2016-07-22T08:55:44.161Z",
|
||||
"description": "pipeline schedule",
|
||||
"ref": "main",
|
||||
"cron": "0 4 * * 0",
|
||||
"cron_timezone": "UTC",
|
||||
"active": value,
|
||||
"project_id": project.id
|
||||
}
|
||||
end
|
||||
|
||||
subject { created_object.active }
|
||||
|
||||
[true, false].each do |v|
|
||||
context "when relation_hash has active set to #{v}" do
|
||||
let(:value) { v }
|
||||
|
||||
it "the created object is not active" do
|
||||
expect(created_object.active).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# `project_id`, `described_class.USER_REFERENCES`, noteable_id, target_id, and some project IDs are already
|
||||
# re-assigned by described_class.
|
||||
context 'Potentially hazardous foreign keys' do
|
||||
|
|
|
@ -375,7 +375,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
expect(pipeline_schedule.ref).to eq('master')
|
||||
expect(pipeline_schedule.cron).to eq('0 4 * * 0')
|
||||
expect(pipeline_schedule.cron_timezone).to eq('UTC')
|
||||
expect(pipeline_schedule.active).to eq(true)
|
||||
expect(pipeline_schedule.active).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -556,7 +556,6 @@ Project:
|
|||
- merge_requests_rebase_enabled
|
||||
- jobs_cache_index
|
||||
- external_authorization_classification_label
|
||||
- external_webhook_token
|
||||
- pages_https_only
|
||||
- merge_requests_disable_committers_approval
|
||||
- require_password_to_approve
|
||||
|
|
33
spec/lib/gitlab/unicode_spec.rb
Normal file
33
spec/lib/gitlab/unicode_spec.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Gitlab::Unicode do
|
||||
describe described_class::BIDI_REGEXP do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:bidi_string, :match) do
|
||||
"\u2066" | true # left-to-right isolate
|
||||
"\u2067" | true # right-to-left isolate
|
||||
"\u2068" | true # first strong isolate
|
||||
"\u2069" | true # pop directional isolate
|
||||
"\u202a" | true # left-to-right embedding
|
||||
"\u202b" | true # right-to-left embedding
|
||||
"\u202c" | true # pop directional formatting
|
||||
"\u202d" | true # left-to-right override
|
||||
"\u202e" | true # right-to-left override
|
||||
"\u2066foobar" | true
|
||||
"" | false
|
||||
"foo" | false
|
||||
"\u2713" | false # checkmark
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:utf8_string) { bidi_string.encode("utf-8") }
|
||||
|
||||
it "matches only the bidi characters" do
|
||||
expect(utf8_string.match?(subject)).to eq(match)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -36,5 +36,26 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do
|
|||
is_expected.to eq(code)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unicode control characters are used' do
|
||||
let(:lang) { 'javascript' }
|
||||
let(:tokens) { lexer.lex(code, continue: false) }
|
||||
let(:code) do
|
||||
<<~JS
|
||||
#!/usr/bin/env node
|
||||
|
||||
var accessLevel = "user";
|
||||
if (accessLevel != "user // Check if admin ") {
|
||||
console.log("You are an admin.");
|
||||
}
|
||||
JS
|
||||
end
|
||||
|
||||
it 'highlights the control characters' do
|
||||
message = "Potentially unwanted character detected: Unicode BiDi Control"
|
||||
|
||||
is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -121,4 +121,16 @@ RSpec.describe Ci::BuildMetadata do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'set_cancel_gracefully' do
|
||||
it 'sets cancel_gracefully' do
|
||||
build.set_cancel_gracefully
|
||||
|
||||
expect(build.cancel_gracefully?).to be true
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(build.cancel_gracefully?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,8 @@ RSpec.describe Ci::Build do
|
|||
|
||||
it { is_expected.to respond_to(:has_trace?) }
|
||||
it { is_expected.to respond_to(:trace) }
|
||||
it { is_expected.to respond_to(:set_cancel_gracefully) }
|
||||
it { is_expected.to respond_to(:cancel_gracefully?) }
|
||||
|
||||
it { is_expected.to delegate_method(:merge_request?).to(:pipeline) }
|
||||
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
|
||||
|
@ -5386,4 +5388,23 @@ RSpec.describe Ci::Build do
|
|||
create(:ci_build)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#runner_features' do
|
||||
subject do
|
||||
build.save!
|
||||
build.cancel_gracefully?
|
||||
end
|
||||
|
||||
let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
|
||||
it 'cannot cancel gracefully' do
|
||||
expect(subject).to be false
|
||||
end
|
||||
|
||||
it 'can cancel gracefully' do
|
||||
build.set_cancel_gracefully
|
||||
|
||||
expect(subject).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
include StubRequests
|
||||
include Ci::SourcePipelineHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user) { create(:user, :public_email) }
|
||||
let_it_be(:namespace) { create_default(:namespace).freeze }
|
||||
let_it_be(:project) { create_default(:project, :repository).freeze }
|
||||
|
||||
|
|
|
@ -188,6 +188,16 @@ RSpec.describe Discussion, ResolvableDiscussion do
|
|||
it "returns true" do
|
||||
expect(subject.can_resolve?(current_user)).to be true
|
||||
end
|
||||
|
||||
context 'when noteable is locked' do
|
||||
before do
|
||||
allow(subject.noteable).to receive(:discussion_locked?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.can_resolve?(current_user)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the signed in user can push to the project" do
|
||||
|
@ -200,8 +210,11 @@ RSpec.describe Discussion, ResolvableDiscussion do
|
|||
end
|
||||
|
||||
context "when the noteable has no author" do
|
||||
before do
|
||||
subject.noteable.author = nil
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
expect(noteable).to receive(:author).and_return(nil)
|
||||
expect(subject.can_resolve?(current_user)).to be true
|
||||
end
|
||||
end
|
||||
|
@ -213,8 +226,11 @@ RSpec.describe Discussion, ResolvableDiscussion do
|
|||
end
|
||||
|
||||
context "when the noteable has no author" do
|
||||
before do
|
||||
subject.noteable.author = nil
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
expect(noteable).to receive(:author).and_return(nil)
|
||||
expect(subject.can_resolve?(current_user)).to be false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1341,6 +1341,7 @@ RSpec.describe Namespace do
|
|||
context 'refreshing project access on updating share_with_group_lock' do
|
||||
let(:group) { create(:group, share_with_group_lock: false) }
|
||||
let(:project) { create(:project, :private, group: group) }
|
||||
let(:another_project) { create(:project, :private, group: group) }
|
||||
|
||||
let_it_be(:shared_with_group_one) { create(:group) }
|
||||
let_it_be(:shared_with_group_two) { create(:group) }
|
||||
|
@ -1353,6 +1354,7 @@ RSpec.describe Namespace do
|
|||
shared_with_group_one.add_developer(group_one_user)
|
||||
shared_with_group_two.add_developer(group_two_user)
|
||||
create(:project_group_link, group: shared_with_group_one, project: project)
|
||||
create(:project_group_link, group: shared_with_group_one, project: another_project)
|
||||
create(:project_group_link, group: shared_with_group_two, project: project)
|
||||
end
|
||||
|
||||
|
@ -1360,6 +1362,9 @@ RSpec.describe Namespace do
|
|||
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker)
|
||||
.to receive(:perform_async).with(project.id).once
|
||||
|
||||
expect(AuthorizedProjectUpdate::ProjectRecalculateWorker)
|
||||
.to receive(:perform_async).with(another_project.id).once
|
||||
|
||||
execute_update
|
||||
end
|
||||
|
||||
|
@ -1392,11 +1397,23 @@ RSpec.describe Namespace do
|
|||
stub_feature_flags(specialized_worker_for_group_lock_update_auth_recalculation: false)
|
||||
end
|
||||
|
||||
it 'refreshes the permissions of the members of the old and new namespace' do
|
||||
it 'updates authorizations leading to users from shared groups losing access', :sidekiq_inline do
|
||||
expect { execute_update }
|
||||
.to change { group_one_user.authorized_projects.include?(project) }.from(true).to(false)
|
||||
.and change { group_two_user.authorized_projects.include?(project) }.from(true).to(false)
|
||||
end
|
||||
|
||||
it 'updates the authorizations in a non-blocking manner' do
|
||||
expect(AuthorizedProjectsWorker).to(
|
||||
receive(:bulk_perform_async)
|
||||
.with([[group_one_user.id]])).once
|
||||
|
||||
expect(AuthorizedProjectsWorker).to(
|
||||
receive(:bulk_perform_async)
|
||||
.with([[group_two_user.id]])).once
|
||||
|
||||
execute_update
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue