Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-02 18:11:52 +00:00
parent 3a52eefc27
commit d1ade10ba6
38 changed files with 649 additions and 144 deletions

View File

@ -84,6 +84,7 @@ review-qa-reliable:
- .review-qa-base
- .review:rules:review-qa-reliable
- .parallel-qa-base
retry: 1
variables:
QA_RUN_TYPE: review-qa-reliable
QA_SCENARIO: Test::Instance::Reliable

View File

@ -1 +1 @@
a191a5d10f0772ae2ed6ec869001ddde6d277827
2247949bf06c7e8b28ef629dccb347868ee3abf4

View File

@ -49,6 +49,7 @@ fragment EpicNode on Epic {
__typename
nodes {
__typename
id
color
description
textColor
@ -140,6 +141,7 @@ query childItems(
__typename
nodes {
__typename
id
color
description
textColor

View File

@ -576,18 +576,12 @@ class Project < ApplicationRecord
.where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.arel_table[ProjectFeature.access_level_attribute(feature)]
enabled_feature = access_level_attribute.gt(ProjectFeature::DISABLED).or(access_level_attribute.eq(nil))
with_project_feature.where(enabled_feature)
with_project_feature.merge(ProjectFeature.with_feature_enabled(feature))
}
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level, ->(feature, level) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => level })
with_project_feature.merge(ProjectFeature.with_feature_access_level(feature, level))
}
# Picks projects which use the given programming language
@ -688,37 +682,8 @@ class Project < ApplicationRecord
end
end
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns projects where
# the feature is either public, enabled, or internal with permission for the user.
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
if user&.can_read_all_resources?
with_feature_enabled(feature)
elsif user
min_access_level = ProjectFeature.required_minimum_access_level(feature)
column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature
.where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
{
public_visible: visible,
private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
})
else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible)
end
with_project_feature.merge(ProjectFeature.with_feature_available_for_user(feature, user))
end
def self.projects_user_can(projects, user, action)

View File

@ -83,6 +83,52 @@ class ProjectFeature < ApplicationRecord
end
end
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
feature_access_level_attribute = arel_table[access_level_attribute(feature)]
enabled_feature = feature_access_level_attribute.gt(DISABLED).or(feature_access_level_attribute.eq(nil))
where(enabled_feature)
}
# Picks a feature where the level is exactly that given.
scope :with_feature_access_level, ->(feature, level) {
feature_access_level_attribute = access_level_attribute(feature)
where(project_features: { feature_access_level_attribute => level })
}
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
# they are only available to team members. This scope returns features where
# the feature is either public, enabled, or internal with permission for the user.
# Note: this scope doesn't enforce that the user has access to the projects, it just checks
# that the user has access to the feature. It's important to use this scope with others
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
#
# This method uses an optimised version of `with_feature_access_level` for
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [ENABLED, PUBLIC]
if user&.can_read_all_resources?
with_feature_enabled(feature)
elsif user
min_access_level = required_minimum_access_level(feature)
column = quoted_access_level_column(feature)
where("#{column} IS NULL OR #{column} IN (:public_visible) OR (#{column} = :private_visible AND EXISTS (:authorizations))",
{
public_visible: visible,
private_visible: PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level, related_project_column: 'project_features.project_id')
})
else
# This has to be added to include features whose value is nil in the db
visible << nil
with_feature_access_level(feature, visible)
end
end
def public_pages?
return true unless Gitlab.config.pages.access_control

View File

@ -3,7 +3,7 @@
.form-group
%b= s_('ProjectSettings|Merge commit message template')
%p.text-secondary
- configure_the_merge_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md', anchor: 'merge-commit-message-template')
- configure_the_merge_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
- configure_the_merge_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_merge_commit_message_help_link_url }
= s_('ProjectSettings|The commit message used when merging, if the merge method creates a merge commit. %{link_start}Learn more about syntax and variables.%{link_end}').html_safe % { link_start: configure_the_merge_commit_message_help_link_start, link_end: '</a>'.html_safe }
.mb-2

View File

@ -3,7 +3,7 @@
.form-group
%b= s_('ProjectSettings|Squash commit message template')
%p.text-secondary
- configure_the_squash_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md', anchor: 'squash-commit-message-template')
- configure_the_squash_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
- configure_the_squash_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_squash_commit_message_help_link_url }
= s_('ProjectSettings|The commit message used when squashing commits. %{link_start}Learn more about syntax and variables.%{link_end}').html_safe % { link_start: configure_the_squash_commit_message_help_link_start, link_end: '</a>'.html_safe }
.mb-2

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
all_changed_files = helper.all_changed_files
def get_ci_config_files(files)
files.select do |file|
file.include?('gitlab/ci/config/entry')
end
end
schema_path = 'app/assets/javascripts/editor/schema/ci.json'
has_schema_update = all_changed_files.include?(schema_path)
return if has_schema_update
ci_config_files = get_ci_config_files(all_changed_files)
return if ci_config_files.empty?
file_list = "- #{ci_config_files.map { |path| "`#{path}`" }.join("\n- ")}"
warn "This merge request changed CI config files but did not update the schema. Please consider updating [the schema](#{schema_path}) to reflect these changes:\n#{file_list}"

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class ImproveIndexOnEventsForCalendar < Gitlab::Database::Migration[1.0]
INDEX_NAME = 'index_events_author_id_project_id_action_target_type_created_at'
def up
prepare_async_index :events, [:author_id, :project_id, :action, :target_type, :created_at], name: INDEX_NAME
end
def down
unprepare_async_index :events, [:author_id, :project_id, :action, :target_type, :created_at], name: INDEX_NAME
end
end

View File

@ -0,0 +1 @@
e010b4c12ae8203d9ea8a4c2035be5e7165aba0030f4d5fd0b0f978f84748707

View File

@ -16,7 +16,7 @@ The configuration for doing so depends on your desired outcome.
## Make the repositories read-only
The first thing you'll want to accomplish is to ensure that no changes can be
The first thing you want to accomplish is to ensure that no changes can be
made to your repositories. There's two ways you can accomplish that:
- Either stop Puma to make the internal API unreachable:
@ -46,7 +46,7 @@ made to your repositories. There's two ways you can accomplish that:
## Shut down the GitLab UI
If you don't mind shutting down the GitLab UI, then the easiest approach is to
stop `sidekiq` and `puma`, and you'll effectively ensure that no
stop `sidekiq` and `puma`, and you effectively ensure that no
changes can be made to GitLab:
```shell
@ -63,7 +63,7 @@ sudo gitlab-ctl start puma
## Make the database read-only
If you want to allow users to use the GitLab UI, then you'll need to ensure that
If you want to allow users to use the GitLab UI, then you need to ensure that
the database is read-only:
1. Take a [GitLab backup](../raketasks/backup_restore.md)
@ -113,7 +113,7 @@ the database is read-only:
sudo gitlab-ctl restart postgresql
```
When you're ready to revert the read-only state, you'll need to remove the added
When you're ready to revert the read-only state, you need to remove the added
lines in `/etc/gitlab/gitlab.rb`, and reconfigure GitLab and restart PostgreSQL:
```shell

View File

@ -850,6 +850,32 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/4/projects/56"
```
## Transfer a group to a new parent group / Turn a subgroup to a top-level group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23831) in GitLab 14.6.
Transfer a group to a new parent group or turn a subgroup to a top-level group. Available to administrators and users:
- With the Owner role for the group to transfer.
- With permission to [create a subgroup](../user/group/subgroups/index.md#creating-a-subgroup) in the new parent group if transferring a group.
- With [permission to create a top-level group](../administration/user_settings.md#prevent-users-from-creating-top-level-groups) if turning a subgroup into a top-level group.
```plaintext
POST /groups/:id/transfer
```
Parameters:
| Attribute | Type | Required | Description |
| ------------ | -------------- | -------- | ----------- |
| `id` | integer | yes | ID of the group to transfer. |
| `group_id` | integer | no | ID of the new parent group. When not specified, the group to transfer is instead turned into a top-level group. |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/groups/4/transfer?group_id=7"
```
## Update group
Updates the project group. Only available to group owners and administrators.

View File

@ -634,6 +634,65 @@ Example request:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/approve_all"
```
## List pending members of a group and its subgroups and projects
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332596) in GitLab 14.6.
For a group and its subgroups and projects, get a list of all members in an `awaiting` state and those who are invited but do not have a GitLab account.
This request returns all matching group and project members from all groups and projects in the root group's hierarchy.
When the member is an invited user that has not signed up for a GitLab account yet, the invited email address is returned.
This API endpoint works on top-level groups only. It does not work on subgroups.
This API endpoint requires permission to administer members for the group.
This API endpoint takes [pagination](index.md#pagination) parameters `page` and `per_page` to restrict the list of members.
```plaintext
GET /groups/:id/pending_members
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/pending_members"
```
Example response:
```json
[
{
"id": 168,
"name": "Alex Garcia",
"username": "alex_garcia",
"email": "alex@example.com",
"avatar_url": "http://example.com/uploads/user/avatar/1/cd8.jpeg",
"web_url": "http://example.com/alex_garcia",
"approved": false,
"invited": false
},
{
"id": 169,
"email": "sidney@example.com",
"avatar_url": "http://gravatar.com/../e346561cd8.jpeg",
"approved": false,
"invited": true
},
{
"id": 170,
"email": "zhang@example.com",
"avatar_url": "http://gravatar.com/../e32131cd8.jpeg",
"approved": true,
"invited": true
}
]
```
## Give a group access to a project
See [share project with group](projects.md#share-project-with-group)

View File

@ -1314,7 +1314,7 @@ POST /projects/user/:user_id
| `jobs_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable jobs for this project. Use `builds_access_level` instead. |
| `lfs_enabled` | boolean | **{dotted-circle}** No | Enable LFS. |
| `merge_commit_template` | string | **{dotted-circle}** No | [Template](../user/project/merge_requests/commit_templates.md) used to create merge commit message in merge requests. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20263) in GitLab 14.5.)_ |
| `squash_commit_template` | string | **{dotted-circle}** No | [Template](../user/project/merge_requests/commit_templates.md#squash-commit-message-template) used to create squash commit message in merge requests. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) in GitLab 14.6.)_ |
| `squash_commit_template` | string | **{dotted-circle}** No | [Template](../user/project/merge_requests/commit_templates.md) used to create squash commit message in merge requests. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) in GitLab 14.6.)_ |
| `merge_method` | string | **{dotted-circle}** No | Set the [merge method](#project-merge-method) used. |
| `merge_requests_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. |
| `merge_requests_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable merge requests for this project. Use `merge_requests_access_level` instead. |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -228,7 +228,13 @@ must create an association between that project and the project you want to appl
project you would like to link from the dropdown menu.
1. Select **Save**.
![Security Policy Project](img/security_policy_project_v14_3.png)
![Security Policy Project](img/security_policy_project_v14_6.png)
### Unlink Security Policy projects
Project owners can unlink Security Policy projects from development projects. To do this, follow
the steps described in [Security Policy project selection](#security-policy-project-selection),
but select the trash can icon in the modal.
### Scan Execution Policy editor

View File

@ -7,15 +7,42 @@ type: reference, howto
# Commit message templates **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20263) in GitLab 14.5.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20263) in GitLab 14.5.
> - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) squash commit templates in GitLab 14.6.
## Merge commit message template
GitLab uses commit templates to create default messages for specific types of
commits. These templates encourage commit messages to follow a particular format,
or contain specific information. Users can override these templates when merging
a merge request.
As a project maintainer, you're able to configure merge commit message template. It will be used during merge to
create commit message. Template uses similar syntax to
Commit templates use syntax similar to the syntax for
[review suggestions](reviews/suggestions.md#configure-the-commit-message-for-applied-suggestions).
Default merge commit message can be recreated using following template:
## Configure commit templates
Change the commit templates for your project if the default templates don't
contain the information you need.
Prerequisite:
- You must have at least the Maintainer role for a project.
To do this:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General** and expand **Merge requests**.
1. Depending on the type of template you want to create, scroll to either
[**Merge commit message template**](#default-template-for-merge-commits) or
[**Squash commit message template**](#default-template-for-squash-commits).
1. For your desired commit type, enter your default message. You can use both static
text and [variables](#supported-variables-in-commit-templates). Each template
is limited to a maximum of 500 characters, though after replacing the templates
with data, the final message may be longer.
1. Select **Save changes**.
## Default template for merge commits
The default template for merge commit messages is:
```plaintext
Merge branch '%{source_branch}' into '%{target_branch}'
@ -27,40 +54,30 @@ Merge branch '%{source_branch}' into '%{target_branch}'
See merge request %{reference}
```
This commit message can be customized to follow any guidelines you might have.
To do so, expand the **Merge requests** tab within your project's **General**
settings and change the **Merge commit message template** text:
## Default template for squash commits
![Custom commit message for merge commit](img/merge_commit_message_template_v14_5.png)
You can use static text and following variables:
| Variable | Description | Output example |
|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
| `%{source_branch}` | The name of the branch that is being merged. | `my-feature-branch` |
| `%{target_branch}` | The name of the branch that the changes are applied to. | `master` |
| `%{title}` | Title of the merge request. | Fix stuff |
| `%{issues}` | String with phrase "Closes <issue numbers>" with all issues mentioned in the MR description matching [issue closing patterns](../issues/managing_issues.md#closing-issues-automatically). It will be empty when no issues were mentioned. | `Closes #465, #190 and #400` |
| `%{description}` | Description of the merge request. | Merge request description.<br>Can be multiline. |
| `%{reference}` | Reference to the merge request. | group-name/project-name!72359 |
NOTE:
Empty variables that are the only word in a line will be removed along with all newline characters preceding it.
Merge commit template field has a limit of 500 characters. This limit only applies to the template
itself.
## Squash commit message template
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) in GitLab 14.6.
As a project maintainer, you're able to configure squash commit message template. It will be used during merge with
squash to create squash commit message. It uses the same syntax and variables as merge commit message template.
![Custom commit message for squash commit](img/squash_commit_message_template_v14_6.png)
Default squash commit message can be recreated using following template:
If you have configured your project to [squash commits on merge](squash_and_merge.md),
GitLab creates a squash commit message with this template:
```plaintext
%{title}
```
## Supported variables in commit templates
Commit message templates support these variables:
| Variable | Description | Output example |
|----------|-------------|----------------|
| `%{source_branch}` | The name of the branch being merged. | `my-feature-branch` |
| `%{target_branch}` | The name of the branch that the changes are applied to. | `main` |
| `%{title}` | Title of the merge request. | `Fix tests and translations` |
| `%{issues}` | String with phrase `Closes <issue numbers>`. Contains all issues mentioned in the merge request description that match [issue closing patterns](../issues/managing_issues.md#closing-issues-automatically). Empty if no issues are mentioned. | `Closes #465, #190 and #400` |
| `%{description}` | Description of the merge request. | `Merge request description.<br>Can be multiline.` |
| `%{reference}` | Reference to the merge request. | `group-name/project-name!72359` |
Empty variables that are the only word in a line are removed, along with all newline characters preceding it.
## Related topics
- [Squash and merge](squash_and_merge.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -28,7 +28,8 @@ NOTE:
The squashed commit in this example is followed by a merge commit, because the merge method for this repository uses a merge commit. You can disable merge commits in
**Project Settings > General > Merge requests > Merge method > Fast-forward merge**.
The squashed commit's default commit message is taken from the merge request title. It can be changed using [squash commit message template](commit_templates.md#squash-commit-message-template).
The squashed commit's default commit message is taken from the merge request title.
You can [edit the default message for squash commits](commit_templates.md).
It can also be customized before merging a merge request.
@ -123,6 +124,10 @@ NOTE:
If your project is set to **Do not allow** Squash and Merge, the users still have the option to
squash commits locally through the command line and force-push to their remote branch before merging.
## Related topics
- [Commit message templates](commit_templates.md).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -382,6 +382,28 @@ module API
end
end
desc 'Transfer a group to a new parent group or promote a subgroup to a root group'
params do
optional :group_id, type: Integer,
desc: 'The ID of the target group to which the group needs to be transferred to.'\
'If not provided, the source group will be promoted to a root group.'
end
post ':id/transfer' do
group = find_group!(params[:id])
authorize! :admin_group, group
new_parent_group = find_group!(params[:group_id]) if params[:group_id].present?
service = ::Groups::TransferService.new(group, current_user)
if service.execute(new_parent_group)
group.preload_shared_group_links
present group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!(service.error, 400)
end
end
desc 'Share a group with a group' do
success Entities::GroupDetail
end

View File

@ -21,9 +21,9 @@ module Banzai
FOOTNOTE_LI_REFERENCE_PATTERN = /\A#{FOOTNOTE_ID_PREFIX}.+\z/.freeze
FOOTNOTE_LINK_REFERENCE_PATTERN = /\A#{FOOTNOTE_LINK_ID_PREFIX}.+\z/.freeze
CSS_SECTION = "ol > li a[href^=\"\##{FOOTNOTE_LINK_ID_PREFIX}\"]"
CSS_SECTION = "section[data-footnotes]"
XPATH_SECTION = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION).freeze
CSS_FOOTNOTE = 'sup > a[id]'
CSS_FOOTNOTE = 'sup > a[data-footnote-ref]'
XPATH_FOOTNOTE = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_FOOTNOTE).freeze
# only needed when feature flag use_cmark_renderer is turned off
@ -37,20 +37,28 @@ module Banzai
XPATH_SECTION_OLD = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SECTION_OLD).freeze
def call
xpath_section = Feature.enabled?(:use_cmark_renderer) ? XPATH_SECTION : XPATH_SECTION_OLD
return doc unless first_footnote = doc.at_xpath(xpath_section)
# Sanitization stripped off the section wrapper - add it back in
if Feature.enabled?(:use_cmark_renderer)
first_footnote.parent.parent.parent.wrap('<section class="footnotes" data-footnotes>')
# Sanitization stripped off the section class - add it back in
return doc unless section_node = doc.at_xpath(XPATH_SECTION)
section_node.append_class('footnotes')
else
return doc unless first_footnote = doc.at_xpath(XPATH_SECTION_OLD)
return doc unless first_footnote.parent
first_footnote.parent.wrap('<section class="footnotes">')
end
rand_suffix = "-#{random_number}"
modified_footnotes = {}
doc.xpath(XPATH_FOOTNOTE).each do |link_node|
xpath_footnote = if Feature.enabled?(:use_cmark_renderer)
XPATH_FOOTNOTE
else
Gitlab::Utils::Nokogiri.css_to_xpath('sup > a[id]')
end
doc.xpath(xpath_footnote).each do |link_node|
if Feature.enabled?(:use_cmark_renderer)
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX)
ref_num.gsub!(/[[:punct:]]/, '\\\\\&')
@ -58,7 +66,8 @@ module Banzai
ref_num = link_node[:id].delete_prefix(FOOTNOTE_LINK_ID_PREFIX_OLD)
end
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath("li[id=#{fn_id(ref_num)}]")
css = Feature.enabled?(:use_cmark_renderer) ? "section[data-footnotes] li[id=#{fn_id(ref_num)}]" : "li[id=#{fn_id(ref_num)}]"
node_xpath = Gitlab::Utils::Nokogiri.css_to_xpath(css)
footnote_node = doc.at_xpath(node_xpath)
if footnote_node || modified_footnotes[ref_num]
@ -69,7 +78,6 @@ module Banzai
# Sanitization stripped off class - add it back in
link_node.parent.append_class('footnote-ref')
link_node['data-footnote-ref'] = nil if Feature.enabled?(:use_cmark_renderer)
unless modified_footnotes[ref_num]
footnote_node[:id] += rand_suffix
@ -78,7 +86,6 @@ module Banzai
if backref_node
backref_node[:href] += rand_suffix
backref_node.append_class('footnote-backref')
backref_node['data-footnote-backref'] = nil if Feature.enabled?(:use_cmark_renderer)
end
modified_footnotes[ref_num] = true

View File

@ -28,6 +28,13 @@ module Banzai
allowlist[:attributes]['li'] = %w[id]
allowlist[:transformers].push(self.class.remove_non_footnote_ids)
if Feature.enabled?(:use_cmark_renderer)
# Allow section elements with data-footnotes attribute
allowlist[:elements].push('section')
allowlist[:attributes]['section'] = %w(data-footnotes)
allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref')
end
allowlist
end

View File

@ -47,6 +47,7 @@ module Gitlab
return unless log?
attributes = {
class: self.class.name.to_s,
pipeline_creation_caller: caller,
project_id: project.id,
pipeline_id: pipeline.id,

View File

@ -23,25 +23,28 @@ module Gitlab
def activity_dates
return @activity_dates if @activity_dates.present?
date_interval = "INTERVAL '#{@contributor_time_instance.now.utc_offset} seconds'"
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 3 different contribution types
date_from = @contributor_time_instance.now.years_ago(1)
repo_events = event_counts(date_from, :repository)
.having(action: :pushed)
issue_events = event_counts(date_from, :issues)
.having(action: [:created, :closed], target_type: "Issue")
mr_events = event_counts(date_from, :merge_requests)
.having(action: [:merged, :created, :closed], target_type: "MergeRequest")
note_events = event_counts(date_from, :merge_requests)
.having(action: :commented)
repo_events = event_created_at(date_from, :repository)
.where(action: :pushed, target_type: nil)
issue_events = event_created_at(date_from, :issues)
.where(action: [:created, :closed], target_type: "Issue")
mr_events = event_created_at(date_from, :merge_requests)
.where(action: [:merged, :created, :closed], target_type: "MergeRequest")
note_events = event_created_at(date_from, :merge_requests)
.where(action: :commented, target_type: "Note")
events = Event
.select(:project_id, :target_type, :action, :date, :total_amount)
.from_union([repo_events, issue_events, mr_events, note_events])
.select("date(created_at + #{date_interval}) AS date", 'COUNT(*) AS num_events')
.from_union([repo_events, issue_events, mr_events, note_events], remove_duplicates: false)
.group(:date)
.map(&:attributes)
@activity_dates = events.each_with_object(Hash.new {|h, k| h[k] = 0 }) do |event, activities|
activities[event["date"]] += event["total_amount"]
activities[event["date"]] += event["num_events"]
end
end
# rubocop: enable CodeReuse/ActiveRecord
@ -74,27 +77,25 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
def event_counts(date_from, feature)
def event_created_at(date_from, feature)
t = Event.arel_table
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
@contributed_project_ids ||= projects.distinct.pluck(:id)
authed_projects = Project.where(id: @contributed_project_ids)
authed_projects = ProjectFeature
.with_feature_available_for_user(feature, current_user)
.where(project_id: @contributed_project_ids)
.reorder(nil)
.select(:id)
.select(:project_id)
conditions = t[:created_at].gteq(date_from.beginning_of_day)
.and(t[:created_at].lteq(@contributor_time_instance.today.end_of_day))
.and(t[:author_id].eq(contributor.id))
date_interval = "INTERVAL '#{@contributor_time_instance.now.utc_offset} seconds'"
Event.reorder(nil)
.select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
.group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
.select(:created_at)
.where(conditions)
.where("events.project_id in (#{authed_projects.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end

View File

@ -180,5 +180,13 @@ module RuboCop
def rails_root
File.expand_path('..', __dir__)
end
def ee?
File.exist?(File.expand_path('../ee/app/models/license.rb', __dir__)) && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
end
def jh?
ee? && Dir.exist?(File.expand_path('../jh', __dir__)) && !%w[true 1].include?(ENV['EE_ONLY'].to_s)
end
end
end

View File

@ -255,14 +255,12 @@ module RuboCop
]
# For EE additionally process `ee/` feature flags
is_ee = File.exist?(File.expand_path('../../../ee/app/models/license.rb', __dir__)) && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
if is_ee
if ee?
flags_paths << 'ee/config/feature_flags/**/*.yml'
end
# For JH additionally process `jh/` feature flags
is_jh = is_ee && Dir.exist?(File.expand_path('../../../jh', __dir__)) && !%w[true 1].include?(ENV['EE_ONLY'].to_s)
if is_jh
if jh?
flags_paths << 'jh/config/feature_flags/**/*.yml'
end

13
scripts/lib/gitlab.rb Normal file
View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Gitlab
module_function
def ee?
File.exist?(File.expand_path('../../ee/app/models/license.rb', __dir__)) && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
end
def jh?
ee? && Dir.exist?(File.expand_path('../../jh', __dir__)) && !%w[true 1].include?(ENV['EE_ONLY'].to_s)
end
end

View File

@ -3,6 +3,7 @@
require 'set'
require 'fileutils'
require_relative 'lib/gitlab'
class String
def red
@ -27,8 +28,7 @@ flags_paths = [
]
# For EE additionally process `ee/` feature flags
is_ee = File.exist?('ee/app/models/license.rb') && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
if is_ee
if Gitlab.ee?
flags_paths << 'ee/config/feature_flags/**/*.yml'
# Geo feature flags are constructed dynamically and there's no explicit checks in the codebase so we mark all
@ -43,8 +43,7 @@ if is_ee
end
# For JH additionally process `jh/` feature flags
is_jh = is_ee && Dir.exist?('jh') && !%w[true 1].include?(ENV['EE_ONLY'].to_s)
if is_jh
if Gitlab.jh?
flags_paths << 'jh/config/feature_flags/**/*.yml'
Dir.glob('jh/app/replicators/geo/*_replicator.rb').each_with_object(Set.new) do |path, memo|

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::FootnoteFilter do
include FilterSpecHelper
using RSpec::Parameterized::TableSyntax
# rubocop:disable Style/AsciiComments
# first[^1] and second[^second] and third[^_😄_]
@ -13,16 +14,16 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
# rubocop:enable Style/AsciiComments
let(:footnote) do
<<~EOF.strip_heredoc
<p>first<sup><a href="#fn-1" id="fnref-1">1</a></sup> and second<sup><a href="#fn-second" id="fnref-second">2</a></sup> and third<sup><a href="#fn-_%F0%9F%98%84_" id="fnref-_%F0%9F%98%84_">3</a></sup></p>
<p>first<sup><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> and second<sup><a href="#fn-second" id="fnref-second" data-footnote-ref>2</a></sup> and third<sup><a href="#fn-_%F0%9F%98%84_" id="fnref-_%F0%9F%98%84_" data-footnote-ref>3</a></sup></p>
<section data-footnotes>
<ol>
<li id="fn-1">
<p>one <a href="#fnref-1" aria-label="Back to content"></a></p>
<p>one <a href="#fnref-1" aria-label="Back to content" data-footnote-backref></a></p>
</li>
<li id="fn-second">
<p>two <a href="#fnref-second" aria-label="Back to content"></a></p>
<p>two <a href="#fnref-second" aria-label="Back to content" data-footnote-backref></a></p>
</li>\n<li id="fn-_%F0%9F%98%84_">
<p>three <a href="#fnref-_%F0%9F%98%84_" aria-label="Back to content"></a></p>
<p>three <a href="#fnref-_%F0%9F%98%84_" aria-label="Back to content" data-footnote-backref></a></p>
</li>
</ol>
EOF
@ -30,19 +31,20 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
let(:filtered_footnote) do
<<~EOF.strip_heredoc
<p>first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref="">1</a></sup> and second<sup class="footnote-ref"><a href="#fn-second-#{identifier}" id="fnref-second-#{identifier}" data-footnote-ref="">2</a></sup> and third<sup class="footnote-ref"><a href="#fn-_%F0%9F%98%84_-#{identifier}" id="fnref-_%F0%9F%98%84_-#{identifier}" data-footnote-ref="">3</a></sup></p>
<section class=\"footnotes\" data-footnotes><ol>
<p>first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref>1</a></sup> and second<sup class="footnote-ref"><a href="#fn-second-#{identifier}" id="fnref-second-#{identifier}" data-footnote-ref>2</a></sup> and third<sup class="footnote-ref"><a href="#fn-_%F0%9F%98%84_-#{identifier}" id="fnref-_%F0%9F%98%84_-#{identifier}" data-footnote-ref>3</a></sup></p>
<section data-footnotes class=\"footnotes\">
<ol>
<li id="fn-1-#{identifier}">
<p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""></a></p>
<p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref"></a></p>
</li>
<li id="fn-second-#{identifier}">
<p>two <a href="#fnref-second-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""></a></p>
<p>two <a href="#fnref-second-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref"></a></p>
</li>
<li id="fn-_%F0%9F%98%84_-#{identifier}">
<p>three <a href="#fnref-_%F0%9F%98%84_-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""></a></p>
<p>three <a href="#fnref-_%F0%9F%98%84_-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref"></a></p>
</li>
</ol></section>
</ol>
</section>
EOF
end
@ -52,7 +54,7 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
let(:identifier) { link_node[:id].delete_prefix('fnref-1-') }
it 'properly adds the necessary ids and classes' do
expect(doc.to_html).to eq filtered_footnote
expect(doc.to_html).to eq filtered_footnote.strip
end
context 'using ruby-based HTML renderer' do
@ -101,4 +103,21 @@ RSpec.describe Banzai::Filter::FootnoteFilter do
end
end
end
context 'when detecting footnotes' do
where(:valid, :markdown) do
true | "1. one[^1]\n[^1]: AbC"
true | "1. one[^abc]\n[^abc]: AbC"
false | '1. [one](#fnref-abc)'
false | "1. one[^1]\n[^abc]: AbC"
end
with_them do
it 'detects valid footnotes' do
result = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)
expect(result[:output].at_css('section.footnotes').present?).to eq(valid)
end
end
end
end

View File

@ -43,26 +43,27 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
let(:filtered_footnote) do
<<~EOF.strip_heredoc
<p dir="auto">first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref="">1</a></sup> and second<sup class="footnote-ref"><a href="#fn-%F0%9F%98%84second-#{identifier}" id="fnref-%F0%9F%98%84second-#{identifier}" data-footnote-ref="">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn-_twenty-#{identifier}" id="fnref-_twenty-#{identifier}" data-footnote-ref="">3</a></sup></p>
<section class="footnotes" data-footnotes><ol>
<p dir="auto">first<sup class="footnote-ref"><a href="#fn-1-#{identifier}" id="fnref-1-#{identifier}" data-footnote-ref>1</a></sup> and second<sup class="footnote-ref"><a href="#fn-%F0%9F%98%84second-#{identifier}" id="fnref-%F0%9F%98%84second-#{identifier}" data-footnote-ref>2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn-_twenty-#{identifier}" id="fnref-_twenty-#{identifier}" data-footnote-ref>3</a></sup></p>
<section data-footnotes class="footnotes">
<ol>
<li id="fn-1-#{identifier}">
<p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
<p>one <a href="#fnref-1-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
<li id="fn-%F0%9F%98%84second-#{identifier}">
<p>two <a href="#fnref-%F0%9F%98%84second-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
<p>two <a href="#fnref-%F0%9F%98%84second-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
<li id="fn-_twenty-#{identifier}">
<p>twenty <a href="#fnref-_twenty-#{identifier}" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
<p>twenty <a href="#fnref-_twenty-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1"></gl-emoji></a></p>
</li>
</ol></section>
</ol>
</section>
EOF
end
it 'properly adds the necessary ids and classes' do
stub_commonmark_sourcepos_disabled
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote
expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote.strip
end
context 'using ruby-based HTML renderer' do

View File

@ -71,6 +71,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Logger do
let(:loggable_data) do
{
'class' => described_class.name.to_s,
'pipeline_id' => pipeline.id,
'pipeline_persisted' => true,
'project_id' => project.id,

View File

@ -1943,6 +1943,116 @@ RSpec.describe API::Groups do
end
end
describe 'POST /groups/:id/transfer' do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:new_parent_group) { create(:group, :private) }
let_it_be_with_reload(:group) { create(:group, :nested, :private) }
before do
new_parent_group.add_owner(user)
group.add_owner(user)
end
def make_request(user)
post api("/groups/#{group.id}/transfer", user), params: params
end
context 'when promoting a subgroup to a root group' do
shared_examples_for 'promotes the subgroup to a root group' do
it 'returns success' do
make_request(user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['parent_id']).to be_nil
end
end
context 'when no group_id is specified' do
let(:params) {}
it_behaves_like 'promotes the subgroup to a root group'
end
context 'when group_id is specified as blank' do
let(:params) { { group_id: '' } }
it_behaves_like 'promotes the subgroup to a root group'
end
context 'when the group is already a root group' do
let(:group) { create(:group) }
let(:params) { { group_id: '' } }
it 'returns error' do
make_request(user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Transfer failed: Group is already a root group.')
end
end
end
context 'when transferring a subgroup to a different group' do
let(:params) { { group_id: new_parent_group.id } }
context 'when the user does not have admin rights to the group being transferred' do
it 'forbids the operation' do
developer_user = create(:user)
group.add_developer(developer_user)
make_request(developer_user)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when the user does not have access to the new parent group' do
it 'fails with 404' do
user_without_access_to_new_parent_group = create(:user)
group.add_owner(user_without_access_to_new_parent_group)
make_request(user_without_access_to_new_parent_group)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the ID of a non-existent group is mentioned as the new parent group' do
let(:params) { { group_id: non_existing_record_id } }
it 'fails with 404' do
make_request(user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when the transfer fails due to an error' do
before do
expect_next_instance_of(::Groups::TransferService) do |service|
expect(service).to receive(:proceed_to_transfer).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
end
end
it 'returns error' do
make_request(user)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq('Transfer failed: namespace directory cannot be moved')
end
end
context 'when the transfer succceds' do
it 'returns success' do
make_request(user)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['parent_id']).to eq(new_parent_group.id)
end
end
end
end
it_behaves_like 'custom attributes endpoints', 'groups' do
let(:attributable) { group1 }
let(:other_attributable) { group2 }

View File

@ -21,6 +21,8 @@ RSpec.describe RuboCop::CodeReuseHelpers do
end.new
end
let(:ee_file_path) { File.expand_path('../../ee/app/models/license.rb', __dir__) }
describe '#send_to_constant?' do
it 'returns true when sending to a constant' do
node = build_and_parse_source('Foo.bar')
@ -312,4 +314,77 @@ RSpec.describe RuboCop::CodeReuseHelpers do
cop.disallow_send_to(def_node, 'Finder', 'oops')
end
end
describe '#ee?' do
before do
stub_env('FOSS_ONLY', nil)
allow(File).to receive(:exist?).with(ee_file_path) { true }
end
it 'returns true when ee/app/models/license.rb exists' do
expect(cop.ee?).to eq(true)
end
end
describe '#jh?' do
context 'when jh directory exists and EE_ONLY is not set' do
before do
stub_env('EE_ONLY', nil)
allow(Dir).to receive(:exist?).with(File.expand_path('../../jh', __dir__)) { true }
end
context 'when ee/app/models/license.rb exists' do
before do
allow(File).to receive(:exist?).with(ee_file_path) { true }
end
context 'when FOSS_ONLY is not set' do
before do
stub_env('FOSS_ONLY', nil)
end
it 'returns true' do
expect(cop.jh?).to eq(true)
end
end
context 'when FOSS_ONLY is set to 1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns false' do
expect(cop.jh?).to eq(false)
end
end
end
context 'when ee/app/models/license.rb not exist' do
before do
allow(File).to receive(:exist?).with(ee_file_path) { false }
end
context 'when FOSS_ONLY is not set' do
before do
stub_env('FOSS_ONLY', nil)
end
it 'returns true' do
expect(cop.jh?).to eq(false)
end
end
context 'when FOSS_ONLY is set to 1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns false' do
expect(cop.jh?).to eq(false)
end
end
end
end
end
end

View File

@ -0,0 +1,81 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../scripts/lib/gitlab'
RSpec.describe 'scripts/lib/gitlab.rb' do
let(:ee_file_path) { File.expand_path('../../../ee/app/models/license.rb', __dir__) }
describe '.ee?' do
before do
stub_env('FOSS_ONLY', nil)
allow(File).to receive(:exist?).with(ee_file_path) { true }
end
it 'returns true when ee/app/models/license.rb exists' do
expect(Gitlab.ee?).to eq(true)
end
end
describe '.jh?' do
context 'when jh directory exists and EE_ONLY is not set' do
before do
stub_env('EE_ONLY', nil)
allow(Dir).to receive(:exist?).with(File.expand_path('../../../jh', __dir__)) { true }
end
context 'when ee/app/models/license.rb exists' do
before do
allow(File).to receive(:exist?).with(ee_file_path) { true }
end
context 'when FOSS_ONLY is not set' do
before do
stub_env('FOSS_ONLY', nil)
end
it 'returns true' do
expect(Gitlab.jh?).to eq(true)
end
end
context 'when FOSS_ONLY is set to 1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns false' do
expect(Gitlab.jh?).to eq(false)
end
end
end
context 'when ee/app/models/license.rb not exist' do
before do
allow(File).to receive(:exist?).with(ee_file_path) { false }
end
context 'when FOSS_ONLY is not set' do
before do
stub_env('FOSS_ONLY', nil)
end
it 'returns true' do
expect(Gitlab.jh?).to eq(false)
end
end
context 'when FOSS_ONLY is set to 1' do
before do
stub_env('FOSS_ONLY', '1')
end
it 'returns false' do
expect(Gitlab.jh?).to eq(false)
end
end
end
end
end
end

View File

@ -284,7 +284,7 @@ RSpec.describe Tooling::Danger::ProjectHelper do
describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do
expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, database, documentation, duplicate_yarn_dependencies, eslint, gitaly, pajamas, pipeline, prettier, product_intelligence, utility_css, vue_shared_documentation')
expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changelog, ci_config, database, documentation, duplicate_yarn_dependencies, eslint, gitaly, pajamas, pipeline, prettier, product_intelligence, utility_css, vue_shared_documentation')
end
end

View File

@ -5,6 +5,7 @@ module Tooling
module ProjectHelper
LOCAL_RULES ||= %w[
changelog
ci_config
database
documentation
duplicate_yarn_dependencies