Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-14 09:09:13 +00:00
parent e761659df2
commit efcfe56681
61 changed files with 527 additions and 938 deletions

View File

@ -119,6 +119,8 @@ gem 'net-ldap', '~> 0.16.3'
gem 'grape', '~> 1.5.2'
gem 'grape-entity', '~> 0.10.0'
gem 'rack-cors', '~> 1.1.1', require: 'rack/cors'
gem 'grape-swagger', '~>1.5.0', group: [:development, :test]
gem 'grape-swagger-entity', '~> 0.5.1', group: [:development, :test]
# GraphQL API
gem 'graphql', '~> 1.13.12'

View File

@ -231,6 +231,8 @@
{"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"},
{"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"},
{"name":"grape-path-helpers","version":"1.7.1","platform":"ruby","checksum":"2e27271a20d4073e3a3b2b955425c7f803e198be3ba8f6e59e3d59643c5381e2"},
{"name":"grape-swagger","version":"1.5.0","platform":"ruby","checksum":"9c885b326ab0644abecf7df4ce866abc2411f359cfd59cbcca545b9b3b25c8ff"},
{"name":"grape-swagger-entity","version":"0.5.1","platform":"ruby","checksum":"f51e372d00ac96cf90d948f87b3f4eb287ab053976ca57ad503d442ad8605523"},
{"name":"grape_logging","version":"1.8.4","platform":"ruby","checksum":"efcc3e322dbd5d620a68f078733b7db043cf12680144cd03c982f14115c792d1"},
{"name":"graphiql-rails","version":"1.8.0","platform":"ruby","checksum":"02e2c5098be2c6c29219a0e9b2910a2cd3c494301587a3199a7c4484d8038ed1"},
{"name":"graphlient","version":"0.5.0","platform":"ruby","checksum":"0f2c9416142e50b6bd4edcd86fe6810f792951732c487f9061aee6d420e0f292"},

View File

@ -642,6 +642,11 @@ GEM
grape (~> 1.3)
rake (> 12)
ruby2_keywords (~> 0.0.2)
grape-swagger (1.5.0)
grape (~> 1.3)
grape-swagger-entity (0.5.1)
grape-entity (>= 0.6.0)
grape-swagger (>= 1.2.0)
grape_logging (1.8.4)
grape
rack
@ -1638,6 +1643,8 @@ DEPENDENCIES
grape (~> 1.5.2)
grape-entity (~> 0.10.0)
grape-path-helpers (~> 1.7.1)
grape-swagger (~> 1.5.0)
grape-swagger-entity (~> 0.5.1)
grape_logging (~> 1.8)
graphiql-rails (~> 1.8)
graphlient (~> 0.5.0)

View File

@ -190,7 +190,10 @@ export default {
<div class="media" :class="{ 'gl-cursor-pointer': isCollapsible }" @click="toggleCollapsed">
<status-icon :status="statusIconName" :size="24" class="align-self-center" />
<div class="media-body gl-display-flex gl-align-items-flex-start gl-flex-direction-row!">
<div data-testid="report-section-code-text" class="js-code-text code-text">
<div
data-testid="report-section-code-text"
class="js-code-text code-text gl-align-self-center gl-flex-grow-1"
>
<div class="gl-display-flex gl-align-items-center">
<p class="gl-line-height-normal gl-m-0">{{ headerText }}</p>
<slot :name="slotName"></slot>

View File

@ -209,9 +209,7 @@ module WikiActions
def wiki
strong_memoize(:wiki) do
wiki = Wiki.for_container(container, current_user)
# Call #wiki to make sure the Wiki Repo is initialized
wiki.wiki
wiki.create_wiki_repository
wiki
end
@ -242,7 +240,7 @@ module WikiActions
def wiki_pages
strong_memoize(:wiki_pages) do
Kaminari.paginate_array(
wiki.list_pages(sort: params[:sort], direction: params[:direction])
wiki.list_pages(direction: params[:direction])
).page(params[:page])
end
end

View File

@ -59,15 +59,13 @@ module WikiHelper
end
end
def wiki_sort_controls(wiki, sort, direction)
sort ||= Wiki::TITLE_ORDER
def wiki_sort_controls(wiki, direction)
link_class = 'gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
icon_class = direction == 'desc' ? 'highest' : 'lowest'
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
link_options = { action: :pages, direction: reversed_direction }
link_options[:sort] = sort unless wiki.disable_sorting?
link_to(wiki_path(wiki, **link_options),
type: 'button', class: link_class, title: title) do

View File

@ -8,7 +8,7 @@ module HasWiki
end
def create_wiki
wiki.wiki
wiki.create_wiki_repository
true
rescue Wiki::CouldNotCreateWikiError
errors.add(:base, _('Failed to create wiki'))

View File

@ -9,6 +9,8 @@ class Wiki
extend ActiveModel::Naming
DuplicatePageError = Class.new(StandardError)
MARKUPS = { # rubocop:disable Style/MultilineIfModifier
markdown: {
name: 'Markdown',
@ -114,6 +116,29 @@ class Wiki
title = Pathname.new(title).relative_path_from('/').to_s
title.tr(' ', '-')
end
def canonicalize_filename(filename)
::File.basename(filename, ::File.extname(filename)).tr('-', ' ')
end
def cname(name, char_white_sub = '-', char_other_sub = '-')
name.to_s.gsub(/\s/, char_white_sub).gsub(/[<>+]/, char_other_sub)
end
def preview_slug(title, format)
ext = format == :markdown ? "md" : format.to_s
name = cname(title) + '.' + ext
canonical_name = canonicalize_filename(name)
path =
if name.include?('/')
name.sub(%r{/[^/]+$}, '/')
else
''
end
path + cname(canonical_name, '-', '-')
end
end
def initialize(container, user = nil)
@ -145,14 +170,6 @@ class Wiki
container.path + '.wiki'
end
# Returns the Gitlab::Git::Wiki object.
def wiki
strong_memoize(:wiki) do
create_wiki_repository
Gitlab::Git::Wiki.new(repository.raw)
end
end
def create_wiki_repository
repository.create_if_not_exists(default_branch)
@ -190,13 +207,9 @@ class Wiki
#
# Returns an Array of GitLab WikiPage instances or an
# empty Array if this Wiki has no pages.
def list_pages(limit: 0, sort: nil, direction: DIRECTION_ASC, load_content: false)
if list_pages_with_repository_rpcs?
create_wiki_repository unless repository_exists?
list_pages_with_repository_rpcs(limit: limit, sort: sort, direction: direction, load_content: load_content)
else
list_pages_with_legacy_wiki_service(limit: limit, sort: sort, direction: direction, load_content: load_content)
end
def list_pages(limit: 0, direction: DIRECTION_ASC, load_content: false)
create_wiki_repository unless repository_exists?
list_pages_with_repository_rpcs(limit: limit, direction: direction, load_content: load_content)
end
def sidebar_entries(limit: Gitlab::WikiPages::MAX_SIDEBAR_PAGES, **options)
@ -215,12 +228,8 @@ class Wiki
#
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil, load_content: true)
if find_page_with_repository_rpcs?
create_wiki_repository unless repository_exists?
find_page_with_repository_rpcs(title, version, load_content: load_content)
else
find_page_with_legacy_wiki_service(title, version, load_content: load_content)
end
create_wiki_repository unless repository_exists?
find_page_with_repository_rpcs(title, version, load_content: load_content)
end
def find_sidebar(version = nil)
@ -254,7 +263,7 @@ class Wiki
raise_duplicate_page_error!
end
end
rescue Gitlab::Git::Wiki::DuplicatePageError => e
rescue DuplicatePageError => e
@error_message = _("Duplicate page: %{error_message}" % { error_message: e.message })
false
@ -270,6 +279,7 @@ class Wiki
extension = page.format != format.to_sym ? default_extension : File.extname(page.path).downcase[1..]
capture_git_error(:updated) do
create_wiki_repository unless repository_exists?
repository.update_file(
user,
sluggified_full_path(title, extension),
@ -288,6 +298,7 @@ class Wiki
return unless page
capture_git_error(:deleted) do
create_wiki_repository unless repository_exists?
repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title))
after_wiki_activity
@ -304,8 +315,10 @@ class Wiki
[title, title_array.join("/")]
end
# TODO: This method is redundant. Should be replaced by create_wiki_repository
def ensure_repository
raise CouldNotCreateWikiError unless wiki.repository_exists?
create_wiki_repository
raise CouldNotCreateWikiError unless repository_exists?
end
def hook_attrs
@ -341,7 +354,7 @@ class Wiki
override :default_branch
def default_branch
super || Gitlab::Git::Wiki.default_ref(container)
super || Gitlab::DefaultBranch.value(object: container)
end
def wiki_base_path
@ -381,10 +394,6 @@ class Wiki
false
end
def disable_sorting?
list_pages_with_repository_rpcs?
end
private
def multi_commit_options(action, message = nil, title = nil)
@ -429,7 +438,7 @@ class Wiki
end
def raise_duplicate_page_error!
raise Gitlab::Git::Wiki::DuplicatePageError, _('A page with that title already exists')
raise ::Wiki::DuplicatePageError, _('A page with that title already exists')
end
def sluggified_full_path(title, extension)
@ -441,15 +450,7 @@ class Wiki
end
def canonicalize_filename(filename)
Gitlab::Git::Wiki::GollumSlug.canonicalize_filename(filename)
end
def find_page_with_legacy_wiki_service(title, version, load_content: false)
page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(title: page_title, version: version, dir: page_dir, load_content: load_content)
WikiPage.new(self, page)
end
self.class.canonicalize_filename(filename)
end
def find_matched_file(title, version)
@ -494,17 +495,6 @@ class Wiki
WikiPage.new(self, page)
end
def find_page_with_repository_rpcs?
group =
if container.is_a?(::Group)
container
else
container.group
end
Feature.enabled?(:wiki_find_page_with_normal_repository_rpcs, group, type: :development)
end
def file_extension_regexp
# We could not use ALLOWED_EXTENSIONS_REGEX constant or similar regexp with
# Regexp.union. The result combination complicated modifiers:
@ -519,17 +509,6 @@ class Wiki
path.sub(/\.[^.]+\z/, "")
end
def list_pages_with_repository_rpcs?
group =
if container.is_a?(::Group)
container
else
container.group
end
Feature.enabled?(:wiki_list_pages_with_normal_repository_rpcs, group, type: :development)
end
def list_page_paths
return [] if repository.empty?
@ -537,7 +516,7 @@ class Wiki
repository.search_files_by_regexp(path_regexp, default_branch)
end
def list_pages_with_repository_rpcs(limit:, sort:, direction:, load_content:)
def list_pages_with_repository_rpcs(limit:, direction:, load_content:)
paths = list_page_paths
return [] if paths.empty?
@ -553,29 +532,18 @@ class Wiki
)
WikiPage.new(self, page)
end
sort_pages!(pages, sort, direction)
sort_pages!(pages, direction)
pages = pages.take(limit) if limit > 0
fetch_pages_content!(pages) if load_content
pages
end
def list_pages_with_legacy_wiki_service(limit:, sort:, direction:, load_content:)
wiki.list_pages(
limit: limit,
sort: sort,
direction_desc: direction == DIRECTION_DESC,
load_content: load_content
).map do |page|
WikiPage.new(self, page)
end
end
# After migrating to normal repository RPCs, it's very expensive to sort the
# pages by created_at. We have to either ListLastCommitsForTree RPC call or
# N+1 LastCommitForPath. Either are efficient for a large repository.
# Therefore, we decide to sort the title only.
def sort_pages!(pages, _sort, direction)
def sort_pages!(pages, direction)
# Sort by path to ensure the files inside a sub-folder are grouped and sorted together
pages.sort_by!(&:path)
pages.reverse! if direction == DIRECTION_DESC

View File

@ -73,7 +73,7 @@ class WikiPage
# The escaped URL path of this page.
def slug
attributes[:slug].presence || wiki.wiki.preview_slug(title, format)
attributes[:slug].presence || ::Wiki.preview_slug(title, format)
end
alias_method :id, :slug # required to use build_stubbed

View File

@ -1,5 +1,7 @@
- page_title s_('WorkItem|Work Items')
- add_page_specific_style 'page_bundles/work_items'
- @gfm_form = true
- @noteable_type = 'WorkItem'
#js-work-items{ data: work_items_index_data(@project) }
= render 'projects/invite_members_modal', project: @project

View File

@ -4,6 +4,6 @@
%div{ class: 'search-result-row gl-pb-3! gl-mt-5 gl-mb-0!' }
%span.gl-display-flex.gl-align-items-center
= link_to wiki_blob_link, data: { track_action: 'click_text', track_label: "wiki_title", track_property: 'search_result' }, class: 'gl-w-full' do
%span.term.str-truncated.gl-font-weight-bold= ::Gitlab::Git::Wiki::GollumSlug.canonicalize_filename(wiki_blob.path)
%span.term.str-truncated.gl-font-weight-bold= ::Wiki.canonicalize_filename(wiki_blob.path)
.description.term.col-sm-10.gl-px-0
= simple_search_highlight_and_truncate(wiki_blob.data, @search_term)

View File

@ -2,7 +2,6 @@
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
- add_page_specific_style 'page_bundles/wiki'
- wiki_sort_options = [{ text: s_("Wiki|Title"), value: 'title', href: wiki_path(@wiki, action: :pages, sort: Wiki::TITLE_ORDER)}, { text: s_("Wiki|Created date"), value: 'created_at', href: wiki_path(@wiki, action: :pages, sort: Wiki::CREATED_AT_ORDER) }]
.wiki-page-header.top-area.flex-column.flex-lg-row
%h1.page-title.gl-font-size-h-display.gl-flex-grow-1
@ -15,9 +14,7 @@
.dropdown.inline.wiki-sort-dropdown
.btn-group{ role: 'group' }
- unless @wiki.disable_sorting?
= gl_redirect_listbox_tag wiki_sort_options, params[:sort], data: { right: true }
= wiki_sort_controls(@wiki, params[:sort], params[:direction])
= wiki_sort_controls(@wiki, params[:direction])
%ul.wiki-pages-list.content-list
= render @wiki_entries, context: 'pages'

View File

@ -1,8 +0,0 @@
---
name: wiki_find_page_with_normal_repository_rpcs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95897
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371487
milestone: '15.4'
type: development
group: group::gitaly
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: wiki_list_pages_with_normal_repository_rpcs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96461
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372259
milestone: '15.4'
type: development
group: group::gitaly
default_enabled: true

View File

@ -10,8 +10,11 @@ value_type: number
status: active
time_frame: 28d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- users_viewing_analytics_group_devops_adoption
- i_analytics_dev_ops_adoption

View File

@ -10,8 +10,11 @@ value_type: number
status: active
time_frame: 28d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- g_project_management_issue_title_changed
- g_project_management_issue_description_changed

View File

@ -12,8 +12,11 @@ milestone: "13.12"
introduced_by_url:
time_frame: 28d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- o_pipeline_authoring_unique_users_committing_ciconfigfile
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile

View File

@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- error_tracking_view_list
- error_tracking_view_details

View File

@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- incident_management_timeline_event_created
- incident_management_timeline_event_edited

View File

@ -10,8 +10,11 @@ value_type: number
status: active
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- users_viewing_analytics_group_devops_adoption
- i_analytics_dev_ops_adoption

View File

@ -10,8 +10,11 @@ value_type: number
status: active
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- g_project_management_issue_title_changed
- g_project_management_issue_description_changed

View File

@ -12,8 +12,11 @@ milestone: "13.12"
introduced_by_url:
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- o_pipeline_authoring_unique_users_committing_ciconfigfile
- o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile

View File

@ -12,8 +12,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82543
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- error_tracking_view_list
- error_tracking_view_details

View File

@ -13,8 +13,11 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90205
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
instrumentation_class: AggregatedMetric
options:
aggregate:
operator: OR
attribute: user_id
events:
- incident_management_timeline_event_created
- incident_management_timeline_event_edited

18
config/open_api.yml Normal file
View File

@ -0,0 +1,18 @@
metadata:
doc_version: v4
info:
title: GitLab API
termsOfService: https://about.gitlab.com/terms/
host: gitlab.com
security_definitions:
access_token_header:
type: apiKey
name: PRIVATE-TOKEN
in: header
access_token_query:
type: apiKey
name: private_token
in: query
tags:
- name: metadata
description: Operations related to metadata of the GitLab instance

View File

@ -0,0 +1,90 @@
---
info:
title: GitLab API
version: v4
swagger: '2.0'
produces:
- application/json
securityDefinitions:
access_token_header:
type: apiKey
name: PRIVATE-TOKEN
in: header
access_token_query:
type: apiKey
name: private_token
in: query
host: gitlab.com
tags:
- name: metadata
description: Operations related to metadata of the GitLab instance
paths:
"/api/v4/metadata":
get:
summary: Retrieve metadata information for this GitLab instance.
description: This feature was introduced in GitLab 15.2.
produces:
- application/json
responses:
'200':
description: successful operation
schema:
"$ref": "#/definitions/API_Entities_Metadata"
examples:
successful_response:
value:
version: 15.0-pre
revision: c401a659d0c
kas:
enabled: true
externalUrl: grpc://gitlab.example.com:8150
version: 15.0.0
'401':
description: unauthorized operation
tags:
- metadata
operationId: getApiV4Metadata
"/api/v4/version":
get:
summary: Get the version information of the GitLab instance.
description: This feature was introduced in GitLab 8.13 and deprecated in 15.5.
We recommend you instead use the Metadata API.
produces:
- application/json
responses:
'200':
description: successful operation
schema:
"$ref": "#/definitions/API_Entities_Metadata"
examples:
Example:
value:
version: 15.0-pre
revision: c401a659d0c
kas:
enabled: true
externalUrl: grpc://gitlab.example.com:8150
version: 15.0.0
'401':
description: unauthorized operation
tags:
- metadata
operationId: getApiV4Version
definitions:
API_Entities_Metadata:
type: object
properties:
version:
type: string
revision:
type: string
kas:
type: object
properties:
enabled:
type: boolean
externalUrl:
type: string
version:
type: string
description: API_Entities_Metadata model

View File

@ -50,7 +50,7 @@ For example, consider a migration that creates a table with two text columns,
`db/migrate/20200401000001_create_db_guides.rb`:
```ruby
class CreateDbGuides < Gitlab::Database::Migration[1.0]
class CreateDbGuides < Gitlab::Database::Migration[2.0]
def change
create_table :db_guides do |t|
t.bigint :stars, default: 0, null: false
@ -74,7 +74,7 @@ For example, consider a migration that adds a new text column `extended_title` t
`db/migrate/20200501000001_add_extended_title_to_sprints.rb`:
```ruby
class AddExtendedTitleToSprints < Gitlab::Database::Migration[1.0]
class AddExtendedTitleToSprints < Gitlab::Database::Migration[2.0]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
@ -89,7 +89,7 @@ A second migration should follow the first one with a limit added to `extended_t
`db/migrate/20200501000002_add_text_limit_to_sprints_extended_title.rb`:
```ruby
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[1.0]
class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
@ -165,7 +165,7 @@ in a post-deployment migration,
`db/post_migrate/20200501000001_add_text_limit_migration.rb`:
```ruby
class AddTextLimitMigration < Gitlab::Database::Migration[1.0]
class AddTextLimitMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
@ -236,7 +236,7 @@ helper in a final post-deployment migration,
`db/post_migrate/20200601000001_validate_text_limit_migration.rb`:
```ruby
class ValidateTextLimitMigration < Gitlab::Database::Migration[1.0]
class ValidateTextLimitMigration < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
@ -255,7 +255,7 @@ Increasing text limits on existing database columns can be safely achieved by fi
and then dropping the previous limit:
```ruby
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[1.0]
class ChangeMaintainerNoteLimitInCiRunner < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up

View File

@ -139,5 +139,5 @@ end
```
The `ApplicationRecord` class uses a different database connection than the `Ci::Build` records.
The two statements in the transaction block are not part of the transaction and are
rolled back in case something goes wrong. They act as 3rd part calls.
The two statements in the transaction block are not part of the transaction and are not
rolled back in case something goes wrong. They act as third-party calls.

View File

@ -9,6 +9,10 @@ comments: false
GitLab can be integrated with external services for enhanced functionality.
## Services
Services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack are available as [integrations](../user/project/integrations/index.md).
## Issue trackers
You can use an [external issue tracker](external-issue-tracker.md) at the same time as the GitLab
@ -61,10 +65,6 @@ or [Kroki](../administration/integration/kroki.md) to use diagrams in AsciiDoc a
- Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md).
- Add [Elasticsearch](advanced_search/elasticsearch.md) for [Advanced Search](../user/search/advanced_search.md).
## Integrations
Integration with services such as Campfire, Flowdock, Jira, Pivotal Tracker, and Slack are available as [Integrations](../user/project/integrations/index.md).
## Troubleshooting
### SSL certificate errors

View File

@ -152,6 +152,8 @@ You need at least the Developer role to edit a wiki page:
1. Edit the content.
1. Select **Save changes**.
Unsaved changes to a wiki page are preserved in local browser storage to prevent accidental data loss.
### Create a table of contents
To generate a table of contents from a wiki page's subheadings, use the `[[_TOC_]]` tag.

View File

@ -3,6 +3,7 @@
module API
class API < ::API::Base
include APIGuard
include Helpers::OpenApi
LOG_FILENAME = Rails.root.join("log", "api_json.log")
@ -165,6 +166,13 @@ module API
::Users::ActivityService.new(@current_user).execute
end
# Mount endpoints to include in the OpenAPI V2 documentation here
namespace do
mount ::API::Metadata
add_open_api_documentation!
end
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::BatchedBackgroundMigrations
@ -250,7 +258,6 @@ module API
mount ::API::MergeRequestApprovals
mount ::API::MergeRequestDiffs
mount ::API::MergeRequests
mount ::API::Metadata
mount ::API::Metrics::Dashboard::Annotations
mount ::API::Metrics::UserStarredDashboards
mount ::API::Namespaces

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module API
module Entities
class Metadata < Grape::Entity
expose :version
expose :revision
expose :kas do
expose :enabled, documentation: { type: 'boolean' }
expose :externalUrl
expose :version
end
end
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module API
module Helpers
module OpenApi
extend ActiveSupport::Concern
class_methods do
def add_open_api_documentation!
return if Rails.env.production?
open_api_config = YAML.load_file(Rails.root.join('config/open_api.yml'))['metadata'].deep_symbolize_keys
add_swagger_documentation(open_api_config)
end
end
end
end
end

View File

@ -35,8 +35,30 @@ module API
end
end
desc 'Get the metadata information of the GitLab instance.' do
desc 'Retrieve metadata information for this GitLab instance.' do
detail 'This feature was introduced in GitLab 15.2.'
success [
{
code: 200,
model: Entities::Metadata,
message: 'successful operation',
examples: {
successful_response: {
'value' => {
version: "15.0-pre",
revision: "c401a659d0c",
kas: {
enabled: true,
externalUrl: "grpc://gitlab.example.com:8150",
version: "15.0.0"
}
}
}
}
}
]
failure [{ code: 401, message: 'unauthorized operation' }]
tags %w[metadata]
end
get '/metadata' do
run_metadata_query
@ -47,7 +69,30 @@ module API
desc 'Get the version information of the GitLab instance.' do
detail 'This feature was introduced in GitLab 8.13 and deprecated in 15.5. ' \
'We recommend you instead use the Metadata API.'
success [
{
code: 200,
model: Entities::Metadata,
message: 'successful operation',
examples: {
'Example' => {
'value' => {
version: "15.0-pre",
revision: "c401a659d0c",
kas: {
enabled: true,
externalUrl: "grpc://gitlab.example.com:8150",
version: "15.0.0"
}
}
}
}
}
]
failure [{ code: 401, message: 'unauthorized operation' }]
tags %w[metadata]
end
get '/version' do
run_metadata_query
end

View File

@ -33,3 +33,8 @@ test:
stage: test
script:
- ./runmytests.sh
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -28,3 +28,8 @@ test:
# If you need to run any migrations or configure the database, this
# would be the point to do it.
- lein test
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -42,3 +42,8 @@ spec:
minitest:
script:
- crystal test/spec_test.cr # change to the file(s) you execute for tests
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -74,3 +74,8 @@ django-tests:
- echo "GRANT ALL on *.* to '${MYSQL_USER}';"| mysql -u root --password="${MYSQL_ROOT_PASSWORD}" -h mysql
# use python3 explicitly. see https://wiki.ubuntu.com/Python/3
- python3 manage.py test
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -111,3 +111,8 @@ tests:
# (e.g. integration tests, unit tests etc).
script:
- 'dotnet test --no-restore'
deploy:
stage: deploy
script: echo "Define your deployment script!"
environment: production

View File

@ -1,127 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Git
class Wiki
include Gitlab::Git::WrapsGitalyErrors
DuplicatePageError = Class.new(StandardError)
DEFAULT_PAGINATION = Kaminari.config.default_per_page
CommitDetails = Struct.new(:user_id, :username, :name, :email, :message) do
def to_h
{ user_id: user_id, username: username, name: name, email: email, message: message }
end
end
# GollumSlug inlines just enough knowledge from Gollum::Page to generate a
# slug, which is used when previewing pages that haven't been persisted
class GollumSlug
class << self
def cname(name, char_white_sub = '-', char_other_sub = '-')
if name.respond_to?(:gsub)
name.gsub(/\s/, char_white_sub).gsub(/[<>+]/, char_other_sub)
else
''
end
end
def format_to_ext(format)
format == :markdown ? "md" : format.to_s
end
def canonicalize_filename(filename)
::File.basename(filename, ::File.extname(filename)).tr('-', ' ')
end
def generate(title, format)
ext = format_to_ext(format.to_sym)
name = cname(title) + '.' + ext
canonical_name = canonicalize_filename(name)
path =
if name.include?('/')
name.sub(%r{/[^/]+$}, '/')
else
''
end
path + cname(canonical_name, '-', '-')
end
end
end
attr_reader :repository
# TODO remove argument when issue
# https://gitlab.com/gitlab-org/gitlab/-/issues/329190
# is closed.
def self.default_ref(container = nil)
Gitlab::DefaultBranch.value(object: container)
end
# Initialize with a Gitlab::Git::Repository instance
def initialize(repository)
@repository = repository
end
def repository_exists?
@repository.exists?
end
def list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
wrapped_gitaly_errors do
gitaly_list_pages(
limit: limit,
sort: sort,
direction_desc: direction_desc,
load_content: load_content
)
end
end
def page(title:, version: nil, dir: nil, load_content: true)
wrapped_gitaly_errors do
gitaly_find_page(title: title, version: version, dir: dir, load_content: load_content)
end
end
def preview_slug(title, format)
GollumSlug.generate(title, format)
end
private
def gitaly_wiki_client
@gitaly_wiki_client ||= Gitlab::GitalyClient::WikiService.new(@repository)
end
def gitaly_find_page(title:, version: nil, dir: nil, load_content: true)
return unless title.present?
wiki_page, version = gitaly_wiki_client.find_page(title: title, version: version, dir: dir, load_content: load_content)
return unless wiki_page
Gitlab::Git::WikiPage.from_gitaly_wiki_page(wiki_page, version)
rescue GRPC::InvalidArgument
nil
end
def gitaly_list_pages(limit: 0, sort: nil, direction_desc: false, load_content: false)
params = { limit: limit, sort: sort, direction_desc: direction_desc }
gitaly_pages =
if load_content
gitaly_wiki_client.load_all_pages(**params)
else
gitaly_wiki_client.list_all_pages(**params)
end
gitaly_pages.map do |wiki_page, version|
Gitlab::Git::WikiPage.from_gitaly_wiki_page(wiki_page, version)
end
end
end
end
end

View File

@ -5,22 +5,6 @@ module Gitlab
class WikiPage
attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :historical, :formatted_data
class << self
# Abstracts away Gitlab::GitalyClient::WikiPage
def from_gitaly_wiki_page(gitaly_page, version)
new(
url_path: gitaly_page.url_path,
title: gitaly_page.title,
format: gitaly_page.format,
path: gitaly_page.path,
raw_data: gitaly_page.raw_data,
name: gitaly_page.name,
historical: gitaly_page.historical?,
version: version
)
end
end
def initialize(hash)
@url_path = hash[:url_path]
@title = hash[:title]

View File

@ -1,169 +0,0 @@
# frozen_string_literal: true
require 'stringio'
module Gitlab
module GitalyClient
class WikiService
include Gitlab::EncodingHelper
MAX_MSG_SIZE = 128.kilobytes.freeze
def initialize(repository)
@gitaly_repo = repository.gitaly_repository
@repository = repository
end
def write_page(name, format, content, commit_details)
request = Gitaly::WikiWritePageRequest.new(
repository: @gitaly_repo,
name: encode_binary(name),
format: format.to_s,
commit_details: gitaly_commit_details(commit_details)
)
strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
request.content = strio.read(MAX_MSG_SIZE)
y.yield request
request = Gitaly::WikiWritePageRequest.new
end
end
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_write_page, enum, timeout: GitalyClient.medium_timeout)
if error = response.duplicate_error.presence
raise Gitlab::Git::Wiki::DuplicatePageError, error
end
end
def update_page(page_path, title, format, content, commit_details)
request = Gitaly::WikiUpdatePageRequest.new(
repository: @gitaly_repo,
page_path: encode_binary(page_path),
title: encode_binary(title),
format: format.to_s,
commit_details: gitaly_commit_details(commit_details)
)
strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
request.content = strio.read(MAX_MSG_SIZE)
y.yield request
request = Gitaly::WikiUpdatePageRequest.new
end
end
GitalyClient.call(@repository.storage, :wiki_service, :wiki_update_page, enum, timeout: GitalyClient.medium_timeout)
end
def find_page(title:, version: nil, dir: nil, load_content: true)
request = Gitaly::WikiFindPageRequest.new(
repository: @gitaly_repo,
title: encode_binary(title),
revision: encode_binary(version),
directory: encode_binary(dir),
skip_content: !load_content
)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_find_page, request, timeout: GitalyClient.fast_timeout)
wiki_page_from_iterator(response)
end
def list_all_pages(limit: 0, sort: nil, direction_desc: false)
sort_value = Gitaly::WikiListPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
params[:sort] = sort_value if sort_value
request = Gitaly::WikiListPagesRequest.new(params)
stream = GitalyClient.call(@repository.storage, :wiki_service, :wiki_list_pages, request, timeout: GitalyClient.medium_timeout)
stream.each_with_object([]) do |message, pages|
page = message.page
next unless page
wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = new_wiki_page_version(page.version)
pages << [wiki_page, version]
end
end
def load_all_pages(limit: 0, sort: nil, direction_desc: false)
sort_value = Gitaly::WikiGetAllPagesRequest::SortBy.resolve(sort.to_s.upcase.to_sym)
params = { repository: @gitaly_repo, limit: limit, direction_desc: direction_desc }
params[:sort] = sort_value if sort_value
request = Gitaly::WikiGetAllPagesRequest.new(params)
response = GitalyClient.call(@repository.storage, :wiki_service, :wiki_get_all_pages, request, timeout: GitalyClient.medium_timeout)
pages = []
loop do
page, version = wiki_page_from_iterator(response) { |message| message.end_of_page }
break unless page && version
pages << [page, version]
end
pages
end
private
# If a block is given and the yielded value is truthy, iteration will be
# stopped early at that point; else the iterator is consumed entirely.
# The iterator is traversed with `next` to allow resuming the iteration.
def wiki_page_from_iterator(iterator)
wiki_page = version = nil
while message = iterator.next
break if block_given? && yield(message)
page = message.page
next unless page
if wiki_page
wiki_page.raw_data << page.raw_data
else
wiki_page = GitalyClient::WikiPage.new(page.to_h)
version = new_wiki_page_version(page.version)
end
end
[wiki_page, version]
rescue StopIteration
[wiki_page, version]
end
def new_wiki_page_version(version)
Gitlab::Git::WikiPageVersion.new(
Gitlab::Git::Commit.decorate(@repository, version.commit),
version.format
)
end
def gitaly_commit_details(commit_details)
Gitaly::WikiCommitDetails.new(
user_id: commit_details.user_id,
user_name: encode_binary(commit_details.username),
name: encode_binary(commit_details.name),
email: encode_binary(commit_details.email),
message: encode_binary(commit_details.message)
)
end
end
end
end

View File

@ -2,6 +2,9 @@
# A dumb middleware that returns a Go HTML document if the go-get=1 query string
# is used irrespective if the namespace/project exists
#
# In order to prevent the project from being exposed,
# auth failure (401 & 403) is regarded as not found (404)
module Gitlab
module Middleware
class Go
@ -21,15 +24,16 @@ module Gitlab
rescue Gitlab::Auth::IpBlacklisted
Gitlab::AuthLogger.error(
message: 'Rack_Attack',
status: 403,
env: :blocklist,
remote_ip: request.ip,
request_method: request.request_method,
path: request.fullpath
)
Rack::Response.new('', 403).finish
Rack::Response.new('', 404).finish
rescue Gitlab::Auth::MissingPersonalAccessTokenError
Rack::Response.new('', 401).finish
Rack::Response.new('', 404).finish
rescue ActiveRecord::RecordNotFound
Rack::Response.new('', 404).finish
end
private
@ -100,7 +104,6 @@ module Gitlab
# We find all potential project paths out of the path segments
path_segments = path.split('/')
simple_project_path = path_segments.first(2).join('/')
project_paths = []
begin
@ -110,28 +113,18 @@ module Gitlab
# We see if a project exists with any of these potential paths
project = project_for_paths(project_paths, request)
if project
# If a project is found and the user has access, we return the full project path
[project.full_path, project.default_branch]
else
# If not, we return the first two components as if it were a simple `namespace/project` path,
# so that we don't reveal the existence of a nested project the user doesn't have access to.
# This means that for an unauthenticated request to `group/subgroup/project/subpackage`
# for a private `group/subgroup/project` with subpackage path `subpackage`, GitLab will respond
# as if the user is looking for project `group/subgroup`, with subpackage path `project/subpackage`.
# Since `go get` doesn't authenticate by default, this means that
# `go get gitlab.com/group/subgroup/project/subpackage` will not work for private projects.
# `go get gitlab.com/group/subgroup/project.git/subpackage` will work, since Go is smart enough
# to figure that out. `import 'gitlab.com/...'` behaves the same as `go get`.
[simple_project_path, 'master']
end
# If a project is found and the user has access, we return the full project path
[project.full_path, project.default_branch]
end
def project_for_paths(paths, request)
project = Project.where_full_path_in(paths).first
return unless authentication_result(request, project).can_perform_action_on_project?(:read_project, project)
raise ActiveRecord::RecordNotFound unless project
unless authentication_result(request, project).can_perform_action_on_project?(:read_project, project)
raise Gitlab::Auth::MissingPersonalAccessTokenError
end
project
end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'logger'
if Rails.env.development?
require 'grape-swagger/rake/oapi_tasks'
GrapeSwagger::Rake::OapiTasks.new('::API::API')
end
namespace :gitlab do
namespace :openapi do
task :generate do
raise 'This task can only be run in the development environment' unless Rails.env.development?
ENV['store'] = 'tmp/openapi.json'
Rake::Task["oapi:fetch"].invoke(['openapi.json'])
yaml_content = Gitlab::Json.parse(File.read('tmp/openapi_swagger_doc.json')).to_yaml
File.write("doc/api/openapi/openapi_v2.yaml", yaml_content)
end
end
end

View File

@ -2,10 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Repository License Detection', product_group: :source_code, quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/376154',
type: :stale
} do
describe 'Repository License Detection', product_group: :source_code do
after do
project.remove_via_api!
end

View File

@ -1,15 +0,0 @@
# frozen_string_literal: true
FactoryBot.define do
factory :git_wiki_commit_details, class: 'Gitlab::Git::Wiki::CommitDetails' do
skip_create
transient do
author { association(:user) }
end
sequence(:message) { |n| "Commit message #{n}" }
initialize_with { new(author.id, author.username, author.name, author.email, message) }
end
end

View File

@ -12,37 +12,14 @@ RSpec.describe 'Project wikis', :js do
let(:wiki) { create(:project_wiki, user: user, project: project) }
let(:project) { create(:project, namespace: user.namespace, creator: user) }
context 'with legacy wiki rpcs' do
before do
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: false)
end
it_behaves_like 'User creates wiki page'
it_behaves_like 'User deletes wiki page'
it_behaves_like 'User previews wiki changes'
it_behaves_like 'User updates wiki page'
it_behaves_like 'User uses wiki shortcuts'
it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages'
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end
context 'with normal repository rpcs' do
before do
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: true)
end
it_behaves_like 'User creates wiki page'
it_behaves_like 'User deletes wiki page'
it_behaves_like 'User previews wiki changes'
it_behaves_like 'User updates wiki page'
it_behaves_like 'User uses wiki shortcuts'
it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages', false
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end
it_behaves_like 'User creates wiki page'
it_behaves_like 'User deletes wiki page'
it_behaves_like 'User previews wiki changes'
it_behaves_like 'User updates wiki page'
it_behaves_like 'User uses wiki shortcuts'
it_behaves_like 'User views AsciiDoc page with includes'
it_behaves_like 'User views a wiki page'
it_behaves_like 'User views wiki pages'
it_behaves_like 'User views wiki sidebar'
it_behaves_like 'User views Git access wiki page'
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Work item', :js do
let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) }
let_it_be(:work_item) { create(:work_item, project: project) }
context 'for signed in user' do
before do
project.add_developer(user)
sign_in(user)
visit project_work_items_path(project, work_items_path: work_item.id)
end
context 'in work item description' do
it 'shows GFM autocomplete', :aggregate_failures do
click_button "Edit description"
find('[aria-label="Description"]').send_keys("@#{user.username}")
wait_for_requests
page.within('.atwho-container') do
expect(page).to have_text(user.name)
end
end
end
end
end

View File

@ -75,78 +75,38 @@ RSpec.describe WikiHelper do
describe '#wiki_sort_controls' do
let(:wiki) { create(:project_wiki) }
let(:wiki_link) { helper.wiki_sort_controls(wiki, sort, direction) }
let(:wiki_link) { helper.wiki_sort_controls(wiki, direction) }
let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
def expected_link(sort, direction, icon_class)
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
path =
if sort
"/#{wiki.project.full_path}/-/wikis/pages?direction=#{reversed_direction}&sort=#{sort}"
else
"/#{wiki.project.full_path}/-/wikis/pages?direction=#{reversed_direction}"
end
title = direction == 'desc' ? _('Sort direction: Descending') : _('Sort direction: Ascending')
def expected_link(direction, icon_class)
path = "/#{wiki.project.full_path}/-/wikis/pages?direction=#{direction}"
title = direction == 'desc' ? _('Sort direction: Ascending') : _('Sort direction: Descending')
helper.link_to(path, type: 'button', class: classes, title: title) do
helper.sprite_icon("sort-#{icon_class}")
end
end
context 'wiki sorting enabled' do
before do
allow(wiki).to receive(:disable_sorting?).and_return(false)
end
context 'initial call' do
let(:direction) { nil }
context 'initial call' do
let(:sort) { nil }
let(:direction) { nil }
it 'renders with default values' do
expect(wiki_link).to eq(expected_link('title', 'asc', 'lowest'))
end
end
context 'sort by title' do
let(:sort) { 'title' }
let(:direction) { 'asc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('title', 'aesc', 'lowest'))
end
end
context 'sort by created_at' do
let(:sort) { 'created_at' }
let(:direction) { 'desc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('created_at', 'desc', 'highest'))
end
it 'renders with default values' do
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
end
end
context 'wiki sorting disabled' do
before do
allow(wiki).to receive(:disable_sorting?).and_return(true)
context 'sort by asc order' do
let(:direction) { 'asc' }
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('desc', 'lowest'))
end
end
context 'sort by created_at' do
let(:sort) { 'created_at' }
let(:direction) { 'asc' }
context 'sort by desc order' do
let(:direction) { 'desc' }
it 'ignores created_at and renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link(nil, 'asc', 'lowest'))
end
end
context 'initial call' do
let(:sort) { nil }
let(:direction) { nil }
it 'renders with default values' do
expect(wiki_link).to eq(expected_link(nil, 'asc', 'lowest'))
end
it 'renders a link with opposite direction' do
expect(wiki_link).to eq(expected_link('asc', 'highest'))
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Helpers::OpenApi do
describe 'class methods' do
let(:klass) { Class.new.include(described_class) }
describe '.add_open_api_documentation!' do
before do
allow(YAML).to receive(:load_file).and_return({ 'metadata' => { 'key' => 'value' } })
end
it 'calls the add_swagger_documentation method' do
expect(klass).to receive(:add_swagger_documentation).with({ key: 'value' })
klass.add_open_api_documentation!
end
end
end
end

View File

@ -1,134 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Git::Wiki do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
let(:user) { project.first_owner }
let(:project_wiki) { ProjectWiki.new(project, user) }
let(:repository) { project_wiki.repository }
let(:default_branch) { described_class.default_ref(project) }
subject(:wiki) { project_wiki.wiki }
before do
repository.create_if_not_exists(project_wiki.default_branch)
end
describe '#pages' do
before do
create_page('page1', 'content')
create_page('page2', 'content2')
end
after do
destroy_page('page1')
destroy_page('page2')
end
it 'returns all the pages' do
expect(subject.list_pages.count).to eq(2)
expect(subject.list_pages.first.title).to eq 'page1'
expect(subject.list_pages.last.title).to eq 'page2'
end
it 'returns only one page' do
pages = subject.list_pages(limit: 1)
expect(pages.count).to eq(1)
expect(pages.first.title).to eq 'page1'
end
end
describe '#page' do
before do
create_page('page1', 'content')
create_page('foo/page1', 'content foo/page1')
end
after do
destroy_page('page1')
destroy_page('foo/page1')
end
it 'returns the right page' do
page = subject.page(title: 'page1', dir: '')
expect(page.url_path).to eq 'page1'
expect(page.raw_data).to eq 'content'
page = subject.page(title: 'page1', dir: 'foo')
expect(page.url_path).to eq 'foo/page1'
expect(page.raw_data).to eq 'content foo/page1'
end
it 'returns nil for invalid arguments' do
expect(subject.page(title: '')).to be_nil
expect(subject.page(title: 'foo', version: ':')).to be_nil
end
it 'does not return content if load_content param is set to false' do
page = subject.page(title: 'page1', dir: '', load_content: false)
expect(page.url_path).to eq 'page1'
expect(page.raw_data).to be_empty
end
end
describe '#preview_slug' do
where(:title, :file_extension, :format, :expected_slug) do
'The Best Thing' | :md | :markdown | 'The-Best-Thing'
'The Best Thing' | :md | :md | 'The-Best-Thing'
'The Best Thing' | :txt | :txt | 'The-Best-Thing'
'A Subject/Title Here' | :txt | :txt | 'A-Subject/Title-Here'
'A subject' | :txt | :txt | 'A-subject'
'A 1/B 2/C 3' | :txt | :txt | 'A-1/B-2/C-3'
'subject/title' | :txt | :txt | 'subject/title'
'subject/title.md' | :txt | :txt | 'subject/title.md'
'foo<bar>+baz' | :txt | :txt | 'foo-bar--baz'
'foo%2Fbar' | :txt | :txt | 'foo%2Fbar'
'' | :md | :markdown | '.md'
'' | :md | :md | '.md'
'' | :txt | :txt | '.txt'
end
with_them do
subject { wiki.preview_slug(title, format) }
let(:gitaly_slug) { wiki.list_pages.first }
it { is_expected.to eq(expected_slug) }
it 'matches the slug generated by gitaly' do
skip('Gitaly cannot generate a slug for an empty title') unless title.present?
create_page(title, 'content', file_extension)
gitaly_slug = wiki.list_pages.first.url_path
is_expected.to eq(gitaly_slug)
end
end
end
def create_page(name, content, extension = :md)
repository.create_file(
user, ::Wiki.sluggified_full_path(name, extension.to_s), content,
branch_name: default_branch,
message: "created page #{name}",
author_email: user.email,
author_name: user.name
)
end
def destroy_page(name, extension = :md)
repository.delete_file(
user, ::Wiki.sluggified_full_path(name, extension.to_s),
branch_name: described_class.default_ref(project),
message: "delete page #{name}",
author_email: user.email,
author_name: user.name
)
end
end

View File

@ -1,118 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::GitalyClient::WikiService do
let(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.disk_path + '.git' }
let(:client) { described_class.new(project.repository) }
let(:commit) { create(:gitaly_commit) }
let(:page_version) { Gitaly::WikiPageVersion.new(format: 'markdown', commit: commit) }
let(:page_info) { { title: 'My Page', raw_data: 'a', version: page_version } }
describe '#find_page' do
let(:response) do
[
Gitaly::WikiFindPageResponse.new(page: Gitaly::WikiPage.new(page_info)),
Gitaly::WikiFindPageResponse.new(page: Gitaly::WikiPage.new(raw_data: 'b'))
]
end
let(:wiki_page) { subject.first }
let(:wiki_page_version) { subject.last }
subject { client.find_page(title: 'My Page', version: 'master', dir: '') }
it 'sends a wiki_find_page message' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_find_page)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([].each)
subject
end
it 'concatenates the raw data and returns a pair of WikiPage and WikiPageVersion' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_find_page)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(response.each)
expect(wiki_page.title).to eq('My Page')
expect(wiki_page.raw_data).to eq('ab')
expect(wiki_page_version.format).to eq('markdown')
expect(wiki_page.title).to be_utf8
expect(wiki_page.path).to be_utf8
expect(wiki_page.name).to be_utf8
end
end
describe '#load_all_pages' do
let(:page_2_info) { { title: 'My Page 2', raw_data: 'c', version: page_version } }
let(:response) do
[
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(page_info)),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(raw_data: 'b')),
Gitaly::WikiGetAllPagesResponse.new(end_of_page: true),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(page_2_info)),
Gitaly::WikiGetAllPagesResponse.new(page: Gitaly::WikiPage.new(raw_data: 'd')),
Gitaly::WikiGetAllPagesResponse.new(end_of_page: true)
]
end
let(:wiki_page_1) { subject[0].first }
let(:wiki_page_1_version) { subject[0].last }
let(:wiki_page_2) { subject[1].first }
let(:wiki_page_2_version) { subject[1].last }
subject { client.load_all_pages }
it 'sends a wiki_get_all_pages message' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([].each)
subject
end
it 'sends a limit of 0 to wiki_get_all_pages' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_params(limit: 0), kind_of(Hash))
.and_return([].each)
subject
end
it 'concatenates the raw data and returns a pair of WikiPage and WikiPageVersion for each page' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return(response.each)
expect(subject.size).to be(2)
expect(wiki_page_1.title).to eq('My Page')
expect(wiki_page_1.raw_data).to eq('ab')
expect(wiki_page_1_version.format).to eq('markdown')
expect(wiki_page_2.title).to eq('My Page 2')
expect(wiki_page_2.raw_data).to eq('cd')
expect(wiki_page_2_version.format).to eq('markdown')
end
context 'with limits' do
subject { client.load_all_pages(limit: 1) }
it 'sends a request with the limit' do
expect_any_instance_of(Gitaly::WikiService::Stub)
.to receive(:wiki_get_all_pages)
.with(gitaly_request_with_params(limit: 1), kind_of(Hash))
.and_return([].each)
subject
end
end
end
end

View File

@ -17,7 +17,7 @@ RSpec.describe Gitlab::ImportExport::WikiRepoSaver do
allow_next_instance_of(Gitlab::ImportExport) do |instance|
allow(instance).to receive(:storage_path).and_return(export_path)
end
project_wiki.wiki
project_wiki.create_wiki_repository
project_wiki.create_page("index", "test content")
end

View File

@ -32,7 +32,7 @@ RSpec.describe Gitlab::Middleware::Go do
shared_examples 'go-get=1' do |enabled_protocol:|
context 'with simple 2-segment project path' do
let!(:project) { create(:project, :private, :repository) }
let!(:project) { create(:project, :public, :repository) }
context 'with subpackages' do
let(:path) { "#{project.full_path}/subpackage" }
@ -51,6 +51,16 @@ RSpec.describe Gitlab::Middleware::Go do
end
end
context 'with nonexistent path' do
let(:path) { 'nonexistent-group/nonexistent-project' }
it 'responses not found' do
status_code, _headers, body = go
expect(status_code).to eq(404)
expect(body).to match_array([''])
end
end
context 'with a nested project path' do
let(:group) { create(:group, :nested) }
let!(:project) { create(:project, :public, :repository, namespace: group) }
@ -68,8 +78,10 @@ RSpec.describe Gitlab::Middleware::Go do
end
shared_examples 'unauthorized' do
it 'returns the 2-segment group path' do
expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch)
it 'returns unauthorized' do
status_code, _headers, body = go
expect(status_code).to eq(404)
expect(body).to match_array([''])
end
end
@ -141,7 +153,7 @@ RSpec.describe Gitlab::Middleware::Go do
expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlacklisted)
response = go
expect(response[0]).to eq(403)
expect(response[0]).to eq(404)
expect(response[1]['Content-Length']).to be_nil
expect(response[2]).to eq([''])
end
@ -158,7 +170,7 @@ RSpec.describe Gitlab::Middleware::Go do
expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::MissingPersonalAccessTokenError)
response = go
expect(response[0]).to eq(401)
expect(response[0]).to eq(404)
expect(response[1]['Content-Length']).to be_nil
expect(response[2]).to eq([''])
end

View File

@ -24,8 +24,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
context 'migration to instrumentation classes data collection' do
let_it_be(:instrumented_events) do
instrumentation_classes = %w[AggregatedMetric RedisHLLMetric]
::Gitlab::Usage::MetricDefinition.all.map do |definition|
next unless definition.attributes[:instrumentation_class] == 'RedisHLLMetric' && definition.available?
next unless definition.available?
next unless instrumentation_classes.include?(definition.attributes[:instrumentation_class])
definition.attributes.dig(:options, :events)&.sort
end.compact.to_set

View File

@ -340,7 +340,7 @@ RSpec.describe Projects::UpdateService do
it 'logs an error and creates a metric when wiki can not be created' do
project.project_feature.update!(wiki_access_level: ProjectFeature::DISABLED)
expect_any_instance_of(ProjectWiki).to receive(:wiki).and_raise(Wiki::CouldNotCreateWikiError)
expect_any_instance_of(ProjectWiki).to receive(:create_wiki_repository).and_raise(Wiki::CouldNotCreateWikiError)
expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}")
counter = double(:counter)

View File

@ -55,6 +55,7 @@
- Security::FindingsFinder
- Security::PipelineVulnerabilitiesFinder
- Security::ScanExecutionPoliciesFinder
- Security::ScanResultPoliciesFinder
- Security::TrainingProviders::BaseUrlFinder
- Security::TrainingUrlsFinder
- Security::TrainingProviders::KontraUrlFinder

View File

@ -48,7 +48,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'when the wiki repository cannot be created' do
before do
expect(Wiki).to receive(:for_container).and_return(wiki)
expect(wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
expect(wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end
it 'redirects to the wiki container and displays an error message' do
@ -200,7 +200,7 @@ RSpec.shared_examples 'wiki controller actions' do
context 'the sidebar fails to load' do
before do
allow(Wiki).to receive(:for_container).and_return(wiki)
wiki.wiki
wiki.create_wiki_repository
expect(wiki).to receive(:find_sidebar) do
raise ::Gitlab::Git::CommandTimedOut, 'Deadline Exceeded'
end

View File

@ -4,7 +4,7 @@
# wiki
# user
RSpec.shared_examples 'User views wiki pages' do |support_sorting_by_created_at = true|
RSpec.shared_examples 'User views wiki pages' do
include WikiHelpers
let!(:wiki_page1) do
@ -53,40 +53,4 @@ RSpec.shared_examples 'User views wiki pages' do |support_sorting_by_created_at
end
end
end
if support_sorting_by_created_at
context 'ordered by created_at' do
let(:pages_ordered_by_created_at) { [wiki_page1, wiki_page2, wiki_page3] }
before do
stub_feature_flags(wiki_list_pages_with_normal_repository_rpcs: false)
page.within('.wiki-sort-dropdown') do
click_button('Title')
click_button('Created date')
end
end
context 'asc' do
it 'pages are displayed in direct order' do
pages.each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
page.find('.rspec-reverse-sort').click
end
end
it 'pages are displayed in reversed order' do
pages.reverse_each.with_index do |page_title, index|
expect(page_title.text).to eq(pages_ordered_by_created_at[index].title)
end
end
end
end
end
end

View File

@ -15,7 +15,7 @@ RSpec.shared_examples 'model with wiki' do
context 'when the repository cannot be created' do
before do
expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
expect(container.wiki).to receive(:create_wiki_repository) { raise Wiki::CouldNotCreateWikiError }
end
it 'returns false and adds a validation error' do

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.shared_examples 'wiki model' do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user, :commit_email) }
let(:wiki_container) { raise NotImplementedError }
@ -124,36 +126,6 @@ RSpec.shared_examples 'wiki model' do
end
end
describe '#wiki' do
it 'contains a Gitlab::Git::Wiki instance' do
expect(subject.wiki).to be_a Gitlab::Git::Wiki
end
it 'creates a new wiki repo if one does not yet exist' do
expect(subject.create_page('index', 'test content')).to be_truthy
end
it 'creates a new wiki repo with a default commit message' do
expect(subject.create_page('index', 'test content', :markdown, '')).to be_truthy
page = subject.find_page('index')
expect(page.last_version.message).to eq("#{user.username} created page: index")
end
context 'when the repository cannot be created' do
let(:wiki_container) { wiki_container_without_repo }
before do
expect(subject.repository).to receive(:create_if_not_exists) { false }
end
it 'raises CouldNotCreateWikiError' do
expect { subject.wiki }.to raise_exception(Wiki::CouldNotCreateWikiError)
end
end
end
describe '#empty?' do
context 'when the wiki repository is empty' do
it 'returns true' do
@ -447,14 +419,6 @@ RSpec.shared_examples 'wiki model' do
end
end
context 'find page with legacy wiki service' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
it_behaves_like 'wiki model #find_page'
end
context 'find page with normal repository RPCs' do
it_behaves_like 'wiki model #find_page'
end
@ -473,14 +437,6 @@ RSpec.shared_examples 'wiki model' do
end
end
context 'find sidebar with legacy wiki service' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
it_behaves_like 'wiki model #find_sidebar'
end
context 'find sidebar with normal repository RPCs' do
it_behaves_like 'wiki model #find_sidebar'
end
@ -490,7 +446,7 @@ RSpec.shared_examples 'wiki model' do
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
before do
subject.wiki # Make sure the wiki repo exists
subject.create_wiki_repository # Make sure the wiki repo exists
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end
@ -694,14 +650,6 @@ RSpec.shared_examples 'wiki model' do
end
it_behaves_like 'create_page tests'
context 'create page with legacy find_page wiki service' do
it_behaves_like 'create_page tests' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
end
end
end
describe '#update_page' do
@ -800,17 +748,6 @@ RSpec.shared_examples 'wiki model' do
include_context 'extended examples'
end
context 'update page with legacy find_page wiki service' do
it_behaves_like 'update_page tests' do
before do
stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false)
end
include_context 'common examples'
include_context 'extended examples'
end
end
context 'when format is invalid' do
let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') }
@ -984,4 +921,40 @@ RSpec.shared_examples 'wiki model' do
end
end
end
describe '#preview_slug' do
where(:title, :file_extension, :format, :expected_slug) do
'The Best Thing' | :md | :markdown | 'The-Best-Thing'
'The Best Thing' | :txt | :plaintext | 'The-Best-Thing'
'A Subject/Title Here' | :txt | :plaintext | 'A-Subject/Title-Here'
'A subject' | :txt | :plaintext | 'A-subject'
'A 1/B 2/C 3' | :txt | :plaintext | 'A-1/B-2/C-3'
'subject/title' | :txt | :plaintext | 'subject/title'
'subject/title.md' | :txt | :plaintext | 'subject/title.md'
'foo%2Fbar' | :txt | :plaintext | 'foo%2Fbar'
'' | :md | :markdown | '.md'
'' | :txt | :plaintext | '.txt'
end
with_them do
before do
subject.repository.create_file(
user, "#{title}.#{file_extension}", 'content',
branch_name: subject.default_branch,
message: "Add #{title}"
)
end
it do
expect(described_class.preview_slug(title, file_extension)).to eq(expected_slug)
end
it 'matches the slug generated by gitaly' do
skip('Gitaly cannot generate a slug for an empty title') unless title.present?
gitaly_slug = subject.list_pages.first.slug
expect(described_class.preview_slug(title, file_extension)).to eq(gitaly_slug)
end
end
end
end