Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-16 12:12:38 +00:00
parent d75ac09b4a
commit 93fb07b8c9
100 changed files with 1243 additions and 188 deletions

View File

@ -791,3 +791,6 @@ Style/ClassAndModuleChildren:
Fips/OpenSSL:
Enabled: false
Gemspec/AvoidExecutingGit:
Enabled: false

View File

@ -0,0 +1,25 @@
import Tracking from '~/tracking';
function addBlobLinksTracking(containerSelector, eventsToTrack) {
const containerEl = document.querySelector(containerSelector);
if (!containerEl) {
return;
}
const eventName = 'click_link';
const label = 'file_line_action';
containerEl.addEventListener('click', (e) => {
eventsToTrack.forEach((event) => {
if (e.target.matches(event.selector)) {
Tracking.event(undefined, eventName, {
label,
property: event.property,
});
}
});
});
}
export default addBlobLinksTracking;

View File

@ -6,6 +6,7 @@ import Code from './code';
import CodeBlockHighlight from './code_block_highlight';
import FootnoteReference from './footnote_reference';
import FootnoteDefinition from './footnote_definition';
import Frontmatter from './frontmatter';
import Heading from './heading';
import HardBreak from './hard_break';
import HorizontalRule from './horizontal_rule';
@ -37,6 +38,7 @@ export default Extension.create({
CodeBlockHighlight.name,
FootnoteReference.name,
FootnoteDefinition.name,
Frontmatter.name,
HardBreak.name,
Heading.name,
HorizontalRule.name,

View File

@ -155,7 +155,7 @@ const defaultSerializerConfig = {
},
inline: true,
}),
[Frontmatter.name]: (state, node) => {
[Frontmatter.name]: preserveUnchanged((state, node) => {
const { language } = node.attrs;
const syntax = {
toml: '+++',
@ -168,7 +168,7 @@ const defaultSerializerConfig = {
state.ensureNewLine();
state.write(syntax);
state.closeBlock(node);
},
}),
[Figure.name]: renderHTMLNode('figure'),
[FigureCaption.name]: renderHTMLNode('figcaption'),
[HardBreak.name]: preserveUnchanged(renderHardBreak),

View File

@ -185,6 +185,14 @@ const factorySpecs = {
identifier: hastNode.properties.identifier,
}),
},
frontmatter: {
type: 'block',
selector: 'frontmatter',
getAttrs: (hastNode) => ({
language: hastNode.properties.language,
}),
},
};
const SANITIZE_ALLOWLIST = ['level', 'identifier', 'numeric', 'language', 'url', 'isReference'];
@ -239,6 +247,9 @@ export default () => {
'definition',
'linkReference',
'imageReference',
'yaml',
'toml',
'json',
],
});

View File

@ -2,10 +2,14 @@ import { pick } from 'lodash';
import normalize from 'mdurl/encode';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';
import remarkRehype, { all } from 'remark-rehype';
import rehypeRaw from 'rehype-raw';
const skipFrontmatterHandler = (language) => (h, node) =>
h(node.position, 'frontmatter', { language }, [{ type: 'text', value: node.value }]);
const skipRenderingHandlers = {
footnoteReference: (h, node) =>
h(node.position, 'footnoteReference', { identifier: node.identifier, label: node.label }, []),
@ -61,12 +65,16 @@ const skipRenderingHandlers = {
all(h, node),
);
},
toml: skipFrontmatterHandler('toml'),
yaml: skipFrontmatterHandler('yaml'),
json: skipFrontmatterHandler('json'),
};
const createParser = ({ skipRendering = [] }) => {
return unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkFrontmatter, ['yaml', 'toml', { type: 'json', marker: ';' }])
.use(remarkRehype, {
allowDangerousHtml: true,
handlers: {

View File

@ -4,6 +4,7 @@ import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import LineHighlighter from '~/blob/line_highlighter';
import initBlobBundle from '~/blob_edit/blob_bundle';
import addBlobLinksTracking from '~/blob/blob_links_tracking';
export default () => {
new LineHighlighter(); // eslint-disable-line no-new
@ -11,10 +12,16 @@ export default () => {
// eslint-disable-next-line no-new
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number], .diff-line-num[data-line-number] *',
'.file-line-num[data-line-number], .file-line-num[data-line-number] *',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
const eventsToTrack = [
{ selector: '.file-line-blame', property: 'blame' },
{ selector: '.file-line-num', property: 'link' },
];
addBlobLinksTracking('#blob-content-holder', eventsToTrack);
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
const fileBlobPermalinkUrl =
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');

View File

@ -46,6 +46,9 @@ export default {
const { name, status } = this.group;
return `${name} - ${status.label}`;
},
jobGroupClasses() {
return [this.cssClassJobName, `job-${this.group.status.group}`];
},
},
errorCaptured(err, _vm, info) {
reportToSentry('job_group_dropdown', `error: ${err}, info: ${info}`);
@ -68,7 +71,7 @@ export default {
type="button"
data-toggle="dropdown"
data-display="static"
:class="cssClassJobName"
:class="jobGroupClasses"
class="dropdown-menu-toggle gl-pipeline-job-width! gl-pr-4!"
>
<div class="gl-display-flex gl-align-items-stretch gl-justify-content-space-between">

View File

@ -200,6 +200,9 @@ export default {
},
{ 'gl-rounded-lg': this.isBridge },
this.cssClassJobName,
{
[`job-${this.status.group}`]: this.isSingleItem,
},
];
},
},

View File

@ -27,6 +27,7 @@ query getBlobInfo(
fileType
language
path
blamePath
editBlobPath
gitpodBlobUrl
ideEditPath

View File

@ -51,6 +51,10 @@ export default {
required: false,
default: null,
},
blamePath: {
type: String,
required: true,
},
},
computed: {
lines() {
@ -76,6 +80,7 @@ export default {
:number="startingFrom + index + 1"
:content="line"
:language="language"
:blame-path="blamePath"
/>
</div>
<div v-else class="gl-display-flex">

View File

@ -1,5 +1,6 @@
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { setAttributes } from '~/lib/utils/dom_utils';
import { BIDI_CHARS, BIDI_CHARS_CLASS_LIST, BIDI_CHAR_TOOLTIP } from '../constants';
@ -7,6 +8,7 @@ export default {
directives: {
SafeHtml: GlSafeHtmlDirective,
},
mixins: [glFeatureFlagMixin()],
props: {
number: {
type: Number,
@ -20,6 +22,10 @@ export default {
type: String,
required: true,
},
blamePath: {
type: String,
required: true,
},
},
computed: {
formattedContent() {
@ -33,9 +39,6 @@ export default {
return content;
},
firstLineClass() {
return { 'gl-mt-3!': this.number === 1 };
},
},
methods: {
wrapBidiChar(bidiChar) {
@ -56,22 +59,26 @@ export default {
</script>
<template>
<div class="gl-display-flex">
<div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers">
<div
class="gl-p-0! gl-absolute gl-z-index-3 diff-line-num gl-border-r gl-display-flex line-links line-numbers"
>
<a
v-if="glFeatures.fileLineBlame"
class="gl-user-select-none gl-shadow-none! file-line-blame"
:href="`${blamePath}#L${number}`"
></a>
<a
:id="`L${number}`"
class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num"
:class="firstLineClass"
class="gl-user-select-none gl-shadow-none! file-line-num"
:href="`#L${number}`"
:data-line-number="number"
data-testid="line-number-anchor"
>
{{ number }}
</a>
</div>
<pre
class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight gl-line-height-normal"
:class="firstLineClass"
class="gl-p-0! gl-w-full gl-overflow-visible! gl-border-none! code highlight gl-line-height-normal"
><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre>
</div>
</template>

View File

@ -199,6 +199,7 @@ export default {
:starting-from="firstChunk.startingFrom"
:is-highlighted="firstChunk.isHighlighted"
:language="firstChunk.language"
:blame-path="blob.blamePath"
/>
<gl-loading-icon v-if="isLoading" size="sm" class="gl-my-5" />
@ -213,6 +214,7 @@ export default {
:is-highlighted="chunk.isHighlighted"
:chunk-index="index"
:language="chunk.language"
:blame-path="blob.blamePath"
@appear="highlightChunk"
/>
</div>

View File

@ -75,6 +75,11 @@ export default {
required: false,
default: false,
},
canInviteMembers: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -321,7 +326,7 @@ export default {
<rect width="280" height="20" x="10" y="130" rx="4" />
</gl-skeleton-loader>
</template>
<template #dropdown-footer>
<template v-if="canInviteMembers" #dropdown-footer>
<gl-dropdown-divider />
<gl-dropdown-item @click="closeDropdown">
<invite-members-trigger

View File

@ -319,6 +319,7 @@ export default {
:assignees="workItemAssignees.assignees.nodes"
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
:work-item-type="workItemType"
:can-invite-members="workItemAssignees.canInviteMembers"
@error="error = $event"
/>
<work-item-labels

View File

@ -24,6 +24,7 @@ fragment WorkItem on WorkItem {
... on WorkItemWidgetAssignees {
type
allowsMultipleAssignees
canInviteMembers
assignees {
nodes {
...User

View File

@ -202,6 +202,10 @@
float: none;
border-left: 1px solid $gray-100;
.file-line-num {
@include gl-min-w-9;
}
i {
float: none;
margin-right: 0;

View File

@ -49,8 +49,9 @@
a {
font-family: $monospace-font;
display: block;
white-space: nowrap;
@include gl-display-flex;
@include gl-justify-content-end;
i,
svg {
@ -91,3 +92,55 @@ td.line-numbers {
cursor: pointer;
text-decoration: underline wavy $red-500;
}
.blob-viewer {
.line-numbers {
// for server-side-rendering
.line-links {
@include gl-display-flex;
&:first-child {
margin-top: 10px;
}
&:last-child {
margin-bottom: 10px;
}
}
// for client
&.line-links {
min-width: 6rem;
border-bottom-left-radius: 0;
+ pre {
margin-left: 6rem;
}
}
}
.line-links {
&:hover a::before,
&:focus-within a::before {
@include gl-visibility-visible;
}
}
.file-line-num {
min-width: 4.5rem;
@include gl-justify-content-end;
@include gl-flex-grow-1;
@include gl-pr-3;
}
.file-line-blame {
@include gl-ml-3;
}
.file-line-num,
.file-line-blame {
@include gl-align-items-center;
@include gl-display-flex;
}
}

View File

@ -98,32 +98,33 @@
}
}
@mixin line-number-link($color) {
min-width: $gl-spacing-scale-9;
@mixin line-link($color, $icon) {
&::before {
@include gl-display-none;
@include gl-visibility-hidden;
@include gl-align-self-center;
@include gl-mt-2;
@include gl-mr-2;
@include gl-w-4;
@include gl-h-4;
@include gl-absolute;
@include gl-left-3;
background-color: $color;
mask-image: asset_url('icons-stacked.svg#link');
@include gl-mr-1;
@include gl-w-5;
@include gl-h-5;
background-color: rgba($color, 0.3);
mask-image: asset_url('icons-stacked.svg##{$icon}');
mask-repeat: no-repeat;
mask-size: cover;
mask-position: center;
content: '';
}
&:hover::before {
@include gl-display-inline-block;
&:hover {
&::before {
background-color: rgba($color, 0.6);
}
}
}
&:focus::before {
@include gl-display-inline-block;
@mixin line-hover-bg($color: $white-normal) {
&:hover,
&:focus-within {
background-color: darken($color, 10);
}
}

View File

@ -127,7 +127,15 @@ $dark-il: #de935f;
.code.dark {
// Line numbers
.file-line-num {
@include line-number-link($dark-line-num-color);
@include line-link($white, 'link');
}
.file-line-blame {
@include line-link($white, 'git');
}
.line-links {
@include line-hover-bg($dark-main-bg);
}
.line-numbers,

View File

@ -120,7 +120,15 @@ $monokai-gh: #75715e;
// Line numbers
.file-line-num {
@include line-number-link($monokai-line-num-color);
@include line-link($white, 'link');
}
.file-line-blame {
@include line-link($white, 'git');
}
.line-links {
@include line-hover-bg($monokai-bg);
}
.line-numbers,

View File

@ -25,7 +25,15 @@
// Line numbers
.file-line-num {
@include line-number-link($black-transparent);
@include line-link($black, 'link');
}
.file-line-blame {
@include line-link($black, 'git');
}
.line-links {
@include line-hover-bg;
}
.line-numbers,

View File

@ -123,7 +123,15 @@ $solarized-dark-il: #2aa198;
// Line numbers
.file-line-num {
@include line-number-link($solarized-dark-line-color);
@include line-link($white, 'link');
}
.file-line-blame {
@include line-link($white, 'git');
}
.line-links {
@include line-hover-bg($solarized-dark-pre-bg);
}
.line-numbers,

View File

@ -109,7 +109,15 @@ $solarized-light-il: #2aa198;
@include hljs-override('title.class_.inherited__', $solarized-light-no);
// Line numbers
.file-line-num {
@include line-number-link($solarized-light-line-color);
@include line-link($black, 'link');
}
.file-line-blame {
@include line-link($black, 'git');
}
.line-links {
@include line-hover-bg($solarized-light-pre-bg);
}
.line-numbers,

View File

@ -95,7 +95,15 @@ $white-gc-bg: #eaf2f5;
// Line numbers
.file-line-num {
@include line-number-link($black-transparent);
@include line-link($black, 'link');
}
.file-line-blame {
@include line-link($black, 'git');
}
.line-links {
@include line-hover-bg;
}
.line-numbers,

View File

@ -228,3 +228,17 @@
.progress-bar.bg-primary {
background-color: var(--blue-500, $blue-500) !important;
}
.ci-job-component {
.job-failed {
background-color: var(--red-50, $red-50);
}
}
.gl-dark {
.ci-job-component {
.job-failed {
background-color: var(--gray-200, $gray-200);
}
}
}

View File

@ -43,6 +43,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:highlight_js, @project)
push_frontend_feature_flag(:file_line_blame, @project)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end

View File

@ -19,6 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project)
push_frontend_feature_flag(:highlight_js, @project)
push_frontend_feature_flag(:file_line_blame, @project)
push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
end

View File

@ -37,6 +37,7 @@ class ProjectsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project)
push_frontend_feature_flag(:highlight_js, @project)
push_frontend_feature_flag(:file_line_blame, @project)
push_frontend_feature_flag(:increase_page_size_exponentially, @project)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
push_licensed_feature(:security_orchestration_policies) if @project.present? && @project.licensed_feature_available?(:security_orchestration_policies)

View File

@ -61,10 +61,14 @@ module Types
Types::TimeTracking::TimelogCategoryType.connection_type,
null: true,
description: "Timelog categories for the namespace.",
_deprecated_feature_flag: :timelog_categories
alpha: { milestone: '15.3' }
markdown_field :description_html, null: true
def timelog_categories
object.timelog_categories if Feature.enabled?(:timelog_categories)
end
def cross_project_pipeline_available?
object.licensed_feature_available?(:cross_project_pipelines)
end

View File

@ -442,14 +442,14 @@ module Types
Types::TimeTracking::TimelogCategoryType.connection_type,
null: true,
description: "Timelog categories for the project.",
_deprecated_feature_flag: :timelog_categories
alpha: { milestone: '15.3' }
field :fork_targets, Types::NamespaceType.connection_type,
resolver: Resolvers::Projects::ForkTargetsResolver,
description: 'Namespaces in which the current user can fork the project into.'
def timelog_categories
object.project_namespace.timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
def label(title:)

View File

@ -2,11 +2,12 @@
class Approval < ApplicationRecord
include CreatedAtFilterable
include Importable
belongs_to :user
belongs_to :merge_request
validates :merge_request_id, presence: true
validates :merge_request_id, presence: true, unless: :importing?
validates :user_id, presence: true, uniqueness: { scope: [:merge_request_id] }
scope :with_user, -> { joins(:user) }

View File

@ -10,8 +10,6 @@ class LooseForeignKeys::DeletedRecord < Gitlab::Database::SharedModel
partitioned_by :partition, strategy: :sliding_list,
next_partition_if: -> (active_partition) do
return false if Feature.disabled?(:lfk_automatic_partition_creation)
oldest_record_in_partition = LooseForeignKeys::DeletedRecord
.select(:id, :created_at)
.for_partition(active_partition)
@ -23,8 +21,6 @@ class LooseForeignKeys::DeletedRecord < Gitlab::Database::SharedModel
oldest_record_in_partition.created_at < PARTITION_DURATION.ago
end,
detach_partition_if: -> (partition) do
return false if Feature.disabled?(:lfk_automatic_partition_dropping)
!LooseForeignKeys::DeletedRecord
.for_partition(partition)
.status_pending

View File

@ -45,7 +45,7 @@ module Issues
# current_user (defined in BaseService) is not available within run_after_commit block
user = current_user
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id)
NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
Issues::PlacementWorker.perform_async(nil, issue.project_id)
Namespaces::OnboardingIssueCreatedWorker.perform_async(issue.project.namespace_id)
end

View File

@ -1,5 +1,5 @@
- title = capture do
= html_escape(_('This commit was signed with a verified signature, but the committer email is %{strong_open}not verified%{strong_close} to belong to the same user.')) % { strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
= html_escape(_('This commit was signed with a verified signature, but the committer email is not associated with the GPG Key.'))
- locals = { signature: signature, title: title, label: _('Unverified'), css_class: ['invalid'], icon: 'status_notfound_borderless', show_user: true }

View File

@ -1,13 +1,17 @@
#blob-content.file-content.code.js-syntax-highlight
- offset = defined?(first_line_number) ? first_line_number : 1
.line-numbers
.line-numbers{ class: "gl-p-0\!" }
- if blob.data.present?
- link = blob_link if defined?(blob_link)
- blame_link = project_blame_path(@project, tree_join(@ref, blob.path))
- blob.data.each_line.each_with_index do |_, index|
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
%a.file-line-num.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= i
.line-links.diff-line-num
- if Feature.enabled?(:file_line_blame)
%a.file-line-blame{ href: "#{blame_link}#L#{i}" }
%a.file-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= i
- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
%pre.code.highlight

View File

@ -13,7 +13,11 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker
worker_resource_boundary :cpu
weight 2
def perform(issue_id, user_id)
attr_reader :issuable_class
def perform(issue_id, user_id, issuable_class = 'Issue')
@issuable_class = issuable_class.constantize
return unless objects_found?(issue_id, user_id)
::EventCreateService.new.open_issue(issuable, user)
@ -25,8 +29,4 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker
.new(project: issuable.project, current_user: user)
.execute(issuable)
end
def issuable_class
Issue
end
end

View File

@ -0,0 +1,8 @@
---
name: file_line_blame
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92538
rollout_issue_url:
milestone: '15.3'
type: development
group: group::source code
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: lfk_automatic_partition_creation
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346907
milestone: '14.6'
type: development
group: group::sharding
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: lfk_automatic_partition_dropping
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346908
milestone: '14.6'
type: development
group: group::sharding
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: rate_limit_gitlab_shell_by_ip
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91599
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367998
milestone: '15.3'
type: development
group: group::source code
default_enabled: false

View File

@ -12183,7 +12183,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupstoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
| <a id="groupsubgroupcreationlevel"></a>`subgroupCreationLevel` | [`String`](#string) | Permission level required to create subgroups within the group. |
| <a id="grouptemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
| <a id="grouptimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="grouptimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the namespace. |
| <a id="grouptotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
| <a id="grouptotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
@ -14768,7 +14768,7 @@ Contains statistics about a milestone.
| <a id="namespacesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
| <a id="namespacestoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
| <a id="namespacetemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
| <a id="namespacetimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="namespacetimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the namespace. |
| <a id="namespacetotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
| <a id="namespacetotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
| <a id="namespacevisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
@ -15536,7 +15536,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectsuggestioncommitmessage"></a>`suggestionCommitMessage` | [`String`](#string) | Commit message used to apply merge request suggestions. |
| <a id="projecttaglist"></a>`tagList` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.12. Use `topics`. |
| <a id="projectterraformstates"></a>`terraformStates` | [`TerraformStateConnection`](#terraformstateconnection) | Terraform states associated with the project. (see [Connections](#connections)) |
| <a id="projecttimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the project. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
| <a id="projecttimelogcategories"></a>`timelogCategories` **{warning-solid}** | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | **Introduced** in 15.3. This feature is in Alpha. It can be changed or removed at any time. Timelog categories for the project. |
| <a id="projecttopics"></a>`topics` | [`[String!]`](#string) | List of project topics. |
| <a id="projectuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
| <a id="projectvisibility"></a>`visibility` | [`String`](#string) | Visibility of the project. |

View File

@ -60,7 +60,7 @@ See the [Rails guides](https://guides.rubyonrails.org/action_mailer_basics.html#
# The email address including the %{key} placeholder that will be replaced to reference the
# item being replied to. This %{key} should be included in its entirety within the email
# address and not replaced by another value.
# For example: emailadress+%{key}@gmail.com.
# For example: emailaddress+%{key}@gmail.com.
# The placeholder must appear in the "user" part of the address (before the `@`). It can be omitted but some features,
# including Service Desk, may not work properly.
address: "gitlab-incoming+%{key}@gmail.com"

View File

@ -8,21 +8,37 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> Moved to GitLab Premium in 13.9.
Code Owners define who owns specific files or directories in a repository.
Code Owners define who develops and maintains a feature, and own the resulting
files or directories in a repository.
- The users you define as Code Owners are displayed in the UI when you browse directories.
- You can set your merge requests so they must be approved by Code Owners before merge.
- You can protect a branch and allow only Code Owners to approve changes to the branch.
If you don't want to use Code Owners for approvals, you can
[configure rules](merge_requests/approvals/rules.md) instead.
Use Code Owners and approvers together with
[approval rules](merge_requests/approvals/rules.md) to build a flexible approval
workflow:
- Use **Code Owners** to define the users who have domain expertise for specific paths in your repository.
- Use **Approvers** and **Approval rules** to define domains of expertise (such as a security team)
that are not scoped to specific file paths in your repository.
- **Approvers** define the users.
- **Approval rules** define when these users can approve work, and whether or not their approval is required.
For example:
| Type | Name | Scope | Comment |
|------|------|--------|------------|
| Approval rule | UX | All files | A user experience (UX) team member reviews the user experience of all changes made in your project. |
| Approval rule | Security | All files | A security team member reviews all changes for vulnerabilities. |
| Code Owner approval rule | Frontend: Code Style | `*.css` files | A frontend engineer reviews CSS file changes for adherence to project style standards. |
| Code Owner approval rule | Backend: Code Review | `*.rb` files | A backend engineer reviews the logic and code style of Ruby files. |
## Set up Code Owners
You can use Code Owners to specify users or [shared groups](members/share_project_with_groups.md)
that are responsible for specific files and directories in a repository.
To set up Code Owners:
Create a `CODEOWNERS` file to specify users or [shared groups](members/share_project_with_groups.md)
that are responsible for specific files and directories in a repository. Each repository
can have a single `CODEOWNERS` file. To create it:
1. Choose the location where you want to specify Code Owners:
- In the root directory of the repository

View File

@ -7,7 +7,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Merge request approval rules **(PREMIUM)**
Approval rules define how many [approvals](index.md) a merge request must receive before it can
be merged, and which users should do the approving. You can define approval rules:
be merged, and which users should do the approving. They can be used in conjunction
with [Code owners](#code-owners-as-eligible-approvers) to ensure that changes are
reviewed both by the group maintaining the feature, and any groups responsible
for specific areas of oversight.
You can define approval rules:
- [As project defaults](#add-an-approval-rule).
- [Per merge request](#edit-or-override-merge-request-approval-rules).

View File

@ -2027,3 +2027,18 @@
07_02__gitlab_specific_markdown__task_list_items__004:
spec_txt_example_position: 678
source_specification: gitlab
07_03__gitlab_specific_markdown__front_matter__001:
spec_txt_example_position: 679
source_specification: gitlab
07_03__gitlab_specific_markdown__front_matter__002:
spec_txt_example_position: 680
source_specification: gitlab
07_03__gitlab_specific_markdown__front_matter__003:
spec_txt_example_position: 681
source_specification: gitlab
07_03__gitlab_specific_markdown__front_matter__004:
spec_txt_example_position: 682
source_specification: gitlab
07_03__gitlab_specific_markdown__front_matter__005:
spec_txt_example_position: 683
source_specification: gitlab

View File

@ -813,8 +813,7 @@
<a id="user-content-bar" class="anchor" href="#bar" aria-hidden="true"></a>Bar</h2>
<p data-sourcepos="6:1-6:3" dir="auto">Baz</p>
wysiwyg: |-
<hr>
<h2>Foo</h2>
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>Foo</code></pre>
<h2>Bar</h2>
<p>Baz</p>
04_03__leaf_blocks__setext_headings__018:
@ -834,8 +833,7 @@
<copy-code></copy-code>
</div>
wysiwyg: |-
<hr>
<hr>
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code></code></pre>
04_03__leaf_blocks__setext_headings__020:
canonical: |
<ul>
@ -7745,3 +7743,76 @@
<p data-sourcepos="3:3-3:20">text in loose list</p>
</li>
</ul>
07_03__gitlab_specific_markdown__front_matter__001:
canonical: |
<pre>
<code>
title: YAML front matter
</code>
</pre>
static: |-
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">YAML front matter</span></span></code></pre>
<copy-code></copy-code>
</div>
wysiwyg: |-
<pre language="yaml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>title: YAML front matter</code></pre>
07_03__gitlab_specific_markdown__front_matter__002:
canonical: |
<pre>
<code>
title: TOML front matter
</code>
</pre>
static: |-
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-toml" lang="toml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="toml"><span class="err">title:</span> <span class="err">TOML</span> <span class="err">front</span> <span class="err">matter</span></span></code></pre>
<copy-code></copy-code>
</div>
wysiwyg: |-
<pre language="toml" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>title: TOML front matter</code></pre>
07_03__gitlab_specific_markdown__front_matter__003:
canonical: |
<pre>
<code>
{
"title": "JSON front matter"
}
</code>
</pre>
static: |-
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="1:1-5:3" class="code highlight js-syntax-highlight language-json" lang="json" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="json"><span class="p">{</span></span>
<span id="LC2" class="line" lang="json"><span class="w"> </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"JSON front matter"</span></span>
<span id="LC3" class="line" lang="json"><span class="p">}</span></span></code></pre>
<copy-code></copy-code>
</div>
wysiwyg: |-
<pre language="json" class="content-editor-code-block undefined code highlight" isfrontmatter="true"><code>{
"title": "JSON front matter"
}</code></pre>
07_03__gitlab_specific_markdown__front_matter__004:
canonical: |
<p>text</p>
<hr>
<h2>title: YAML front matter</h2>
static: |-
<p data-sourcepos="1:1-1:4" dir="auto">text</p>
<hr data-sourcepos="3:1-3:3">
<h2 data-sourcepos="4:1-5:3" dir="auto">
<a id="user-content-title-yaml-front-matter" class="anchor" href="#title-yaml-front-matter" aria-hidden="true"></a>title: YAML front matter</h2>
wysiwyg: |-
<p>text</p>
<hr>
<h2>title: YAML front matter</h2>
07_03__gitlab_specific_markdown__front_matter__005:
canonical: |
<hr>
<h2>title: YAML front matter</h2>
static: |-
<hr data-sourcepos="1:2-1:4">
<h2 data-sourcepos="2:1-3:3" dir="auto">
<a id="user-content-title-yaml-front-matter" class="anchor" href="#title-yaml-front-matter" aria-hidden="true"></a>title: YAML front matter</h2>
wysiwyg: |-
<hr>
<h2>title: YAML front matter</h2>

View File

@ -2203,3 +2203,27 @@
- [~] inapplicable
text in loose list
07_03__gitlab_specific_markdown__front_matter__001: |
---
title: YAML front matter
---
07_03__gitlab_specific_markdown__front_matter__002: |
+++
title: TOML front matter
+++
07_03__gitlab_specific_markdown__front_matter__003: |
;;;
{
"title": "JSON front matter"
}
;;;
07_03__gitlab_specific_markdown__front_matter__004: |
text
---
title: YAML front matter
---
07_03__gitlab_specific_markdown__front_matter__005: |2
---
title: YAML front matter
---

View File

@ -1702,12 +1702,11 @@
"type": "doc",
"content": [
{
"type": "horizontalRule"
},
{
"type": "heading",
"type": "frontmatter",
"attrs": {
"level": 2
"language": "yaml",
"class": "code highlight",
"isFrontmatter": true
},
"content": [
{
@ -1759,10 +1758,12 @@
"type": "doc",
"content": [
{
"type": "horizontalRule"
},
{
"type": "horizontalRule"
"type": "frontmatter",
"attrs": {
"language": "yaml",
"class": "code highlight",
"isFrontmatter": true
}
}
]
}
@ -20670,3 +20671,114 @@
Inapplicable task list items not yet implemented for WYSYWIG
07_02__gitlab_specific_markdown__task_list_items__004: |-
Inapplicable task list items not yet implemented for WYSYWIG
07_03__gitlab_specific_markdown__front_matter__001: |-
{
"type": "doc",
"content": [
{
"type": "frontmatter",
"attrs": {
"language": "yaml",
"class": "code highlight",
"isFrontmatter": true
},
"content": [
{
"type": "text",
"text": "title: YAML front matter"
}
]
}
]
}
07_03__gitlab_specific_markdown__front_matter__002: |-
{
"type": "doc",
"content": [
{
"type": "frontmatter",
"attrs": {
"language": "toml",
"class": "code highlight",
"isFrontmatter": true
},
"content": [
{
"type": "text",
"text": "title: TOML front matter"
}
]
}
]
}
07_03__gitlab_specific_markdown__front_matter__003: |-
{
"type": "doc",
"content": [
{
"type": "frontmatter",
"attrs": {
"language": "json",
"class": "code highlight",
"isFrontmatter": true
},
"content": [
{
"type": "text",
"text": "{\n \"title\": \"JSON front matter\"\n}"
}
]
}
]
}
07_03__gitlab_specific_markdown__front_matter__004: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "text"
}
]
},
{
"type": "horizontalRule"
},
{
"type": "heading",
"attrs": {
"level": 2
},
"content": [
{
"type": "text",
"text": "title: YAML front matter"
}
]
}
]
}
07_03__gitlab_specific_markdown__front_matter__005: |-
{
"type": "doc",
"content": [
{
"type": "horizontalRule"
},
{
"type": "heading",
"attrs": {
"level": 2
},
"content": [
{
"type": "text",
"text": "title: YAML front matter"
}
]
}
]
}

View File

@ -120,3 +120,82 @@ text in loose list
</li>
</ul>
````````````````````````````````
## Front matter
See
[Front matter](https://docs.gitlab.com/ee/user/markdown.html#front-matter) in the GitLab Flavored Markdown documentation.
Front matter is metadata included at the beginning of a Markdown document, preceding the content.
This data can be used by static site generators like Jekyll, Hugo, and many other applications.
YAML front matter:
```````````````````````````````` example gitlab frontmatter
---
title: YAML front matter
---
.
<pre>
<code>
title: YAML front matter
</code>
</pre>
````````````````````````````````
TOML front matter:
```````````````````````````````` example gitlab frontmatter
+++
title: TOML front matter
+++
.
<pre>
<code>
title: TOML front matter
</code>
</pre>
````````````````````````````````
JSON front matter:
```````````````````````````````` example gitlab frontmatter
;;;
{
"title": "JSON front matter"
}
;;;
.
<pre>
<code>
{
"title": "JSON front matter"
}
</code>
</pre>
````````````````````````````````
Front matter blocks should be inserted at the top of the document:
```````````````````````````````` example gitlab frontmatter
text
---
title: YAML front matter
---
.
<p>text</p>
<hr>
<h2>title: YAML front matter</h2>
````````````````````````````````
Front matter block delimiters shouldnt be preceded by space characters:
```````````````````````````````` example gitlab frontmatter
---
title: YAML front matter
---
.
<hr>
<h2>title: YAML front matter</h2>
````````````````````````````````

View File

@ -9723,6 +9723,85 @@ text in loose list
</ul>
````````````````````````````````
## Front matter
See
[Front matter](https://docs.gitlab.com/ee/user/markdown.html#front-matter) in the GitLab Flavored Markdown documentation.
Front matter is metadata included at the beginning of a Markdown document, preceding the content.
This data can be used by static site generators like Jekyll, Hugo, and many other applications.
YAML front matter:
```````````````````````````````` example gitlab frontmatter
---
title: YAML front matter
---
.
<pre>
<code>
title: YAML front matter
</code>
</pre>
````````````````````````````````
TOML front matter:
```````````````````````````````` example gitlab frontmatter
+++
title: TOML front matter
+++
.
<pre>
<code>
title: TOML front matter
</code>
</pre>
````````````````````````````````
JSON front matter:
```````````````````````````````` example gitlab frontmatter
;;;
{
"title": "JSON front matter"
}
;;;
.
<pre>
<code>
{
"title": "JSON front matter"
}
</code>
</pre>
````````````````````````````````
Front matter blocks should be inserted at the top of the document:
```````````````````````````````` example gitlab frontmatter
text
---
title: YAML front matter
---
.
<p>text</p>
<hr>
<h2>title: YAML front matter</h2>
````````````````````````````````
Front matter block delimiters shouldnt be preceded by space characters:
```````````````````````````````` example gitlab frontmatter
---
title: YAML front matter
---
.
<hr>
<h2>title: YAML front matter</h2>
````````````````````````````````
<!-- END TESTS -->
# Appendix: A parsing strategy

View File

@ -39,6 +39,7 @@ module API
container.lfs_http_url_to_repo
end
# rubocop: disable Metrics/AbcSize
def check_allowed(params)
# This is a separate method so that EE can alter its behaviour more
# easily.
@ -47,6 +48,14 @@ module API
check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], actor.key_or_user])
end
if Feature.enabled?(:rate_limit_gitlab_shell_by_ip, actor.user)
rate_limiter = Gitlab::Auth::IpRateLimiter.new(request.ip)
unless rate_limiter.trusted_ip?
check_rate_limit!(:gitlab_shell_operation, scope: [params[:action], params[:project], rate_limiter.ip])
end
end
# Stores some Git-specific env thread-safely
env = parse_env
Gitlab::Git::HookEnv.set(gl_repository, env) if container
@ -101,6 +110,7 @@ module API
response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
end
end
# rubocop: enable Metrics/AbcSize
def send_git_audit_streaming_event(msg)
# Defined in EE

View File

@ -33,6 +33,10 @@ module Gitlab
Rack::Attack::Allow2Ban.banned?(ip)
end
def trusted_ip?
trusted_ips.any? { |netmask| netmask.include?(ip) }
end
private
def skip_rate_limit?
@ -47,10 +51,6 @@ module Gitlab
Gitlab.config.rack_attack.git_basic_auth
end
def trusted_ip?
trusted_ips.any? { |netmask| netmask.include?(ip) }
end
def trusted_ips
strong_memoize(:trusted_ips) do
config.ip_whitelist.map do |proxy|

View File

@ -129,7 +129,7 @@ module Gitlab
# When an assignee (or any other listed association) did not exist in the members mapper, the importer is
# assigned. We only need to assign each user once.
def remove_duplicate_assignees
associations = %w[issue_assignees merge_request_assignees merge_request_reviewers]
associations = %w[issue_assignees merge_request_assignees merge_request_reviewers approvals]
associations.each do |association|
next unless @relation_hash.key?(association)

View File

@ -53,6 +53,7 @@ tree:
- project_members:
- :user
- merge_requests:
- :approvals
- :metrics
- :award_emoji
- :merge_request_assignees
@ -122,6 +123,10 @@ included_attributes:
- :username
author:
- :name
approvals:
- :user_id
- :created_at
- :updated_at
ci_cd_settings:
- :group_runners_enabled
- :runner_token_expiration_interval
@ -776,6 +781,9 @@ excluded_attributes:
- :repository_size_limit
- :external_webhook_token
- :incident_management_issuable_escalation_statuses
approvals:
- :id
- :merge_request_id
namespaces:
- :runners_token
- :runners_token_encrypted

View File

@ -20964,6 +20964,9 @@ msgstr ""
msgid "Insights"
msgstr ""
msgid "Insights|Configure a custom report for insights into your group processes such as amount of issues, bugs, and merge requests per month. %{linkStart}How do I configure an insights report?%{linkEnd}"
msgstr ""
msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)."
msgstr ""
@ -39946,7 +39949,7 @@ msgstr ""
msgid "This commit was signed with a different user's verified signature."
msgstr ""
msgid "This commit was signed with a verified signature, but the committer email is %{strong_open}not verified%{strong_close} to belong to the same user."
msgid "This commit was signed with a verified signature, but the committer email is not associated with the GPG Key."
msgstr ""
msgid "This commit was signed with an %{strong_open}unverified%{strong_close} signature."

View File

@ -158,6 +158,7 @@
"raphael": "^2.2.7",
"raw-loader": "^4.0.2",
"rehype-raw": "^6.1.1",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",

View File

@ -13,8 +13,7 @@ module QA
:github_personal_access_token,
:github_repository_path,
:gitlab_repository_path,
:personal_namespace,
:description
:personal_namespace
attr_reader :repository_storage
@ -27,7 +26,8 @@ module QA
:template_name,
:import,
:import_status,
:import_error
:import_error,
:description
attribute :group do
Group.fabricate! do |group|
@ -459,10 +459,12 @@ module QA
response = post(request_url(api_housekeeping_path), nil)
unless response.code == HTTP_STATUS_CREATED
raise ResourceQueryError,
"Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
end
return if response.code == HTTP_STATUS_CREATED
raise(
ResourceQueryError,
"Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
)
end
# Gets project statistics.

View File

@ -65,10 +65,8 @@ module QA
end
def verify_repository_import
expect(imported_project.api_response).to include(
description: 'Project for github import test',
import_error: nil
)
expect(imported_project.reload!.description).to eq('Project for github import test')
expect(imported_project.api_response[:import_error]).to be_nil
end
def verify_commits_import

View File

@ -44,10 +44,6 @@ module QA
it_behaves_like 'successful project creation'
end
after do
project.remove_via_api!
end
end
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Gemspec
# Checks that `git` is not executed in a vendored gemspec file.
# In some installed containers, `git` is not available.
#
# @example
#
# # bad
# Gem::Specification.new do |spec|
# spec.test_files = `git ls-files -- test/*`.split("\n")
# end
#
# # good
# Gem::Specification.new do |spec|
# spec.name = 'your_cool_gem_name'
# spec.test_files += Dir.glob('test/**/*')
# end
#
class AvoidExecutingGit < Base
include RangeHelp
MSG = 'Do not execute `git` in gemspec.'
# @!method gem_specification(node)
def_node_matcher :gem_specification, <<~PATTERN
(block
(send
(const
(const {cbase nil?} :Gem) :Specification) :new)
...)
PATTERN
def_node_matcher :send_node?, <<~PATTERN
send
PATTERN
def_node_search :executes_string, <<~PATTERN
$(xstr (str $_))
PATTERN
def on_block(block_node)
return unless gem_specification(block_node)
block_node.descendants.each do |node|
next unless send_node?(node)
str = executes_string(node)
str.each do |execute_node, val|
break unless val.start_with?('git ')
add_offense(execute_node, message: message)
end
end
end
end
end
end
end

5
scripts/lint-vendored-gems.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
# Rubocop doesn't have a good way to run excluded files without a separate invocation:
# https://github.com/rubocop/rubocop/issues/6323
find vendor/gems -name \*.gemspec | xargs bundle exec rubocop --only Gemspec/AvoidExecutingGit

View File

@ -59,7 +59,8 @@ class StaticAnalysis
Task.new(%w[yarn run block-dependencies], 1),
Task.new(%w[yarn run check-dependencies], 1),
Task.new(%w[scripts/lint-rugged], 1),
Task.new(%w[scripts/gemfile_lock_changed.sh], 1)
Task.new(%w[scripts/gemfile_lock_changed.sh], 1),
Task.new(%w[scripts/lint-vendored-gems.sh], 1)
].compact.freeze
def run_tasks!(options = {})

View File

@ -93,7 +93,7 @@ RSpec.describe 'GPG signed commits' do
page.find('.gpg-status-box', text: 'Unverified').click
within '.popover' do
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.'
expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not associated with the GPG Key.'
expect(page).to have_content 'Bette Cartwright'
expect(page).to have_content '@bette.cartwright'
expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}"

View File

@ -3193,6 +3193,28 @@
"created_at": "2020-01-10T11:21:21.235Z",
"state": "unreviewed"
}
],
"approvals": [
{
"user_id": 1,
"created_at": "2020-01-07T11:21:21.235Z",
"updated_at": "2020-01-08T11:21:21.235Z"
},
{
"user_id": 15,
"created_at": "2020-01-07T11:21:21.235Z",
"updated_at": "2020-01-08T11:21:21.235Z"
},
{
"user_id": 16,
"created_at": "2020-01-07T11:21:21.235Z",
"updated_at": "2020-01-08T11:21:21.235Z"
},
{
"user_id": 6,
"created_at": "2020-01-07T11:21:21.235Z",
"updated_at": "2020-01-08T11:21:21.235Z"
}
]
},
{
@ -3462,7 +3484,8 @@
}
],
"merge_request_assignees": [],
"merge_request_reviewers": []
"merge_request_reviewers": [],
"approvals": []
},
{
"id": 15,

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import addBlobLinksTracking from '~/blob/blob_links_tracking';
import Tracking from '~/tracking';
describe('Blob links Tracking', () => {
const eventName = 'click_link';
const label = 'file_line_action';
const eventsToTrack = [
{ selector: '.file-line-blame', property: 'blame' },
{ selector: '.file-line-num', property: 'link' },
];
const [blameLinkClickEvent, numLinkClickEvent] = eventsToTrack;
beforeEach(() => {
setHTMLFixture(`
<div id="blob-content-holder">
<div class="line-links diff-line-num">
<a href="#L5" class="file-line-blame"></a>
<a id="L5" href="#L5" data-line-number="5" class="file-line-num">5</a>
</div>
<pre id="LC5">Line 5 content</pre>
</div>
`);
addBlobLinksTracking('#blob-content-holder', eventsToTrack);
jest.spyOn(Tracking, 'event');
});
afterEach(() => {
resetHTMLFixture();
});
it('tracks blame link click event', () => {
const blameButton = document.querySelector(blameLinkClickEvent.selector);
blameButton.click();
expect(Tracking.event).toHaveBeenCalledWith(undefined, eventName, {
label,
property: blameLinkClickEvent.property,
});
});
it('tracks num link click event', () => {
const numLinkButton = document.querySelector(numLinkClickEvent.selector);
numLinkButton.click();
expect(Tracking.event).toHaveBeenCalledWith(undefined, eventName, {
label,
property: numLinkClickEvent.property,
});
});
it("doesn't fire tracking if the user clicks on any element that is not a link", () => {
const codeLine = document.querySelector('#LC5');
codeLine.click();
expect(Tracking.event).not.toHaveBeenCalled();
});
});

View File

@ -5,6 +5,7 @@ import Code from '~/content_editor/extensions/code';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
import Frontmatter from '~/content_editor/extensions/frontmatter';
import HardBreak from '~/content_editor/extensions/hard_break';
import HTMLNodes from '~/content_editor/extensions/html_nodes';
import Heading from '~/content_editor/extensions/heading';
@ -38,6 +39,7 @@ const tiptapEditor = createTestEditor({
CodeBlockHighlight,
FootnoteDefinition,
FootnoteReference,
Frontmatter,
HardBreak,
Heading,
HorizontalRule,
@ -71,6 +73,7 @@ const {
div,
footnoteDefinition,
footnoteReference,
frontmatter,
hardBreak,
heading,
horizontalRule,
@ -99,6 +102,7 @@ const {
codeBlock: { nodeType: CodeBlockHighlight.name },
footnoteDefinition: { nodeType: FootnoteDefinition.name },
footnoteReference: { nodeType: FootnoteReference.name },
frontmatter: { nodeType: Frontmatter.name },
hardBreak: { nodeType: HardBreak.name },
heading: { nodeType: Heading.name },
horizontalRule: { nodeType: HorizontalRule.name },
@ -1190,6 +1194,45 @@ _world_.
),
),
},
{
markdown: `
---
title: 'layout'
---
`,
expectedDoc: doc(
frontmatter(
{ ...source("---\ntitle: 'layout'\n---"), language: 'yaml' },
"title: 'layout'",
),
),
},
{
markdown: `
+++
title: 'layout'
+++
`,
expectedDoc: doc(
frontmatter(
{ ...source("+++\ntitle: 'layout'\n+++"), language: 'toml' },
"title: 'layout'",
),
),
},
{
markdown: `
;;;
{ title: 'layout' }
;;;
`,
expectedDoc: doc(
frontmatter(
{ ...source(";;;\n{ title: 'layout' }\n;;;"), language: 'json' },
"{ title: 'layout' }",
),
),
},
];
const runOnly = examples.find((example) => example.only === true);

View File

@ -16,6 +16,7 @@ import FigureCaption from '~/content_editor/extensions/figure_caption';
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
import FootnotesSection from '~/content_editor/extensions/footnotes_section';
import Frontmatter from '~/content_editor/extensions/frontmatter';
import HardBreak from '~/content_editor/extensions/hard_break';
import Heading from '~/content_editor/extensions/heading';
import HorizontalRule from '~/content_editor/extensions/horizontal_rule';
@ -52,6 +53,7 @@ const tiptapEditor = createTestEditor({
FootnoteDefinition,
FootnoteReference,
FootnotesSection,
Frontmatter,
Figure,
FigureCaption,
HardBreak,

View File

@ -157,7 +157,7 @@ describe('Configure Feature Flags Modal', () => {
beforeEach(factory.bind(null, { isRotating: true }));
it('should disable the project name input', async () => {
expect(findProjectNameInput().attributes('disabled')).toBeTruthy();
expect(findProjectNameInput().attributes('disabled')).toBe('true');
});
});
});

View File

@ -230,4 +230,32 @@ console.log('Hola');
});
});
});
describe('when skipping the rendering of frontmatter types', () => {
it.each`
type | input
${'yaml'} | ${'---\ntitle: page\n---'}
${'toml'} | ${'+++\ntitle: page\n+++'}
${'json'} | ${';;;\ntitle: page\n;;;'}
`('transforms $type nodes into frontmatter html tags', async ({ input, type }) => {
const result = await markdownToAST(input, [type]);
expectInRoot(
result,
expect.objectContaining({
type: 'element',
tagName: 'frontmatter',
properties: {
language: type,
},
children: [
{
type: 'text',
value: 'title: page',
},
],
}),
);
});
});
});

View File

@ -105,7 +105,10 @@ describe('operation settings external dashboard component', () => {
it('uses description text', () => {
const description = formGroup.find('small');
expect(description.text()).not.toBeFalsy();
const expectedDescription =
"Choose whether to display dashboard metrics in UTC or the user's local timezone.";
expect(description.text()).toBe(expectedDescription);
});
});
@ -138,7 +141,10 @@ describe('operation settings external dashboard component', () => {
it('uses description text', () => {
const description = formGroup.find('small');
expect(description.text()).not.toBeFalsy();
const expectedDescription =
'Add a button to the metrics dashboard linking directly to your existing external dashboard.';
expect(description.text()).toBe(expectedDescription);
});
});
@ -151,7 +157,6 @@ describe('operation settings external dashboard component', () => {
});
it('defaults to externalDashboardUrl', () => {
expect(input.attributes().value).toBeTruthy();
expect(input.attributes().value).toBe(externalDashboardUrl);
});

View File

@ -226,7 +226,6 @@ describe('Timezone Dropdown', () => {
it('returns the correct object if the identifier exists', () => {
const res = findTimezoneByIdentifier(tzList, identifier);
expect(res).toBeTruthy();
expect(res).toBe(tzList[2]);
});

View File

@ -8,6 +8,7 @@ export const simpleViewerMock = {
language: 'javascript',
path: 'some_file.js',
webPath: 'some_file.js',
blamePath: 'blame/file.js',
editBlobPath: 'some_file.js/edit',
gitpodBlobUrl: 'https://gitpod.io#path/to/blob.js',
ideEditPath: 'some_file.js/ide/edit',

View File

@ -130,7 +130,7 @@ describe('AlertDetails', () => {
environmentData = { name: null, path: null };
mountComponent();
expect(findTableFieldValueByKey('Environment').text()).toBeFalsy();
expect(findTableFieldValueByKey('Environment').text()).toBe('');
});
});

View File

@ -10,16 +10,26 @@ const DEFAULT_PROPS = {
number: 2,
content: '// Line content',
language: 'javascript',
blamePath: 'blame/file.js',
};
describe('Chunk Line component', () => {
let wrapper;
const fileLineBlame = true;
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(ChunkLine, { propsData: { ...DEFAULT_PROPS, ...props } });
wrapper = shallowMountExtended(ChunkLine, {
propsData: { ...DEFAULT_PROPS, ...props },
provide: {
glFeatures: {
fileLineBlame,
},
},
});
};
const findLink = () => wrapper.findByTestId('line-number-anchor');
const findLineLink = () => wrapper.find('.file-line-num');
const findBlameLink = () => wrapper.find('.file-line-blame');
const findContent = () => wrapper.findByTestId('content');
const findWrappedBidiChars = () => wrapper.findAllByTestId('bidi-wrapper');
@ -46,14 +56,22 @@ describe('Chunk Line component', () => {
});
});
it('renders a blame link', () => {
expect(findBlameLink().attributes()).toMatchObject({
href: `${DEFAULT_PROPS.blamePath}#L${DEFAULT_PROPS.number}`,
});
expect(findBlameLink().text()).toBe('');
});
it('renders a line number', () => {
expect(findLink().attributes()).toMatchObject({
expect(findLineLink().attributes()).toMatchObject({
'data-line-number': `${DEFAULT_PROPS.number}`,
href: `#L${DEFAULT_PROPS.number}`,
id: `L${DEFAULT_PROPS.number}`,
});
expect(findLink().text()).toBe(DEFAULT_PROPS.number.toString());
expect(findLineLink().text()).toBe(DEFAULT_PROPS.number.toString());
});
it('renders content', () => {

View File

@ -10,6 +10,7 @@ const DEFAULT_PROPS = {
startingFrom: 140,
totalLines: 50,
language: 'javascript',
blamePath: 'blame/file.js',
};
describe('Chunk component', () => {
@ -76,6 +77,7 @@ describe('Chunk component', () => {
number: DEFAULT_PROPS.startingFrom + 1,
content: splitContent[0],
language: DEFAULT_PROPS.language,
blamePath: DEFAULT_PROPS.blamePath,
});
});
});

View File

@ -40,8 +40,9 @@ describe('Source Viewer component', () => {
const chunk2 = generateContent('// Some source code 2', 70);
const content = chunk1 + chunk2;
const path = 'some/path.js';
const blamePath = 'some/blame/path.js';
const fileType = 'javascript';
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, fileType };
const DEFAULT_BLOB_DATA = { language, rawTextBlob: content, path, blamePath, fileType };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
const createComponent = async (blob = {}) => {

View File

@ -9,6 +9,7 @@ import { stripTypenames } from 'helpers/graphql_helpers';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
@ -35,6 +36,7 @@ describe('WorkItemAssignees component', () => {
const findAssigneeLinks = () => wrapper.findAllComponents(GlLink);
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger);
const findEmptyState = () => wrapper.findByTestId('empty-state');
const findAssignSelfButton = () => wrapper.findByTestId('assign-self');
@ -57,6 +59,7 @@ describe('WorkItemAssignees component', () => {
currentUserQueryHandler = successCurrentUserQueryHandler,
updateWorkItemMutationHandler = successUpdateWorkItemMutationHandler,
allowsMultipleAssignees = true,
canInviteMembers = false,
canUpdate = true,
} = {}) => {
const apolloProvider = createMockApollo(
@ -89,6 +92,7 @@ describe('WorkItemAssignees component', () => {
allowsMultipleAssignees,
workItemType: TASK_TYPE_NAME,
canUpdate,
canInviteMembers,
},
attachTo: document.body,
apolloProvider,
@ -446,4 +450,18 @@ describe('WorkItemAssignees component', () => {
});
});
});
describe('invite members', () => {
it('does not render `Invite members` link if user has no permission to invite members', () => {
createComponent();
expect(findInviteMembersTrigger().exists()).toBe(false);
});
it('renders `Invite members` link if user has a permission to invite members', () => {
createComponent({ canInviteMembers: true });
expect(findInviteMembersTrigger().exists()).toBe(true);
});
});
});

View File

@ -48,6 +48,7 @@ export const workItemQueryResponse = {
__typename: 'WorkItemWidgetAssignees',
type: 'ASSIGNEES',
allowsMultipleAssignees: true,
canInviteMembers: true,
assignees: {
nodes: mockAssignees,
},
@ -110,6 +111,7 @@ export const updateWorkItemMutationResponse = {
__typename: 'WorkItemWidgetAssignees',
type: 'ASSIGNEES',
allowsMultipleAssignees: true,
canInviteMembers: true,
assignees: {
nodes: [mockAssignees[0]],
},
@ -136,6 +138,7 @@ export const workItemResponseFactory = ({
assigneesWidgetPresent = true,
weightWidgetPresent = true,
confidential = false,
canInviteMembers = false,
parent = mockParent.parent,
} = {}) => ({
data: {
@ -169,6 +172,7 @@ export const workItemResponseFactory = ({
__typename: 'WorkItemWidgetAssignees',
type: 'ASSIGNEES',
allowsMultipleAssignees,
canInviteMembers,
assignees: {
nodes: mockAssignees,
},

View File

@ -308,19 +308,19 @@ describe('Fly out sidebar navigation', () => {
describe('canShowSubItems', () => {
it('returns true if on desktop size', () => {
expect(canShowSubItems()).toBeTruthy();
expect(canShowSubItems()).toBe(true);
});
it('returns false if on mobile size', () => {
breakpointSize = 'xs';
expect(canShowSubItems()).toBeFalsy();
expect(canShowSubItems()).toBe(false);
});
});
describe('canShowActiveSubItems', () => {
it('returns true by default', () => {
expect(canShowActiveSubItems(el)).toBeTruthy();
expect(canShowActiveSubItems(el)).toBe(true);
});
it('returns false when active & expanded sidebar', () => {
@ -329,7 +329,7 @@ describe('Fly out sidebar navigation', () => {
setSidebar(sidebar);
expect(canShowActiveSubItems(el)).toBeFalsy();
expect(canShowActiveSubItems(el)).toBe(false);
});
it('returns true when active & collapsed sidebar', () => {
@ -339,7 +339,7 @@ describe('Fly out sidebar navigation', () => {
setSidebar(sidebar);
expect(canShowActiveSubItems(el)).toBeTruthy();
expect(canShowActiveSubItems(el)).toBe(true);
});
});

View File

@ -15,7 +15,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
}
end
subject { described_class.new(ip) }
subject(:rate_limiter) { described_class.new(ip) }
before do
stub_rack_attack_setting(options)
@ -25,7 +25,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
end
after do
subject.reset!
rate_limiter.reset!
end
describe '#register_fail!' do
@ -86,7 +86,7 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
end
end
context 'when IP is whitlisted' do
context 'when IP is allow listed' do
let(:ip) { '127.0.0.1' }
it_behaves_like 'skips the rate limiter'
@ -97,4 +97,20 @@ RSpec.describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_cachin
it_behaves_like 'skips the rate limiter'
end
describe '#trusted_ip?' do
subject { rate_limiter.trusted_ip? }
context 'when ip is in the trusted list' do
let(:ip) { '127.0.0.1' }
it { is_expected.to be_truthy }
end
context 'when ip is not in the trusted list' do
let(:ip) { '10.0.0.1' }
it { is_expected.to be_falsey }
end
end
end

View File

@ -818,3 +818,6 @@ bulk_import_export:
- group
service_desk_setting:
- file_template_project
approvals:
- user
- merge_request

View File

@ -272,6 +272,11 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
expect(ProjectLabel.count).to eq(3)
end
it 'has merge request approvals' do
expect(MergeRequest.find_by(title: 'MR1').approvals.pluck(:user_id)).to contain_exactly(@user.id, *@existing_members.map(&:id))
expect(MergeRequest.find_by(title: 'MR2').approvals).to be_empty
end
it 'has no group labels' do
expect(GroupLabel.count).to eq(0)
end

View File

@ -100,6 +100,13 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
expect(subject.first['notes'].first['author']).not_to be_empty
end
it 'has merge request approvals' do
approval = subject.first['approvals'].first
expect(approval).not_to be_nil
expect(approval['user_id']).to eq(user.id)
end
it 'has merge request resource label events' do
expect(subject.first['resource_label_events']).not_to be_empty
end
@ -483,6 +490,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeSaver do
create(:label_priority, label: group_label, priority: 1)
milestone = create(:milestone, project: project)
merge_request = create(:merge_request, source_project: project, milestone: milestone, assignees: [user], reviewers: [user])
create(:approval, merge_request: merge_request, user: user)
ci_build = create(:ci_build, project: project, when: nil)
ci_build.pipeline.update!(project: project)

View File

@ -913,3 +913,7 @@ MergeRequest::CleanupSchedule:
- completed_at
- created_at
- updated_at
Approval:
- user_id
- created_at
- updated_at

View File

@ -27,7 +27,7 @@ RSpec.describe Gitlab::Octokit::Middleware do
it_behaves_like 'Public URL'
end
context 'when the URL is a localhost adresss' do
context 'when the URL is a localhost address' do
let(:env) { { url: 'http://127.0.0.1' } }
context 'when localhost requests are not allowed' do

View File

@ -94,14 +94,6 @@ RSpec.describe LooseForeignKeys::DeletedRecord, type: :model do
end
it { is_expected.to eq(true) }
context 'when the lfk_automatic_partition_creation FF is off' do
before do
stub_feature_flags(lfk_automatic_partition_creation: false)
end
it { is_expected.to eq(false) }
end
end
end
@ -126,14 +118,6 @@ RSpec.describe LooseForeignKeys::DeletedRecord, type: :model do
end
it { is_expected.to eq(true) }
context 'when the lfk_automatic_partition_dropping FF is off' do
before do
stub_feature_flags(lfk_automatic_partition_dropping: false)
end
it { is_expected.to eq(false) }
end
end
end

View File

@ -26,31 +26,20 @@ RSpec.describe ProjectStatistics do
end
describe 'statistics columns' do
it "support values up to 8 exabytes" do
statistics.update!(
commit_count: 8.exabytes - 1,
repository_size: 2.exabytes,
wiki_size: 1.exabytes,
lfs_objects_size: 2.exabytes,
build_artifacts_size: 1.exabyte,
snippets_size: 1.exabyte,
pipeline_artifacts_size: 512.petabytes - 1,
uploads_size: 512.petabytes,
container_registry_size: 12.petabytes
)
statistics.reload
expect(statistics.commit_count).to eq(8.exabytes - 1)
expect(statistics.repository_size).to eq(2.exabytes)
expect(statistics.wiki_size).to eq(1.exabytes)
expect(statistics.lfs_objects_size).to eq(2.exabytes)
expect(statistics.build_artifacts_size).to eq(1.exabyte)
expect(statistics.storage_size).to eq(8.exabytes - 1)
expect(statistics.snippets_size).to eq(1.exabyte)
expect(statistics.pipeline_artifacts_size).to eq(512.petabytes - 1)
expect(statistics.uploads_size).to eq(512.petabytes)
expect(statistics.container_registry_size).to eq(12.petabytes)
it "supports bigint values" do
expect do
statistics.update!(
commit_count: 3.gigabytes,
repository_size: 3.gigabytes,
wiki_size: 3.gigabytes,
lfs_objects_size: 3.gigabytes,
build_artifacts_size: 3.gigabytes,
snippets_size: 3.gigabytes,
pipeline_artifacts_size: 3.gigabytes,
uploads_size: 3.gigabytes,
container_registry_size: 3.gigabytes
)
end.not_to raise_error
end
end

View File

@ -145,6 +145,18 @@ RSpec.describe 'getting group information' do
expect(graphql_data_at(:group, :timelogCategories, :nodes))
.to contain_exactly(a_graphql_entity_for(timelog_category))
end
context 'when timelog_categories flag is disabled' do
before do
stub_feature_flags(timelog_categories: false)
end
it 'returns no timelog categories' do
post_graphql(group_query(group), current_user: user2)
expect(graphql_data_at(:group, :timelogCategories)).to be_nil
end
end
end
context 'for N+1 queries' do

View File

@ -229,6 +229,18 @@ RSpec.describe 'getting project information' do
expect(graphql_data_at(:project, :timelogCategories, :nodes))
.to contain_exactly(a_graphql_entity_for(timelog_category))
end
context 'when timelog_categories flag is disabled' do
before do
stub_feature_flags(timelog_categories: false)
end
it 'returns no timelog categories' do
post_graphql(query, current_user: current_user)
expect(graphql_data_at(:project, :timelogCategories)).to be_nil
end
end
end
context 'for N+1 queries' do

View File

@ -376,10 +376,17 @@ RSpec.describe API::Internal::Base do
shared_examples 'rate limited request' do
let(:action) { 'git-upload-pack' }
let(:actor) { key }
let(:rate_limiter) { double(:rate_limiter, ip: "127.0.0.1", trusted_ip?: false) }
before do
allow(::Gitlab::Auth::IpRateLimiter).to receive(:new).with("127.0.0.1").and_return(rate_limiter)
end
it 'is throttled by rate limiter' do
allow(::Gitlab::ApplicationRateLimiter).to receive(:threshold).and_return(1)
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:gitlab_shell_operation, scope: [action, project.full_path, actor]).twice.and_call_original
expect(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(:gitlab_shell_operation, scope: [action, project.full_path, "127.0.0.1"]).and_call_original
request
@ -402,6 +409,28 @@ RSpec.describe API::Internal::Base do
subject
end
end
context 'when rate_limit_gitlab_shell_by_ip feature flag is disabled' do
before do
stub_feature_flags(rate_limit_gitlab_shell_by_ip: false)
end
it 'is not throttled by rate limiter' do
expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
subject
end
end
context 'when the IP is in a trusted range' do
let(:rate_limiter) { double(:rate_limiter, ip: "127.0.0.1", trusted_ip?: true) }
it 'is not throttled by rate limiter' do
expect(::Gitlab::ApplicationRateLimiter).not_to receive(:throttled?)
subject
end
end
end
context "access granted" do

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/gemspec/avoid_executing_git'
RSpec.describe RuboCop::Cop::Gemspec::AvoidExecutingGit do
subject(:cop) { described_class.new }
it 'flags violation for executing git' do
expect_offense(<<~RUBY)
Gem::Specification.new do |gem|
gem.executable = `git ls-files -- bin/*`.split("\\n").map{ |f| File.basename(f) }
^^^^^^^^^^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
gem.files = `git ls-files`.split("\\n")
^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\\n")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not execute `git` in gemspec.
end
RUBY
end
it 'does not flag violation for using a glob' do
expect_no_offenses(<<~RUBY)
Gem::Specification.new do |gem|
gem.files = Dir.glob("lib/**/*.*")
gem.test_files = Dir.glob("spec/**/**/*.*")
end
RUBY
end
end

View File

@ -69,6 +69,12 @@ RSpec.describe Issues::CreateService do
expect(issue.issue_customer_relations_contacts).to be_empty
end
it 'calls NewIssueWorker with correct arguments' do
expect(NewIssueWorker).to receive(:perform_async).with(Integer, user.id, 'Issue')
issue
end
context 'when a build_service is provided' do
let(:issue) { described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params, build_service: build_service).execute }
@ -143,6 +149,12 @@ RSpec.describe Issues::CreateService do
issue
end
it 'calls NewIssueWorker with correct arguments' do
expect(NewIssueWorker).to receive(:perform_async).with(Integer, reporter.id, 'Issue')
issue
end
context 'when invalid' do
before do
opts.merge!(title: '')

View File

@ -65,6 +65,12 @@ RSpec.describe WorkItems::CreateService do
expect(work_item.description).to eq('please fix')
expect(work_item.work_item_type.base_type).to eq('issue')
end
it 'calls NewIssueWorker with correct arguments' do
expect(NewIssueWorker).to receive(:perform_async).with(Integer, current_user.id, 'WorkItem')
service_result
end
end
context 'when params are invalid' do

View File

@ -24,6 +24,7 @@ RSpec.describe 'projects/blob/_viewer.html.haml' do
before do
assign(:project, project)
assign(:blob, blob)
assign(:ref, 'master')
assign(:id, File.join('master', blob.path))
controller.params[:controller] = 'projects/blob'

View File

@ -74,6 +74,8 @@ RSpec.describe NewIssueWorker do
it 'creates a new event record' do
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
expect(Event.last).to have_attributes(target_id: issue.id, target_type: 'Issue')
end
it 'creates a notification for the mentioned user' do
@ -89,6 +91,14 @@ RSpec.describe NewIssueWorker do
worker.perform(issue.id, user.id)
end
context 'when a class is set' do
it 'creates event with the correct type' do
expect { worker.perform(issue.id, user.id, 'WorkItem') }.to change { Event.count }.from(0).to(1)
expect(Event.last).to have_attributes(target_id: issue.id, target_type: 'WorkItem')
end
end
end
end
end

View File

@ -20,7 +20,7 @@ require (
github.com/johannesboyne/gofakes3 v0.0.0-20220627085814-c3ac35da23b2
github.com/jpillora/backoff v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/prometheus/client_golang v1.12.2
github.com/prometheus/client_golang v1.13.0
github.com/rafaeljusto/redigomock/v3 v3.1.1
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/sirupsen/logrus v1.9.0
@ -89,8 +89,8 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
github.com/shirou/gopsutil/v3 v3.21.2 // indirect

View File

@ -425,9 +425,11 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
@ -949,8 +951,9 @@ github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -964,16 +967,18 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rafaeljusto/redigomock/v3 v3.1.1 h1:SdWE9v+SPy3x6G5hS3aofIJgHJY3OdBJ0BdUTk4dYbA=
github.com/rafaeljusto/redigomock/v3 v3.1.1/go.mod h1:F9zPqz8rMriScZkPtUiLJoLruYcpGo/XXREpeyasREM=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=

View File

@ -8100,6 +8100,13 @@ mdast-util-from-markdown@^1.0.0:
unist-util-stringify-position "^3.0.0"
uvu "^0.5.0"
mdast-util-frontmatter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-frontmatter/-/mdast-util-frontmatter-1.0.0.tgz#ef12469379782e4a0fd995fed60cc3b871e6c819"
integrity sha512-7itKvp0arEVNpCktOET/eLFAYaZ+0cNjVtFtIPxgQ5tV+3i+D4SDDTjTzPWl44LT59PC+xdx+glNTawBdF98Mw==
dependencies:
micromark-extension-frontmatter "^1.0.0"
mdast-util-gfm-autolink-literal@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz#4032dcbaddaef7d4f2f3768ed830475bb22d3970"
@ -8321,6 +8328,15 @@ micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
micromark-util-types "^1.0.1"
uvu "^0.5.0"
micromark-extension-frontmatter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-1.0.0.tgz#612498e6dad87c132c95e25f0918e7cc0cd535f6"
integrity sha512-EXjmRnupoX6yYuUJSQhrQ9ggK0iQtQlpi6xeJzVD5xscyAI+giqco5fdymayZhJMbIFecjnE2yz85S9NzIgQpg==
dependencies:
fault "^2.0.0"
micromark-util-character "^1.0.0"
micromark-util-symbol "^1.0.0"
micromark-extension-gfm-autolink-literal@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz#dc589f9c37eaff31a175bab49f12290edcf96058"
@ -10176,6 +10192,16 @@ rehype-raw@^6.1.1:
hast-util-raw "^7.2.0"
unified "^10.0.0"
remark-frontmatter@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-4.0.1.tgz#84560f7ccef114ef076d3d3735be6d69f8922309"
integrity sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==
dependencies:
"@types/mdast" "^3.0.0"
mdast-util-frontmatter "^1.0.0"
micromark-extension-frontmatter "^1.0.0"
unified "^10.0.0"
remark-gfm@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f"